Skip to main content

script/dom/document/
document.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::cell::{Cell, RefCell};
8use std::cmp::Ordering;
9use std::collections::hash_map::Entry::{Occupied, Vacant};
10use std::collections::{HashMap, HashSet, VecDeque};
11use std::default::Default;
12use std::ops::Deref;
13use std::rc::Rc;
14use std::str::FromStr;
15use std::sync::{Arc as StdArc, LazyLock};
16use std::time::Duration;
17
18use bitflags::bitflags;
19use chrono::Local;
20use content_security_policy::sandboxing_directive::SandboxingFlagSet;
21use content_security_policy::{CspList, Policy as CspPolicy, PolicyDisposition};
22use cookie::Cookie;
23use data_url::mime::Mime;
24use devtools_traits::ScriptToDevtoolsControlMsg;
25use dom_struct::dom_struct;
26use embedder_traits::{
27    AllowOrDeny, AnimationState, CustomHandlersAutomationMode, EmbedderMsg, Image, LoadStatus,
28    Theme,
29};
30use encoding_rs::{Encoding, UTF_8};
31use html5ever::{LocalName, QualName, local_name, ns};
32use hyper_serde::Serde;
33use indexmap::IndexSet;
34use js::context::{JSContext, NoGC};
35use js::jsapi::JSObject;
36use js::realm::CurrentRealm;
37use js::rust::{HandleObject, HandleValue, MutableHandleValue};
38use layout_api::{
39    LCPCandidate, PendingRestyle, ReflowGoal, ReflowPhasesRun, ReflowStatistics, RestyleReason,
40    ScrollContainerQueryFlags, TrustedNodeAddress,
41};
42use malloc_size_of::MallocSizeOfOps;
43use metrics::{InteractiveFlag, InteractiveWindow, ProgressiveWebMetrics};
44use net_traits::CookieSource::NonHTTP;
45use net_traits::CoreResourceMsg::{GetCookieStringForUrl, SetCookiesForUrl};
46use net_traits::image_cache::ImageCache;
47use net_traits::policy_container::PolicyContainer;
48use net_traits::pub_domains::is_pub_domain;
49use net_traits::request::{
50    InsecureRequestsPolicy, PreloadId, PreloadKey, PreloadedResources, RequestBuilder,
51};
52use net_traits::{ReferrerPolicy, ResourceFetchTiming};
53use paint_api::display_list::PaintTimingInfo;
54use percent_encoding::percent_decode;
55use profile_traits::mem::{Report, ReportKind};
56use profile_traits::time::TimerMetadataFrameType;
57use profile_traits::{generic_channel as profile_generic_channel, path};
58use regex::bytes::Regex;
59use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
60use script_bindings::callback::{RootedCallback, ThisReflector};
61use script_bindings::cell::{DomRefCell, Ref, RefMut};
62use script_bindings::interfaces::DocumentHelpers;
63use script_bindings::reflector::reflect_dom_object_with_proto;
64use script_bindings::trace::CustomTraceable;
65use script_traits::{DocumentActivity, ProgressiveWebMetricType};
66use servo_arc::Arc;
67use servo_base::cross_process_instant::CrossProcessInstant;
68use servo_base::generic_channel::GenericSend;
69use servo_base::id::{LCPCandidateID, PipelineId, WebViewId};
70use servo_base::{Epoch, generic_channel};
71use servo_config::pref;
72use servo_constellation_traits::{
73    NavigationHistoryBehavior, PaintMetricEvent, ScriptToConstellationMessage,
74};
75use servo_media::{ClientContextId, ServoMedia};
76use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
77use style::attr::AttrValue;
78use style::context::QuirksMode;
79use style::dom::OpaqueNode;
80use style::invalidation::element::restyle_hints::RestyleHint;
81use style::selector_parser::Snapshot;
82use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
83use style::str::{split_html_space_chars, str_join};
84use style::stylesheet_set::DocumentStylesheetSet;
85use style::stylesheets::{Origin, OriginSet, Stylesheet};
86use style::stylist::Stylist;
87use stylo_atoms::Atom;
88use time::Duration as TimeDuration;
89use url::{Host, Position};
90
91use crate::css::stylesheet_loader::StylesheetContextId;
92use crate::css::stylesheet_set::StylesheetSetRef;
93use crate::dom::animationtimeline::AnimationTimeline;
94use crate::dom::attr::Attr;
95use crate::dom::beforeunloadevent::BeforeUnloadEvent;
96use crate::dom::bindings::callback::{ExceptionHandling, TracedCallback};
97use crate::dom::bindings::codegen::Bindings::AnimationFrameProviderBinding::FrameRequestCallback;
98use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEvent_Binding::BeforeUnloadEventMethods;
99use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
100    DocumentMethods, DocumentReadyState, DocumentVisibilityState, ElementCreationOptions,
101    NamedPropertyValue,
102};
103use crate::dom::bindings::codegen::Bindings::ElementBinding::ScrollLogicalPosition;
104use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
105use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
106use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElement_Binding::HTMLIFrameElementMethods;
107#[cfg(any(feature = "webxr", feature = "gamepad"))]
108use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
109use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
110use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter;
111use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
112use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
113use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
114    SetHTMLOptions, SetHTMLUnsafeOptions,
115};
116use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, WindowMethods};
117use crate::dom::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
118use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
119use crate::dom::bindings::codegen::UnionTypes::{
120    BooleanOrImportNodeOptions, NodeOrString, StringOrElementCreationOptions, TrustedHTMLOrString,
121};
122use crate::dom::bindings::domname::{
123    self, is_valid_attribute_local_name, is_valid_element_local_name, namespace_from_domstring,
124};
125use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
126use crate::dom::bindings::frozenarray::CachedFrozenArray;
127use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
128use crate::dom::bindings::num::Finite;
129use crate::dom::bindings::refcounted::Trusted;
130use crate::dom::bindings::reflector::DomGlobal;
131use crate::dom::bindings::root::{
132    Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout, ToLayoutOptional, UnrootedDom,
133};
134use crate::dom::bindings::str::{DOMString, USVString};
135use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
136use crate::dom::bindings::weakref::DOMTracker;
137use crate::dom::bindings::xmlname::matches_name_production;
138use crate::dom::cdatasection::CDATASection;
139use crate::dom::comment::Comment;
140use crate::dom::compositionevent::CompositionEvent;
141use crate::dom::css::cssstylesheet::CSSStyleSheet;
142use crate::dom::css::fontfaceset::FontFaceSet;
143use crate::dom::css::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
144use crate::dom::customelementregistry::{CustomElementReactionStack, CustomElementRegistry};
145use crate::dom::customevent::CustomEvent;
146use crate::dom::document::accessibility_data::AccessibilityData;
147use crate::dom::document::animation_manager::AnimationManager;
148use crate::dom::document::focus::{DocumentFocusHandler, FocusableArea};
149use crate::dom::document::iframe_collection::IFrameCollection;
150use crate::dom::document::tree_ordered_index_map::TreeOrderedIndexMap;
151use crate::dom::document::websocket::WebSocket;
152use crate::dom::document_embedder_controls::DocumentEmbedderControls;
153use crate::dom::document_event_handler::DocumentEventHandler;
154use crate::dom::documentfragment::DocumentFragment;
155use crate::dom::documentorshadowroot::{
156    DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource,
157};
158use crate::dom::documenttimeline::DocumentTimeline;
159use crate::dom::documenttype::DocumentType;
160use crate::dom::domimplementation::DOMImplementation;
161use crate::dom::domstringlist::DOMStringList;
162use crate::dom::element::attributes::storage::AttrRef;
163use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
164use crate::dom::event::{Event, EventBubbles, EventCancelable};
165use crate::dom::eventtarget::EventTarget;
166use crate::dom::execcommand::basecommand::{CommandName, DefaultSingleLineContainerName};
167use crate::dom::execcommand::execcommands::DocumentExecCommandSupport;
168use crate::dom::focusevent::FocusEvent;
169use crate::dom::globalscope::GlobalScope;
170use crate::dom::hashchangeevent::HashChangeEvent;
171use crate::dom::history::History;
172use crate::dom::html::htmlanchorelement::HTMLAnchorElement;
173use crate::dom::html::htmlareaelement::HTMLAreaElement;
174use crate::dom::html::htmlbaseelement::HTMLBaseElement;
175use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
176use crate::dom::html::htmlelement::HTMLElement;
177use crate::dom::html::htmlembedelement::HTMLEmbedElement;
178use crate::dom::html::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
179use crate::dom::html::htmlheadelement::HTMLHeadElement;
180use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
181use crate::dom::html::htmliframeelement::HTMLIFrameElement;
182use crate::dom::html::htmlimageelement::HTMLImageElement;
183use crate::dom::html::htmlscriptelement::{HTMLScriptElement, ScriptResult};
184use crate::dom::html::htmltitleelement::HTMLTitleElement;
185use crate::dom::htmldetailselement::DetailsNameGroups;
186use crate::dom::intersectionobserver::IntersectionObserver;
187use crate::dom::iterators::ShadowIncluding;
188use crate::dom::keyboardevent::KeyboardEvent;
189use crate::dom::largestcontentfulpaint::LargestContentfulPaint;
190use crate::dom::location::Location;
191use crate::dom::messageevent::MessageEvent;
192use crate::dom::mouseevent::MouseEvent;
193use crate::dom::node::focus::FocusTrigger;
194use crate::dom::node::treewalker::TreeWalker;
195use crate::dom::node::virtualmethods::vtable_for;
196use crate::dom::node::{Node, NodeDamage, NodeFlags, NodeTraits};
197use crate::dom::nodeiterator::NodeIterator;
198use crate::dom::nodelist::NodeList;
199use crate::dom::pagetransitionevent::PageTransitionEvent;
200use crate::dom::performance::performanceentry::PerformanceEntry;
201use crate::dom::performance::performancepainttiming::PerformancePaintTiming;
202use crate::dom::processinginstruction::ProcessingInstruction;
203use crate::dom::range::Range;
204use crate::dom::resizeobserver::{ResizeObservationDepth, ResizeObserver};
205use crate::dom::sanitizer::Sanitizer;
206use crate::dom::selection::Selection;
207use crate::dom::servoparser::ServoParser;
208use crate::dom::shadowroot::shadowroot::ShadowRoot;
209use crate::dom::storageevent::StorageEvent;
210use crate::dom::text::Text;
211use crate::dom::textevent::TextEvent;
212use crate::dom::touchevent::TouchEvent as DomTouchEvent;
213use crate::dom::touchlist::TouchList;
214use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
215use crate::dom::types::{HTMLCanvasElement, VisibilityStateEntry};
216use crate::dom::uievent::UIEvent;
217use crate::dom::window::Window;
218use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollingBox};
219use crate::dom::windowproxy::WindowProxy;
220use crate::dom::xpathevaluator::XPathEvaluator;
221use crate::dom::xpathexpression::XPathExpression;
222use crate::dom::{FlatTreeParent, RootedPromise, WeakRangeVec};
223use crate::event_loop::document_loader::{DocumentLoader, LoadType};
224use crate::event_loop::script_thread::{ScriptThread, SharedRwLocks};
225use crate::event_loop::timers::{OneshotTimerCallback, OneshotTimers};
226use crate::fetch::fetch::{DeferredFetchRecordInvokeState, FetchCanceller};
227use crate::fetch::network_listener::FetchResponseListener;
228use crate::mime::{APPLICATION, CHARSET};
229use crate::modules::script_module::{ModuleRequest, ModuleStatus};
230use crate::navigation::navigate;
231use crate::runtime::script_runtime::compute_size;
232use crate::tasks::task::NonSendTaskBox;
233use crate::tasks::task_manager::TaskManager;
234use crate::tasks::task_source::TaskSourceName;
235use crate::xpath::parse_expression;
236
237#[derive(Clone, Copy, PartialEq)]
238pub(crate) enum FireMouseEventType {
239    Move,
240    Over,
241    Out,
242    Enter,
243    Leave,
244}
245
246impl FireMouseEventType {
247    pub(crate) fn as_str(&self) -> &str {
248        match *self {
249            FireMouseEventType::Move => "mousemove",
250            FireMouseEventType::Over => "mouseover",
251            FireMouseEventType::Out => "mouseout",
252            FireMouseEventType::Enter => "mouseenter",
253            FireMouseEventType::Leave => "mouseleave",
254        }
255    }
256}
257
258#[derive(JSTraceable, MallocSizeOf)]
259pub(crate) struct RefreshRedirectDue {
260    #[no_trace]
261    pub(crate) url: ServoUrl,
262    /// Whether the refresh originated from a `<meta>` element.
263    pub(crate) from_meta_element: bool,
264}
265
266#[derive(Debug, Copy, Clone)]
267pub(crate) enum AbortReason {
268    PipelineExited,
269    DocumentOpen,
270    Destroy,
271    StopLoading,
272    Navigate,
273}
274
275#[derive(Debug)]
276pub(crate) enum SetParserReason {
277    AboutBlankComplete,
278    ParsingHtmlDocument,
279    ParsingHtmlScriptInput,
280    ParsingHtmlFragment,
281    ParsingXmlDocument,
282    #[expect(dead_code)]
283    // AbortReason is only read by the Debug impl.
284    Abort(AbortReason),
285    ParsingBytesChunk,
286    FinishingParser,
287}
288
289/// An LCP candidate paired with its resolved element.
290///
291/// <https://www.w3.org/TR/largest-contentful-paint/#largest-contentful-paint-candidate>
292#[derive(JSTraceable, MallocSizeOf)]
293#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
294struct LCPCandidateAndElement {
295    /// <https://www.w3.org/TR/largest-contentful-paint/#largest-contentful-paint-candidate-element>
296    element: Option<Dom<Element>>,
297    #[no_trace]
298    candidate: LCPCandidate,
299    /// The time the candidate's image became completely available, if any.
300    #[no_trace]
301    load_time: Option<CrossProcessInstant>,
302}
303
304impl RefreshRedirectDue {
305    /// Step 13 of <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
306    pub(crate) fn invoke(self, cx: &mut JSContext, global: &GlobalScope) {
307        let window = global
308            .downcast::<Window>()
309            .expect("Queued a RefreshRedirectDue on a non-Window globalscope");
310
311        // After the refresh has come due (as defined below),
312        // if the user has not canceled the redirect and, if meta is given,
313        // document's active sandboxing flag set does not have the sandboxed
314        // automatic features browsing context flag set,
315        // then navigate document's node navigable to urlRecord using document,
316        // with historyHandling set to "replace".
317        if self.from_meta_element &&
318            window.Document().has_active_sandboxing_flag(
319                SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
320            )
321        {
322            return;
323        }
324        let load_data = window.load_data_for_document(self.url, window.pipeline_id());
325        navigate(
326            cx,
327            window,
328            NavigationHistoryBehavior::Replace,
329            false,
330            load_data,
331        );
332    }
333}
334
335#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
336pub(crate) enum IsHTMLDocument {
337    HTMLDocument,
338    NonHTMLDocument,
339}
340
341#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
342pub(crate) enum TheEndLoadingPhase {
343    #[default]
344    Initial,
345    ProcessingDeferredScripts,
346    ProcessingAsSoonAsPossibleScripts,
347    WaitingForLoadEventBlockers,
348    Done,
349}
350
351/// Information about a declarative refresh
352#[derive(JSTraceable, MallocSizeOf)]
353pub(crate) enum DeclarativeRefresh {
354    PendingLoad {
355        #[no_trace]
356        url: ServoUrl,
357        time: u64,
358        /// Whether the refresh originated from a `<meta>` element.
359        from_meta_element: bool,
360    },
361    CreatedAfterLoad,
362}
363
364#[derive(JSTraceable, MallocSizeOf, PartialEq)]
365#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
366struct PendingScrollEvent {
367    /// The target of this pending scroll event.
368    target: Dom<EventTarget>,
369    /// The kind of event.
370    #[no_trace]
371    event: Atom,
372}
373
374impl PendingScrollEvent {
375    fn equivalent(&self, target: &EventTarget, event: &Atom) -> bool {
376        &*self.target == target && self.event == *event
377    }
378}
379
380/// Reasons why a [`Document`] might need a rendering update that is otherwise
381/// untracked via other [`Document`] properties.
382#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf)]
383pub(crate) struct RenderingUpdateReason(u8);
384
385bitflags! {
386    impl RenderingUpdateReason: u8 {
387        /// When a `ResizeObserver` starts observing a target, this becomes true, which in turn is a
388        /// signal to the [`ScriptThread`] that a rendering update should happen.
389        const ResizeObserverStartedObservingTarget = 1 << 0;
390        /// When an `IntersectionObserver` starts observing a target, this becomes true, which in turn is a
391        /// signal to the [`ScriptThread`] that a rendering update should happen.
392        const IntersectionObserverStartedObservingTarget = 1 << 1;
393        /// All web fonts have loaded and `fonts.ready` promise has been fulfilled. We want to trigger
394        /// one more rendering update possibility after this happens, so that any potential screenshot
395        /// reflects the up-to-date contents.
396        const FontReadyPromiseFulfilled = 1 << 2;
397    }
398}
399
400/// <https://html.spec.whatwg.org/multipage/#document-load-timing-info>
401#[derive(Clone, Debug, Default, MallocSizeOf)]
402pub(crate) struct NavigationTiming {
403    pub(crate) dom_loading: Cell<Option<CrossProcessInstant>>,
404    /// <https://html.spec.whatwg.org/multipage/#navigation-start-time>
405    pub(crate) navigation_start: Cell<Option<CrossProcessInstant>>,
406    /// <https://html.spec.whatwg.org/multipage/#unload-event-start-time>
407    pub(crate) unload_event_start: Cell<Option<CrossProcessInstant>>,
408    /// <https://html.spec.whatwg.org/multipage/#unload-event-end-time>
409    pub(crate) unload_event_end: Cell<Option<CrossProcessInstant>>,
410    /// <https://html.spec.whatwg.org/multipage/#dom-interactive-time>
411    pub(crate) dom_interactive: Cell<Option<CrossProcessInstant>>,
412    /// <https://html.spec.whatwg.org/multipage/#dom-content-loaded-event-start-time>
413    pub(crate) dom_content_loaded_event_start: Cell<Option<CrossProcessInstant>>,
414    /// <https://html.spec.whatwg.org/multipage/#dom-content-loaded-event-end-time>
415    pub(crate) dom_content_loaded_event_end: Cell<Option<CrossProcessInstant>>,
416    /// <https://html.spec.whatwg.org/multipage/#dom-complete-time>
417    pub(crate) dom_complete: Cell<Option<CrossProcessInstant>>,
418    /// <https://html.spec.whatwg.org/multipage/#load-event-start-time>
419    pub(crate) load_event_start: Cell<Option<CrossProcessInstant>>,
420    /// <https://html.spec.whatwg.org/multipage/#load-event-end-time>
421    pub(crate) load_event_end: Cell<Option<CrossProcessInstant>>,
422    /// Servo-only timing for when top-level content (not iframes) is complete
423    pub(crate) top_level_dom_complete: Cell<Option<CrossProcessInstant>>,
424}
425
426/// <https://dom.spec.whatwg.org/#document>
427#[dom_struct]
428pub(crate) struct Document {
429    node: Node,
430    document_or_shadow_root: DocumentOrShadowRoot,
431    window: Dom<Window>,
432    implementation: MutNullableDom<DOMImplementation>,
433    #[ignore_malloc_size_of = "type from external crate"]
434    #[no_trace]
435    content_type: Mime,
436    last_modified: Option<String>,
437    #[no_trace]
438    encoding: Cell<&'static Encoding>,
439    has_browsing_context: bool,
440    is_html_document: bool,
441    #[no_trace]
442    activity: Cell<DocumentActivity>,
443    /// <https://html.spec.whatwg.org/multipage/#the-document%27s-address>
444    #[no_trace]
445    url: DomRefCell<ServoUrl>,
446    /// <https://html.spec.whatwg.org/multipage/#concept-document-about-base-url>
447    #[no_trace]
448    about_base_url: DomRefCell<Option<ServoUrl>>,
449    #[ignore_malloc_size_of = "defined in selectors"]
450    #[no_trace]
451    quirks_mode: Cell<QuirksMode>,
452    /// A helper used to process and store data related to input event handling.
453    event_handler: DocumentEventHandler,
454    /// A helper used to process and store data related to focus handling.
455    focus_handler: DocumentFocusHandler,
456    /// A helper to handle showing and hiding user interface controls in the embedding layer.
457    embedder_controls: DocumentEmbedderControls,
458    id_map: TreeOrderedIndexMap,
459    name_map: TreeOrderedIndexMap,
460    tag_map: DomRefCell<HashMapTracedValues<LocalName, Dom<HTMLCollection>, FxBuildHasher>>,
461    tagns_map: DomRefCell<HashMapTracedValues<QualName, Dom<HTMLCollection>, FxBuildHasher>>,
462    classes_map: DomRefCell<HashMapTracedValues<Vec<Atom>, Dom<HTMLCollection>>>,
463    images: MutNullableDom<HTMLCollection>,
464    embeds: MutNullableDom<HTMLCollection>,
465    links: MutNullableDom<HTMLCollection>,
466    forms: MutNullableDom<HTMLCollection>,
467    scripts: MutNullableDom<HTMLCollection>,
468    anchors: MutNullableDom<HTMLCollection>,
469    applets: MutNullableDom<HTMLCollection>,
470    /// Information about the `<iframes>` in this [`Document`].
471    iframes: IFrameCollection,
472    /// Shared locks used for style attributes, author-origin stylesheets, and user and
473    /// user agent stylesheets in this document. Can be acquired once for accessing many
474    /// objects. This is shared with the owning [`ScriptThread`].
475    #[no_trace]
476    shared_style_locks: SharedRwLocks,
477    /// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed.
478    #[custom_trace]
479    stylesheets: DomRefCell<DocumentStylesheetSet<ServoStylesheetInDocument>>,
480    stylesheet_list: MutNullableDom<StyleSheetList>,
481    ready_state: Cell<DocumentReadyState>,
482    /// The script element that is currently executing.
483    current_script: MutNullableDom<HTMLScriptElement>,
484    #[no_trace]
485    current_the_end_loading_phase: Cell<TheEndLoadingPhase>,
486    /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script>
487    pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>,
488    /// <https://html.spec.whatwg.org/multipage/#script-blocking-style-sheet-set>
489    /// > A Document has a script-blocking style sheet set, which is an ordered set, initially empty.
490    script_blocking_stylesheet_set: DomRefCell<IndexSet<StylesheetContextId>>,
491    /// Number of elements that block the rendering of the page.
492    /// <https://html.spec.whatwg.org/multipage/#implicitly-potentially-render-blocking>
493    render_blocking_element_count: Cell<u32>,
494    /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing>
495    deferred_scripts: PendingInOrderScriptVec,
496    /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible>
497    asap_in_order_scripts_list: PendingInOrderScriptVec,
498    /// <https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible>
499    asap_scripts_set: DomRefCell<Vec<Dom<HTMLScriptElement>>>,
500    /// <https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier>
501    /// Current identifier of animation frame callback
502    animation_frame_ident: Cell<u32>,
503    /// <https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks>
504    /// List of animation frame callbacks
505    animation_frame_list: DomRefCell<VecDeque<(u32, Option<AnimationFrameCallback>)>>,
506    /// Whether we're in the process of running animation callbacks.
507    ///
508    /// Tracking this is not necessary for correctness. Instead, it is an optimization to avoid
509    /// sending needless `ChangeRunningAnimationsState` messages to `Paint`.
510    running_animation_callbacks: Cell<bool>,
511    /// Tracks all outstanding loads related to this document.
512    loader: DomRefCell<DocumentLoader>,
513    /// The current active HTML parser, to allow resuming after interruptions.
514    current_parser: MutNullableDom<ServoParser>,
515    /// The cached first `base` element with an `href` attribute.
516    base_element: MutNullableDom<HTMLBaseElement>,
517    /// The cached first `base` element, used for its target (doesn't need a href)
518    target_base_element: MutNullableDom<HTMLBaseElement>,
519    /// This field is set to the document itself for inert documents.
520    /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document>
521    appropriate_template_contents_owner_document: MutNullableDom<Document>,
522    /// Information on elements needing restyle to ship over to layout when the
523    /// time comes.
524    pending_restyles: DomRefCell<FxHashMap<Dom<Element>, NoTrace<PendingRestyle>>>,
525    /// A collection of reasons that the [`Document`] needs to be restyled at the next
526    /// opportunity for a reflow. If this is empty, then the [`Document`] does not need to
527    /// be restyled.
528    #[no_trace]
529    needs_restyle: Cell<RestyleReason>,
530    /// The document's origin.
531    #[no_trace]
532    origin: DomRefCell<MutableOrigin>,
533    /// <https://html.spec.whatwg.org/multipage/#dom-document-referrer>
534    referrer: Option<String>,
535    /// <https://html.spec.whatwg.org/multipage/#target-element>
536    target_element: MutNullableDom<Element>,
537    /// <https://html.spec.whatwg.org/multipage/#concept-document-policy-container>
538    #[no_trace]
539    #[conditional_malloc_size_of]
540    policy_container: DomRefCell<StdArc<PolicyContainer>>,
541    /// <https://html.spec.whatwg.org/multipage/#map-of-preloaded-resources>
542    #[no_trace]
543    preloaded_resources: DomRefCell<PreloadedResources>,
544    /// <https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter>
545    ignore_destructive_writes_counter: Cell<u32>,
546    /// <https://html.spec.whatwg.org/multipage/#ignore-opens-during-unload-counter>
547    ignore_opens_during_unload_counter: Cell<u32>,
548    /// The number of spurious `requestAnimationFrame()` requests we've received.
549    ///
550    /// A rAF request is considered spurious if nothing was actually reflowed.
551    spurious_animation_frames: Cell<u8>,
552
553    /// Entry node for fullscreen.
554    fullscreen_element: MutNullableDom<Element>,
555    /// Map from ID to set of form control elements that have that ID as
556    /// their 'form' content attribute. Used to reset form controls
557    /// whenever any element with the same ID as the form attribute
558    /// is inserted or removed from the document.
559    /// See <https://html.spec.whatwg.org/multipage/#form-owner>
560    /// It is safe to use FxBuildHasher here as Atoms are in the string_cache
561    form_id_listener_map:
562        DomRefCell<HashMapTracedValues<Atom, HashSet<Dom<Element>>, FxBuildHasher>>,
563    #[no_trace]
564    interactive_time: DomRefCell<ProgressiveWebMetrics>,
565    #[no_trace]
566    tti_window: DomRefCell<InteractiveWindow>,
567    /// RAII canceller for Fetch
568    canceller: FetchCanceller,
569    /// <https://html.spec.whatwg.org/multipage/#throw-on-dynamic-markup-insertion-counter>
570    throw_on_dynamic_markup_insertion_counter: Cell<u64>,
571    /// <https://html.spec.whatwg.org/multipage/#page-showing>
572    page_showing: Cell<bool>,
573    /// Whether the document is salvageable.
574    salvageable: Cell<bool>,
575    /// Whether the document was aborted with an active parser
576    active_parser_was_aborted: Cell<bool>,
577    /// Whether the unload event has already been fired.
578    fired_unload: Cell<bool>,
579    /// List of responsive images
580    responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>,
581
582    /// [`NavigationTiming`] information for this [`Document`].
583    /// <https://html.spec.whatwg.org/multipage/#load-timing-info>
584    #[no_trace]
585    #[conditional_malloc_size_of]
586    navigation_timing: Rc<NavigationTiming>,
587
588    /// A [`ResourceFetchTiming`] that holds timing information for this [`Document`].
589    #[no_trace]
590    resource_fetch_timing: RefCell<Option<ResourceFetchTiming>>,
591
592    /// Number of outstanding requests to prevent JS or layout from running.
593    script_and_layout_blockers: Cell<u32>,
594    /// List of tasks to execute as soon as last script/layout blocker is removed.
595    #[ignore_malloc_size_of = "Measuring trait objects is hard"]
596    delayed_tasks: DomRefCell<Vec<Box<dyn NonSendTaskBox>>>,
597    /// <https://html.spec.whatwg.org/multipage/#completely-loaded>
598    completely_loaded: Cell<bool>,
599    /// Set of shadow roots connected to the document tree.
600    shadow_roots: DomRefCell<FxHashSet<Dom<ShadowRoot>>>,
601    /// Whether any of the shadow roots need the stylesheets flushed.
602    shadow_roots_styles_changed: Cell<bool>,
603    /// List of registered media controls.
604    /// We need to keep this list to allow the media controls to
605    /// access the "privileged" document.servoGetMediaControls(id) API,
606    /// where `id` needs to match any of the registered ShadowRoots
607    /// hosting the media controls UI.
608    media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
609    /// A set of dirty HTML canvas elements that need their WebRender images updated the
610    /// next time the rendering is updated.
611    dirty_canvases: DomRefCell<Vec<Dom<HTMLCanvasElement>>>,
612    /// Whether or not animated images need to have their contents updated.
613    has_pending_animated_image_update: Cell<bool>,
614    /// <https://w3c.github.io/slection-api/#dfn-selection>
615    selection: MutNullableDom<Selection>,
616    /// A timeline for animations which is used for synchronizing animations.
617    /// <https://drafts.csswg.org/web-animations/#timeline>
618    timeline: Dom<DocumentTimeline>,
619    /// Animations for this Document
620    animation_manager: AnimationManager,
621    /// The nearest inclusive ancestors to all the nodes that require a restyle.
622    dirty_root: MutNullableDom<Element>,
623    /// <https://html.spec.whatwg.org/multipage/#will-declaratively-refresh>
624    declarative_refresh: DomRefCell<Option<DeclarativeRefresh>>,
625    /// <https://drafts.csswg.org/resize-observer/#dom-document-resizeobservers-slot>
626    ///
627    /// Note: we are storing, but never removing, resize observers.
628    /// The lifetime of resize observers is specified at
629    /// <https://drafts.csswg.org/resize-observer/#lifetime>.
630    /// But implementing it comes with known problems:
631    /// - <https://bugzilla.mozilla.org/show_bug.cgi?id=1596992>
632    /// - <https://github.com/w3c/csswg-drafts/issues/4518>
633    resize_observers: DomRefCell<Vec<Dom<ResizeObserver>>>,
634    /// The set of all fonts loaded by this document.
635    /// <https://drafts.csswg.org/css-font-loading/#font-face-source>
636    fonts: MutNullableDom<FontFaceSet>,
637    /// <https://html.spec.whatwg.org/multipage/#visibility-state>
638    visibility_state: Cell<DocumentVisibilityState>,
639    /// <https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml>
640    status_code: Option<u16>,
641    /// <https://html.spec.whatwg.org/multipage/#is-initial-about:blank>
642    is_initial_about_blank: Cell<bool>,
643    /// <https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots>
644    allow_declarative_shadow_roots: Cell<bool>,
645    /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
646    #[no_trace]
647    inherited_insecure_requests_policy: Cell<Option<InsecureRequestsPolicy>>,
648    /// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
649    has_trustworthy_ancestor_origin: Cell<bool>,
650    /// <https://w3c.github.io/IntersectionObserver/#document-intersectionobservertaskqueued>
651    intersection_observer_task_queued: Cell<bool>,
652    /// Active intersection observers that should be processed by this document in
653    /// the update intersection observation steps.
654    /// <https://w3c.github.io/IntersectionObserver/#run-the-update-intersection-observations-steps>
655    /// > Let observer list be a list of all IntersectionObservers whose root is in the DOM tree of document.
656    /// > For the top-level browsing context, this includes implicit root observers.
657    ///
658    /// Details of which document that should process an observers is discussed further at
659    /// <https://github.com/w3c/IntersectionObserver/issues/525>.
660    ///
661    /// The lifetime of an intersection observer is specified at
662    /// <https://github.com/w3c/IntersectionObserver/issues/525>.
663    intersection_observers: DomRefCell<Vec<Dom<IntersectionObserver>>>,
664    /// The node that is currently highlighted by the devtools
665    highlighted_dom_node: MutNullableDom<Node>,
666    /// Resolved LCP candidate elements, keyed by their [LCPCandidateID].
667    lcp_candidates: DomRefCell<HashMapTracedValues<LCPCandidateID, LCPCandidateAndElement>>,
668    /// The [`PaintTimingInfo`] for this document with [`rendering_update_end_time`] set.
669    /// <https://www.w3.org/TR/paint-timing/#paint-timing-info>
670    #[no_trace]
671    paint_timing_info: Cell<PaintTimingInfo>,
672    /// The constructed stylesheet that is adopted by this [Document].
673    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
674    adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>,
675    /// Cached frozen array of [`Self::adopted_stylesheets`]
676    #[ignore_malloc_size_of = "mozjs"]
677    adopted_stylesheets_frozen_types: CachedFrozenArray,
678    /// <https://drafts.csswg.org/cssom-view/#document-pending-scroll-events>
679    /// > Each Document has an associated list of pending scroll events, which stores
680    /// > pairs of (EventTarget, DOMString), initially empty.
681    pending_scroll_events: DomRefCell<Vec<PendingScrollEvent>>,
682    /// Other reasons that a rendering update might be required for this [`Document`].
683    rendering_update_reasons: Cell<RenderingUpdateReason>,
684    /// Whether or not this [`Document`] is waiting on canvas image updates. If it is
685    /// waiting it will not do any new layout until the canvas images are up-to-date in
686    /// the renderer.
687    waiting_on_canvas_image_updates: Cell<bool>,
688    /// Whether we have already noted that the document element was removed.
689    root_removal_noted: Cell<bool>,
690    /// The current rendering epoch, which is used to track updates in the renderer.
691    ///
692    ///   - Every display list update also advances the Epoch, so that the renderer knows
693    ///     when a particular display list is ready in order to take a screenshot.
694    ///   - Canvas image updates happen asynchronously and are tagged with this Epoch. Until
695    ///     those asynchronous updates are complete, the `Document` will not perform any
696    ///     more rendering updates.
697    #[no_trace]
698    current_rendering_epoch: Cell<Epoch>,
699    /// The global custom element reaction stack for this script thread.
700    #[conditional_malloc_size_of]
701    custom_element_reaction_stack: Rc<CustomElementReactionStack>,
702    #[no_trace]
703    /// <https://html.spec.whatwg.org/multipage/#active-sandboxing-flag-set>,
704    active_sandboxing_flag_set: Cell<SandboxingFlagSet>,
705    #[no_trace]
706    /// The [`SandboxingFlagSet`] use to create the browsing context for this [`Document`].
707    /// These are cached here as they cannot always be retrieved readily if the owner of
708    /// browsing context (either `<iframe>` or popup) might be in a different `ScriptThread`.
709    ///
710    /// See
711    /// <https://html.spec.whatwg.org/multipage/#determining-the-creation-sandboxing-flags>.
712    creation_sandboxing_flag_set: Cell<SandboxingFlagSet>,
713    /// The cached favicon for that document.
714    #[no_trace]
715    favicon: RefCell<Option<Image>>,
716
717    /// All websockets created that are associated with this document.
718    websockets: DOMTracker<WebSocket>,
719
720    /// <https://html.spec.whatwg.org/multipage/#details-name-group>
721    details_name_groups: DomRefCell<Option<DetailsNameGroups>>,
722
723    /// <https://html.spec.whatwg.org/multipage/#registerprotocolhandler()-automation-mode>
724    #[no_trace]
725    protocol_handler_automation_mode: RefCell<CustomHandlersAutomationMode>,
726
727    /// Reflect the value of that preferences to prevent paying the cost of a RwLock access.
728    layout_animations_test_enabled: bool,
729
730    /// <https://w3c.github.io/editing/docs/execCommand/#state-override>
731    #[no_trace]
732    state_override: DomRefCell<FxHashMap<CommandName, bool>>,
733
734    /// <https://w3c.github.io/editing/docs/execCommand/#value-override>
735    #[no_trace]
736    value_override: DomRefCell<FxHashMap<CommandName, DOMString>>,
737
738    /// <https://w3c.github.io/editing/docs/execCommand/#default-single-line-container-name>
739    #[no_trace]
740    default_single_line_container_name: Cell<DefaultSingleLineContainerName>,
741
742    /// <https://w3c.github.io/editing/docs/execCommand/#css-styling-flag>
743    css_styling_flag: Cell<bool>,
744
745    /// Data necessary for maintaining the accessibility tree.
746    accessibility_data: DomRefCell<AccessibilityData>,
747
748    /// <https://html.spec.whatwg.org/multipage/#iframe-load-in-progress>
749    iframe_load_in_progress: Cell<bool>,
750    /// <https://html.spec.whatwg.org/multipage/#mute-iframe-load>
751    mute_iframe_load: Cell<bool>,
752
753    /// The mechanism by which time-outs and intervals are scheduled.
754    /// <https://html.spec.whatwg.org/multipage/#timers>
755    timers: OneshotTimers,
756
757    #[no_trace]
758    pipeline_id: PipelineId,
759
760    /// A [`TaskManager`] for this [`Window`].
761    #[conditional_malloc_size_of]
762    task_manager: Rc<TaskManager>,
763
764    #[ignore_malloc_size_of = "ImageCache"]
765    #[no_trace]
766    image_cache: StdArc<dyn ImageCache>,
767
768    /// <https://html.spec.whatwg.org/multipage/#doc-history>
769    history: MutNullableDom<History>,
770
771    /// Theme specific for this document, set by a meta element
772    #[no_trace]
773    theme: Cell<Option<Theme>>,
774
775    /// A theme override provided by devtools.
776    #[no_trace]
777    theme_override: Cell<Option<Theme>>,
778
779    /// Language specific for this document, set by a meta element
780    default_language: DomRefCell<Option<String>>,
781
782    /// True if this document is no longer the active document of its associated
783    /// window.
784    window_detached: Cell<bool>,
785
786    /// <https://html.spec.whatwg.org/multipage/#concept-document-ancestor-origins-list>
787    ancestor_origins_list: MutNullableDom<DOMStringList>,
788
789    /// <https://html.spec.whatwg.org/multipage/#concept-document-internal-ancestor-origin-objects-list>
790    #[no_trace]
791    internal_ancestor_origin_objects_list: RefCell<Option<Vec<ImmutableOrigin>>>,
792
793    /// A vector of weak references to Range instances that are live on
794    /// this document.
795    live_ranges: WeakRangeVec,
796
797    /// module map is used when importing JavaScript modules
798    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-module-map>
799    #[ignore_malloc_size_of = "mozjs"]
800    module_map: DomRefCell<HashMapTracedValues<ModuleRequest, ModuleStatus>>,
801}
802
803impl Document {
804    pub(crate) fn module_map(
805        &self,
806    ) -> &DomRefCell<HashMapTracedValues<ModuleRequest, ModuleStatus>> {
807        &self.module_map
808    }
809
810    pub(crate) fn history(&self, cx: &mut JSContext) -> DomRoot<History> {
811        self.history.or_init(|| History::new(cx, &self.window))
812    }
813
814    pub(crate) fn image_cache(&self) -> StdArc<dyn ImageCache> {
815        self.image_cache.clone()
816    }
817
818    pub(crate) fn task_manager(&self) -> Rc<TaskManager> {
819        self.task_manager.clone()
820    }
821
822    pub(crate) fn timers(&self) -> &OneshotTimers {
823        &self.timers
824    }
825
826    pub(crate) fn pipeline_id(&self) -> PipelineId {
827        self.pipeline_id
828    }
829
830    /// <https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen>
831    fn fully_exit_fullscreen(&self, cx: &mut JSContext) {
832        // Step 1. If document’s fullscreen element is null, terminate these
833        // steps.
834        if self.fullscreen_element().is_none() {
835            return;
836        };
837
838        // TODO Step 2. Unfullscreen elements whose fullscreen flag is set,
839        // within document’s top layer, except for document’s fullscreen element.
840
841        // Step 3. Exit fullscreen document.
842        let _ = self.exit_fullscreen(cx);
843    }
844
845    /// <https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps>
846    fn unloading_cleanup_steps(&self, cx: &mut JSContext) {
847        // > https://fullscreen.spec.whatwg.org/#model
848        // > "Whenever the unloading document cleanup steps run with a document, fully exit fullscreen document."
849        self.fully_exit_fullscreen(cx);
850
851        // Step 1. Let window be document's relevant global object.
852        // Step 2. For each WebSocket object webSocket whose relevant global object is window, make disappear webSocket.
853        if self.close_outstanding_websockets() {
854            // If this affected any WebSocket objects, then make document unsalvageable given document and "websocket".
855            self.salvageable.set(false);
856        }
857
858        // Step 3. For each WebTransport object transport whose relevant global object is window, run the context cleanup steps given transport.
859        // TODO
860
861        // Step 4. If document's salvageable state is false, then:
862        if !self.salvageable.get() && !self.window_detached() {
863            let global_scope = self.window.as_global_scope();
864
865            // Step 4.1. For each EventSource object eventSource whose relevant global object is equal to window, forcibly close eventSource.
866            global_scope.close_event_sources();
867
868            // Step 4.2. Clear window's map of active timers.
869            self.timers.clear();
870
871            // Ensure the constellation discards all bfcache information for this document.
872            let msg = ScriptToConstellationMessage::DiscardDocument;
873            let _ = global_scope.script_to_constellation_chan().send(msg);
874        }
875    }
876
877    pub(crate) fn track_websocket(&self, websocket: &WebSocket) {
878        self.websockets.track(websocket);
879    }
880
881    fn close_outstanding_websockets(&self) -> bool {
882        let mut closed_any_websocket = false;
883        self.websockets.for_each(|websocket: DomRoot<WebSocket>| {
884            if websocket.make_disappear() {
885                closed_any_websocket = true;
886            }
887        });
888        closed_any_websocket
889    }
890
891    fn document_element_changed(&self) {
892        if self.GetDocumentElement().is_some() {
893            // This ensures that if the document element is removed in the future, it
894            // will trigger a new empty display list.
895            self.root_removal_noted.set(false);
896        } else if !self.root_removal_noted.get() {
897            // If there is no document element, attempt to trigger a new root removal update,
898            // but do not do any updating of the dirty root or HAS_DIRTY_DESCENDANTS flags.
899            self.add_restyle_reason(RestyleReason::DOMChanged);
900            self.root_removal_noted.set(true);
901        }
902    }
903
904    /// This is a port of Gecko's restyle root architecture. The idea is that we track a
905    /// node which is the root of restyle damage. Below that root, certain nodes can be
906    /// marked with a HAS_DIRTY_DESCENDANTS flag which means they should be traversed
907    /// during styling and damage propagation. Above the dirty root nothing should be
908    /// marked with the HAS_DIRTY_DESCENDANTS flag.
909    ///
910    /// The overall algorithm is as follows:
911    /// * When the first dirty element is noted, we just set it as the restyle root.
912    /// * When additional dirty elements are noted, we propagate the given bit up
913    ///   the tree, until we either reach the dirty root or the document root.
914    /// * If we reach the document root, we then propagate the HAS_DIRTY_DESCENDANTS
915    ///   flags up the tree until we cross the path of the new root. Once
916    ///   we find this common ancestor, we record it as the restyle root, and then
917    ///   clear the bits between the new restyle root and the document root.
918    pub(crate) fn note_dirty_element(&self, no_gc: &NoGC, element: &Element) {
919        let node = element.upcast::<Node>();
920
921        debug_assert!(*node.owner_doc() == *self);
922        if !node.is_connected() {
923            return;
924        }
925
926        let parent_element = match node.parent_in_flat_tree(no_gc) {
927            FlatTreeParent::Parent(parent) => UnrootedDom::downcast::<Element>(parent),
928            FlatTreeParent::NotInFlatTree | FlatTreeParent::RootNode => return,
929        };
930
931        // The node may not have a parent element if it is a direct descendant of the
932        // `Document` node (i.e. it is the document element aka the `<html>` element in HTML
933        // documents).
934        if let Some(parent_element) = parent_element {
935            // If the parent isn't styled, then it either isn't part of the flat tree or will
936            // be styled later, ensuring the layout of the dirtied node as well.
937            if !parent_element.is_styled() {
938                return;
939            }
940            // If the parent has `display: none`, the change that caused the node to be dirty
941            // will not affect style or layout.
942            if parent_element.is_display_none() {
943                return;
944            }
945        }
946
947        let Some(old_dirty_root) = self.dirty_root.get() else {
948            node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
949            self.set_dirty_root(no_gc, Some(element));
950            return;
951        };
952
953        let old_dirty_root_node = old_dirty_root.upcast::<Node>();
954        for ancestor in element
955            .upcast::<Node>()
956            .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
957        {
958            // Never mark the Document node as having dirty descendants. It's never the dirty root.
959            if !ancestor.is::<Element>() {
960                break;
961            }
962
963            if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
964                return;
965            }
966
967            ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
968
969            // If this node is already under the existing dirty root, there is nothing else to
970            // do apart from marking this node as having dirty descendants. We need to ensure
971            // we mark the root as having dirty descendants now because that has become true.
972            if old_dirty_root_node == &**ancestor {
973                return;
974            }
975        }
976
977        let common_element_ancestor = old_dirty_root_node
978            .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
979            .skip(1) // Skip the old root itself.
980            .find_map(|ancestor| {
981                // Never mark the Document node as having dirty descendants. It's never the dirty root.
982                let element = ancestor.downcast::<Element>().map(DomRoot::from_ref)?;
983                if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
984                    return Some(element);
985                }
986                ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
987                None
988            });
989
990        // In the case that there was no common ancestor dirty root, one or both of the nodes
991        // is not in the flat tree any longer. When this happens just move the dirty root to
992        // the document element.
993        let Some(new_dirty_root) = common_element_ancestor else {
994            let new_dirty_root = self.GetDocumentElement();
995            if let Some(new_dirty_root) = new_dirty_root.as_ref() {
996                new_dirty_root
997                    .upcast::<Node>()
998                    .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
999            }
1000            self.set_dirty_root(no_gc, new_dirty_root.as_deref());
1001            return;
1002        };
1003
1004        // Now mark all nodes *above* the new dirty root as not having dirty descendants
1005        // to ensure our invariant that nothing above the dirty root is marked with this
1006        // flag.
1007        for ancestor in new_dirty_root
1008            .upcast::<Node>()
1009            .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
1010            .skip(1)
1011        {
1012            ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
1013        }
1014
1015        self.set_dirty_root(no_gc, Some(&*new_dirty_root));
1016    }
1017
1018    fn set_dirty_root(&self, no_gc: &NoGC, new_dirty_root: Option<&Element>) {
1019        // Assertion: No nodes above the dirty root should be marked with the HAS_DIRTY_DESCENDANTS flag.
1020        debug_assert!(new_dirty_root.as_ref().is_none_or(|new_dirty_root| {
1021            new_dirty_root
1022                .upcast::<Node>()
1023                .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
1024                .skip(1)
1025                .all(|node| !node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS))
1026        }));
1027        self.dirty_root.set(new_dirty_root);
1028    }
1029
1030    pub(crate) fn take_dirty_root(&self) -> Option<DomRoot<Element>> {
1031        self.dirty_root.take()
1032    }
1033
1034    #[inline]
1035    pub(crate) fn loader(&self) -> Ref<'_, DocumentLoader> {
1036        self.loader.borrow()
1037    }
1038
1039    #[inline]
1040    pub(crate) fn loader_mut(&self) -> RefMut<'_, DocumentLoader> {
1041        self.loader.borrow_mut()
1042    }
1043
1044    #[inline]
1045    pub(crate) fn has_browsing_context(&self) -> bool {
1046        self.has_browsing_context
1047    }
1048
1049    /// <https://html.spec.whatwg.org/multipage/#concept-document-bc>
1050    #[inline]
1051    pub(crate) fn browsing_context(&self) -> Option<DomRoot<WindowProxy>> {
1052        if self.has_browsing_context {
1053            self.window.undiscarded_window_proxy()
1054        } else {
1055            None
1056        }
1057    }
1058
1059    pub(crate) fn webview_id(&self) -> WebViewId {
1060        self.window.webview_id()
1061    }
1062
1063    #[inline]
1064    pub(crate) fn window(&self) -> &Window {
1065        &self.window
1066    }
1067
1068    #[inline]
1069    pub(crate) fn is_html_document(&self) -> bool {
1070        self.is_html_document
1071    }
1072
1073    pub(crate) fn is_xhtml_document(&self) -> bool {
1074        self.content_type.matches(APPLICATION, "xhtml+xml")
1075    }
1076
1077    /// <https://html.spec.whatwg.org/multipage/#fully-active>
1078    pub(crate) fn is_fully_active(&self) -> bool {
1079        // > A Document d is said to be fully active when d is the active document of a
1080        // > navigable navigable, and either navigable is a top-level traversable or
1081        // > navigable's container document is fully active.
1082        self.is_active() &&
1083            (self.window.is_top_level() || self.activity.get() == DocumentActivity::FullyActive)
1084    }
1085
1086    /// <https://html.spec.whatwg.org/multipage/#nav-document>
1087    pub(crate) fn is_active(&self) -> bool {
1088        // > A navigable's active document is its active session history entry's document.
1089        //
1090        // The first two checks stand in for when the document is not the active session history
1091        // entry's document, as when that happens they will be false. The session history entry
1092        // is not really implemented in script in the same way the specification says.
1093        self.browsing_context().is_some() &&
1094            !self.window_detached() &&
1095            self.activity.get() != DocumentActivity::Inactive
1096    }
1097
1098    #[inline]
1099    pub(crate) fn current_rendering_epoch(&self) -> Epoch {
1100        self.current_rendering_epoch.get()
1101    }
1102
1103    /// Get the [`Selection`] instance for this [`Document`] if there is one or `None`.
1104    #[inline]
1105    pub(crate) fn selection(&self) -> Option<DomRoot<Selection>> {
1106        self.selection.get()
1107    }
1108
1109    pub(crate) fn set_activity(&self, cx: &mut JSContext, activity: DocumentActivity) {
1110        // This function should only be called on documents with a browsing context
1111        assert!(self.has_browsing_context);
1112        if activity == self.activity.get() {
1113            return;
1114        }
1115
1116        // Set the document's activity level, reflow if necessary, and suspend or resume timers.
1117        self.activity.set(activity);
1118        let media = ServoMedia::get();
1119        let pipeline_id = self.window().pipeline_id();
1120        let client_context_id =
1121            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
1122
1123        if activity != DocumentActivity::FullyActive {
1124            if !self.window_detached() {
1125                self.window().suspend(cx);
1126            }
1127            media.suspend(&client_context_id);
1128            return;
1129        }
1130
1131        if self.window_detached() {
1132            return;
1133        }
1134
1135        self.title_changed();
1136        self.notify_embedder_favicon();
1137        self.dirty_all_nodes(cx.no_gc());
1138        self.window().resume(cx);
1139        media.resume(&client_context_id);
1140
1141        if self.ready_state.get() != DocumentReadyState::Complete {
1142            return;
1143        }
1144
1145        // This step used to be Step 4.6 in html.spec.whatwg.org/multipage/#history-traversal
1146        // But it's now Step 4 in https://html.spec.whatwg.org/multipage/#reactivate-a-document
1147        // TODO: See #32687 for more information.
1148        let document = Trusted::new(self);
1149        self.owner_global()
1150            .task_manager()
1151            .dom_manipulation_task_source()
1152            .queue(task!(fire_pageshow_event: move |cx| {
1153                let document = document.root();
1154                let window = document.window();
1155                // Step 4.6.1
1156                if document.page_showing.get() {
1157                    return;
1158                }
1159                // Step 4.6.2 Set document's page showing flag to true.
1160                document.page_showing.set(true);
1161                // Step 4.6.3 Update the visibility state of document to "visible".
1162                document.update_visibility_state(cx, DocumentVisibilityState::Visible);
1163                // Step 4.6.4 Fire a page transition event named pageshow at document's relevant
1164                // global object with true.
1165                let event = PageTransitionEvent::new(
1166                    cx,
1167                    window,
1168                    atom!("pageshow"),
1169                    false, // bubbles
1170                    false, // cancelable
1171                    true, // persisted
1172                );
1173                let event = event.upcast::<Event>();
1174                event.set_trusted(true);
1175                window.dispatch_event_with_target_override(cx, event);
1176            }))
1177    }
1178
1179    pub(crate) fn origin(&self) -> Ref<'_, MutableOrigin> {
1180        self.origin.borrow()
1181    }
1182
1183    /// <https://www.w3.org/TR/paint-timing/#paint-timing-eligible>
1184    pub(crate) fn paint_timing_eligible(&self) -> bool {
1185        // A browsing context ctx is paint-timing eligible when one of the
1186        // following apply:
1187        // > ctx is a top-level browsing context.
1188        if self.window().is_top_level() {
1189            return true;
1190        }
1191        // > ctx is a nested browsing context, and the user agent has
1192        // > configured ctx to report paint timing.
1193        if let Some(top_level_document) = self.window().top_level_document_if_local() {
1194            // > > a user agent may decide to disable paint-timing for
1195            // > > cross-origin iframes, as in some scenarios their
1196            // > > paint-timing might reveal information about the main frame.
1197            return self.origin().same_origin(&top_level_document.origin());
1198        };
1199        false
1200    }
1201
1202    /// Part of <https://html.spec.whatwg.org/multipage/#navigate-ua-inline>
1203    /// TODO: Remove this when we create documents after processing headers
1204    pub(crate) fn mark_as_internal(&self) {
1205        *self.origin.borrow_mut() = MutableOrigin::new(ImmutableOrigin::new_opaque());
1206        self.window().update_jsprincipals_from_document(self);
1207    }
1208
1209    pub(crate) fn set_protocol_handler_automation_mode(&self, mode: CustomHandlersAutomationMode) {
1210        *self.protocol_handler_automation_mode.borrow_mut() = mode;
1211    }
1212
1213    /// <https://dom.spec.whatwg.org/#concept-document-url>
1214    pub(crate) fn url(&self) -> ServoUrl {
1215        self.url.borrow().clone()
1216    }
1217
1218    pub(crate) fn set_url(&self, url: ServoUrl) {
1219        *self.url.borrow_mut() = url;
1220    }
1221
1222    pub(crate) fn about_base_url(&self) -> Option<ServoUrl> {
1223        self.about_base_url.borrow().clone()
1224    }
1225
1226    pub(crate) fn set_about_base_url(&self, about_base_url: Option<ServoUrl>) {
1227        *self.about_base_url.borrow_mut() = about_base_url;
1228    }
1229
1230    /// <https://html.spec.whatwg.org/multipage/#fallback-base-url>
1231    pub(crate) fn fallback_base_url(&self) -> ServoUrl {
1232        let document_url = self.url();
1233        // Step 1: If document is an iframe srcdoc document:
1234        if document_url.as_str() == "about:srcdoc" {
1235            // Step 1.1: Assert: document's about base URL is non-null.
1236            // Step 1.2: Return document's about base URL.
1237            return self
1238                .about_base_url()
1239                .expect("about:srcdoc page should always have an about base URL");
1240        }
1241
1242        // Step 2: If document's URL matches about:blank and document's about base URL is
1243        // non-null, then return document's about base URL.
1244        if document_url.matches_about_blank() &&
1245            let Some(about_base_url) = self.about_base_url()
1246        {
1247            return about_base_url;
1248        }
1249
1250        // Step 3: Return document's URL.
1251        document_url
1252    }
1253
1254    /// <https://html.spec.whatwg.org/multipage/#document-base-url>
1255    pub(crate) fn base_url(&self) -> ServoUrl {
1256        match self.base_element() {
1257            // Step 1.
1258            None => self.fallback_base_url(),
1259            // Step 2.
1260            Some(base) => base.frozen_base_url(),
1261        }
1262    }
1263
1264    pub(crate) fn add_restyle_reason(&self, reason: RestyleReason) {
1265        self.needs_restyle.set(self.needs_restyle.get() | reason)
1266    }
1267
1268    pub(crate) fn clear_restyle_reasons(&self) {
1269        self.needs_restyle.set(RestyleReason::empty());
1270    }
1271
1272    pub(crate) fn stylesheets_changed_since_last_reflow(&self) -> bool {
1273        self.stylesheets.borrow().has_changed()
1274    }
1275
1276    pub(crate) fn restyle_reason(&self, no_gc: &NoGC) -> RestyleReason {
1277        let mut condition = self.needs_restyle.get();
1278        if self.stylesheets_changed_since_last_reflow() {
1279            condition.insert(RestyleReason::StylesheetsChanged);
1280        }
1281
1282        // FIXME: This should check the dirty bit on the document,
1283        // not the document element. Needs some layout changes to make
1284        // that workable.
1285        if let Some(root) = self.get_document_element_unrooted(no_gc) &&
1286            root.has_dirty_descendants()
1287        {
1288            condition.insert(RestyleReason::DOMChanged);
1289        }
1290
1291        if !self.pending_restyles.borrow().is_empty() {
1292            condition.insert(RestyleReason::PendingRestyles);
1293        }
1294
1295        condition
1296    }
1297
1298    /// Returns the first `base` element in the DOM that has an `href` attribute.
1299    pub(crate) fn base_element(&self) -> Option<DomRoot<HTMLBaseElement>> {
1300        self.base_element.get()
1301    }
1302
1303    /// Returns the first `base` element in the DOM (doesn't need to have an `href` attribute).
1304    pub(crate) fn target_base_element(&self) -> Option<DomRoot<HTMLBaseElement>> {
1305        self.target_base_element.get()
1306    }
1307
1308    /// Refresh the cached first base element in the DOM.
1309    pub(crate) fn refresh_base_element(&self, cx: &mut JSContext) {
1310        if let Some(base_element) = self.base_element.get() {
1311            base_element.clear_frozen_base_url();
1312        }
1313        let new_base_element = self
1314            .upcast::<Node>()
1315            .traverse_preorder(ShadowIncluding::No)
1316            .filter_map(DomRoot::downcast::<HTMLBaseElement>)
1317            .find(|element| {
1318                element
1319                    .upcast::<Element>()
1320                    .has_attribute(&local_name!("href"))
1321            });
1322        if let Some(ref new_base_element) = new_base_element {
1323            new_base_element.set_frozen_base_url(cx);
1324        }
1325        self.base_element.set(new_base_element.as_deref());
1326
1327        let new_target_base_element = self
1328            .upcast::<Node>()
1329            .traverse_preorder(ShadowIncluding::No)
1330            .filter_map(DomRoot::downcast::<HTMLBaseElement>)
1331            .next();
1332        self.target_base_element
1333            .set(new_target_base_element.as_deref());
1334    }
1335
1336    pub(crate) fn quirks_mode(&self) -> QuirksMode {
1337        self.quirks_mode.get()
1338    }
1339
1340    pub(crate) fn set_quirks_mode(&self, new_mode: QuirksMode) {
1341        let old_mode = self.quirks_mode.replace(new_mode);
1342
1343        if old_mode != new_mode {
1344            self.window.layout_mut().set_quirks_mode(new_mode);
1345        }
1346    }
1347
1348    pub(crate) fn encoding(&self) -> &'static Encoding {
1349        self.encoding.get()
1350    }
1351
1352    pub(crate) fn set_encoding(&self, encoding: &'static Encoding) {
1353        self.encoding.set(encoding);
1354    }
1355
1356    pub(crate) fn content_and_heritage_changed(&self, no_gc: &NoGC, node: &Node) {
1357        if node.is::<Document>() {
1358            self.document_element_changed();
1359        }
1360
1361        // TODO: A change to the children of a node only affects style when dealing with
1362        // selectors like `:has()`, so the application of this restyle should be more
1363        // targeted like in Gecko.
1364        // See https://searchfox.org/firefox-main/rev/7d438b99e58d16388e4327f2460d14ad4c8be075/layout/style/RestyleManager.cpp#245.
1365        node.dirty(no_gc, NodeDamage::ContentOrHeritage);
1366    }
1367
1368    /// Remove any existing association between the provided id and any elements in this document.
1369    pub(crate) fn unregister_element_id(&self, cx: &mut JSContext, id: &Atom) {
1370        self.id_map.remove(id);
1371        self.reset_form_owner_for_listeners(cx, id);
1372    }
1373
1374    /// Associate an element present in this document with the provided id.
1375    pub(crate) fn register_element_id(&self, cx: &mut JSContext, element: &Element, id: &Atom) {
1376        self.id_map.add(id, element);
1377        self.reset_form_owner_for_listeners(cx, id);
1378    }
1379
1380    /// Remove any existing association between the provided name and any elements in this document.
1381    pub(crate) fn unregister_element_name(&self, name: &Atom) {
1382        self.name_map.remove(name);
1383    }
1384
1385    /// Associate an element present in this document with the provided name.
1386    pub(crate) fn register_element_name(&self, element: &Element, name: &Atom) {
1387        self.name_map.add(name, element);
1388    }
1389
1390    pub(crate) fn register_form_id_listener<T: ?Sized + FormControl>(
1391        &self,
1392        id: DOMString,
1393        listener: &T,
1394    ) {
1395        let mut map = self.form_id_listener_map.borrow_mut();
1396        let listener = listener.to_element();
1397        let set = map.entry(Atom::from(id)).or_default();
1398        set.insert(Dom::from_ref(listener));
1399    }
1400
1401    pub(crate) fn unregister_form_id_listener<T: ?Sized + FormControl>(
1402        &self,
1403        id: DOMString,
1404        listener: &T,
1405    ) {
1406        let mut map = self.form_id_listener_map.borrow_mut();
1407        if let Occupied(mut entry) = map.entry(Atom::from(id)) {
1408            entry
1409                .get_mut()
1410                .remove(&Dom::from_ref(listener.to_element()));
1411            if entry.get().is_empty() {
1412                entry.remove();
1413            }
1414        }
1415    }
1416
1417    /// <https://html.spec.whatwg.org/multipage/#find-a-potential-indicated-element>
1418    fn find_a_potential_indicated_element(
1419        &self,
1420        cx: &mut JSContext,
1421        fragment: &str,
1422    ) -> Option<DomRoot<Element>> {
1423        // Step 1. If there is an element in the document tree whose root is
1424        // document and that has an ID equal to fragment, then return the first such element in tree order.
1425        // Step 3. Return null.
1426        self.get_element_by_id(cx.no_gc(), &Atom::from(fragment))
1427            // Step 2. If there is an a element in the document tree whose root is
1428            // document that has a name attribute whose value is equal to fragment,
1429            // then return the first such element in tree order.
1430            .or_else(|| self.get_anchor_by_name(cx, fragment))
1431    }
1432
1433    /// Attempt to find a named element in this page's document.
1434    /// <https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document>
1435    fn select_indicated_part(&self, cx: &mut JSContext, fragment: &str) -> Option<DomRoot<Node>> {
1436        // Step 1. If document's URL does not equal url with exclude fragments set to true, then return null.
1437        //
1438        // Already handled by calling function
1439
1440        // Step 2. Let fragment be url's fragment.
1441        //
1442        // Already handled by calling function
1443
1444        // Step 3. If fragment is the empty string, then return the special value top of the document.
1445        if fragment.is_empty() {
1446            return Some(DomRoot::from_ref(self.upcast()));
1447        }
1448        // Step 4. Let potentialIndicatedElement be the result of finding a potential indicated element given document and fragment.
1449        if let Some(potential_indicated_element) =
1450            self.find_a_potential_indicated_element(cx, fragment)
1451        {
1452            // Step 5. If potentialIndicatedElement is not null, then return potentialIndicatedElement.
1453            return Some(DomRoot::upcast(potential_indicated_element));
1454        }
1455        // Step 6. Let fragmentBytes be the result of percent-decoding fragment.
1456        let fragment_bytes = percent_decode(fragment.as_bytes());
1457        // Step 7. Let decodedFragment be the result of running UTF-8 decode without BOM on fragmentBytes.
1458        let Ok(decoded_fragment) = fragment_bytes.decode_utf8() else {
1459            return None;
1460        };
1461        // Step 8. Set potentialIndicatedElement to the result of finding a potential indicated element given document and decodedFragment.
1462        if let Some(potential_indicated_element) =
1463            self.find_a_potential_indicated_element(cx, &decoded_fragment)
1464        {
1465            // Step 9. If potentialIndicatedElement is not null, then return potentialIndicatedElement.
1466            return Some(DomRoot::upcast(potential_indicated_element));
1467        }
1468        // Step 10. If decodedFragment is an ASCII case-insensitive match for the string top, then return the top of the document.
1469        if decoded_fragment.eq_ignore_ascii_case("top") {
1470            return Some(DomRoot::from_ref(self.upcast()));
1471        }
1472        // Step 11. Return null.
1473        None
1474    }
1475
1476    /// <https://html.spec.whatwg.org/multipage/#scroll-to-the-fragment-identifier>
1477    pub(crate) fn scroll_to_the_fragment(&self, cx: &mut JSContext, fragment: &str) {
1478        // Step 1. If document's indicated part is null, then set document's target element to null.
1479        //
1480        // > For an HTML document document, its indicated part is the result of
1481        // > selecting the indicated part given document and document's URL.
1482        let Some(indicated_part) = self.select_indicated_part(cx, fragment) else {
1483            self.set_target_element(None);
1484            return;
1485        };
1486        // Step 2. Otherwise, if document's indicated part is top of the document, then:
1487        if *indicated_part == *self.upcast() {
1488            // Step 2.1. Set document's target element to null.
1489            self.set_target_element(None);
1490            // Step 2.2. Scroll to the beginning of the document for document. [CSSOMVIEW]
1491            //
1492            // FIXME(stshine): this should be the origin of the stacking context space,
1493            // which may differ under the influence of writing mode.
1494            self.window.scroll(cx, 0.0, 0.0, ScrollBehavior::Instant);
1495            // Step 2.3. Return.
1496            return;
1497        }
1498        // Step 3. Otherwise:
1499        // Step 3.2. Let target be document's indicated part.
1500        let Some(target) = indicated_part.downcast::<Element>() else {
1501            // Step 3.1. Assert: document's indicated part is an element.
1502            unreachable!("Indicated part should always be an element");
1503        };
1504        // Step 3.3. Set document's target element to target.
1505        self.set_target_element(Some(target));
1506        // Step 3.4. Run the ancestor revealing algorithm on target.
1507        // TODO
1508        // Step 3.5. Scroll target into view, with behavior set to "auto", block set to "start", and inline set to "nearest". [CSSOMVIEW]
1509        target.scroll_into_view_with_options(
1510            cx,
1511            ScrollBehavior::Auto,
1512            ScrollAxisState::new_always_scroll_position(ScrollLogicalPosition::Start),
1513            ScrollAxisState::new_always_scroll_position(ScrollLogicalPosition::Nearest),
1514            None,
1515            None,
1516        );
1517
1518        // Step 3.6. Run the focusing steps for target, with the Document's viewport as the fallback
1519        // target.
1520        indicated_part.run_the_focusing_steps(
1521            cx,
1522            Some(FocusableArea::Viewport),
1523            FocusTrigger::Other,
1524        );
1525
1526        // Step 3.7. Move the sequential focus navigation starting point to target.
1527        self.focus_handler()
1528            .set_sequential_focus_navigation_starting_point(target.upcast());
1529    }
1530
1531    fn get_anchor_by_name(&self, cx: &mut JSContext, name: &str) -> Option<DomRoot<Element>> {
1532        let document_element = self.GetDocumentElement()?;
1533        self.name_map
1534            .get_all(cx.no_gc(), document_element.upcast(), &Atom::from(name))
1535            .iter()
1536            .find(|element| element.is::<HTMLAnchorElement>())
1537            .map(|element| DomRoot::from_ref(&**element))
1538    }
1539
1540    pub(crate) fn notify_embedder_of_load_completion(&self) {
1541        if self.window().is_top_level() {
1542            self.send_to_embedder(EmbedderMsg::NotifyLoadStatusChanged(
1543                self.webview_id(),
1544                LoadStatus::Complete,
1545            ));
1546        }
1547    }
1548
1549    /// Set the `readyState` of this [`Document`] to `loading` for the purposes of the
1550    /// "initialize a document object" part of the specification.
1551    ///
1552    /// See <https://html.spec.whatwg.org/multipage/#initialise-the-document-object>.
1553    pub(crate) fn set_document_readiness_to_loading_for_initialization(&self) {
1554        // https://html.spec.whatwg.org/multipage/#initialise-the-document-object
1555        // > such initial about:blank Document are never created by this algorithm
1556        // TODO(47417): Once "creating a new browsing context" properly exists, remove this check
1557        if self.is_initial_about_blank() {
1558            return;
1559        }
1560
1561        // From <https://w3c.github.io/navigation-timing/#dom-performancetiming-domloading>:
1562        // > This attribute must return the time immediately before the user agent sets the
1563        // > current document readiness to "loading".
1564        update_with_current_instant(&self.navigation_timing.dom_loading);
1565
1566        if self.window.is_top_level() {
1567            let webview_id = self.webview_id();
1568            self.send_to_embedder(EmbedderMsg::NotifyLoadStatusChanged(
1569                webview_id,
1570                LoadStatus::Started,
1571            ));
1572            self.send_to_embedder(EmbedderMsg::Status(webview_id, None));
1573        }
1574
1575        self.ready_state.set(DocumentReadyState::Loading);
1576    }
1577
1578    /// <https://html.spec.whatwg.org/multipage/#update-the-current-document-readiness>
1579    pub(crate) fn update_the_current_document_readiness(
1580        &self,
1581        cx: &mut JSContext,
1582        state: DocumentReadyState,
1583    ) {
1584        // Step 1. If document's current document readiness equals readinessValue, then return.
1585        if self.ready_state.get() == state {
1586            return;
1587        }
1588
1589        // Step 2. Set document's current document readiness to readinessValue.
1590        self.ready_state.set(state);
1591
1592        // Step 3. If document is associated with an HTML parser:
1593        // Step 3.1: Let now be the current high resolution time given document's relevant
1594        // global object.
1595        // Note: Handled implicitly by update_with_current_instant.
1596        match state {
1597            DocumentReadyState::Loading => {},
1598            DocumentReadyState::Complete => {
1599                // This isn't part of the specification, but it's useful to have it here to
1600                // avoid code duplication.
1601                self.notify_embedder_of_load_completion();
1602
1603                // Step 3.2: If readinessValue is "complete", and document's load timing info's
1604                // DOM complete time is 0, then set document's load timing info's DOM complete
1605                // time to now.
1606                // Note: "check for zero" is handled by `.update_with_current_instant`.
1607                update_with_current_instant(&self.navigation_timing.dom_complete);
1608            },
1609            DocumentReadyState::Interactive => {
1610                // Step 3.3: Otherwise, if readinessValue is "interactive", and document's load timing
1611                // info's DOM interactive time is 0, then set document's load timing info's DOM
1612                // interactive time to now.
1613                // Note: "check for zero" is handled by `.update_with_current_instant`.
1614                update_with_current_instant(&self.navigation_timing.dom_interactive)
1615            },
1616        };
1617
1618        // Step 4. Fire an event named readystatechange at document.
1619        self.upcast::<EventTarget>()
1620            .fire_event(cx, atom!("readystatechange"));
1621    }
1622
1623    /// Return whether scripting is enabled or not
1624    /// <https://html.spec.whatwg.org/multipage/#concept-n-script>
1625    pub(crate) fn scripting_enabled(&self) -> bool {
1626        // Scripting is enabled for a node node if node's node document's browsing context is non-null,
1627        // and scripting is enabled for node's relevant settings object.
1628        self.has_browsing_context() &&
1629        // Either settings's global object is not a Window object,
1630        // or settings's global object's associated Document's active sandboxing flag
1631        // set does not have its sandboxed scripts browsing context flag set.
1632            !self.has_active_sandboxing_flag(
1633                SandboxingFlagSet::SANDBOXED_SCRIPTS_BROWSING_CONTEXT_FLAG,
1634            )
1635    }
1636
1637    /// Handles any updates when the document's title has changed.
1638    pub(crate) fn title_changed(&self) {
1639        if self.browsing_context().is_some() {
1640            self.send_title_to_embedder();
1641            let title = String::from(self.Title());
1642            self.window
1643                .send_to_constellation(ScriptToConstellationMessage::TitleChanged(
1644                    self.window.pipeline_id(),
1645                    title.clone(),
1646                ));
1647            if let Some(chan) = self.window.as_global_scope().devtools_chan() {
1648                let _ = chan.send(ScriptToDevtoolsControlMsg::TitleChanged(
1649                    self.window.pipeline_id(),
1650                    title,
1651                ));
1652            }
1653        }
1654    }
1655
1656    /// Determine the title of the [`Document`] according to the specification at:
1657    /// <https://html.spec.whatwg.org/multipage/#document.title>. The difference
1658    /// here is that when the title isn't specified `None` is returned.
1659    fn title(&self) -> Option<DOMString> {
1660        let title = self.GetDocumentElement().and_then(|root| {
1661            if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") {
1662                // Step 1.
1663                root.upcast::<Node>()
1664                    .child_elements()
1665                    .find(|node| {
1666                        node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
1667                    })
1668                    .map(DomRoot::upcast::<Node>)
1669            } else {
1670                // Step 2.
1671                root.upcast::<Node>()
1672                    .traverse_preorder(ShadowIncluding::No)
1673                    .find(|node| node.is::<HTMLTitleElement>())
1674            }
1675        });
1676
1677        title.map(|title| {
1678            // Steps 3-4.
1679            let value = title.child_text_content();
1680            DOMString::from(str_join(value.str().split_html_space_characters(), " "))
1681        })
1682    }
1683
1684    /// Sends this document's title to the constellation.
1685    pub(crate) fn send_title_to_embedder(&self) {
1686        let window = self.window();
1687        if window.is_top_level() {
1688            let title = self.title().map(String::from);
1689            self.send_to_embedder(EmbedderMsg::ChangePageTitle(self.webview_id(), title));
1690        }
1691    }
1692
1693    pub(crate) fn send_to_embedder(&self, msg: EmbedderMsg) {
1694        let window = self.window();
1695        window.send_to_embedder(msg);
1696    }
1697
1698    pub(crate) fn dirty_all_nodes(&self, no_gc: &NoGC) {
1699        let root = match self.GetDocumentElement() {
1700            Some(root) => root,
1701            None => return,
1702        };
1703        for node in root
1704            .upcast::<Node>()
1705            .traverse_preorder_unrooted(no_gc, ShadowIncluding::Yes)
1706        {
1707            node.dirty(no_gc, NodeDamage::Other)
1708        }
1709    }
1710
1711    /// <https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps>
1712    pub(crate) fn run_the_scroll_steps(&self, cx: &mut JSContext) {
1713        // Step 1: For each scrolling box `box` that was scrolled:
1714        //
1715        // Note: Since scrolling is currently synchronous (no scroll animations /
1716        // smooth scrolling), we consider any box event target that had a scroll
1717        // event to be a box that scrolled. Once scrolling is asynchronous this
1718        // should reflect scrolling targets which have finished their scroll
1719        // animation.
1720        let boxes_that_were_scrolled: Vec<_> = self
1721            .pending_scroll_events
1722            .borrow()
1723            .iter()
1724            .filter_map(|pending_event| {
1725                if &*pending_event.event == "scroll" {
1726                    Some(pending_event.target.as_rooted())
1727                } else {
1728                    None
1729                }
1730            })
1731            .collect();
1732
1733        for target in boxes_that_were_scrolled.into_iter() {
1734            // Step 1.1: If box belongs to a viewport, let doc be the viewport’s associated
1735            // Document and target be the viewport. If box belongs to a VisualViewport,
1736            // let doc be the VisualViewport’s associated document and target be the
1737            // VisualViewport. Otherwise, box belongs to an element and let doc be the
1738            // element’s node document and target be the element.
1739            let Some(element) = target.downcast::<Element>() else {
1740                continue;
1741            };
1742            let document = element.owner_document();
1743
1744            // Step 1.2: If box belongs to a snap container, snapcontainer, run the
1745            // update scrollsnapchange targets steps for snapcontainer.
1746            // TODO: Implement this.
1747
1748            // Step 1.3: If (target, "scrollend") is already in doc’s pending scroll
1749            // events, abort these steps.
1750            let mut pending_scroll_events = document.pending_scroll_events.borrow_mut();
1751            let event = "scrollend".into();
1752            if pending_scroll_events
1753                .iter()
1754                .any(|existing| existing.equivalent(&target, &event))
1755            {
1756                continue;
1757            }
1758
1759            // > Step 1.4: Append (target, "scrollend") to doc’s pending scroll events.
1760            pending_scroll_events.push(PendingScrollEvent {
1761                target: target.as_traced(),
1762                event: "scrollend".into(),
1763            });
1764        }
1765
1766        // Step 2: For each item (target, type) in doc’s pending scroll events, in
1767        // the order they were added to the list, run these substeps:
1768        rooted_vec!(let pending_scroll_events <- self.pending_scroll_events.take().into_iter());
1769        for pending_event in pending_scroll_events.iter() {
1770            // Step 2.1: If target is a Document, and type is "scroll" or "scrollend",
1771            // fire an event named type that bubbles at target.
1772            let event = pending_event.event.clone();
1773            if pending_event.target.is::<Document>() {
1774                pending_event.target.fire_bubbling_event(cx, event);
1775            }
1776            // Step 2.2: Otherwise, if type is "scrollsnapchange", then:
1777            //  ....
1778            // TODO: Implement this.
1779            // Step 2.3: Otherwise, if type is "scrollsnapchanging", then:
1780            //  ...
1781            // TODO: Implement this.
1782            //
1783            // Step 2.4: Otherwise, fire an event named type at target.
1784            else {
1785                pending_event.target.fire_event(cx, event);
1786            }
1787        }
1788
1789        // Step 3. Empty doc’s pending scroll events.
1790        // Note: This is done above.
1791    }
1792
1793    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
1794    ///
1795    /// > Whenever a viewport gets scrolled (whether in response to user interaction or
1796    /// > by an API), the user agent must run these steps:
1797    pub(crate) fn handle_viewport_scroll_event(&self) {
1798        // Step 1: Let doc be the viewport’s associated Document.
1799        //
1800        // Note: This is self.
1801
1802        // >> Step 2: If doc is a snap container, run the steps to update scrollsnapchanging targets
1803        // > for doc with doc’s eventual snap target in the block axis as newBlockTarget and
1804        // > doc’s eventual snap target in the inline axis as newInlineTarget.
1805        //
1806        // TODO(#7673): Implement scroll snapping
1807
1808        // Steps 3 and 4 are shared with other scroll targets.
1809        self.finish_handle_scroll_event(self.upcast());
1810    }
1811
1812    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
1813    ///
1814    /// These are the shared steps 3 and 4 from all scroll targets listed in the
1815    /// first section of the specification.
1816    pub(crate) fn finish_handle_scroll_event(&self, event_target: &EventTarget) {
1817        // Step 3.
1818        // > If the element is already in doc’s pending scroll event targets, abort these steps.
1819        let event = "scroll".into();
1820        if self
1821            .pending_scroll_events
1822            .borrow()
1823            .iter()
1824            .any(|existing| existing.equivalent(event_target, &event))
1825        {
1826            return;
1827        }
1828
1829        // Step 4.
1830        // > Append the element to doc’s pending scroll event targets.
1831        self.pending_scroll_events
1832            .borrow_mut()
1833            .push(PendingScrollEvent {
1834                target: Dom::from_ref(event_target),
1835                event: "scroll".into(),
1836            });
1837    }
1838
1839    /// <https://dom.spec.whatwg.org/#converting-nodes-into-a-node>
1840    pub(crate) fn node_from_nodes_and_strings(
1841        &self,
1842        cx: &mut JSContext,
1843        mut nodes: Vec<NodeOrString>,
1844    ) -> Fallible<DomRoot<Node>> {
1845        if nodes.len() == 1 {
1846            Ok(match nodes.pop().unwrap() {
1847                NodeOrString::Node(node) => node,
1848                NodeOrString::String(string) => DomRoot::upcast(self.CreateTextNode(cx, string)),
1849            })
1850        } else {
1851            let fragment = DomRoot::upcast::<Node>(self.CreateDocumentFragment(cx));
1852            for node in nodes {
1853                match node {
1854                    NodeOrString::Node(node) => {
1855                        fragment.AppendChild(cx, &node)?;
1856                    },
1857                    NodeOrString::String(string) => {
1858                        let node = DomRoot::upcast::<Node>(self.CreateTextNode(cx, string));
1859                        // No try!() here because appending a text node
1860                        // should not fail.
1861                        fragment.AppendChild(cx, &node).unwrap();
1862                    },
1863                }
1864            }
1865            Ok(fragment)
1866        }
1867    }
1868
1869    pub(crate) fn get_body_attribute(&self, local_name: &LocalName) -> DOMString {
1870        match self.GetBody() {
1871            Some(ref body) if body.is_body_element() => {
1872                body.upcast::<Element>().get_string_attribute(local_name)
1873            },
1874            _ => DOMString::new(),
1875        }
1876    }
1877
1878    pub(crate) fn set_body_attribute(
1879        &self,
1880        cx: &mut JSContext,
1881        local_name: &LocalName,
1882        value: DOMString,
1883    ) {
1884        if let Some(ref body) = self.GetBody().filter(|elem| elem.is_body_element()) {
1885            let body = body.upcast::<Element>();
1886            let value = body.parse_attribute(&ns!(), local_name, value);
1887            body.set_attribute(cx, local_name, value);
1888        }
1889    }
1890
1891    pub(crate) fn set_current_script(&self, script: Option<&HTMLScriptElement>) {
1892        self.current_script.set(script);
1893    }
1894
1895    /// <https://html.spec.whatwg.org/multipage/#has-a-style-sheet-that-is-blocking-scripts>
1896    pub(crate) fn has_a_stylesheet_that_is_blocking_scripts(&self) -> bool {
1897        !self.script_blocking_stylesheet_set.borrow().is_empty()
1898    }
1899
1900    pub(crate) fn add_script_blocking_stylesheet(&self, id: StylesheetContextId) {
1901        self.script_blocking_stylesheet_set.borrow_mut().insert(id);
1902    }
1903
1904    pub(crate) fn remove_script_blocking_stylesheet(&self, id: StylesheetContextId) {
1905        self.script_blocking_stylesheet_set
1906            .borrow_mut()
1907            .shift_remove(&id);
1908    }
1909
1910    pub(crate) fn render_blocking_element_count(&self) -> u32 {
1911        self.render_blocking_element_count.get()
1912    }
1913
1914    /// <https://html.spec.whatwg.org/multipage/#block-rendering>
1915    pub(crate) fn increment_render_blocking_element_count(&self) {
1916        // Step 1. Let document be el's node document.
1917        //
1918        // That's self
1919
1920        // Step 2. If document allows adding render-blocking elements,
1921        // then append el to document's render-blocking element set.
1922        assert!(self.allows_adding_render_blocking_elements());
1923        let count_cell = &self.render_blocking_element_count;
1924        count_cell.set(count_cell.get() + 1);
1925    }
1926
1927    /// <https://html.spec.whatwg.org/multipage/#unblock-rendering>
1928    pub(crate) fn decrement_render_blocking_element_count(&self) {
1929        // Step 1. Let document be el's node document.
1930        //
1931        // That's self
1932
1933        // Step 2. Remove el from document's render-blocking element set.
1934        let count_cell = &self.render_blocking_element_count;
1935        assert!(count_cell.get() > 0);
1936        count_cell.set(count_cell.get() - 1);
1937    }
1938
1939    /// <https://html.spec.whatwg.org/multipage/#allows-adding-render-blocking-elements>
1940    pub(crate) fn allows_adding_render_blocking_elements(&self) -> bool {
1941        // > A Document document allows adding render-blocking elements
1942        // > if document's content type is "text/html" and the body element of document is null.
1943        self.is_html_document && self.GetBody().is_none()
1944    }
1945
1946    /// <https://html.spec.whatwg.org/multipage/#render-blocked>
1947    pub(crate) fn is_render_blocked(&self) -> bool {
1948        // > A Document document is render-blocked if both of the following are true:
1949        // > document's render-blocking element set is non-empty,
1950        // > or document allows adding render-blocking elements.
1951        self.render_blocking_element_count() > 0
1952        // TODO: add `allows_adding_render_blocking_elements` which currently breaks for empty iframes
1953        // > The current high resolution time given document's relevant global object
1954        // has not exceeded an implementation-defined timeout value.
1955        // TODO
1956    }
1957
1958    pub(crate) fn invalidate_stylesheets(&self, no_gc: &NoGC) {
1959        self.stylesheets.borrow_mut().force_dirty(OriginSet::all());
1960
1961        // Mark the document element dirty so a reflow will be performed.
1962        //
1963        // FIXME(emilio): Use the DocumentStylesheetSet invalidation stuff.
1964        if let Some(element) = self.GetDocumentElement() {
1965            element.upcast::<Node>().dirty(no_gc, NodeDamage::Style);
1966        }
1967    }
1968
1969    /// Whether or not this `Document` has any active requestAnimationFrame callbacks
1970    /// registered.
1971    pub(crate) fn has_active_request_animation_frame_callbacks(&self) -> bool {
1972        !self.animation_frame_list.borrow().is_empty()
1973    }
1974
1975    /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe>
1976    pub(crate) fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 {
1977        let ident = self.animation_frame_ident.get() + 1;
1978        self.animation_frame_ident.set(ident);
1979
1980        let had_animation_frame_callbacks;
1981        {
1982            let mut animation_frame_list = self.animation_frame_list.borrow_mut();
1983            had_animation_frame_callbacks = !animation_frame_list.is_empty();
1984            animation_frame_list.push_back((ident, Some(callback)));
1985        }
1986
1987        // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks:
1988        // we're guaranteed to already be in the "animation callbacks present" state.
1989        //
1990        // This reduces CPU usage by avoiding needless thread wakeups in the common case of
1991        // repeated rAF.
1992        if !self.running_animation_callbacks.get() && !had_animation_frame_callbacks {
1993            self.window().send_to_constellation(
1994                ScriptToConstellationMessage::ChangeRunningAnimationsState(
1995                    AnimationState::AnimationCallbacksPresent,
1996                ),
1997            );
1998        }
1999
2000        ident
2001    }
2002
2003    /// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe>
2004    pub(crate) fn cancel_animation_frame(&self, ident: u32) {
2005        let mut list = self.animation_frame_list.borrow_mut();
2006        if let Some(pair) = list.iter_mut().find(|pair| pair.0 == ident) {
2007            pair.1 = None;
2008        }
2009    }
2010
2011    /// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks>
2012    pub(crate) fn run_the_animation_frame_callbacks(&self, cx: &mut CurrentRealm) {
2013        self.running_animation_callbacks.set(true);
2014        let timing = self.global().performance(cx).Now();
2015
2016        let num_callbacks = self.animation_frame_list.borrow().len();
2017        for _ in 0..num_callbacks {
2018            rooted!(&in(cx) let maybe_callback = self
2019                .animation_frame_list
2020                .borrow_mut()
2021                .pop_front()
2022                .unwrap()
2023                .1);
2024            if let Some(ref callback) = *maybe_callback {
2025                callback.call(cx, self, *timing);
2026            }
2027        }
2028        self.running_animation_callbacks.set(false);
2029
2030        if self.animation_frame_list.borrow().is_empty() {
2031            self.window().send_to_constellation(
2032                ScriptToConstellationMessage::ChangeRunningAnimationsState(
2033                    AnimationState::AnimationCallbacksAbsent,
2034                ),
2035            );
2036        }
2037    }
2038
2039    pub(crate) fn policy_container(&self) -> Ref<'_, StdArc<PolicyContainer>> {
2040        self.policy_container.borrow()
2041    }
2042
2043    pub(crate) fn set_policy_container(&self, policy_container: StdArc<PolicyContainer>) {
2044        *self.policy_container.borrow_mut() = policy_container;
2045    }
2046
2047    pub(crate) fn set_csp_list(&self, csp_list: Option<CspList>) {
2048        StdArc::make_mut(&mut *self.policy_container.borrow_mut()).set_csp_list(csp_list);
2049    }
2050
2051    /// <https://www.w3.org/TR/CSP/#enforced>
2052    pub(crate) fn enforce_csp_policy(&self, policy: CspPolicy) {
2053        // > A policy is enforced or monitored for a global object by inserting it into the global object’s CSP list.
2054        let mut csp_list = self.get_csp_list().clone().unwrap_or(CspList(vec![]));
2055        csp_list.push(policy);
2056        StdArc::make_mut(&mut *self.policy_container.borrow_mut()).set_csp_list(Some(csp_list));
2057    }
2058
2059    pub(crate) fn get_csp_list(&self) -> Ref<'_, Option<CspList>> {
2060        Ref::map(self.policy_container.borrow(), |policy_container| {
2061            &policy_container.csp_list
2062        })
2063    }
2064
2065    pub(crate) fn preloaded_resources(&self) -> std::cell::Ref<'_, PreloadedResources> {
2066        self.preloaded_resources.borrow()
2067    }
2068
2069    pub(crate) fn insert_preloaded_resource(&self, key: PreloadKey, preload_id: PreloadId) {
2070        self.preloaded_resources
2071            .borrow_mut()
2072            .insert(key, preload_id);
2073    }
2074
2075    pub(crate) fn fetch_blocking<Listener: FetchResponseListener>(
2076        &self,
2077        load: LoadType,
2078        request: RequestBuilder,
2079        listener: Listener,
2080    ) {
2081        self.loader_mut().add_blocking_load(load);
2082        self.fetch_background(request, listener);
2083    }
2084
2085    pub(crate) fn fetch_background<Listener: FetchResponseListener>(
2086        &self,
2087        request_builder: RequestBuilder,
2088        listener: Listener,
2089    ) {
2090        let networking_task_source = self
2091            .owner_global()
2092            .task_manager()
2093            .networking_task_source()
2094            .to_sendable();
2095        self.window()
2096            .as_global_scope()
2097            .fetch(request_builder, listener, networking_task_source);
2098    }
2099
2100    /// <https://fetch.spec.whatwg.org/#deferred-fetch-control-document>
2101    fn deferred_fetch_control_document(&self) -> DomRoot<Document> {
2102        match self.window().window_proxy().frame_element() {
2103            // Step 1. If document’ node navigable’s container document is null
2104            // or a document whose origin is not same origin with document, then return document;
2105            None => DomRoot::from_ref(self),
2106            // otherwise, return the deferred-fetch control document given document’s node navigable’s container document.
2107            Some(container) => container.owner_document().deferred_fetch_control_document(),
2108        }
2109    }
2110
2111    /// <https://fetch.spec.whatwg.org/#available-deferred-fetch-quota>
2112    pub(crate) fn available_deferred_fetch_quota(&self, origin: ImmutableOrigin) -> isize {
2113        // Step 1. Let controlDocument be document’s deferred-fetch control document.
2114        let control_document = self.deferred_fetch_control_document();
2115        // Step 2. Let navigable be controlDocument’s node navigable.
2116        let navigable = control_document.window();
2117        // Step 3. Let isTopLevel be true if controlDocument’s node navigable
2118        // is a top-level traversable; otherwise false.
2119        let is_top_level = navigable.is_top_level();
2120        // Step 4. Let deferredFetchAllowed be true if controlDocument is allowed
2121        // to use the policy-controlled feature "deferred-fetch"; otherwise false.
2122        // TODO
2123        let deferred_fetch_allowed = true;
2124        // Step 5. Let deferredFetchMinimalAllowed be true if controlDocument
2125        // is allowed to use the policy-controlled feature "deferred-fetch-minimal"; otherwise false.
2126        // TODO
2127        let deferred_fetch_minimal_allowed = true;
2128        // Step 6. Let quota be the result of the first matching statement:
2129        let mut quota = match is_top_level {
2130            // isTopLevel is true and deferredFetchAllowed is false
2131            true if !deferred_fetch_allowed => 0,
2132            // isTopLevel is true and deferredFetchMinimalAllowed is false
2133            true if !deferred_fetch_minimal_allowed => 640 * 1024,
2134            // isTopLevel is true
2135            true => 512 * 1024,
2136            // deferredFetchAllowed is true, and navigable’s navigable container’s
2137            // reserved deferred-fetch quota is normal quota
2138            // TODO
2139            _ if deferred_fetch_allowed => 0,
2140            // deferredFetchMinimalAllowed is true, and navigable’s navigable container’s
2141            // reserved deferred-fetch quota is minimal quota
2142            // TODO
2143            _ if deferred_fetch_minimal_allowed => 8 * 1024,
2144            // Otherwise
2145            _ => 0,
2146        } as isize;
2147        // Step 7. Let quotaForRequestOrigin be 64 kibibytes.
2148        let mut quota_for_request_origin = 64 * 1024_isize;
2149        // Step 8. For each navigable in controlDocument’s node navigable’s
2150        // inclusive descendant navigables whose active document’s deferred-fetch control document is controlDocument:
2151        // TODO
2152        // Step 8.1. For each container in navigable’s active document’s shadow-including inclusive descendants
2153        // which is a navigable container, decrement quota by container’s reserved deferred-fetch quota.
2154        // TODO
2155        // Step 8.2. For each deferred fetch record deferredRecord of navigable’s active document’s
2156        // relevant settings object’s fetch group’s deferred fetch records:
2157        let deferred_fetches = navigable.as_global_scope().fetch_group().deferred_fetches();
2158        for deferred_fetch in deferred_fetches {
2159            // Step 8.2.1. If deferredRecord’s invoke state is not "pending", then continue.
2160            if deferred_fetch.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
2161                continue;
2162            }
2163            // Step 8.2.2. Let requestLength be the total request length of deferredRecord’s request.
2164            let request_length = deferred_fetch.request.total_request_length();
2165            // Step 8.2.3. Decrement quota by requestLength.
2166            quota -= request_length as isize;
2167            // Step 8.2.4. If deferredRecord’s request’s URL’s origin is same origin with origin,
2168            // then decrement quotaForRequestOrigin by requestLength.
2169            if deferred_fetch.request.url().origin() == origin {
2170                quota_for_request_origin -= request_length as isize;
2171            }
2172        }
2173        // Step 9. If quota is equal or less than 0, then return 0.
2174        if quota <= 0 {
2175            return 0;
2176        }
2177        // Step 10. If quota is less than quotaForRequestOrigin, then return quota.
2178        if quota < quota_for_request_origin {
2179            return quota;
2180        }
2181        // Step 11. Return quotaForRequestOrigin.
2182        quota_for_request_origin
2183    }
2184
2185    /// <https://html.spec.whatwg.org/multipage/#update-document-for-history-step-application>
2186    pub(crate) fn update_document_for_history_step_application(
2187        &self,
2188        old_url: &ServoUrl,
2189        new_url: &ServoUrl,
2190    ) {
2191        // Step 6. If documentsEntryChanged is true, then:
2192        //
2193        // It is right now since we already have a document and a new_url
2194
2195        // Step 6.1. Let oldURL be document's latest entry's URL.
2196        // Passed in as argument
2197
2198        // Step 6.2. Set document's latest entry to entry.
2199        // TODO
2200        // Step 6.3. Restore the history object state given document and entry.
2201        // TODO
2202        // Step 6.4. If documentIsNew is false, then:
2203        // TODO
2204        // Step 6.4.1. Assert: navigationType is not null.
2205        // TODO
2206        // Step 6.4.2. Update the navigation API entries for a same-document navigation given navigation, entry, and navigationType.
2207        // TODO
2208        // Step 6.4.3. Fire an event named popstate at document's relevant global object, using PopStateEvent,
2209        // with the state attribute initialized to document's history object's state and hasUAVisualTransition
2210        // initialized to true if a visual transition, to display a cached rendered state of the latest entry, was done by the user agent.
2211        // TODO
2212        // Step 6.4.4. Restore persisted state given entry.
2213        // TODO
2214
2215        // Step 6.4.5. If oldURL's fragment is not equal to entry's URL's fragment,
2216        // then queue a global task on the DOM manipulation task source given document's relevant global object
2217        // to fire an event named hashchange at document's relevant global object, using HashChangeEvent,
2218        // with the oldURL attribute initialized to the serialization of oldURL
2219        // and the newURL attribute initialized to the serialization of entry's URL.
2220        if old_url.as_url()[Position::BeforeFragment..] !=
2221            new_url.as_url()[Position::BeforeFragment..]
2222        {
2223            let window = Trusted::new(self.owner_window().deref());
2224            let old_url = old_url.to_string();
2225            let new_url = new_url.to_string();
2226            self.owner_global()
2227                .task_manager()
2228                .dom_manipulation_task_source()
2229                .queue(task!(hashchange_event: move |cx| {
2230                        let window = window.root();
2231                        HashChangeEvent::new(
2232                            cx,
2233                            &window,
2234                            atom!("hashchange"),
2235                            false,
2236                            false,
2237                            old_url,
2238                            new_url,
2239                        )
2240                        .upcast::<Event>()
2241                        .fire(cx, window.upcast());
2242                }));
2243        }
2244    }
2245
2246    pub(crate) fn finish_load_for_dropped_blocker(&self, load: LoadType) {
2247        let this = Trusted::new(self);
2248        self.owner_global()
2249            .task_manager()
2250            .dom_manipulation_task_source()
2251            .queue(task!(check_finished_load: move |cx| {
2252                this.root().finish_load(load, cx);
2253            }));
2254    }
2255
2256    /// Step 8 of <https://html.spec.whatwg.org/multipage/#the-end>
2257    /// <https://html.spec.whatwg.org/multipage/#delay-the-load-event>
2258    pub(crate) fn finish_load(&self, load: LoadType, cx: &mut JSContext) {
2259        // This does not delay the load event anymore.
2260        debug!("Document {:?} got finish_load: {:?}", self.url(), load);
2261        self.loader.borrow_mut().finish_load(&load);
2262
2263        match load {
2264            LoadType::Stylesheet(_) => {
2265                // A stylesheet finishing to load may unblock any pending
2266                // parsing-blocking script or deferred script.
2267                self.process_pending_parsing_blocking_script(cx);
2268
2269                // Step 3.
2270                self.process_deferred_scripts(cx);
2271            },
2272            LoadType::PageSource(_) => {
2273                // We finished loading the page, so if the `Window` is still waiting for
2274                // the first layout, allow it.
2275                if self.has_browsing_context && self.is_fully_active() {
2276                    self.window().allow_layout_if_necessary(cx);
2277                }
2278
2279                // Deferred scripts have to wait for page to finish loading,
2280                // this is the first opportunity to process them.
2281
2282                // Step 3.
2283                self.process_deferred_scripts(cx);
2284            },
2285            _ => {},
2286        }
2287
2288        // Step 8. Spin the event loop until there is nothing that delays the load event in the Document.
2289        let document = Trusted::new(self);
2290        self.owner_global()
2291            .task_manager()
2292            .dom_manipulation_task_source()
2293            .queue(task!(wait_for_load_blockers: move |cx| {
2294                document.root().wait_until_load_blockers_have_resolved(cx);
2295            }));
2296    }
2297
2298    /// <https://html.spec.whatwg.org/multipage/#checking-if-unloading-is-canceled>
2299    pub(crate) fn check_if_unloading_is_cancelled(
2300        &self,
2301        cx: &mut JSContext,
2302        recursive_flag: bool,
2303    ) -> bool {
2304        // TODO: Step 1, increase the event loop's termination nesting level by 1.
2305        // Step 2
2306        self.incr_ignore_opens_during_unload_counter();
2307        // Step 3-5.
2308        let beforeunload_event = BeforeUnloadEvent::new(
2309            cx,
2310            &self.window,
2311            atom!("beforeunload"),
2312            EventBubbles::Bubbles,
2313            EventCancelable::Cancelable,
2314        );
2315        let event = beforeunload_event.upcast::<Event>();
2316        event.set_trusted(true);
2317        let event_target = self.window.upcast::<EventTarget>();
2318        let has_listeners = event_target.has_listeners_for(&atom!("beforeunload"));
2319        self.window.dispatch_event_with_target_override(cx, event);
2320        // TODO: Step 6, decrease the event loop's termination nesting level by 1.
2321        // Step 7
2322        if has_listeners {
2323            self.salvageable.set(false);
2324        }
2325        let mut can_unload = true;
2326        // TODO: Step 8, also check sandboxing modals flag.
2327        let default_prevented = event.DefaultPrevented();
2328        let return_value_not_empty = !event
2329            .downcast::<BeforeUnloadEvent>()
2330            .unwrap()
2331            .ReturnValue()
2332            .is_empty();
2333        if default_prevented || return_value_not_empty {
2334            let (chan, port) = generic_channel::channel().expect("Failed to create IPC channel!");
2335            let msg = EmbedderMsg::AllowUnload(self.webview_id(), chan);
2336            self.send_to_embedder(msg);
2337            can_unload = port.recv().unwrap() == AllowOrDeny::Allow;
2338        }
2339        // Step 9
2340        if !recursive_flag {
2341            // `check_if_unloading_is_cancelled` might cause futher modifications to the DOM so collecting here prevents
2342            // a double borrow if the `IFrameCollection` needs to be validated again.
2343            let iframes: Vec<_> = self.iframes().iter().collect();
2344            for iframe in &iframes {
2345                // TODO: handle the case of cross origin iframes.
2346                let document = iframe.owner_document();
2347                can_unload = document.check_if_unloading_is_cancelled(cx, true);
2348                if !document.salvageable() {
2349                    self.salvageable.set(false);
2350                }
2351                if !can_unload {
2352                    break;
2353                }
2354            }
2355        }
2356        // Step 10
2357        self.decr_ignore_opens_during_unload_counter();
2358        can_unload
2359    }
2360
2361    /// <https://html.spec.whatwg.org/multipage/#unload-a-document>
2362    pub(crate) fn unload(&self, cx: &mut JSContext, recursive_flag: bool) {
2363        if self.window_detached() {
2364            return;
2365        }
2366
2367        // TODO: Step 1, increase the event loop's termination nesting level by 1.
2368        // Step 2
2369        self.incr_ignore_opens_during_unload_counter();
2370        // Step 3-6 If oldDocument's page showing is true:
2371        if self.page_showing.get() {
2372            // Set oldDocument's page showing to false.
2373            self.page_showing.set(false);
2374            // Fire a page transition event named pagehide at oldDocument's relevant global object with oldDocument's
2375            // salvageable state.
2376            let event = PageTransitionEvent::new(
2377                cx,
2378                &self.window,
2379                atom!("pagehide"),
2380                false,                  // bubbles
2381                false,                  // cancelable
2382                self.salvageable.get(), // persisted
2383            );
2384            let event = event.upcast::<Event>();
2385            event.set_trusted(true);
2386            self.window.dispatch_event_with_target_override(cx, event);
2387            // Step 6 Update the visibility state of oldDocument to "hidden".
2388            self.update_visibility_state(cx, DocumentVisibilityState::Hidden);
2389        }
2390        // Step 7
2391        if !self.fired_unload.get() {
2392            let event = Event::new(
2393                cx,
2394                self.window.upcast(),
2395                atom!("unload"),
2396                EventBubbles::Bubbles,
2397                EventCancelable::Cancelable,
2398            );
2399            event.set_trusted(true);
2400            let event_target = self.window.upcast::<EventTarget>();
2401            let has_listeners = event_target.has_listeners_for(&atom!("unload"));
2402            self.window.dispatch_event_with_target_override(cx, &event);
2403            self.fired_unload.set(true);
2404            // Step 9
2405            if has_listeners {
2406                self.salvageable.set(false);
2407            }
2408        }
2409        // TODO: Step 8, decrease the event loop's termination nesting level by 1.
2410
2411        // Step 13
2412        if !recursive_flag {
2413            // `unload` might cause futher modifications to the DOM so collecting here prevents
2414            // a double borrow if the `IFrameCollection` needs to be validated again.
2415            let iframes: Vec<_> = self.iframes().iter().collect();
2416            for iframe in &iframes {
2417                // TODO: handle the case of cross origin iframes.
2418                let document = iframe.owner_document();
2419                document.unload(cx, true);
2420                if !document.salvageable() {
2421                    self.salvageable.set(false);
2422                }
2423            }
2424        }
2425
2426        // Step 18. Run any unloading document cleanup steps for oldDocument that are defined by this specification and other applicable specifications.
2427        self.unloading_cleanup_steps(cx);
2428
2429        // https://w3c.github.io/FileAPI/#lifeTime
2430        self.window.as_global_scope().clean_up_all_file_resources();
2431
2432        // Step 15, End
2433        self.decr_ignore_opens_during_unload_counter();
2434
2435        // Step 20. If oldDocument's salvageable state is false, then destroy oldDocument.
2436        // TODO
2437    }
2438
2439    /// <https://html.spec.whatwg.org/multipage/#completely-finish-loading>
2440    fn completely_finish_loading(&self) {
2441        // Step 1. Assert: document's browsing context is non-null.
2442        // TODO: Adding this assert fails a lot of tests
2443
2444        // Step 2. Set document's completely loaded time to the current time.
2445        self.completely_loaded.set(true);
2446        // Step 3. Let container be document's node navigable's container.
2447        // TODO
2448
2449        // Step 4. If container is an iframe element, then queue an element task
2450        // on the DOM manipulation task source given container to run the iframe load event steps given container.
2451        //
2452        // Note: this will also result in the "iframe-load-event-steps" being run.
2453        // https://html.spec.whatwg.org/multipage/#iframe-load-event-steps
2454        self.notify_constellation_load();
2455
2456        // Step 5. Otherwise, if container is non-null, then queue an element task on the DOM manipulation task source
2457        // given container to fire an event named load at container.
2458        // TODO
2459
2460        // Step 13 of https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps
2461        //
2462        // At least time seconds have elapsed since document's completely loaded time,
2463        // adjusted to take into account user or user agent preferences.
2464        if let Some(DeclarativeRefresh::PendingLoad {
2465            url,
2466            time,
2467            from_meta_element,
2468        }) = &*self.declarative_refresh.borrow()
2469        {
2470            self.window.as_global_scope().schedule_callback(
2471                OneshotTimerCallback::RefreshRedirectDue(RefreshRedirectDue {
2472                    url: url.clone(),
2473                    from_meta_element: *from_meta_element,
2474                }),
2475                Duration::from_secs(*time),
2476            );
2477        }
2478    }
2479
2480    /// Step 9 of <https://html.spec.whatwg.org/multipage/#the-end>
2481    fn queue_document_completion(&self, cx: &mut JSContext) {
2482        // The initial about:blank document passes through
2483        // https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context
2484        // instead of the steps used by other documents.
2485        assert!(!self.is_initial_about_blank());
2486
2487        self.loader.borrow_mut().inhibit_events();
2488
2489        // The rest will ever run only once per document.
2490
2491        // Step 9. Queue a global task on the DOM manipulation task source given
2492        // the Document's relevant global object to run the following steps:
2493        debug!("Document loads are complete.");
2494        let document = Trusted::new(self);
2495        self.owner_global()
2496            .task_manager()
2497            .dom_manipulation_task_source()
2498            .queue(task!(fire_load_event: move |cx| {
2499                let document = document.root();
2500                // Step 9.3. Let window be the Document's relevant global object.
2501                let window = document.window();
2502                if !window.is_alive() || document.window_detached() {
2503                    return;
2504                }
2505
2506                // Step 9.1. Update the current document readiness to "complete".
2507                document.update_the_current_document_readiness(cx, DocumentReadyState::Complete);
2508
2509                // Step 9.2. If the Document object's browsing context is null, then abort these steps.
2510                if document.browsing_context().is_none() {
2511                    return;
2512                }
2513
2514                // Step 9.4. Set the Document's load timing info's load event start time to the current high resolution time given window.
2515                update_with_current_instant(&document.navigation_timing.load_event_start);
2516
2517                // Step 9.5. Fire an event named load at window, with legacy target override flag set.
2518                let load_event = Event::new(
2519                    cx,
2520                    window.upcast(),
2521                    atom!("load"),
2522                    EventBubbles::DoesNotBubble,
2523                    EventCancelable::NotCancelable,
2524                );
2525                load_event.set_trusted(true);
2526                debug!("About to dispatch load for {:?}", document.url());
2527                window.dispatch_event_with_target_override(cx, &load_event);
2528
2529                // Step 9.6. Invoke WebDriver BiDi load complete with the Document's browsing context,
2530                // and a new WebDriver BiDi navigation status whose id is the Document object's during-loading navigation ID
2531                // for WebDriver BiDi, status is "complete", and url is the Document object's URL.
2532                // TODO
2533
2534                // Step 9.7. Set the Document object's during-loading navigation ID for WebDriver BiDi to null.
2535                // TODO
2536
2537                // Step 9.8. Set the Document's load timing info's load event end time to the current high resolution time given window.
2538                update_with_current_instant(&document.navigation_timing.load_event_end);
2539
2540                // Step 9.9. Assert: Document's page showing is false.
2541                // TODO: Adding this assert fails a lot of tests
2542
2543                // Step 9.10. Set the Document's page showing to true.
2544                document.page_showing.set(true);
2545
2546                // Step 9.11. Fire a page transition event named pageshow at window with false.
2547                let page_show_event = PageTransitionEvent::new(
2548                    cx,
2549                    window,
2550                    atom!("pageshow"),
2551                    false, // bubbles
2552                    false, // cancelable
2553                    false, // persisted
2554                );
2555                let page_show_event = page_show_event.upcast::<Event>();
2556                page_show_event.set_trusted(true);
2557                page_show_event.fire(cx, window.upcast());
2558
2559                // Step 9.12. Completely finish loading the Document.
2560                document.completely_finish_loading();
2561
2562                // Step 9.13. Queue the navigation timing entry for the Document.
2563                // TODO
2564
2565                if let Some(fragment) = document.url().fragment() {
2566                    document.scroll_to_the_fragment(cx, fragment);
2567                }
2568            }));
2569
2570        // Step 9.
2571        // TODO: pending application cache download process tasks.
2572
2573        // Step 10.
2574        // TODO: printing steps.
2575
2576        // Step 11.
2577        // TODO: ready for post-load tasks.
2578
2579        // The dom.webxr.sessionavailable pref allows webxr
2580        // content to immediately begin a session without waiting for a user gesture.
2581        // TODO: should this only happen on the first document loaded?
2582        // https://immersive-web.github.io/webxr/#user-intention
2583        // https://github.com/immersive-web/navigation/issues/10
2584        #[cfg(feature = "webxr")]
2585        if pref!(dom_webxr_sessionavailable) && self.window.is_top_level() {
2586            self.window.Navigator(cx).Xr(cx).dispatch_sessionavailable();
2587        }
2588    }
2589
2590    pub(crate) fn completely_loaded(&self) -> bool {
2591        self.completely_loaded.get()
2592    }
2593
2594    pub(crate) fn start_the_end_loading_phase(&self) {
2595        if self.is_initial_about_blank() {
2596            // TODO(47417): Once "creating a new browsing context" properly exists, remove this check
2597            self.current_the_end_loading_phase
2598                .set(TheEndLoadingPhase::Done);
2599        } else {
2600            self.current_the_end_loading_phase
2601                .set(TheEndLoadingPhase::ProcessingDeferredScripts);
2602        }
2603    }
2604
2605    /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script>
2606    pub(crate) fn set_pending_parsing_blocking_script(
2607        &self,
2608        script: &HTMLScriptElement,
2609        load: Option<ScriptResult>,
2610    ) {
2611        assert!(!self.has_pending_parsing_blocking_script());
2612        *self.pending_parsing_blocking_script.borrow_mut() =
2613            Some(PendingScript::new_with_load(script, load));
2614    }
2615
2616    /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script>
2617    pub(crate) fn has_pending_parsing_blocking_script(&self) -> bool {
2618        self.pending_parsing_blocking_script.borrow().is_some()
2619    }
2620
2621    /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> step 22.d.
2622    pub(crate) fn pending_parsing_blocking_script_loaded(
2623        &self,
2624        element: &HTMLScriptElement,
2625        result: ScriptResult,
2626        cx: &mut JSContext,
2627    ) {
2628        {
2629            let mut blocking_script = self.pending_parsing_blocking_script.borrow_mut();
2630            let entry = blocking_script.as_mut().unwrap();
2631            assert!(&*entry.element == element);
2632            entry.loaded(result);
2633        }
2634        self.process_pending_parsing_blocking_script(cx);
2635    }
2636
2637    fn process_pending_parsing_blocking_script(&self, cx: &mut JSContext) {
2638        if self.has_a_stylesheet_that_is_blocking_scripts() {
2639            return;
2640        }
2641        let pair = self
2642            .pending_parsing_blocking_script
2643            .borrow_mut()
2644            .as_mut()
2645            .and_then(PendingScript::take_result);
2646        if let Some((element, result)) = pair {
2647            *self.pending_parsing_blocking_script.borrow_mut() = None;
2648            self.get_current_parser()
2649                .unwrap()
2650                .resume_with_pending_parsing_blocking_script(cx, &element, result);
2651        }
2652    }
2653
2654    /// <https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible>
2655    pub(crate) fn add_asap_script(&self, script: &HTMLScriptElement) {
2656        self.asap_scripts_set
2657            .borrow_mut()
2658            .push(Dom::from_ref(script));
2659    }
2660
2661    /// <https://html.spec.whatwg.org/multipage/#the-end> step 5.
2662    /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> step 22.d.
2663    pub(crate) fn asap_script_loaded(
2664        &self,
2665        cx: &mut JSContext,
2666        element: &HTMLScriptElement,
2667        result: ScriptResult,
2668    ) {
2669        {
2670            let mut scripts = self.asap_scripts_set.borrow_mut();
2671            let idx = scripts
2672                .iter()
2673                .position(|entry| &**entry == element)
2674                .unwrap();
2675            scripts.swap_remove(idx);
2676        }
2677        element.execute(cx, result);
2678        self.wait_until_asap_scripts_have_executed();
2679    }
2680
2681    /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible>
2682    pub(crate) fn push_asap_in_order_script(&self, script: &HTMLScriptElement) {
2683        self.asap_in_order_scripts_list.push(script);
2684    }
2685
2686    /// <https://html.spec.whatwg.org/multipage/#the-end> step 5.
2687    /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> step> 22.c.
2688    pub(crate) fn asap_in_order_script_loaded(
2689        &self,
2690        cx: &mut JSContext,
2691        element: &HTMLScriptElement,
2692        result: ScriptResult,
2693    ) {
2694        self.asap_in_order_scripts_list.loaded(element, result);
2695        while let Some((element, result)) = self
2696            .asap_in_order_scripts_list
2697            .take_next_ready_to_be_executed()
2698        {
2699            element.execute(cx, result);
2700        }
2701
2702        self.wait_until_asap_scripts_have_executed();
2703    }
2704
2705    /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing>
2706    pub(crate) fn add_deferred_script(&self, script: &HTMLScriptElement) {
2707        self.deferred_scripts.push(script);
2708    }
2709
2710    /// <https://html.spec.whatwg.org/multipage/#the-end> step 3.
2711    /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> step 22.d.
2712    pub(crate) fn deferred_script_loaded(
2713        &self,
2714        cx: &mut JSContext,
2715        element: &HTMLScriptElement,
2716        result: ScriptResult,
2717    ) {
2718        self.deferred_scripts.loaded(element, result);
2719        self.process_deferred_scripts(cx);
2720    }
2721
2722    /// Step 5 of <https://html.spec.whatwg.org/multipage/#the-end>
2723    fn process_deferred_scripts(&self, cx: &mut JSContext) {
2724        if self.current_the_end_loading_phase.get() != TheEndLoadingPhase::ProcessingDeferredScripts
2725        {
2726            return;
2727        }
2728
2729        // Step 5.1. Spin the event loop until the first script in the list of scripts that will execute when the
2730        // document has finished parsing has its ready to be parser-executed set to true and the parser's Document
2731        // has no style sheet that is blocking scripts.
2732        loop {
2733            if self.has_a_stylesheet_that_is_blocking_scripts() {
2734                return;
2735            }
2736            // Step 5.3. Remove the first script element from the list of scripts that will execute when the
2737            // document has finished parsing (i.e. shift out the first entry in the list).
2738            if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed()
2739            {
2740                // Step 5.2. Execute the script element given by the first script in the list of scripts that will execute when the document has finished parsing.
2741                element.execute(cx, result);
2742            } else {
2743                break;
2744            }
2745        }
2746        // Step 5. While the list of scripts that will execute when the document has finished parsing is not empty:
2747        if self.deferred_scripts.is_empty() {
2748            self.current_the_end_loading_phase
2749                .set(TheEndLoadingPhase::ProcessingAsSoonAsPossibleScripts);
2750            self.dispatch_dom_content_loaded();
2751        }
2752    }
2753
2754    /// Step 6 of <https://html.spec.whatwg.org/multipage/#the-end>
2755    fn dispatch_dom_content_loaded(&self) {
2756        assert_ne!(
2757            self.ReadyState(),
2758            DocumentReadyState::Complete,
2759            "Complete before DOMContentLoaded?"
2760        );
2761
2762        // Step 6. Queue a global task on the DOM manipulation task source given the Document's
2763        // relevant global object to run the following substeps:
2764        let document = Trusted::new(self);
2765        self.owner_global()
2766            .task_manager()
2767            .dom_manipulation_task_source()
2768            .queue(task!(fire_dom_content_loaded_event: move |cx| {
2769                // Step 6.1. Set the Document's load timing info's DOM content loaded event start time to
2770                // the current high resolution time given the Document's relevant global object.
2771                let document = document.root();
2772                update_with_current_instant(&document.navigation_timing.dom_content_loaded_event_start);
2773                // Step 6.2. Fire an event named DOMContentLoaded at the Document object, with its bubbles attribute initialized to true.
2774                document.upcast::<EventTarget>().fire_bubbling_event(cx, atom!("DOMContentLoaded"));
2775                // Step 6.3. Set the Document's load timing info's DOM content loaded event end time to
2776                // the current high resolution time given the Document's relevant global object.
2777                update_with_current_instant(&document.navigation_timing.dom_content_loaded_event_end);
2778                // Step 6.4. Enable the client message queue of the ServiceWorkerContainer object
2779                // whose associated service worker client is the Document object's relevant settings object.
2780                // TODO
2781
2782                // Step 6.5. Invoke WebDriver BiDi DOM content loaded with the Document's browsing context,
2783                // and a new WebDriver BiDi navigation status whose id is the Document object's during-loading
2784                // navigation ID for WebDriver BiDi, status is "pending", and url is the Document object's URL.
2785                // TODO
2786            }));
2787
2788        // html parsing has finished - set dom content loaded
2789        self.interactive_time
2790            .borrow()
2791            .maybe_set_tti(InteractiveFlag::DOMContentLoaded);
2792
2793        self.wait_until_asap_scripts_have_executed();
2794    }
2795
2796    fn has_finished_all_asap_scripts(&self) -> bool {
2797        self.asap_scripts_set.borrow().is_empty() && self.asap_in_order_scripts_list.is_empty()
2798    }
2799
2800    /// Step 7 of <https://html.spec.whatwg.org/multipage/#the-end>
2801    fn wait_until_asap_scripts_have_executed(&self) {
2802        if self.current_the_end_loading_phase.get() !=
2803            TheEndLoadingPhase::ProcessingAsSoonAsPossibleScripts
2804        {
2805            return;
2806        }
2807        // Step 7. Spin the event loop until the set of scripts that will execute as soon as possible
2808        // and the list of scripts that will execute in order as soon as possible are empty.
2809        if self.has_finished_all_asap_scripts() {
2810            let document = Trusted::new(self);
2811            self.owner_global()
2812                .task_manager()
2813                .dom_manipulation_task_source()
2814                .queue(task!(transition_away_from_asap_scripts: move |cx| {
2815                    let document = document.root();
2816                    // Ensure that if this task is fired multiple times, we only progress the
2817                    // end of loading phase once.
2818                    if document.current_the_end_loading_phase.get() !=
2819                        TheEndLoadingPhase::ProcessingAsSoonAsPossibleScripts
2820                    {
2821                        return;
2822                    }
2823                    // Check again if we still fulfil the goal
2824                    if !document.has_finished_all_asap_scripts() {
2825                        return;
2826                    }
2827                    document.current_the_end_loading_phase
2828                        .set(TheEndLoadingPhase::WaitingForLoadEventBlockers);
2829                    document.wait_until_load_blockers_have_resolved(cx);
2830                }));
2831        }
2832    }
2833
2834    /// Step 8 of <https://html.spec.whatwg.org/multipage/#the-end>
2835    pub(crate) fn wait_until_load_blockers_have_resolved(&self, cx: &mut JSContext) {
2836        if self.current_the_end_loading_phase.get() !=
2837            TheEndLoadingPhase::WaitingForLoadEventBlockers
2838        {
2839            return;
2840        }
2841        // Step 8. Spin the event loop until there is nothing that delays the load event in the Document.
2842        {
2843            let loader = self.loader.borrow();
2844
2845            // Servo measures when the top-level content (not iframes) is loaded.
2846            if self
2847                .navigation_timing
2848                .top_level_dom_complete
2849                .get()
2850                .is_none() &&
2851                loader.is_only_blocked_by_iframes()
2852            {
2853                update_with_current_instant(&self.navigation_timing.top_level_dom_complete);
2854            }
2855
2856            let not_ready_for_load = loader.is_blocked() || loader.events_inhibited();
2857            if not_ready_for_load {
2858                return;
2859            }
2860        }
2861
2862        self.current_the_end_loading_phase
2863            .set(TheEndLoadingPhase::Done);
2864        self.queue_document_completion(cx);
2865    }
2866
2867    /// <https://html.spec.whatwg.org/multipage/#destroy-a-document-and-its-descendants>
2868    pub(crate) fn destroy_document_and_its_descendants(&self, cx: &mut JSContext) {
2869        // Step 1. If document is not fully active, then:
2870        if !self.is_fully_active() {
2871            // Step 1.1. Let reason be a string from user-agent specific blocking reasons.
2872            // If none apply, then let reason be "masked".
2873            // TODO
2874            // Step 1.2. Make document unsalvageable given document and reason.
2875            self.salvageable.set(false);
2876            // Step 1.3. If document's node navigable is a top-level traversable,
2877            // build not restored reasons for a top-level traversable and its descendants given document's node navigable.
2878            // TODO
2879        }
2880        // TODO(#31973): all of the steps below are implemented synchronously at the moment.
2881        // They need to become asynchronous later, at which point the counting of
2882        // numberDestroyed becomes relevant.
2883
2884        // Step 2. Let childNavigables be document's child navigables.
2885        // Step 3. Let numberDestroyed be 0.
2886        // Step 4. For each childNavigable of childNavigables, queue a global task on
2887        // the navigation and traversal task source given childNavigable's active
2888        // window to perform the following steps:
2889        // Step 4.1. Let incrementDestroyed be an algorithm step which increments numberDestroyed.
2890        // Step 4.2. Destroy a document and its descendants given childNavigable's active document and incrementDestroyed.
2891        // Step 5. Wait until numberDestroyed equals childNavigable's size.
2892        for exited_iframe in self.iframes().iter() {
2893            debug!("Destroying nested iframe document");
2894            exited_iframe.destroy_document_and_its_descendants(cx);
2895        }
2896        // Step 6. Queue a global task on the navigation and traversal task source
2897        // given document's relevant global object to perform the following steps:
2898        // TODO
2899        // Step 6.1. Destroy document.
2900        self.destroy(cx);
2901        // Step 6.2. If afterAllDestruction was given, then run it.
2902        // TODO
2903    }
2904
2905    /// <https://html.spec.whatwg.org/multipage/#destroy-a-document>
2906    pub(crate) fn destroy(&self, cx: &mut JSContext) {
2907        let exited_window = self.window();
2908        // Step 2. Abort document.
2909        self.abort(cx, AbortReason::Destroy);
2910        // Step 3. Set document's salvageable state to false.
2911        self.salvageable.set(false);
2912        // Step 4. Let ports be the list of MessagePorts whose relevant
2913        // global object's associated Document is document.
2914        // TODO
2915
2916        // Step 5. For each port in ports, disentangle port.
2917        // TODO
2918
2919        // Step 6. Run any unloading document cleanup steps for document that
2920        // are defined by this specification and other applicable specifications.
2921        self.unloading_cleanup_steps(cx);
2922
2923        // Step 7. Remove any tasks whose document is document from any task queue
2924        // (without running those tasks).
2925        exited_window
2926            .as_global_scope()
2927            .task_manager()
2928            .cancel_all_tasks_and_ignore_future_tasks();
2929
2930        // Step 8. Set document's browsing context to null.
2931        exited_window.discard_browsing_context();
2932
2933        // Step 9. Set document's node navigable's active session history entry's
2934        // document state's document to null.
2935        // TODO
2936
2937        // Step 10. Remove document from the owner set of each WorkerGlobalScope
2938        // object whose set contains document.
2939        exited_window
2940            .as_global_scope()
2941            .disable_owned_worker_animation_frame_providers();
2942
2943        // Step 11. For each workletGlobalScope in document's worklet global scopes,
2944        // terminate workletGlobalScope.
2945        // TODO
2946    }
2947
2948    /// <https://html.spec.whatwg.org/multipage/#active-parser>
2949    fn active_parser(&self) -> Option<DomRoot<ServoParser>> {
2950        // > A Document is said to have an active parser if it is associated with
2951        // > an HTML parser or an XML parser that has not yet been stopped or aborted.
2952        self.get_current_parser()
2953            .filter(|parser| !(parser.has_stopped() || parser.has_aborted()))
2954    }
2955
2956    /// <https://html.spec.whatwg.org/multipage/#abort-a-document>
2957    pub(crate) fn abort(&self, cx: &mut JSContext, reason: AbortReason) {
2958        // We need to inhibit the loader before anything else.
2959        self.loader.borrow_mut().inhibit_events();
2960
2961        // Step 1. Assert: this is running as part of a task queued on document's relevant agent's event loop.
2962        // TODO
2963
2964        // Step 2. Cancel any instances of the fetch algorithm in the context of document,
2965        // discarding any tasks queued for them, and discarding any further data received
2966        // from the network for them. If this resulted in any instances of the fetch algorithm
2967        // being canceled or any queued tasks or any network data getting discarded,
2968        // then make document unsalvageable given document and "fetch".
2969        self.script_blocking_stylesheet_set.borrow_mut().clear();
2970        *self.pending_parsing_blocking_script.borrow_mut() = None;
2971        *self.asap_scripts_set.borrow_mut() = vec![];
2972        self.asap_in_order_scripts_list.clear();
2973        self.deferred_scripts.clear();
2974
2975        let global = self.window.as_global_scope();
2976        let loads_cancelled = global.fetch_group_mut().terminate(global);
2977        let event_sources_canceled = global.close_event_sources();
2978
2979        if loads_cancelled || event_sources_canceled {
2980            // If any loads were canceled.
2981            self.salvageable.set(false);
2982        };
2983
2984        // Also Step 2.
2985        // Note: the spec says to discard any tasks queued for fetch.
2986        // This cancels all tasks on the networking task source, which might be too broad.
2987        // See https://github.com/whatwg/html/issues/3837
2988        self.owner_global()
2989            .task_manager()
2990            .cancel_pending_tasks_for_source(TaskSourceName::Networking);
2991
2992        // Step 3. If document's during-loading navigation ID for WebDriver BiDi is non-null, then:
2993        // TODO
2994
2995        // Step 4. If document has an active parser, then:
2996        if let Some(parser) = self.active_parser() {
2997            // Step 4.1. Set document's active parser was aborted to true.
2998            self.active_parser_was_aborted.set(true);
2999            // Step 4.2. Abort that parser.
3000            parser.abort(cx, reason);
3001            // Step 4.3. Make document unsalvageable given document and "parser-aborted".
3002            self.salvageable.set(false);
3003        }
3004    }
3005
3006    /// <https://html.spec.whatwg.org/multipage/#abort-a-document-and-its-descendants>
3007    pub(crate) fn abort_a_document_and_its_descendants(
3008        &self,
3009        cx: &mut JSContext,
3010        reason: AbortReason,
3011    ) {
3012        // Step 1. Assert: this is running as part of a task queued on document's relevant agent's event loop.
3013        // TODO
3014
3015        // Step 2. Let descendantNavigables be document's descendant navigables.
3016        // Step 3. For each descendantNavigable of descendantNavigables,
3017        // queue a global task on the navigation and traversal task source given
3018        // descendantNavigable's active window to perform the following steps:
3019        for iframe in self.iframes().iter() {
3020            if let Some(descendant_document) = iframe.GetContentDocument() {
3021                let trusted_descendant_document = Trusted::new(&*descendant_document);
3022                let document = Trusted::new(self);
3023                descendant_document
3024                    .owner_global()
3025                    .task_manager()
3026                    .navigation_and_traversal_task_source()
3027                    .queue(task!(abort_iframe_document: move |cx| {
3028                        let descendant_document = trusted_descendant_document.root();
3029                        // Step 3.1. Abort descendantNavigable's active document.
3030                        descendant_document.abort(cx, reason);
3031                        // Step 3.2. If descendantNavigable's active document's salvageable is false, then set document's salvageable to false.
3032                        if !descendant_document.salvageable.get() {
3033                            document.root().salvageable.set(false);
3034                        }
3035                    }));
3036            }
3037        }
3038
3039        // Step 4. Abort document.
3040        self.abort(cx, reason);
3041    }
3042
3043    pub(crate) fn notify_constellation_load(&self) {
3044        self.window()
3045            .send_to_constellation(ScriptToConstellationMessage::LoadComplete);
3046    }
3047
3048    pub(crate) fn set_current_parser(&self, script: Option<&ServoParser>, reason: SetParserReason) {
3049        trace!(
3050            "{} parser for {:?} due to {:?}",
3051            if script.is_some() {
3052                "setting"
3053            } else {
3054                "clearing"
3055            },
3056            self.url(),
3057            reason
3058        );
3059        self.current_parser.set(script);
3060    }
3061
3062    pub(crate) fn get_current_parser(&self) -> Option<DomRoot<ServoParser>> {
3063        self.current_parser.get()
3064    }
3065
3066    pub(crate) fn get_current_parser_line(&self) -> u32 {
3067        self.get_current_parser()
3068            .map(|parser| parser.get_current_line())
3069            .unwrap_or(0)
3070    }
3071
3072    /// <https://html.spec.whatwg.org/multipage/#concept-document-ancestor-origins-list>
3073    pub(crate) fn set_ancestor_origins_list(&self, ancestor_origins_list: &DOMStringList) {
3074        self.ancestor_origins_list.set(Some(ancestor_origins_list));
3075    }
3076
3077    /// <https://html.spec.whatwg.org/multipage/#concept-document-ancestor-origins-list>
3078    pub(crate) fn ancestor_origins_list(&self) -> Option<DomRoot<DOMStringList>> {
3079        self.ancestor_origins_list.get()
3080    }
3081
3082    /// <https://html.spec.whatwg.org/multipage/#concept-document-internal-ancestor-origin-objects-list>
3083    pub(crate) fn set_internal_ancestor_origin_objects_list(
3084        &self,
3085        internal_ancestor_origin_objects_list: Vec<ImmutableOrigin>,
3086    ) {
3087        *self.internal_ancestor_origin_objects_list.borrow_mut() =
3088            Some(internal_ancestor_origin_objects_list);
3089    }
3090
3091    /// <https://html.spec.whatwg.org/multipage/#concept-document-internal-ancestor-origin-objects-list>
3092    pub(crate) fn internal_ancestor_origin_objects_list(
3093        &self,
3094    ) -> Ref<'_, Option<Vec<ImmutableOrigin>>> {
3095        self.internal_ancestor_origin_objects_list.borrow()
3096    }
3097
3098    /// A reference to the [`IFrameCollection`] of this [`Document`], holding information about
3099    /// `<iframe>`s found within it.
3100    pub(crate) fn iframes(&self) -> &IFrameCollection {
3101        &self.iframes
3102    }
3103
3104    pub(crate) fn set_navigation_start(&self, navigation_start: CrossProcessInstant) {
3105        self.interactive_time
3106            .borrow_mut()
3107            .set_navigation_start(navigation_start);
3108    }
3109
3110    pub(crate) fn get_interactive_metrics(&self) -> Ref<'_, ProgressiveWebMetrics> {
3111        self.interactive_time.borrow()
3112    }
3113
3114    pub(crate) fn has_recorded_tti_metric(&self) -> bool {
3115        self.get_interactive_metrics().get_tti().is_some()
3116    }
3117
3118    pub(crate) fn start_tti(&self) {
3119        if self.get_interactive_metrics().needs_tti() {
3120            self.tti_window.borrow_mut().start_window();
3121        }
3122    }
3123
3124    /// check tti for this document
3125    /// if it's been 10s since this doc encountered a task over 50ms, then we consider the
3126    /// main thread available and try to set tti
3127    pub(crate) fn record_tti_if_necessary(&self) {
3128        if self.has_recorded_tti_metric() {
3129            return;
3130        }
3131        if self.tti_window.borrow().needs_check() {
3132            self.get_interactive_metrics()
3133                .maybe_set_tti(InteractiveFlag::TimeToInteractive(
3134                    self.tti_window.borrow().get_start(),
3135                ));
3136        }
3137    }
3138
3139    /// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object>
3140    pub(crate) fn is_cookie_averse(&self) -> bool {
3141        !self.has_browsing_context || !url_has_network_scheme(&self.url())
3142    }
3143
3144    pub(crate) fn custom_element_registry(&self) -> Option<DomRoot<CustomElementRegistry>> {
3145        self.document_or_shadow_root.custom_element_registry()
3146    }
3147
3148    pub(crate) fn set_custom_element_registry(&self, registry: &CustomElementRegistry) {
3149        self.document_or_shadow_root
3150            .set_custom_element_registry(Some(registry));
3151    }
3152
3153    /// <https://dom.spec.whatwg.org/#effective-global-custom-element-registry>
3154    pub(crate) fn effective_global_custom_element_registry(
3155        &self,
3156    ) -> Option<DomRoot<CustomElementRegistry>> {
3157        // Step 1. If document's custom element registry is a global custom element
3158        // registry, then return document's custom element registry..
3159        let document_custom_element_registry = self.custom_element_registry();
3160        if CustomElementRegistry::is_a_global_element_registry(
3161            document_custom_element_registry.as_deref(),
3162        ) {
3163            return document_custom_element_registry;
3164        }
3165        // Step 2. Return null.
3166        None
3167    }
3168
3169    /// Cleans up any active promises
3170    /// <https://github.com/servo/servo/issues/15318>
3171    pub(crate) fn teardown_custom_element_registry(&self) {
3172        if let Some(custom_elements) = self.custom_element_registry() {
3173            custom_elements.teardown();
3174        }
3175    }
3176
3177    pub(crate) fn increment_throw_on_dynamic_markup_insertion_counter(&self) {
3178        let counter = self.throw_on_dynamic_markup_insertion_counter.get();
3179        self.throw_on_dynamic_markup_insertion_counter
3180            .set(counter + 1);
3181    }
3182
3183    pub(crate) fn decrement_throw_on_dynamic_markup_insertion_counter(&self) {
3184        let counter = self.throw_on_dynamic_markup_insertion_counter.get();
3185        self.throw_on_dynamic_markup_insertion_counter
3186            .set(counter - 1);
3187    }
3188
3189    pub(crate) fn react_to_environment_changes(&self, cx: &JSContext) {
3190        for image in self.responsive_images.borrow().iter() {
3191            image.react_to_environment_changes(cx);
3192        }
3193    }
3194
3195    pub(crate) fn register_responsive_image(&self, img: &HTMLImageElement) {
3196        self.responsive_images.borrow_mut().push(Dom::from_ref(img));
3197    }
3198
3199    pub(crate) fn unregister_responsive_image(&self, img: &HTMLImageElement) {
3200        let index = self
3201            .responsive_images
3202            .borrow()
3203            .iter()
3204            .position(|x| **x == *img);
3205        if let Some(i) = index {
3206            self.responsive_images.borrow_mut().remove(i);
3207        }
3208    }
3209
3210    pub(crate) fn register_media_controls(&self, id: &str, controls: &ShadowRoot) {
3211        let did_have_these_media_controls = self
3212            .media_controls
3213            .borrow_mut()
3214            .insert(id.to_string(), Dom::from_ref(controls))
3215            .is_some();
3216        debug_assert!(
3217            !did_have_these_media_controls,
3218            "Trying to register known media controls"
3219        );
3220    }
3221
3222    pub(crate) fn unregister_media_controls(&self, id: &str) {
3223        let did_have_these_media_controls = self.media_controls.borrow_mut().remove(id).is_some();
3224        debug_assert!(
3225            did_have_these_media_controls,
3226            "Trying to unregister unknown media controls"
3227        );
3228    }
3229
3230    pub(crate) fn mark_canvas_as_dirty(&self, canvas: &Dom<HTMLCanvasElement>) {
3231        let mut dirty_canvases = self.dirty_canvases.borrow_mut();
3232        if dirty_canvases
3233            .iter()
3234            .any(|dirty_canvas| dirty_canvas == canvas)
3235        {
3236            return;
3237        }
3238        dirty_canvases.push(canvas.clone());
3239    }
3240
3241    /// Whether or not this [`Document`] needs a rendering update, due to changed
3242    /// contents or pending events. This is used to decide whether or not to schedule
3243    /// a call to the "update the rendering" algorithm.
3244    pub(crate) fn needs_rendering_update(&self, no_gc: &NoGC) -> bool {
3245        if !self.is_fully_active() {
3246            return false;
3247        }
3248        if !self.window().layout_blocked() &&
3249            (!self.restyle_reason(no_gc).is_empty() ||
3250                self.window().layout().needs_new_display_list() ||
3251                self.window().layout().needs_accessibility_update())
3252        {
3253            return true;
3254        }
3255        if !self.rendering_update_reasons.get().is_empty() {
3256            return true;
3257        }
3258        if self.event_handler.has_pending_input_events() {
3259            return true;
3260        }
3261        if self.has_pending_scroll_events() {
3262            return true;
3263        }
3264        if self.window().has_unhandled_resize_event() {
3265            return true;
3266        }
3267        if self.has_pending_animated_image_update.get() || !self.dirty_canvases.borrow().is_empty()
3268        {
3269            return true;
3270        }
3271        if self.window().has_pending_media_query_evaluation() {
3272            return true;
3273        }
3274        if self
3275            .selection()
3276            .is_some_and(|selection| selection.visible_selection_dirty())
3277        {
3278            return true;
3279        }
3280
3281        false
3282    }
3283
3284    /// <https://www.w3.org/TR/paint-timing/#mark-paint-timing>
3285    pub(crate) fn mark_paint_timing(&self) {
3286        // Step 2. Let paintTimingInfo be a new paint timing info, whose
3287        // rendering update end time is the current high resolution time given
3288        // document's relevant global object.
3289        self.paint_timing_info.set(PaintTimingInfo::now());
3290    }
3291
3292    /// <https://www.w3.org/TR/paint-timing/#paint-timing-info>
3293    pub(crate) fn paint_timing_info(&self) -> PaintTimingInfo {
3294        self.paint_timing_info.get()
3295    }
3296
3297    /// An implementation of step 21, 22 from
3298    /// <https://html.spec.whatwg.org/multipage/#update-the-rendering>:
3299    ///
3300    /// Returns the set of reflow phases run as a [`ReflowPhasesRun`].
3301    pub(crate) fn update_the_rendering(
3302        &self,
3303        cx: &mut JSContext,
3304    ) -> (ReflowPhasesRun, ReflowStatistics) {
3305        assert!(!self.is_render_blocked());
3306        // Step 21. For each doc of docs, mark paint timing for doc.
3307        self.mark_paint_timing();
3308
3309        // Step 22: For each doc of docs, update the rendering or user interface of
3310        // doc and its node navigable to reflect the current state.
3311        let mut phases = ReflowPhasesRun::empty();
3312        if self.has_pending_animated_image_update.get() {
3313            self.animation_manager.update_active_image_animation_frames(
3314                &self.window,
3315                self.current_animation_timeline_value(),
3316            );
3317            self.has_pending_animated_image_update.set(false);
3318            phases.insert(ReflowPhasesRun::UpdatedImageData);
3319        }
3320
3321        self.current_rendering_epoch
3322            .set(self.current_rendering_epoch.get().next());
3323        let current_rendering_epoch = self.current_rendering_epoch.get();
3324
3325        // All dirty canvases are flushed before updating the rendering.
3326        let image_keys: Vec<_> = self
3327            .dirty_canvases
3328            .borrow_mut()
3329            .drain(..)
3330            .filter_map(|canvas| canvas.update_rendering(current_rendering_epoch))
3331            .collect();
3332
3333        // The renderer should wait to display the frame until all canvas images are
3334        // uploaded. This allows canvas image uploading to happen asynchronously.
3335        let pipeline_id = self.window().pipeline_id();
3336        if !image_keys.is_empty() {
3337            phases.insert(ReflowPhasesRun::UpdatedImageData);
3338            self.waiting_on_canvas_image_updates.set(true);
3339            self.window().paint_api().delay_new_frame_for_canvas(
3340                self.webview_id(),
3341                self.window().pipeline_id(),
3342                current_rendering_epoch,
3343                image_keys,
3344            );
3345        }
3346
3347        let (reflow_phases, statistics) = self.window().reflow(cx, ReflowGoal::UpdateTheRendering);
3348        let phases = phases.union(reflow_phases);
3349
3350        self.window().paint_api().update_epoch(
3351            self.webview_id(),
3352            pipeline_id,
3353            current_rendering_epoch,
3354        );
3355
3356        (phases, statistics)
3357    }
3358
3359    pub(crate) fn handle_no_longer_waiting_on_asynchronous_image_updates(&self) {
3360        self.waiting_on_canvas_image_updates.set(false);
3361    }
3362
3363    pub(crate) fn waiting_on_canvas_image_updates(&self) -> bool {
3364        self.waiting_on_canvas_image_updates.get()
3365    }
3366
3367    /// From <https://drafts.csswg.org/css-font-loading/#fontfaceset-pending-on-the-environment>:
3368    ///
3369    /// > A FontFaceSet is pending on the environment if any of the following are true:
3370    /// >  - the document is still loading
3371    /// >  - the document has pending stylesheet requests
3372    /// >  - the document has pending layout operations which might cause the user agent to request
3373    /// >    a font, or which depend on recently-loaded fonts
3374    ///
3375    /// Returns true if the promise was fulfilled.
3376    pub(crate) fn maybe_fulfill_font_ready_promise(&self, cx: &mut JSContext) -> bool {
3377        if !self.is_fully_active() {
3378            return false;
3379        }
3380
3381        let fonts = self.Fonts(cx);
3382        if !fonts.waiting_to_fullfill_promise() {
3383            return false;
3384        }
3385        if self.window().font_context().web_fonts_still_loading() != 0 {
3386            return false;
3387        }
3388        if self.ReadyState() != DocumentReadyState::Complete {
3389            return false;
3390        }
3391        if !self.restyle_reason(cx.no_gc()).is_empty() {
3392            return false;
3393        }
3394        if !self.rendering_update_reasons.get().is_empty() {
3395            return false;
3396        }
3397
3398        let result = fonts.fulfill_ready_promise_if_needed(cx);
3399
3400        // Add a rendering update after the `fonts.ready` promise is fulfilled just for
3401        // the sake of taking screenshots. This has the effect of delaying screenshots
3402        // until layout has taken a shot at updating the rendering.
3403        if result {
3404            self.add_rendering_update_reason(RenderingUpdateReason::FontReadyPromiseFulfilled);
3405        }
3406
3407        result
3408    }
3409
3410    pub(crate) fn id_map(&self) -> &TreeOrderedIndexMap {
3411        &self.id_map
3412    }
3413
3414    /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-resizeobserver>
3415    pub(crate) fn add_resize_observer(&self, resize_observer: &ResizeObserver) {
3416        self.resize_observers
3417            .borrow_mut()
3418            .push(Dom::from_ref(resize_observer));
3419    }
3420
3421    /// <https://drafts.csswg.org/resize-observer/#gather-active-observations-h>
3422    /// <https://drafts.csswg.org/resize-observer/#has-active-resize-observations>
3423    pub(crate) fn gather_active_resize_observations_at_depth(
3424        &self,
3425        no_gc: &NoGC,
3426        depth: &ResizeObservationDepth,
3427    ) -> bool {
3428        let mut has_active_resize_observations = false;
3429        for observer in self.resize_observers.borrow_mut().iter_mut() {
3430            observer.gather_active_resize_observations_at_depth(
3431                no_gc,
3432                depth,
3433                &mut has_active_resize_observations,
3434            );
3435        }
3436        has_active_resize_observations
3437    }
3438
3439    /// <https://drafts.csswg.org/resize-observer/#broadcast-active-resize-observations>
3440    pub(crate) fn broadcast_active_resize_observations(
3441        &self,
3442        cx: &mut JSContext,
3443    ) -> ResizeObservationDepth {
3444        let mut shallowest = ResizeObservationDepth::max();
3445        // Breaking potential re-borrow cycle on `resize_observers`:
3446        // broadcasting resize observations calls into a JS callback,
3447        // which can add new observers.
3448        let iterator: Vec<DomRoot<ResizeObserver>> = self
3449            .resize_observers
3450            .borrow()
3451            .iter()
3452            .map(|obs| obs.as_rooted())
3453            .collect();
3454        for observer in iterator {
3455            observer.broadcast_active_resize_observations(cx, &mut shallowest);
3456        }
3457        shallowest
3458    }
3459
3460    /// <https://drafts.csswg.org/resize-observer/#has-skipped-observations-h>
3461    pub(crate) fn has_skipped_resize_observations(&self) -> bool {
3462        self.resize_observers
3463            .borrow()
3464            .iter()
3465            .any(|observer| observer.has_skipped_resize_observations())
3466    }
3467
3468    /// <https://drafts.csswg.org/resize-observer/#deliver-resize-loop-error-notification>
3469    pub(crate) fn deliver_resize_loop_error_notification(&self, cx: &mut JSContext) {
3470        let error_info: ErrorInfo = crate::dom::bindings::error::ErrorInfo {
3471            message: "ResizeObserver loop completed with undelivered notifications.".to_string(),
3472            ..Default::default()
3473        };
3474        self.window
3475            .as_global_scope()
3476            .report_an_error(cx, error_info, HandleValue::null());
3477    }
3478
3479    pub(crate) fn status_code(&self) -> Option<u16> {
3480        self.status_code
3481    }
3482
3483    /// <https://html.spec.whatwg.org/multipage/#encoding-parsing-a-url>
3484    pub(crate) fn encoding_parse_a_url(&self, url: &str) -> Result<ServoUrl, url::ParseError> {
3485        // NOTE: This algorithm is defined for both Document and environment settings objects.
3486        // This implementation is only for documents.
3487
3488        // Step 1. Let encoding be UTF-8.
3489        // Step 2. If environment is a Document object, then set encoding to environment's character encoding.
3490        let encoding = self.encoding.get();
3491
3492        // Step 3. Otherwise, if environment's relevant global object is a Window object, set encoding to environment's
3493        // relevant global object's associated Document's character encoding.
3494
3495        // Step 4. Let baseURL be environment's base URL, if environment is a Document object;
3496        // otherwise environment's API base URL.
3497        let base_url = self.base_url();
3498
3499        // Step 5. Return the result of applying the URL parser to url, with baseURL and encoding.
3500        url::Url::options()
3501            .base_url(Some(base_url.as_url()))
3502            .encoding_override(Some(&|input| {
3503                servo_url::encoding::encode_as_url_query_string(input, encoding)
3504            }))
3505            .parse(url)
3506            .map(ServoUrl::from)
3507    }
3508
3509    /// <https://html.spec.whatwg.org/multipage/#allowed-to-use>
3510    pub(crate) fn allowed_to_use_feature(&self, _feature: PermissionName) -> bool {
3511        // Step 1. If document's browsing context is null, then return false.
3512        if !self.has_browsing_context {
3513            return false;
3514        }
3515
3516        // Step 2. If document is not fully active, then return false.
3517        if !self.is_fully_active() {
3518            return false;
3519        }
3520
3521        // Step 3. If the result of running is feature enabled in document for origin on
3522        // feature, document, and document's origin is "Enabled", then return true.
3523        // Step 4. Return false.
3524        // TODO: All features are currently enabled for `Document`s because we do not
3525        // implement the Permissions Policy specification.
3526        true
3527    }
3528
3529    /// Add an [`IntersectionObserver`] to the [`Document`], to be processed in the [`Document`]'s event loop.
3530    /// <https://github.com/w3c/IntersectionObserver/issues/525>
3531    pub(crate) fn add_intersection_observer(&self, intersection_observer: &IntersectionObserver) {
3532        self.intersection_observers
3533            .borrow_mut()
3534            .push(Dom::from_ref(intersection_observer));
3535    }
3536
3537    /// Remove an [`IntersectionObserver`] from [`Document`], ommiting it from the event loop.
3538    /// An observer without any target, ideally should be removed to be conformant with
3539    /// <https://w3c.github.io/IntersectionObserver/#lifetime>.
3540    pub(crate) fn remove_intersection_observer(
3541        &self,
3542        intersection_observer: &IntersectionObserver,
3543    ) {
3544        self.intersection_observers
3545            .borrow_mut()
3546            .retain(|observer| *observer != intersection_observer)
3547    }
3548
3549    /// <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
3550    pub(crate) fn update_intersection_observer_steps(
3551        &self,
3552        cx: &mut JSContext,
3553        time: CrossProcessInstant,
3554    ) {
3555        if self.intersection_observers.borrow().is_empty() {
3556            return;
3557        }
3558        // Ensure that any layout changes are flushed for subsequent queries.
3559        self.window()
3560            .reflow_for_non_flushing_update_the_rendering_queries(cx);
3561
3562        // Step 1-2
3563        for intersection_observer in &*self.intersection_observers.borrow() {
3564            self.update_single_intersection_observer_steps(cx, intersection_observer, time);
3565        }
3566    }
3567
3568    /// Step 2.1-2.2 of <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
3569    fn update_single_intersection_observer_steps(
3570        &self,
3571        cx: &mut JSContext,
3572        intersection_observer: &IntersectionObserver,
3573        time: CrossProcessInstant,
3574    ) {
3575        // Step 1
3576        // > Let rootBounds be observer’s root intersection rectangle.
3577        let root_bounds = intersection_observer.root_intersection_rectangle();
3578
3579        // Step 2
3580        // > For each target in observer’s internal [[ObservationTargets]] slot,
3581        // > processed in the same order that observe() was called on each target:
3582        intersection_observer.update_intersection_observations_steps(cx, self, time, root_bounds);
3583    }
3584
3585    /// <https://w3c.github.io/IntersectionObserver/#notify-intersection-observers-algo>
3586    pub(crate) fn notify_intersection_observers(&self, cx: &mut JSContext) {
3587        // Step 1
3588        // > Set document’s IntersectionObserverTaskQueued flag to false.
3589        self.intersection_observer_task_queued.set(false);
3590
3591        // Step 2
3592        // > Let notify list be a list of all IntersectionObservers whose root is in the DOM tree of document.
3593        // We will copy the observers because callback could modify the current list.
3594        // It will rooted to prevent GC in the iteration.
3595        rooted_vec!(let notify_list <- self.intersection_observers.clone().take().into_iter());
3596
3597        // Step 3
3598        // > For each IntersectionObserver object observer in notify list, run these steps:
3599        for intersection_observer in notify_list.iter() {
3600            // Step 3.1-3.5
3601            intersection_observer.invoke_callback_if_necessary(cx);
3602        }
3603    }
3604
3605    /// <https://w3c.github.io/IntersectionObserver/#queue-intersection-observer-task>
3606    pub(crate) fn queue_an_intersection_observer_task(&self) {
3607        // Step 1
3608        // > If document’s IntersectionObserverTaskQueued flag is set to true, return.
3609        if self.intersection_observer_task_queued.get() {
3610            return;
3611        }
3612
3613        // Step 2
3614        // > Set document’s IntersectionObserverTaskQueued flag to true.
3615        self.intersection_observer_task_queued.set(true);
3616
3617        // Step 3
3618        // > Queue a task on the IntersectionObserver task source associated with
3619        // > the document's event loop to notify intersection observers.
3620        let document = Trusted::new(self);
3621        self.owner_global()
3622            .task_manager()
3623            .intersection_observer_task_source()
3624            .queue(task!(notify_intersection_observers: move |cx| {
3625                document.root().notify_intersection_observers(cx);
3626            }));
3627    }
3628
3629    pub(crate) fn store_lcp_candidate(&self, candidate: LCPCandidate, element: Option<&Element>) {
3630        let load_time = element
3631            .and_then(|element| element.downcast::<HTMLImageElement>())
3632            .and_then(HTMLImageElement::load_time);
3633        self.lcp_candidates.borrow_mut().insert(
3634            candidate.id,
3635            LCPCandidateAndElement {
3636                element: element.map(Dom::from_ref),
3637                candidate,
3638                load_time,
3639            },
3640        );
3641    }
3642
3643    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
3644    pub(crate) fn handle_paint_metric(&self, cx: &mut JSContext, event: PaintMetricEvent) {
3645        let metrics = self.interactive_time.borrow();
3646        let entry = match event {
3647            PaintMetricEvent::FirstPaint(paint_timing_info, first_reflow) => {
3648                metrics.set_first_paint(paint_timing_info.default_paint_timestamp(), first_reflow);
3649                DomRoot::upcast::<PerformanceEntry>(PerformancePaintTiming::new(
3650                    cx,
3651                    self.window.as_global_scope(),
3652                    ProgressiveWebMetricType::FirstPaint,
3653                    paint_timing_info,
3654                ))
3655            },
3656            PaintMetricEvent::FirstContentfulPaint(paint_timing_info, first_reflow) => {
3657                metrics.set_first_contentful_paint(
3658                    paint_timing_info.default_paint_timestamp(),
3659                    first_reflow,
3660                );
3661                DomRoot::upcast::<PerformanceEntry>(PerformancePaintTiming::new(
3662                    cx,
3663                    self.window.as_global_scope(),
3664                    ProgressiveWebMetricType::FirstContentfulPaint,
3665                    paint_timing_info,
3666                ))
3667            },
3668            PaintMetricEvent::LargestContentfulPaint(paint_timing_info, id) => {
3669                let Some(stored_candidate) = self.lcp_candidates.borrow_mut().remove(&id) else {
3670                    warn!("Received LCP paint metric for unknown candidate: {id:?}");
3671                    return;
3672                };
3673                metrics
3674                    .set_largest_contentful_paint(id, paint_timing_info.default_paint_timestamp());
3675                DomRoot::upcast::<PerformanceEntry>(LargestContentfulPaint::new(
3676                    cx,
3677                    self.window.as_global_scope(),
3678                    &stored_candidate.candidate,
3679                    stored_candidate.element.as_deref(),
3680                    stored_candidate.load_time,
3681                    paint_timing_info,
3682                ))
3683            },
3684        };
3685        self.window.Performance(cx).queue_entry(&entry);
3686    }
3687
3688    /// <https://html.spec.whatwg.org/multipage/#document-write-steps>
3689    fn write(
3690        &self,
3691        cx: &mut JSContext,
3692        text: Vec<TrustedHTMLOrString>,
3693        line_feed: bool,
3694        containing_class: &str,
3695        field: &str,
3696    ) -> ErrorResult {
3697        // Step 1: Let string be the empty string.
3698        let mut strings: Vec<String> = Vec::with_capacity(text.len());
3699        // Step 2: Let isTrusted be false if text contains a string; otherwise true.
3700        let mut is_trusted = true;
3701        // Step 3: For each value of text:
3702        for value in text {
3703            match value {
3704                // Step 3.1: If value is a TrustedHTML object, then append value's associated data to string.
3705                TrustedHTMLOrString::TrustedHTML(trusted_html) => {
3706                    strings.push(trusted_html.to_string());
3707                },
3708                TrustedHTMLOrString::String(str_) => {
3709                    // Step 2: Let isTrusted be false if text contains a string; otherwise true.
3710                    is_trusted = false;
3711                    // Step 3.2: Otherwise, append value to string.
3712                    strings.push(str_.into());
3713                },
3714            };
3715        }
3716        let mut string = itertools::join(strings, "");
3717        // Step 4: If isTrusted is false, set string to the result of invoking the
3718        // Get Trusted Type compliant string algorithm with TrustedHTML,
3719        // this's relevant global object, string, sink, and "script".
3720        if !is_trusted {
3721            string = TrustedHTML::get_trusted_type_compliant_string(
3722                cx,
3723                &self.global(),
3724                TrustedHTMLOrString::String(string.into()),
3725                &format!("{} {}", containing_class, field),
3726            )?
3727            .str()
3728            .to_owned();
3729        }
3730        // Step 5: If lineFeed is true, append U+000A LINE FEED to string.
3731        if line_feed {
3732            string.push('\n');
3733        }
3734        // Step 6: If document is an XML document, then throw an "InvalidStateError" DOMException.
3735        if !self.is_html_document() {
3736            return Err(Error::InvalidState(Some(
3737                "Document must be a HTML document".into(),
3738            )));
3739        }
3740
3741        // Step 7: If document's throw-on-dynamic-markup-insertion counter is greater than 0,
3742        // then throw an "InvalidStateError" DOMException.
3743        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
3744            return Err(Error::InvalidState(Some(
3745                "A custom element constructor attempted to open, close or write to this document"
3746                    .into(),
3747            )));
3748        }
3749
3750        // Step 8: If document's active parser was aborted is true, then return.
3751        if self.active_parser_was_aborted.get() {
3752            return Ok(());
3753        }
3754
3755        let parser = match self.get_current_parser() {
3756            Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser),
3757            // Step 9: If the insertion point is undefined, then:
3758            _ => {
3759                // Step 9.1: If document's unload counter is greater than 0 or
3760                // document's ignore-destructive-writes counter is greater than 0, then return.
3761                if self.is_prompting_or_unloading() ||
3762                    self.ignore_destructive_writes_counter.get() > 0
3763                {
3764                    return Ok(());
3765                }
3766                // Step 9.2: Run the document open steps with document.
3767                self.Open(cx, None, None)?;
3768                self.get_current_parser().unwrap()
3769            },
3770        };
3771
3772        // Steps 10-11.
3773        parser.write(cx, string.into());
3774
3775        Ok(())
3776    }
3777
3778    pub(crate) fn details_name_groups<'a: 'b, 'b>(
3779        &'a self,
3780        no_gc: &'b NoGC,
3781    ) -> RefMut<'b, DetailsNameGroups> {
3782        RefMut::map(
3783            self.details_name_groups.safe_borrow_mut(no_gc),
3784            |details_name_groups| details_name_groups.get_or_insert_default(),
3785        )
3786    }
3787
3788    pub(crate) fn accessibility_data_mut(&self) -> RefMut<'_, AccessibilityData> {
3789        self.accessibility_data.borrow_mut()
3790    }
3791
3792    pub(crate) fn accessibility_active(&self) -> bool {
3793        self.window().layout().accessibility_active()
3794    }
3795
3796    pub(crate) fn rooted_nodes_for_accessibility_integrity_check(
3797        &self,
3798    ) -> Option<FxHashSet<OpaqueNode>> {
3799        if !self.accessibility_active() {
3800            return None;
3801        }
3802
3803        let mut accessibility_data = self.accessibility_data_mut();
3804
3805        if pref!(expensive_accessibility_test_assertions_enabled) {
3806            return Some(accessibility_data.unroot_and_drain_all_removed_nodes());
3807        }
3808
3809        accessibility_data.unroot_all_removed_nodes();
3810        None
3811    }
3812
3813    pub(crate) fn get_document_element_unrooted<'a>(
3814        &self,
3815        no_gc: &'a NoGC,
3816    ) -> Option<UnrootedDom<'a, Element>> {
3817        self.upcast::<Node>().child_elements_unrooted(no_gc).next()
3818    }
3819
3820    pub(crate) fn collect_reports(
3821        &self,
3822        reports: &mut Vec<Report>,
3823        ops: &mut MallocSizeOfOps,
3824    ) -> HashSet<*const JSObject> {
3825        let mut computed_objects = HashSet::new();
3826        let mut sizes = DocumentSizes::default();
3827
3828        for node in self
3829            .upcast::<Node>()
3830            .traverse_preorder(ShadowIncluding::Yes)
3831        {
3832            let size = compute_size(node.jsobject(), ops, &computed_objects, None);
3833
3834            match node.type_id() {
3835                NodeTypeId::Element(_) => {
3836                    sizes.element_nodes_size += size;
3837
3838                    let element = node.downcast::<Element>().expect("node must be Element");
3839                    for attr in element.attrs().borrow().iter() {
3840                        if let Some(attr) = attr.as_attr() {
3841                            let size = compute_size(
3842                                attr.upcast::<Node>().jsobject(),
3843                                ops,
3844                                &computed_objects,
3845                                None,
3846                            );
3847                            sizes.attribute_nodes_size += size;
3848                            computed_objects.insert(attr.upcast::<Node>().jsobject());
3849                        }
3850                    }
3851                },
3852                NodeTypeId::CharacterData(_) => sizes.text_nodes_size += size,
3853                _ => sizes.other_nodes_size += size,
3854            };
3855
3856            computed_objects.insert(node.jsobject());
3857        }
3858
3859        let prefix = format!("url({})", self.url());
3860        reports.push(Report {
3861            path: path![prefix, "js", "dom", "element-nodes"],
3862            kind: ReportKind::ExplicitJemallocHeapSize,
3863            size: sizes.element_nodes_size,
3864        });
3865        reports.push(Report {
3866            path: path![prefix, "js", "dom", "text-nodes"],
3867            kind: ReportKind::ExplicitJemallocHeapSize,
3868            size: sizes.text_nodes_size,
3869        });
3870        reports.push(Report {
3871            path: path![prefix, "js", "dom", "attribute-nodes"],
3872            kind: ReportKind::ExplicitJemallocHeapSize,
3873            size: sizes.attribute_nodes_size,
3874        });
3875        reports.push(Report {
3876            path: path![prefix, "js", "dom", "other-nodes"],
3877            kind: ReportKind::ExplicitJemallocHeapSize,
3878            size: sizes.other_nodes_size,
3879        });
3880
3881        computed_objects
3882    }
3883
3884    /// Get a reference to this [`Document`]'s vector of weak live ranges.
3885    pub(crate) fn live_ranges(&self) -> &WeakRangeVec {
3886        &self.live_ranges
3887    }
3888
3889    pub(crate) fn gained_or_lost_system_focus(&self, cx: &mut JSContext, gained_focus: bool) {
3890        let focus_handler = self.focus_handler();
3891        if !self.is_fully_active() || !focus_handler.has_focus() {
3892            return;
3893        }
3894        focus_handler.gained_or_lost_system_focus(cx, gained_focus);
3895        self.refresh_focus_rendering();
3896    }
3897
3898    pub(crate) fn refresh_focus_rendering(&self) {
3899        self.window().layout().set_needs_new_display_list();
3900    }
3901}
3902
3903/// Holds DOM object memory sizes for fine-grained memory reports.
3904#[derive(Default)]
3905struct DocumentSizes {
3906    element_nodes_size: usize,
3907    text_nodes_size: usize,
3908    attribute_nodes_size: usize,
3909    other_nodes_size: usize,
3910}
3911
3912impl<'dom> LayoutDom<'dom, Document> {
3913    #[inline]
3914    pub(crate) fn is_html_document_for_layout(&self) -> bool {
3915        self.unsafe_get().is_html_document
3916    }
3917
3918    #[inline]
3919    pub(crate) fn quirks_mode(self) -> QuirksMode {
3920        self.unsafe_get().quirks_mode.get()
3921    }
3922
3923    #[inline]
3924    pub(crate) fn shared_style_locks(self) -> &'dom SharedRwLocks {
3925        self.unsafe_get().shared_style_locks()
3926    }
3927
3928    #[inline]
3929    pub(crate) fn flush_shadow_root_stylesheets_if_necessary(
3930        self,
3931        stylist: &mut Stylist,
3932        guard: &SharedRwLockReadGuard,
3933    ) {
3934        (*self.unsafe_get()).flush_shadow_root_stylesheets_if_necessary_for_layout(stylist, guard)
3935    }
3936
3937    pub(crate) fn elements_with_id(self, id: &Atom) -> &[LayoutDom<'dom, Element>] {
3938        self.unsafe_get().id_map.get_all_for_layout(id)
3939    }
3940
3941    #[expect(unsafe_code)]
3942    pub(crate) fn url_for_layout(self) -> ServoUrl {
3943        unsafe { self.unsafe_get().url.borrow_for_layout() }.clone()
3944    }
3945
3946    #[expect(unsafe_code)]
3947    pub(crate) fn visible_selection_for_layout(&self) -> Option<LayoutDom<'dom, Selection>> {
3948        unsafe { self.unsafe_get().selection.to_layout() }
3949    }
3950
3951    #[expect(unsafe_code)]
3952    pub(crate) fn default_language_for_layout(&self) -> Option<&'dom str> {
3953        unsafe { self.unsafe_get().default_language.borrow_for_layout() }.as_deref()
3954    }
3955}
3956
3957// https://html.spec.whatwg.org/multipage/#is-a-registrable-domain-suffix-of-or-is-equal-to
3958// The spec says to return a bool, we actually return an Option<Host> containing
3959// the parsed host in the successful case, to avoid having to re-parse the host.
3960pub(crate) fn get_registrable_domain_suffix_of_or_is_equal_to(
3961    host_suffix_string: &str,
3962    original_host: Host,
3963) -> Option<Host> {
3964    // Step 1
3965    if host_suffix_string.is_empty() {
3966        return None;
3967    }
3968
3969    // Step 2-3.
3970    let host = match Host::parse(host_suffix_string) {
3971        Ok(host) => host,
3972        Err(_) => return None,
3973    };
3974
3975    // Step 4.
3976    if host != original_host {
3977        // Step 4.1
3978        let host = match host {
3979            Host::Domain(ref host) => host,
3980            _ => return None,
3981        };
3982        let original_host = match original_host {
3983            Host::Domain(ref original_host) => original_host,
3984            _ => return None,
3985        };
3986
3987        // Step 4.2
3988        let index = original_host.len().checked_sub(host.len())?;
3989        let (prefix, suffix) = original_host.split_at(index);
3990
3991        if !prefix.ends_with('.') {
3992            return None;
3993        }
3994        if suffix != host {
3995            return None;
3996        }
3997
3998        // Step 4.3
3999        if is_pub_domain(host) {
4000            return None;
4001        }
4002    }
4003
4004    // Step 5
4005    Some(host)
4006}
4007
4008/// <https://url.spec.whatwg.org/#network-scheme>
4009fn url_has_network_scheme(url: &ServoUrl) -> bool {
4010    matches!(url.scheme(), "ftp" | "http" | "https")
4011}
4012
4013#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
4014pub(crate) enum HasBrowsingContext {
4015    No,
4016    Yes,
4017}
4018
4019impl Document {
4020    #[expect(clippy::too_many_arguments)]
4021    pub(crate) fn new_inherited(
4022        window: &Window,
4023        has_browsing_context: HasBrowsingContext,
4024        url: Option<ServoUrl>,
4025        about_base_url: Option<ServoUrl>,
4026        origin: MutableOrigin,
4027        is_html_document: IsHTMLDocument,
4028        content_type: Option<Mime>,
4029        last_modified: Option<String>,
4030        activity: DocumentActivity,
4031        doc_loader: DocumentLoader,
4032        referrer: Option<String>,
4033        status_code: Option<u16>,
4034        canceller: FetchCanceller,
4035        is_initial_about_blank: bool,
4036        allow_declarative_shadow_roots: bool,
4037        inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
4038        has_trustworthy_ancestor_origin: bool,
4039        custom_element_reaction_stack: Rc<CustomElementReactionStack>,
4040        creation_sandboxing_flag_set: SandboxingFlagSet,
4041        timeline: &DocumentTimeline,
4042        pipeline_id: PipelineId,
4043        image_cache: StdArc<dyn ImageCache>,
4044    ) -> Document {
4045        let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
4046
4047        let frame_type = match window.is_top_level() {
4048            true => TimerMetadataFrameType::RootWindow,
4049            false => TimerMetadataFrameType::IFrame,
4050        };
4051        let interactive_time = ProgressiveWebMetrics::new(
4052            window.time_profiler_chan().clone(),
4053            url.clone(),
4054            frame_type,
4055        );
4056
4057        let content_type = content_type.unwrap_or_else(|| {
4058            match is_html_document {
4059                // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
4060                IsHTMLDocument::HTMLDocument => "text/html",
4061                // https://dom.spec.whatwg.org/#concept-document-content-type
4062                IsHTMLDocument::NonHTMLDocument => "application/xml",
4063            }
4064            .parse()
4065            .unwrap()
4066        });
4067
4068        let encoding = content_type
4069            .get_parameter(CHARSET)
4070            .and_then(|charset| Encoding::for_label(charset.as_bytes()))
4071            .unwrap_or(UTF_8);
4072
4073        let has_focus = window.parent_info().is_none();
4074        let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes;
4075        let shared_style_locks = window.script_thread().shared_style_locks().clone();
4076        // <https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context>
4077        // Step 15. Let document be a new Document, with:
4078        // - mode: "quirks"
4079        let quirks_mode = if is_initial_about_blank {
4080            QuirksMode::Quirks
4081        } else {
4082            // <https://dom.spec.whatwg.org/#concept-document-quirks>
4083            QuirksMode::NoQuirks
4084        };
4085
4086        Document {
4087            node: Node::new_document_node(),
4088            document_or_shadow_root: DocumentOrShadowRoot::new(window),
4089            window: Dom::from_ref(window),
4090            has_browsing_context,
4091            implementation: Default::default(),
4092            content_type,
4093            last_modified,
4094            url: DomRefCell::new(url),
4095            about_base_url: DomRefCell::new(about_base_url),
4096            quirks_mode: Cell::new(quirks_mode),
4097            event_handler: DocumentEventHandler::new(window),
4098            focus_handler: DocumentFocusHandler::new(window, has_focus),
4099            embedder_controls: DocumentEmbedderControls::new(window),
4100            id_map: TreeOrderedIndexMap::id(),
4101            name_map: TreeOrderedIndexMap::name(),
4102            // https://dom.spec.whatwg.org/#concept-document-encoding
4103            encoding: Cell::new(encoding),
4104            is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
4105            activity: Cell::new(activity),
4106            tag_map: DomRefCell::new(HashMapTracedValues::new_fx()),
4107            tagns_map: DomRefCell::new(HashMapTracedValues::new_fx()),
4108            classes_map: DomRefCell::new(HashMapTracedValues::new()),
4109            images: Default::default(),
4110            embeds: Default::default(),
4111            links: Default::default(),
4112            forms: Default::default(),
4113            scripts: Default::default(),
4114            anchors: Default::default(),
4115            applets: Default::default(),
4116            iframes: IFrameCollection::new(),
4117            shared_style_locks,
4118            stylesheets: DomRefCell::new(DocumentStylesheetSet::new()),
4119            stylesheet_list: MutNullableDom::new(None),
4120            // https://html.spec.whatwg.org/multipage/#current-document-readiness
4121            // > Each Document has a current document readiness, a string, initially "complete".
4122            ready_state: Cell::new(DocumentReadyState::Complete),
4123            current_script: Default::default(),
4124            current_the_end_loading_phase: Default::default(),
4125            pending_parsing_blocking_script: Default::default(),
4126            script_blocking_stylesheet_set: Default::default(),
4127            render_blocking_element_count: Default::default(),
4128            deferred_scripts: Default::default(),
4129            asap_in_order_scripts_list: Default::default(),
4130            asap_scripts_set: Default::default(),
4131            animation_frame_ident: Cell::new(0),
4132            animation_frame_list: DomRefCell::new(VecDeque::new()),
4133            running_animation_callbacks: Cell::new(false),
4134            loader: DomRefCell::new(doc_loader),
4135            current_parser: Default::default(),
4136            base_element: Default::default(),
4137            target_base_element: Default::default(),
4138            ancestor_origins_list: Default::default(),
4139            internal_ancestor_origin_objects_list: Default::default(),
4140            appropriate_template_contents_owner_document: Default::default(),
4141            pending_restyles: DomRefCell::new(FxHashMap::default()),
4142            needs_restyle: Cell::new(RestyleReason::DOMChanged),
4143            origin: DomRefCell::new(origin),
4144            referrer,
4145            target_element: MutNullableDom::new(None),
4146            policy_container: DomRefCell::new(StdArc::new(PolicyContainer::default())),
4147            preloaded_resources: Default::default(),
4148            ignore_destructive_writes_counter: Default::default(),
4149            ignore_opens_during_unload_counter: Default::default(),
4150            spurious_animation_frames: Cell::new(0),
4151            fullscreen_element: MutNullableDom::new(None),
4152            form_id_listener_map: Default::default(),
4153            interactive_time: DomRefCell::new(interactive_time),
4154            tti_window: DomRefCell::new(InteractiveWindow::default()),
4155            canceller,
4156            throw_on_dynamic_markup_insertion_counter: Cell::new(0),
4157            page_showing: Cell::new(false),
4158            salvageable: Cell::new(true),
4159            active_parser_was_aborted: Cell::new(false),
4160            fired_unload: Cell::new(false),
4161            responsive_images: Default::default(),
4162            navigation_timing: Default::default(),
4163            resource_fetch_timing: RefCell::new(None),
4164            completely_loaded: Cell::new(false),
4165            script_and_layout_blockers: Cell::new(0),
4166            delayed_tasks: Default::default(),
4167            shadow_roots: Default::default(),
4168            shadow_roots_styles_changed: Cell::new(false),
4169            media_controls: DomRefCell::new(HashMap::new()),
4170            dirty_canvases: DomRefCell::new(Default::default()),
4171            has_pending_animated_image_update: Cell::new(false),
4172            selection: MutNullableDom::new(None),
4173            timeline: Dom::from_ref(timeline),
4174            animation_manager: AnimationManager::new(),
4175            dirty_root: Default::default(),
4176            declarative_refresh: Default::default(),
4177            resize_observers: Default::default(),
4178            fonts: Default::default(),
4179            // TODO: This is intended to workaround the issue where `visibilityState`
4180            // is always hidden. This should really be hooked with system visibility
4181            // which involves more work.
4182            visibility_state: Cell::new(DocumentVisibilityState::Visible),
4183            status_code,
4184            is_initial_about_blank: Cell::new(is_initial_about_blank),
4185            allow_declarative_shadow_roots: Cell::new(allow_declarative_shadow_roots),
4186            inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
4187            has_trustworthy_ancestor_origin: Cell::new(has_trustworthy_ancestor_origin),
4188            intersection_observer_task_queued: Cell::new(false),
4189            intersection_observers: Default::default(),
4190            highlighted_dom_node: Default::default(),
4191            lcp_candidates: DomRefCell::new(Default::default()),
4192            paint_timing_info: Cell::new(PaintTimingInfo::now()),
4193            adopted_stylesheets: Default::default(),
4194            adopted_stylesheets_frozen_types: CachedFrozenArray::new(),
4195            pending_scroll_events: Default::default(),
4196            rendering_update_reasons: Default::default(),
4197            waiting_on_canvas_image_updates: Cell::new(false),
4198            root_removal_noted: Cell::new(true),
4199            current_rendering_epoch: Default::default(),
4200            custom_element_reaction_stack,
4201            active_sandboxing_flag_set: Cell::new(creation_sandboxing_flag_set),
4202            creation_sandboxing_flag_set: Cell::new(creation_sandboxing_flag_set),
4203            favicon: RefCell::new(None),
4204            websockets: DOMTracker::new(),
4205            details_name_groups: Default::default(),
4206            protocol_handler_automation_mode: Default::default(),
4207            layout_animations_test_enabled: pref!(layout_animations_test_enabled),
4208            state_override: Default::default(),
4209            value_override: Default::default(),
4210            default_single_line_container_name: Default::default(),
4211            css_styling_flag: Default::default(),
4212            accessibility_data: Default::default(),
4213            iframe_load_in_progress: Default::default(),
4214            mute_iframe_load: Default::default(),
4215            timers: OneshotTimers::new(window.upcast()),
4216            pipeline_id,
4217            task_manager: Rc::new(TaskManager::new(
4218                Some(window.event_loop_sender()),
4219                pipeline_id,
4220                None,
4221            )),
4222            image_cache,
4223            history: Default::default(),
4224            theme: Default::default(),
4225            theme_override: Default::default(),
4226            default_language: Default::default(),
4227            window_detached: Default::default(),
4228            live_ranges: Default::default(),
4229            module_map: Default::default(),
4230        }
4231    }
4232
4233    pub(crate) fn detach_window(&self) {
4234        self.window_detached.set(true);
4235    }
4236
4237    pub(crate) fn window_detached(&self) -> bool {
4238        self.window_detached.get()
4239    }
4240
4241    /// Returns a policy value that should be used for fetches initiated by this document.
4242    pub(crate) fn insecure_requests_policy(&self) -> InsecureRequestsPolicy {
4243        if let Some(csp_list) = self.get_csp_list().as_ref() {
4244            for policy in &csp_list.0 {
4245                if policy.contains_a_directive_whose_name_is("upgrade-insecure-requests") &&
4246                    policy.disposition == PolicyDisposition::Enforce
4247                {
4248                    return InsecureRequestsPolicy::Upgrade;
4249                }
4250            }
4251        }
4252
4253        self.inherited_insecure_requests_policy
4254            .get()
4255            .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade)
4256    }
4257
4258    /// Get the [`Document`]'s [`DocumentEventHandler`].
4259    pub(crate) fn event_handler(&self) -> &DocumentEventHandler {
4260        &self.event_handler
4261    }
4262
4263    /// Get the [`Document`]'s [`DocumentFocusHandler`].
4264    pub(crate) fn focus_handler(&self) -> &DocumentFocusHandler {
4265        &self.focus_handler
4266    }
4267
4268    /// Get the [`Document`]'s [`DocumentEmbedderControls`].
4269    pub(crate) fn embedder_controls(&self) -> &DocumentEmbedderControls {
4270        &self.embedder_controls
4271    }
4272
4273    /// Whether or not this [`Document`] has any pending scroll events to be processed during
4274    /// "update the rendering."
4275    fn has_pending_scroll_events(&self) -> bool {
4276        !self.pending_scroll_events.borrow().is_empty()
4277    }
4278
4279    /// Add a [`RenderingUpdateReason`] to this [`Document`] which will trigger a
4280    /// rendering update at a later time.
4281    pub(crate) fn add_rendering_update_reason(&self, reason: RenderingUpdateReason) {
4282        self.rendering_update_reasons
4283            .set(self.rendering_update_reasons.get().union(reason));
4284    }
4285
4286    /// Clear all [`RenderingUpdateReason`]s from this [`Document`].
4287    pub(crate) fn clear_rendering_update_reasons(&self) {
4288        self.rendering_update_reasons
4289            .set(RenderingUpdateReason::empty())
4290    }
4291
4292    /// Prevent any JS or layout from running until the corresponding call to
4293    /// `remove_script_and_layout_blocker`. Used to isolate periods in which
4294    /// the DOM is in an unstable state and should not be exposed to arbitrary
4295    /// web content. Any attempts to invoke content JS or query layout during
4296    /// that time will trigger a panic. `add_delayed_task` will cause the
4297    /// provided task to be executed as soon as the last blocker is removed.
4298    pub(crate) fn add_script_and_layout_blocker(&self) {
4299        self.script_and_layout_blockers
4300            .set(self.script_and_layout_blockers.get() + 1);
4301    }
4302
4303    /// Terminate the period in which JS or layout is disallowed from running.
4304    /// If no further blockers remain, any delayed tasks in the queue will
4305    /// be executed in queue order until the queue is empty.
4306    pub(crate) fn remove_script_and_layout_blocker(&self, cx: &mut JSContext) {
4307        assert!(self.script_and_layout_blockers.get() > 0);
4308        self.script_and_layout_blockers
4309            .set(self.script_and_layout_blockers.get() - 1);
4310        while self.script_and_layout_blockers.get() == 0 && !self.delayed_tasks.borrow().is_empty()
4311        {
4312            let task = self.delayed_tasks.borrow_mut().remove(0);
4313            task.run_box(cx);
4314        }
4315    }
4316
4317    /// Enqueue a task to run as soon as any JS and layout blockers are removed.
4318    pub(crate) fn add_delayed_task<T: 'static + NonSendTaskBox>(&self, task: T) {
4319        self.delayed_tasks.borrow_mut().push(Box::new(task));
4320    }
4321
4322    /// Returns true if the DOM is in a state that will allow running content JS or
4323    /// performing a layout operation.
4324    pub(crate) fn is_safe_to_run_script_or_layout(&self) -> bool {
4325        self.script_and_layout_blockers.get() == 0
4326    }
4327
4328    /// Assert that the DOM is in a state that will allow running content JS or
4329    /// performing a layout operation.
4330    pub(crate) fn ensure_safe_to_run_script_or_layout(&self) {
4331        assert!(
4332            self.is_safe_to_run_script_or_layout(),
4333            "Attempt to use script or layout while DOM not in a stable state"
4334        );
4335    }
4336
4337    #[expect(clippy::too_many_arguments)]
4338    pub(crate) fn new(
4339        cx: &mut JSContext,
4340        window: &Window,
4341        has_browsing_context: HasBrowsingContext,
4342        url: Option<ServoUrl>,
4343        about_base_url: Option<ServoUrl>,
4344        origin: MutableOrigin,
4345        doctype: IsHTMLDocument,
4346        content_type: Option<Mime>,
4347        last_modified: Option<String>,
4348        activity: DocumentActivity,
4349        doc_loader: DocumentLoader,
4350        referrer: Option<String>,
4351        status_code: Option<u16>,
4352        canceller: FetchCanceller,
4353        is_initial_about_blank: bool,
4354        allow_declarative_shadow_roots: bool,
4355        inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
4356        has_trustworthy_ancestor_origin: bool,
4357        custom_element_reaction_stack: Rc<CustomElementReactionStack>,
4358        creation_sandboxing_flag_set: SandboxingFlagSet,
4359        pipeline_id: PipelineId,
4360        image_cache: StdArc<dyn ImageCache>,
4361    ) -> DomRoot<Document> {
4362        Self::new_with_proto(
4363            cx,
4364            window,
4365            None,
4366            has_browsing_context,
4367            url,
4368            about_base_url,
4369            origin,
4370            doctype,
4371            content_type,
4372            last_modified,
4373            activity,
4374            doc_loader,
4375            referrer,
4376            status_code,
4377            canceller,
4378            is_initial_about_blank,
4379            allow_declarative_shadow_roots,
4380            inherited_insecure_requests_policy,
4381            has_trustworthy_ancestor_origin,
4382            custom_element_reaction_stack,
4383            creation_sandboxing_flag_set,
4384            pipeline_id,
4385            image_cache,
4386        )
4387    }
4388
4389    #[expect(clippy::too_many_arguments)]
4390    fn new_with_proto(
4391        cx: &mut JSContext,
4392        window: &Window,
4393        proto: Option<HandleObject>,
4394        has_browsing_context: HasBrowsingContext,
4395        url: Option<ServoUrl>,
4396        about_base_url: Option<ServoUrl>,
4397        origin: MutableOrigin,
4398        doctype: IsHTMLDocument,
4399        content_type: Option<Mime>,
4400        last_modified: Option<String>,
4401        activity: DocumentActivity,
4402        doc_loader: DocumentLoader,
4403        referrer: Option<String>,
4404        status_code: Option<u16>,
4405        canceller: FetchCanceller,
4406        is_initial_about_blank: bool,
4407        allow_declarative_shadow_roots: bool,
4408        inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
4409        has_trustworthy_ancestor_origin: bool,
4410        custom_element_reaction_stack: Rc<CustomElementReactionStack>,
4411        creation_sandboxing_flag_set: SandboxingFlagSet,
4412        pipeline_id: PipelineId,
4413        image_cache: StdArc<dyn ImageCache>,
4414    ) -> DomRoot<Document> {
4415        let timeline = DocumentTimeline::new(cx, window);
4416        let document = reflect_dom_object_with_proto(
4417            cx,
4418            Box::new(Document::new_inherited(
4419                window,
4420                has_browsing_context,
4421                url,
4422                about_base_url,
4423                origin,
4424                doctype,
4425                content_type,
4426                last_modified,
4427                activity,
4428                doc_loader,
4429                referrer,
4430                status_code,
4431                canceller,
4432                is_initial_about_blank,
4433                allow_declarative_shadow_roots,
4434                inherited_insecure_requests_policy,
4435                has_trustworthy_ancestor_origin,
4436                custom_element_reaction_stack,
4437                creation_sandboxing_flag_set,
4438                &timeline,
4439                pipeline_id,
4440                image_cache,
4441            )),
4442            window,
4443            proto,
4444        );
4445        {
4446            let node = document.upcast::<Node>();
4447            node.set_owner_doc(&document);
4448        }
4449        document
4450    }
4451
4452    pub(crate) fn get_redirect_count(&self) -> u16 {
4453        self.resource_fetch_timing()
4454            .as_ref()
4455            .map_or(0, |resource_fetch_timing| {
4456                resource_fetch_timing.redirect_count
4457            })
4458    }
4459
4460    pub(crate) fn set_resource_fetch_timing(&self, timing: ResourceFetchTiming) {
4461        self.resource_fetch_timing.replace(Some(timing));
4462    }
4463
4464    pub(crate) fn resource_fetch_timing(&self) -> Ref<'_, Option<ResourceFetchTiming>> {
4465        self.resource_fetch_timing.borrow()
4466    }
4467
4468    pub(crate) fn navigation_timing(&self) -> Rc<NavigationTiming> {
4469        self.navigation_timing.clone()
4470    }
4471
4472    pub(crate) fn performance_timing_attribute(
4473        &self,
4474        name: &str,
4475    ) -> Fallible<Option<CrossProcessInstant>> {
4476        Ok(match name {
4477            "unloadEventStart" => self.navigation_timing().unload_event_start.get(),
4478            "unloadEventEnd" => self.navigation_timing().unload_event_end.get(),
4479            "domInteractive" => self.navigation_timing().dom_interactive.get(),
4480            "domContentLoadedEventStart" => self
4481                .navigation_timing()
4482                .dom_content_loaded_event_start
4483                .get(),
4484            "domContentLoadedEventEnd" => {
4485                self.navigation_timing().dom_content_loaded_event_end.get()
4486            },
4487            "domComplete" => self.navigation_timing().dom_complete.get(),
4488            "loadEventStart" => self.navigation_timing().load_event_start.get(),
4489            "loadEventEnd" => self.navigation_timing().load_event_end.get(),
4490            "redirectStart" | "redirectEnd" | "secureConnectionStart" | "responseEnd" => self
4491                .resource_fetch_timing()
4492                .as_ref()
4493                .and_then(|resource_fetch_timing| match name {
4494                    "redirectStart" => resource_fetch_timing.redirect_start,
4495                    "redirectEnd" => resource_fetch_timing.redirect_end,
4496                    "secureConnectionStart" => resource_fetch_timing.secure_connection_start,
4497                    "responseEnd" => resource_fetch_timing.response_end,
4498                    _ => None,
4499                }),
4500            _ => {
4501                return Err(Error::Operation(Some(format!(
4502                    "{name} hasn't been implemented."
4503                ))));
4504            },
4505        })
4506    }
4507
4508    pub(crate) fn elements_by_name_count(&self, name: &DOMString) -> u32 {
4509        if name.is_empty() {
4510            return 0;
4511        }
4512        self.count_node_list(|n| Document::is_element_in_get_by_name(n, name))
4513    }
4514
4515    pub(crate) fn nth_element_by_name<'a>(
4516        &self,
4517        no_gc: &'a NoGC,
4518        index: u32,
4519        name: &DOMString,
4520    ) -> Option<UnrootedDom<'a, Node>> {
4521        if name.is_empty() {
4522            return None;
4523        }
4524        self.nth_in_node_list(no_gc, index, |n| {
4525            Document::is_element_in_get_by_name(n, name)
4526        })
4527    }
4528
4529    // Note that document.getByName does not match on the same conditions
4530    // as the document named getter.
4531    fn is_element_in_get_by_name(node: &Node, name: &DOMString) -> bool {
4532        let element = match node.downcast::<Element>() {
4533            Some(element) => element,
4534            None => return false,
4535        };
4536        if element.namespace() != &ns!(html) {
4537            return false;
4538        }
4539        element.get_name().is_some_and(|n| &*n == name)
4540    }
4541
4542    fn count_node_list<F: Fn(&Node) -> bool>(&self, callback: F) -> u32 {
4543        let doc = self.GetDocumentElement();
4544        let maybe_node = doc.as_deref().map(Castable::upcast::<Node>);
4545        maybe_node
4546            .iter()
4547            .flat_map(|node| node.traverse_preorder(ShadowIncluding::No))
4548            .filter(|node| callback(node))
4549            .count() as u32
4550    }
4551
4552    fn nth_in_node_list<'a, F: Fn(&Node) -> bool>(
4553        &self,
4554        no_gc: &'a NoGC,
4555        index: u32,
4556        callback: F,
4557    ) -> Option<UnrootedDom<'a, Node>> {
4558        let doc = self.get_document_element_unrooted(no_gc)?;
4559        doc.upcast::<Node>()
4560            .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
4561            .filter(|node| callback(node))
4562            .nth(index as usize)
4563    }
4564
4565    fn get_html_element(&self) -> Option<DomRoot<HTMLHtmlElement>> {
4566        self.GetDocumentElement().and_then(DomRoot::downcast)
4567    }
4568
4569    /// Return a reference to the per-ScriptThread shared locks used for stylesheets.
4570    pub(crate) fn shared_style_locks(&self) -> &SharedRwLocks {
4571        &self.shared_style_locks
4572    }
4573
4574    /// Return a reference to the per-ScriptThread shared lock used for author stylesheets.
4575    pub(crate) fn style_shared_author_lock(&self) -> &SharedRwLock {
4576        &self.shared_style_locks.author
4577    }
4578
4579    /// Flushes the stylesheet list, and returns whether any stylesheet changed.
4580    pub(crate) fn flush_stylesheets_for_reflow(&self) -> bool {
4581        // NOTE(emilio): The invalidation machinery is used on the replicated
4582        // list in layout.
4583        //
4584        // FIXME(emilio): This really should differentiate between CSSOM changes
4585        // and normal stylesheets additions / removals, because in the last case
4586        // layout already has that information and we could avoid dirtying the whole thing.
4587        let mut stylesheets = self.stylesheets.borrow_mut();
4588        let have_changed = stylesheets.has_changed();
4589        stylesheets.flush_without_invalidation();
4590        have_changed
4591    }
4592
4593    pub(crate) fn salvageable(&self) -> bool {
4594        self.salvageable.get()
4595    }
4596
4597    /// <https://html.spec.whatwg.org/multipage/#make-document-unsalvageable>
4598    pub(crate) fn make_document_unsalvageable(&self) {
4599        // Step 1. Let details be a new not restored reason details whose reason is reason.
4600        // TODO
4601        // Step 2. Append details to document's bfcache blocking details.
4602        // TODO
4603        // Step 3. Set document's salvageable state to false.
4604        self.salvageable.set(false);
4605    }
4606
4607    /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document>
4608    pub(crate) fn appropriate_template_contents_owner_document(
4609        &self,
4610        cx: &mut JSContext,
4611    ) -> DomRoot<Document> {
4612        self.appropriate_template_contents_owner_document
4613            .or_init(|| {
4614                let doctype = if self.is_html_document {
4615                    IsHTMLDocument::HTMLDocument
4616                } else {
4617                    IsHTMLDocument::NonHTMLDocument
4618                };
4619                let new_doc = Document::new(
4620                    cx,
4621                    self.window(),
4622                    HasBrowsingContext::No,
4623                    None,
4624                    None,
4625                    // https://github.com/whatwg/html/issues/2109
4626                    MutableOrigin::new(ImmutableOrigin::new_opaque()),
4627                    doctype,
4628                    None,
4629                    None,
4630                    DocumentActivity::Inactive,
4631                    DocumentLoader::new(&self.loader()),
4632                    None,
4633                    None,
4634                    Default::default(),
4635                    false,
4636                    self.allow_declarative_shadow_roots(),
4637                    Some(self.insecure_requests_policy()),
4638                    self.has_trustworthy_ancestor_or_current_origin(),
4639                    self.custom_element_reaction_stack.clone(),
4640                    self.creation_sandboxing_flag_set(),
4641                    self.pipeline_id(),
4642                    self.image_cache.clone(),
4643                );
4644                new_doc
4645                    .appropriate_template_contents_owner_document
4646                    .set(Some(&new_doc));
4647                new_doc
4648            })
4649    }
4650
4651    pub(crate) fn get_element_by_id(&self, no_gc: &NoGC, id: &Atom) -> Option<DomRoot<Element>> {
4652        self.id_map.get(no_gc, self.upcast(), id)
4653    }
4654
4655    pub(crate) fn ensure_pending_restyle(&self, el: &Element) -> RefMut<'_, PendingRestyle> {
4656        let map = self.pending_restyles.borrow_mut();
4657        RefMut::map(map, |m| {
4658            &mut m
4659                .entry(Dom::from_ref(el))
4660                .or_insert_with(|| NoTrace(PendingRestyle::default()))
4661                .0
4662        })
4663    }
4664
4665    pub(crate) fn element_attr_will_change(&self, el: &Element, attr: AttrRef<'_>) {
4666        // FIXME(emilio): Kind of a shame we have to duplicate this.
4667        //
4668        // I'm getting rid of the whole hashtable soon anyway, since all it does
4669        // right now is populate the element restyle data in layout, and we
4670        // could in theory do it in the DOM I think.
4671        let mut entry = self.ensure_pending_restyle(el);
4672        if entry.snapshot.is_none() {
4673            entry.snapshot = Some(Snapshot::new());
4674        }
4675        if attr.local_name() == &local_name!("style") {
4676            entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
4677        }
4678
4679        if vtable_for(el.upcast()).attribute_affects_presentational_hints(attr) ||
4680            el.check_style_on_self_or_eager_pseudos(|style| {
4681                if let Some(ref attribute_references) = style.attribute_references {
4682                    return attribute_references.contains_key(attr.local_name());
4683                }
4684                false
4685            })
4686        {
4687            entry.hint.insert(RestyleHint::RESTYLE_SELF);
4688        }
4689
4690        let snapshot = entry.snapshot.as_mut().unwrap();
4691        if attr.local_name() == &local_name!("id") {
4692            if snapshot.id_changed {
4693                return;
4694            }
4695            snapshot.id_changed = true;
4696        } else if attr.local_name() == &local_name!("class") {
4697            if snapshot.class_changed {
4698                return;
4699            }
4700            snapshot.class_changed = true;
4701        } else {
4702            snapshot.other_attributes_changed = true;
4703        }
4704        let local_name = style::LocalName::cast(attr.local_name());
4705        if !snapshot.changed_attrs.contains(local_name) {
4706            snapshot.changed_attrs.push(local_name.clone());
4707        }
4708        if snapshot.attrs.is_none() {
4709            let attrs = el
4710                .attrs()
4711                .borrow()
4712                .iter()
4713                .map(|attr| (attr.as_identifier(), attr.value().clone()))
4714                .collect();
4715            snapshot.attrs = Some(attrs);
4716        }
4717    }
4718
4719    pub(crate) fn set_referrer_policy(&self, policy: ReferrerPolicy) {
4720        StdArc::make_mut(&mut *self.policy_container.borrow_mut()).set_referrer_policy(policy);
4721    }
4722
4723    pub(crate) fn get_referrer_policy(&self) -> ReferrerPolicy {
4724        self.policy_container.borrow().get_referrer_policy()
4725    }
4726
4727    pub(crate) fn set_target_element(&self, node: Option<&Element>) {
4728        if let Some(ref element) = self.target_element.get() {
4729            element.set_target_state(false);
4730        }
4731
4732        self.target_element.set(node);
4733
4734        if let Some(ref element) = self.target_element.get() {
4735            element.set_target_state(true);
4736        }
4737    }
4738
4739    pub(crate) fn incr_ignore_destructive_writes_counter(&self) {
4740        self.ignore_destructive_writes_counter
4741            .set(self.ignore_destructive_writes_counter.get() + 1);
4742    }
4743
4744    pub(crate) fn decr_ignore_destructive_writes_counter(&self) {
4745        self.ignore_destructive_writes_counter
4746            .set(self.ignore_destructive_writes_counter.get() - 1);
4747    }
4748
4749    pub(crate) fn is_prompting_or_unloading(&self) -> bool {
4750        self.ignore_opens_during_unload_counter.get() > 0
4751    }
4752
4753    fn incr_ignore_opens_during_unload_counter(&self) {
4754        self.ignore_opens_during_unload_counter
4755            .set(self.ignore_opens_during_unload_counter.get() + 1);
4756    }
4757
4758    fn decr_ignore_opens_during_unload_counter(&self) {
4759        self.ignore_opens_during_unload_counter
4760            .set(self.ignore_opens_during_unload_counter.get() - 1);
4761    }
4762
4763    pub(crate) fn set_fullscreen_element(&self, element: Option<&Element>) {
4764        self.fullscreen_element.set(element);
4765    }
4766
4767    fn reset_form_owner_for_listeners(&self, cx: &mut JSContext, id: &Atom) {
4768        let map = self.form_id_listener_map.borrow();
4769        if let Some(listeners) = map.get(id) {
4770            for listener in listeners {
4771                listener
4772                    .as_maybe_form_control()
4773                    .expect("Element must be a form control")
4774                    .reset_form_owner(cx);
4775            }
4776        }
4777    }
4778
4779    pub(crate) fn register_shadow_root(&self, shadow_root: &ShadowRoot) {
4780        self.shadow_roots
4781            .borrow_mut()
4782            .insert(Dom::from_ref(shadow_root));
4783        self.invalidate_shadow_roots_stylesheets();
4784    }
4785
4786    pub(crate) fn unregister_shadow_root(&self, shadow_root: &ShadowRoot) {
4787        let mut shadow_roots = self.shadow_roots.borrow_mut();
4788        shadow_roots.remove(&Dom::from_ref(shadow_root));
4789    }
4790
4791    pub(crate) fn invalidate_shadow_roots_stylesheets(&self) {
4792        self.shadow_roots_styles_changed.set(true);
4793    }
4794
4795    pub(crate) fn flush_shadow_root_stylesheets_if_necessary_for_layout(
4796        &self,
4797        stylist: &mut Stylist,
4798        guard: &SharedRwLockReadGuard,
4799    ) {
4800        if !self.shadow_roots_styles_changed.get() {
4801            return;
4802        }
4803        #[expect(unsafe_code)]
4804        unsafe {
4805            for shadow_root in self.shadow_roots.borrow_for_layout().iter() {
4806                let layout: LayoutDom<'_, _> = shadow_root.to_layout();
4807                layout.flush_stylesheets_for_layout(stylist, guard);
4808            }
4809        }
4810        self.shadow_roots_styles_changed.set(false);
4811    }
4812
4813    pub(crate) fn stylesheet_count(&self) -> usize {
4814        self.stylesheets.borrow().len()
4815    }
4816
4817    pub(crate) fn stylesheet_at(
4818        &self,
4819        cx: &mut JSContext,
4820        index: usize,
4821    ) -> Option<DomRoot<CSSStyleSheet>> {
4822        let stylesheets = self.stylesheets.borrow();
4823
4824        stylesheets
4825            .get(Origin::Author, index)
4826            .and_then(|s| s.owner.get_cssom_object(cx))
4827    }
4828
4829    /// Add a stylesheet owned by `owner_node` to the list of document sheets, in the
4830    /// correct tree position. Additionally, ensure that the owned stylesheet is inserted
4831    /// before any constructed stylesheet.
4832    ///
4833    /// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
4834    #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily.
4835    pub(crate) fn add_owned_stylesheet(
4836        &self,
4837        no_gc: &NoGC,
4838        owner_node: &Element,
4839        sheet: Arc<Stylesheet>,
4840    ) {
4841        let insertion_point = {
4842            let stylesheets = &mut *self.stylesheets.borrow_mut();
4843
4844            // FIXME(stevennovaryo): This is almost identical with the one in ShadowRoot::add_stylesheet.
4845            stylesheets
4846                .iter()
4847                .map(|(sheet, _origin)| sheet)
4848                .find(|sheet_in_doc| {
4849                    match &sheet_in_doc.owner {
4850                        StylesheetSource::Element(other_node) => owner_node
4851                            .upcast::<Node>()
4852                            .is_before(no_gc, other_node.upcast()),
4853                        // Non-constructed stylesheet should be ordered before the
4854                        // constructed ones.
4855                        StylesheetSource::Constructed(_) => true,
4856                    }
4857                })
4858                .cloned()
4859        };
4860
4861        if self.has_browsing_context() {
4862            self.add_stylesheet_to_stylist(
4863                sheet.clone(),
4864                insertion_point.as_ref().map(|s| s.sheet.clone()),
4865            );
4866        }
4867
4868        let stylesheets = &mut *self.stylesheets.borrow_mut();
4869        DocumentOrShadowRoot::add_stylesheet(
4870            StylesheetSource::Element(Dom::from_ref(owner_node)),
4871            StylesheetSetRef::Document(stylesheets),
4872            sheet,
4873            insertion_point,
4874            self.style_shared_author_lock(),
4875        );
4876    }
4877
4878    /// Append a constructed stylesheet to the back of document stylesheet set. Because
4879    /// it would be the last element, we therefore would not mess with the ordering.
4880    ///
4881    /// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
4882    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
4883    pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) {
4884        debug_assert!(cssom_stylesheet.is_constructed());
4885
4886        let sheet = cssom_stylesheet.style_stylesheet().clone();
4887        let insertion_point = {
4888            let stylesheets = &mut *self.stylesheets.borrow_mut();
4889
4890            stylesheets
4891                .iter()
4892                .last()
4893                .map(|(sheet, _origin)| sheet)
4894                .cloned()
4895        };
4896
4897        if self.has_browsing_context() {
4898            self.add_stylesheet_to_stylist(
4899                sheet.clone(),
4900                insertion_point.as_ref().map(|s| s.sheet.clone()),
4901            );
4902        }
4903
4904        let stylesheets = &mut *self.stylesheets.borrow_mut();
4905        DocumentOrShadowRoot::add_stylesheet(
4906            StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)),
4907            StylesheetSetRef::Document(stylesheets),
4908            sheet,
4909            insertion_point,
4910            self.style_shared_author_lock(),
4911        );
4912    }
4913
4914    pub(crate) fn add_stylesheet_to_stylist(
4915        &self,
4916        stylesheet: Arc<Stylesheet>,
4917        before_stylesheet: Option<Arc<Stylesheet>>,
4918    ) {
4919        self.window
4920            .layout_mut()
4921            .add_stylesheet(stylesheet, before_stylesheet);
4922    }
4923
4924    /// Remove a stylesheet owned by `owner` from the list of document sheets.
4925    #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily.
4926    pub(crate) fn remove_stylesheet(&self, owner: StylesheetSource, stylesheet: &Arc<Stylesheet>) {
4927        if self.has_browsing_context() {
4928            self.window
4929                .layout_mut()
4930                .remove_stylesheet(stylesheet.clone());
4931        }
4932
4933        DocumentOrShadowRoot::remove_stylesheet(
4934            owner,
4935            stylesheet,
4936            StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()),
4937        )
4938    }
4939
4940    pub(crate) fn get_elements_with_id(
4941        &self,
4942        cx: &mut JSContext,
4943        id: &Atom,
4944    ) -> Ref<'_, [Dom<Element>]> {
4945        self.id_map.get_all(cx.no_gc(), self.upcast(), id)
4946    }
4947
4948    pub(crate) fn get_elements_with_name(
4949        &self,
4950        cx: &mut JSContext,
4951        name: &Atom,
4952    ) -> Ref<'_, [Dom<Element>]> {
4953        self.name_map.get_all(cx.no_gc(), self.upcast(), name)
4954    }
4955
4956    pub(crate) fn drain_pending_restyles(
4957        &self,
4958        no_gc: &NoGC,
4959    ) -> Vec<(TrustedNodeAddress, PendingRestyle)> {
4960        self.pending_restyles
4961            .borrow_mut()
4962            .drain()
4963            .filter_map(|(element, restyle)| {
4964                let node = element.upcast::<Node>();
4965                if !node.get_flag(NodeFlags::IS_CONNECTED) {
4966                    return None;
4967                }
4968                element.note_dirty_descendants(no_gc);
4969                Some((node.to_trusted_node_address(), restyle.0))
4970            })
4971            .collect()
4972    }
4973
4974    pub(crate) fn advance_animation_timeline_for_testing(&self, delta: TimeDuration) {
4975        self.timeline.advance_specific(delta);
4976        let current_timeline_value = self.current_animation_timeline_value();
4977        self.animation_manager
4978            .update_for_new_timeline_value(&self.window, current_timeline_value);
4979    }
4980
4981    pub(crate) fn maybe_mark_animating_nodes_as_dirty(&self, no_gc: &NoGC) {
4982        let current_timeline_value = self.current_animation_timeline_value();
4983        self.animation_manager
4984            .mark_animating_nodes_as_dirty(no_gc, current_timeline_value);
4985    }
4986
4987    pub(crate) fn current_animation_timeline_value(&self) -> f64 {
4988        self.timeline
4989            .upcast::<AnimationTimeline>()
4990            .current_time_in_seconds()
4991    }
4992
4993    pub(crate) fn animation_manager(&self) -> &AnimationManager {
4994        &self.animation_manager
4995    }
4996
4997    pub(crate) fn update_animations_post_reflow(&self) {
4998        let current_timeline_value = self.current_animation_timeline_value();
4999        self.animation_manager
5000            .do_post_reflow_update(&self.window, current_timeline_value);
5001    }
5002
5003    pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
5004        self.animation_manager.cancel_animations_for_node(node);
5005    }
5006
5007    /// Clear style and layout data on this [`Node`] and all descendants. This is used to clean
5008    /// up the data when a [`Node`] becomes detached from the flat tree. Note that this
5009    /// operates on shadow-including descendants.
5010    pub(crate) fn remove_style_and_layout_data_from_subtree(
5011        &self,
5012        no_gc: &NoGC,
5013        subtree_root: &Node,
5014    ) {
5015        for node in subtree_root.traverse_preorder_unrooted(no_gc, ShadowIncluding::Yes) {
5016            self.clean_up_style_and_layout_data_for_node(&node);
5017        }
5018    }
5019
5020    pub(crate) fn clean_up_style_and_layout_data_for_node(&self, node: &Node) {
5021        node.clear_layout_data();
5022        if let Some(element) = node.downcast::<Element>() {
5023            element.clean_up_style_data();
5024
5025            // If this element no longer has any layout or style data, nothing underneath it
5026            // can either, and it no longer needs to serve as a layout root. This method is
5027            // generally called when a node is leaving the flat tree and no longer takes part
5028            // in layout.
5029            if self.dirty_root == Some(element) {
5030                self.dirty_root.clear();
5031            }
5032        }
5033
5034        node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, false);
5035        node.set_flag(NodeFlags::SELECTION_INHIBITED, false);
5036        node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false);
5037    }
5038
5039    /// An implementation of <https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events>.
5040    pub(crate) fn update_animations_and_send_events(&self, cx: &mut CurrentRealm) {
5041        // Only update the time if it isn't being managed by a test.
5042        if !self.layout_animations_test_enabled {
5043            self.timeline.update(self.window());
5044        }
5045
5046        // > 1. Update the current time of all timelines associated with doc passing now
5047        // > as the timestamp.
5048        // > 2. Remove replaced animations for doc.
5049        //
5050        // We still want to update the animations, because our timeline
5051        // value might have been advanced previously via the TestBinding.
5052        let current_timeline_value = self.current_animation_timeline_value();
5053        self.animation_manager
5054            .update_for_new_timeline_value(&self.window, current_timeline_value);
5055        self.maybe_mark_animating_nodes_as_dirty(cx.no_gc());
5056
5057        // > 3. Perform a microtask checkpoint.
5058        self.window().perform_a_microtask_checkpoint(cx);
5059
5060        // Steps 4 through 7 occur inside `send_pending_events().`
5061        self.animation_manager()
5062            .send_pending_events(self.window(), cx);
5063    }
5064
5065    pub(crate) fn set_has_pending_animated_image_update(&self) {
5066        self.has_pending_animated_image_update.set(true);
5067    }
5068
5069    /// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
5070    pub(crate) fn shared_declarative_refresh_steps(&self, content: &[u8], from_meta_element: bool) {
5071        // 1. If document's will declaratively refresh is true, then return.
5072        if self.will_declaratively_refresh() {
5073            return;
5074        }
5075
5076        // 2-11 Parsing
5077        static REFRESH_REGEX: LazyLock<Regex> = LazyLock::new(|| {
5078            // s flag is used to match . on newlines since the only places we use . in the
5079            // regex is to go "to end of the string"
5080            // (?s-u:.) is used to consume invalid unicode bytes
5081            Regex::new(
5082                r#"(?xs)
5083                    ^
5084                    \s* # 3
5085                    ((?<time>[0-9]+)|\.) # 5-6
5086                    [0-9.]* # 8
5087                    (
5088                        (
5089                            (\s*;|\s*,|\s) # 10.3
5090                            \s* # 10.4
5091                        )
5092                        (
5093                            (
5094                                (U|u)(R|r)(L|l) # 11.2-11.4
5095                                \s*=\s* # 11.5-11.7
5096                            )?
5097                        ('(?<url1>[^']*)'(?s-u:.)*|"(?<url2>[^"]*)"(?s-u:.)*|['"]?(?<url3>(?s-u:.)*)) # 11.8 - 11.10
5098                        |
5099                        (?<url4>(?s-u:.)*)
5100                    )
5101                )?
5102                $
5103            "#,
5104            )
5105            .unwrap()
5106        });
5107
5108        // 9. Let urlRecord be document's URL.
5109        let mut url_record = self.url();
5110        let captures = if let Some(captures) = REFRESH_REGEX.captures(content) {
5111            captures
5112        } else {
5113            return;
5114        };
5115        let time = if let Some(time_string) = captures.name("time") {
5116            u64::from_str(&String::from_utf8_lossy(time_string.as_bytes())).unwrap_or(0)
5117        } else {
5118            0
5119        };
5120        let captured_url = captures.name("url1").or(captures
5121            .name("url2")
5122            .or(captures.name("url3").or(captures.name("url4"))));
5123
5124        // 11.11 Parse: Set urlRecord to the result of encoding-parsing a URL given urlString, relative to document.
5125        if let Some(url_match) = captured_url {
5126            url_record = if let Ok(url) = ServoUrl::parse_with_base(
5127                Some(&url_record),
5128                &String::from_utf8_lossy(url_match.as_bytes()),
5129            ) {
5130                info!("Refresh to {}", url.debug_compact());
5131                url
5132            } else {
5133                // 11.12 If urlRecord is failure, then return.
5134                return;
5135            };
5136            // 11.13 If urlRecord's scheme is "javascript", then return.
5137            if url_record.scheme() == "javascript" {
5138                return;
5139            }
5140        }
5141        // 12. Set document's will declaratively refresh to true.
5142        if self.completely_loaded() {
5143            self.window.as_global_scope().schedule_callback(
5144                OneshotTimerCallback::RefreshRedirectDue(RefreshRedirectDue {
5145                    url: url_record,
5146                    from_meta_element,
5147                }),
5148                Duration::from_secs(time),
5149            );
5150            self.set_declarative_refresh(DeclarativeRefresh::CreatedAfterLoad);
5151        } else {
5152            self.set_declarative_refresh(DeclarativeRefresh::PendingLoad {
5153                url: url_record,
5154                time,
5155                from_meta_element,
5156            });
5157        }
5158    }
5159
5160    pub(crate) fn will_declaratively_refresh(&self) -> bool {
5161        self.declarative_refresh.borrow().is_some()
5162    }
5163    pub(crate) fn set_declarative_refresh(&self, refresh: DeclarativeRefresh) {
5164        *self.declarative_refresh.borrow_mut() = Some(refresh);
5165    }
5166
5167    /// <https://html.spec.whatwg.org/multipage/#visibility-state>
5168    fn update_visibility_state(
5169        &self,
5170        cx: &mut JSContext,
5171        visibility_state: DocumentVisibilityState,
5172    ) {
5173        // Step 1 If document's visibility state equals visibilityState, then return.
5174        if self.visibility_state.get() == visibility_state {
5175            return;
5176        }
5177        // Step 2 Set document's visibility state to visibilityState.
5178        self.visibility_state.set(visibility_state);
5179        // Step 3 Queue a new VisibilityStateEntry whose visibility state is visibilityState and whose timestamp is
5180        // the current high resolution time given document's relevant global object.
5181        let entry = VisibilityStateEntry::new(
5182            cx,
5183            &self.global(),
5184            visibility_state,
5185            CrossProcessInstant::now(),
5186        );
5187        self.window
5188            .Performance(cx)
5189            .queue_entry(entry.upcast::<PerformanceEntry>());
5190
5191        // Step 4 Run the screen orientation change steps with document.
5192        // TODO ScreenOrientation hasn't implemented yet
5193
5194        // Step 5 Run the view transition page visibility change steps with document.
5195        // TODO ViewTransition hasn't implemented yet
5196
5197        // Step 6 Run any page visibility change steps which may be defined in other specifications, with visibility
5198        // state and document. Any other specs' visibility steps will go here.
5199
5200        // <https://www.w3.org/TR/gamepad/#handling-visibility-change>
5201        #[cfg(feature = "gamepad")]
5202        if visibility_state == DocumentVisibilityState::Hidden {
5203            self.window
5204                .Navigator(cx)
5205                .GetGamepads(cx)
5206                .unwrap_or_default()
5207                .iter_mut()
5208                .for_each(|gamepad| {
5209                    if let Some(g) = gamepad {
5210                        g.vibration_actuator().handle_visibility_change();
5211                    }
5212                });
5213        }
5214
5215        // Step 7 Fire an event named visibilitychange at document, with its bubbles attribute initialized to true.
5216        self.upcast::<EventTarget>()
5217            .fire_bubbling_event(cx, atom!("visibilitychange"));
5218    }
5219
5220    /// <https://html.spec.whatwg.org/multipage/#is-initial-about:blank>
5221    pub(crate) fn is_initial_about_blank(&self) -> bool {
5222        self.is_initial_about_blank.get()
5223    }
5224
5225    /// <https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots>
5226    pub(crate) fn allow_declarative_shadow_roots(&self) -> bool {
5227        self.allow_declarative_shadow_roots.get()
5228    }
5229
5230    pub(crate) fn has_trustworthy_ancestor_origin(&self) -> bool {
5231        self.has_trustworthy_ancestor_origin.get()
5232    }
5233
5234    pub(crate) fn has_trustworthy_ancestor_or_current_origin(&self) -> bool {
5235        self.has_trustworthy_ancestor_origin.get() ||
5236            self.origin().immutable().is_potentially_trustworthy()
5237    }
5238
5239    pub(crate) fn highlight_dom_node(&self, node: Option<&Node>) {
5240        self.highlighted_dom_node.set(node);
5241        self.add_restyle_reason(RestyleReason::HighlightedDOMNodeChanged);
5242    }
5243
5244    pub(crate) fn highlighted_dom_node(&self) -> Option<DomRoot<Node>> {
5245        self.highlighted_dom_node.get()
5246    }
5247
5248    pub(crate) fn custom_element_reaction_stack(&self) -> Rc<CustomElementReactionStack> {
5249        self.custom_element_reaction_stack.clone()
5250    }
5251
5252    pub(crate) fn active_sandboxing_flag_set(&self) -> SandboxingFlagSet {
5253        self.active_sandboxing_flag_set.get()
5254    }
5255
5256    pub(crate) fn has_active_sandboxing_flag(&self, flag: SandboxingFlagSet) -> bool {
5257        self.active_sandboxing_flag_set.get().contains(flag)
5258    }
5259
5260    pub(crate) fn set_active_sandboxing_flag_set(&self, flags: SandboxingFlagSet) {
5261        self.active_sandboxing_flag_set.set(flags)
5262    }
5263
5264    pub(crate) fn creation_sandboxing_flag_set(&self) -> SandboxingFlagSet {
5265        self.creation_sandboxing_flag_set.get()
5266    }
5267
5268    pub(crate) fn creation_sandboxing_flag_set_considering_parent_iframe(
5269        &self,
5270    ) -> SandboxingFlagSet {
5271        self.window()
5272            .window_proxy()
5273            .frame_element()
5274            .and_then(|element| element.downcast::<HTMLIFrameElement>())
5275            .map(HTMLIFrameElement::sandboxing_flag_set)
5276            .unwrap_or_else(|| self.creation_sandboxing_flag_set())
5277    }
5278
5279    pub(crate) fn viewport_scrolling_box(&self, flags: ScrollContainerQueryFlags) -> ScrollingBox {
5280        self.window()
5281            .scrolling_box_query(None, flags)
5282            .expect("We should always have a ScrollingBox for the Viewport")
5283    }
5284
5285    pub(crate) fn notify_embedder_favicon(&self) {
5286        if let Some(ref image) = *self.favicon.borrow() {
5287            self.send_to_embedder(EmbedderMsg::NewFavicon(self.webview_id(), image.clone()));
5288        }
5289    }
5290
5291    pub(crate) fn set_favicon(&self, favicon: Image) {
5292        *self.favicon.borrow_mut() = Some(favicon);
5293        self.notify_embedder_favicon();
5294    }
5295
5296    pub(crate) fn fullscreen_element(&self) -> Option<DomRoot<Element>> {
5297        self.fullscreen_element.get()
5298    }
5299
5300    /// <https://w3c.github.io/editing/docs/execCommand/#state-override>
5301    pub(crate) fn state_override(&self, command_name: &CommandName) -> Option<bool> {
5302        self.state_override.borrow().get(command_name).copied()
5303    }
5304
5305    /// <https://w3c.github.io/editing/docs/execCommand/#state-override>
5306    pub(crate) fn set_state_override(&self, command_name: CommandName, state: Option<bool>) {
5307        if let Some(state) = state {
5308            self.state_override.borrow_mut().insert(command_name, state);
5309        } else {
5310            self.value_override.borrow_mut().remove(&command_name);
5311        }
5312    }
5313
5314    /// <https://w3c.github.io/editing/docs/execCommand/#value-override>
5315    pub(crate) fn value_override(&self, command_name: &CommandName) -> Option<DOMString> {
5316        self.value_override.borrow().get(command_name).cloned()
5317    }
5318
5319    /// <https://w3c.github.io/editing/docs/execCommand/#value-override>
5320    pub(crate) fn set_value_override(&self, command_name: CommandName, value: Option<DOMString>) {
5321        if let Some(value) = value {
5322            self.value_override.borrow_mut().insert(command_name, value);
5323        } else {
5324            self.value_override.borrow_mut().remove(&command_name);
5325        }
5326    }
5327
5328    /// <https://w3c.github.io/editing/docs/execCommand/#value-override>
5329    /// and <https://w3c.github.io/editing/docs/execCommand/#state-override>
5330    pub(crate) fn clear_command_overrides(&self) {
5331        self.state_override.borrow_mut().clear();
5332        self.value_override.borrow_mut().clear();
5333    }
5334
5335    /// <https://w3c.github.io/editing/docs/execCommand/#default-single-line-container-name>
5336    pub(crate) fn default_single_line_container_name(&self) -> DefaultSingleLineContainerName {
5337        self.default_single_line_container_name.get()
5338    }
5339
5340    /// <https://w3c.github.io/editing/docs/execCommand/#default-single-line-container-name>
5341    pub(crate) fn set_default_single_line_container_name(
5342        &self,
5343        value: DefaultSingleLineContainerName,
5344    ) {
5345        self.default_single_line_container_name.set(value)
5346    }
5347
5348    /// <https://w3c.github.io/editing/docs/execCommand/#css-styling-flag>
5349    pub(crate) fn css_styling_flag(&self) -> bool {
5350        self.css_styling_flag.get()
5351    }
5352
5353    /// <https://w3c.github.io/editing/docs/execCommand/#css-styling-flag>
5354    pub(crate) fn set_css_styling_flag(&self, value: bool) {
5355        self.css_styling_flag.set(value)
5356    }
5357
5358    pub(crate) fn mute_iframe_load_flag(&self) -> bool {
5359        self.mute_iframe_load.get()
5360    }
5361
5362    pub(crate) fn set_iframe_load_in_progress(&self, value: bool) {
5363        self.iframe_load_in_progress.set(value)
5364    }
5365
5366    pub(crate) fn theme(&self) -> Option<Theme> {
5367        self.theme.get()
5368    }
5369
5370    pub(crate) fn set_theme(&self, new_theme: Option<Theme>) {
5371        self.theme.set(new_theme);
5372        self.window.refresh_theme();
5373    }
5374
5375    pub(crate) fn theme_override(&self) -> Option<Theme> {
5376        self.theme_override.get()
5377    }
5378
5379    pub(crate) fn set_theme_override(&self, new_theme: Option<Theme>) {
5380        self.theme_override.set(new_theme);
5381        self.window.refresh_theme();
5382    }
5383
5384    pub(crate) fn default_language(&self) -> Option<String> {
5385        self.default_language.borrow().clone()
5386    }
5387
5388    pub(crate) fn set_default_language(&self, new_language: Option<String>) {
5389        *self.default_language.borrow_mut() = new_language;
5390    }
5391
5392    pub(crate) fn create_element(&self, cx: &mut JSContext, name: &str) -> DomRoot<Element> {
5393        let element_options =
5394            StringOrElementCreationOptions::ElementCreationOptions(ElementCreationOptions {
5395                is: None,
5396            });
5397        self.CreateElement(cx, name.into(), element_options)
5398            .expect("Must always be able to create element")
5399    }
5400}
5401
5402impl DocumentMethods<crate::DomTypeHolder> for Document {
5403    /// <https://dom.spec.whatwg.org/#dom-document-document>
5404    fn Constructor(
5405        cx: &mut JSContext,
5406        window: &Window,
5407        proto: Option<HandleObject>,
5408    ) -> Fallible<DomRoot<Document>> {
5409        // The new Document() constructor steps are to set this’s origin to the origin of current global object’s associated Document. [HTML]
5410        let doc = window.Document();
5411        let docloader = DocumentLoader::new(&doc.loader());
5412        Ok(Document::new_with_proto(
5413            cx,
5414            window,
5415            proto,
5416            HasBrowsingContext::No,
5417            None,
5418            None,
5419            doc.origin().clone(),
5420            IsHTMLDocument::NonHTMLDocument,
5421            None,
5422            None,
5423            DocumentActivity::Inactive,
5424            docloader,
5425            None,
5426            None,
5427            Default::default(),
5428            false,
5429            doc.allow_declarative_shadow_roots(),
5430            Some(doc.insecure_requests_policy()),
5431            doc.has_trustworthy_ancestor_or_current_origin(),
5432            doc.custom_element_reaction_stack(),
5433            doc.active_sandboxing_flag_set.get(),
5434            doc.pipeline_id(),
5435            doc.image_cache(),
5436        ))
5437    }
5438
5439    /// <https://html.spec.whatwg.org/multipage/#dom-parsehtmlunsafe>
5440    fn ParseHTMLUnsafe(
5441        cx: &mut JSContext,
5442        window: &Window,
5443        s: TrustedHTMLOrString,
5444        options: &SetHTMLUnsafeOptions,
5445    ) -> Fallible<DomRoot<Self>> {
5446        // Step 1. Let compliantHTML be the result of invoking the
5447        // Get Trusted Type compliant string algorithm with TrustedHTML, the current global object,
5448        // html, "Document parseHTMLUnsafe", and "script".
5449        let compliant_html = TrustedHTML::get_trusted_type_compliant_string(
5450            cx,
5451            window.as_global_scope(),
5452            s,
5453            "Document parseHTMLUnsafe",
5454        )?;
5455
5456        let url = window.get_url();
5457        let doc = window.Document();
5458        let loader = DocumentLoader::new(&doc.loader());
5459
5460        let content_type = "text/html"
5461            .parse()
5462            .expect("Supported type is not a MIME type");
5463        // Step 2. Let document be a new Document, whose content type is "text/html".
5464        // Step 3. Set document's allow declarative shadow roots to true.
5465        let document = Document::new(
5466            cx,
5467            window,
5468            HasBrowsingContext::No,
5469            Some(ServoUrl::parse("about:blank").unwrap()),
5470            None,
5471            doc.origin().clone(),
5472            IsHTMLDocument::HTMLDocument,
5473            Some(content_type),
5474            None,
5475            DocumentActivity::Inactive,
5476            loader,
5477            None,
5478            None,
5479            Default::default(),
5480            false,
5481            true,
5482            Some(doc.insecure_requests_policy()),
5483            doc.has_trustworthy_ancestor_or_current_origin(),
5484            doc.custom_element_reaction_stack(),
5485            doc.creation_sandboxing_flag_set(),
5486            doc.pipeline_id(),
5487            doc.image_cache(),
5488        );
5489        // Step 4. Parse HTML from string given document and compliantHTML.
5490        ServoParser::parse_html_document(cx, &document, Some(compliant_html), url, None, None);
5491
5492        // Step 5. Let sanitizer be the result of calling get a sanitizer instance from options with
5493        // options and false.
5494        let sanitizer = Sanitizer::get_sanitizer_instance_from_options(cx, window, options, false)?;
5495
5496        // Step 6. Call sanitize on document with sanitizer and false.
5497        sanitizer.sanitize(cx, document.upcast(), false)?;
5498
5499        // Step 7. Return document.
5500        document.update_the_current_document_readiness(cx, DocumentReadyState::Complete);
5501        Ok(document)
5502    }
5503
5504    /// <https://wicg.github.io/sanitizer-api/#dom-document-parsehtml>
5505    fn ParseHTML(
5506        cx: &mut JSContext,
5507        window: &Window,
5508        html: DOMString,
5509        options: &SetHTMLOptions,
5510    ) -> Fallible<DomRoot<Document>> {
5511        // Step 1. Let document be a new Document, whose content type is "text/html".
5512        // Step 2. Set document's allow declarative shadow roots to true.
5513        let url = window.get_url();
5514        let doc = window.Document();
5515        let loader = DocumentLoader::new(&doc.loader());
5516        let content_type = "text/html"
5517            .parse()
5518            .expect("Supported type is not a MIME type");
5519        let document = Document::new(
5520            cx,
5521            window,
5522            HasBrowsingContext::No,
5523            Some(ServoUrl::parse("about:blank").unwrap()),
5524            None,
5525            doc.origin().clone(),
5526            IsHTMLDocument::HTMLDocument,
5527            Some(content_type),
5528            None,
5529            DocumentActivity::Inactive,
5530            loader,
5531            None,
5532            None,
5533            Default::default(),
5534            false,
5535            true,
5536            Some(doc.insecure_requests_policy()),
5537            doc.has_trustworthy_ancestor_or_current_origin(),
5538            doc.custom_element_reaction_stack(),
5539            doc.creation_sandboxing_flag_set(),
5540            doc.pipeline_id(),
5541            doc.image_cache(),
5542        );
5543
5544        // Step 3. Parse HTML from a string given document and html.
5545        ServoParser::parse_html_document(cx, &document, Some(html), url, None, None);
5546
5547        // Step 4. Let sanitizer be the result of calling get a sanitizer instance from options with
5548        // options and true.
5549        let sanitizer = Sanitizer::get_sanitizer_instance_from_options(cx, window, options, true)?;
5550
5551        // Step 5. Call sanitize on document with sanitizer and true.
5552        sanitizer.sanitize(cx, document.upcast(), true)?;
5553
5554        // Step 6. Return document.
5555        Ok(document)
5556    }
5557
5558    /// <https://drafts.csswg.org/cssom/#dom-document-stylesheets>
5559    fn StyleSheets(&self, cx: &mut JSContext) -> DomRoot<StyleSheetList> {
5560        self.stylesheet_list.or_init(|| {
5561            StyleSheetList::new(
5562                cx,
5563                &self.window,
5564                StyleSheetListOwner::Document(Dom::from_ref(self)),
5565            )
5566        })
5567    }
5568
5569    /// <https://dom.spec.whatwg.org/#dom-document-implementation>
5570    fn Implementation(&self, cx: &mut JSContext) -> DomRoot<DOMImplementation> {
5571        self.implementation
5572            .or_init(|| DOMImplementation::new(cx, self))
5573    }
5574
5575    /// <https://dom.spec.whatwg.org/#dom-document-url>
5576    fn URL(&self) -> USVString {
5577        USVString(String::from(self.url().as_str()))
5578    }
5579
5580    /// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement>
5581    fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
5582        self.document_or_shadow_root.active_element(self.upcast())
5583    }
5584
5585    /// <https://dom.spec.whatwg.org/#dom-documentorshadowroot-customelementregistry>
5586    fn GetCustomElementRegistry(&self) -> Option<DomRoot<CustomElementRegistry>> {
5587        self.custom_element_registry()
5588    }
5589
5590    /// <https://html.spec.whatwg.org/multipage/#dom-document-hasfocus>
5591    fn HasFocus(&self) -> bool {
5592        // <https://html.spec.whatwg.org/multipage/#has-focus-steps>
5593        //
5594        // > The has focus steps, given a `Document` object `target`, are as
5595        // > follows:
5596        // >
5597        // > 1. If `target`'s browsing context's top-level browsing context does
5598        // >    not have system focus, then return false.
5599        if !self.window().webview_state().has_system_focus.get() {
5600            return false;
5601        }
5602
5603        // > 2. Let `candidate` be `target`'s browsing context's top-level
5604        // >    browsing context's active document.
5605        // >
5606        // > 3. While true:
5607        // >
5608        // >    3.1. If `candidate` is target, then return true.
5609        // >
5610        // >    3.2. If the focused area of `candidate` is a browsing context
5611        // >         container with a non-null nested browsing context, then set
5612        // >         `candidate` to the active document of that browsing context
5613        // >         container's nested browsing context.
5614        // >
5615        // >    3.3. Otherwise, return false.
5616        if self.window().parent_info().is_none() {
5617            // 2 → 3 → (3.1 || ⋯ → 3.3)
5618            self.is_fully_active()
5619        } else {
5620            // 2 → 3 → 3.2 → (⋯ → 3.1 || ⋯ → 3.3)
5621            self.is_fully_active() && self.focus_handler.has_focus()
5622        }
5623    }
5624
5625    /// <https://html.spec.whatwg.org/multipage/#dom-document-domain>
5626    fn Domain(&self) -> DOMString {
5627        // Step 1. Let effectiveDomain be this's origin's effective domain.
5628        match self.origin().effective_domain() {
5629            // Step 2. If effectiveDomain is null, then return the empty string.
5630            None => DOMString::new(),
5631            // Step 3. Return effectiveDomain, serialized.
5632            Some(Host::Domain(domain)) => DOMString::from(domain),
5633            Some(host) => DOMString::from(host.to_string()),
5634        }
5635    }
5636
5637    /// <https://html.spec.whatwg.org/multipage/#dom-document-domain>
5638    fn SetDomain(&self, value: DOMString) -> ErrorResult {
5639        // Step 1. If this's browsing context is null, then throw a "SecurityError" DOMException.
5640        if !self.has_browsing_context {
5641            return Err(Error::Security(Some(
5642                "Document has no browsing context".into(),
5643            )));
5644        }
5645
5646        // Step 2. If this Document object's active sandboxing flag set has its sandboxed
5647        // document.domain browsing context flag set, then throw a "SecurityError" DOMException.
5648        if self.has_active_sandboxing_flag(
5649            SandboxingFlagSet::SANDBOXED_DOCUMENT_DOMAIN_BROWSING_CONTEXT_FLAG,
5650        ) {
5651            return Err(Error::Security(Some(
5652                "Sandboxed document cannot set its domain".into(),
5653            )));
5654        }
5655
5656        // Step 3. Let effectiveDomain be this's origin's effective domain.
5657        let effective_domain = match self.origin().effective_domain() {
5658            Some(effective_domain) => effective_domain,
5659            // Step 4. If effectiveDomain is null, then throw a "SecurityError" DOMException.
5660            None => return Err(Error::Security(Some("Document's origin is opaque".into()))),
5661        };
5662
5663        // Step 5. If the given value is not a registrable domain suffix of and is not equal to effectiveDomain, then throw a "SecurityError" DOMException.
5664        let host =
5665            match get_registrable_domain_suffix_of_or_is_equal_to(&value.str(), effective_domain) {
5666                None => return Err(Error::Security(Some("Provided domain is not a registrable domain suffix and is not equal to document's effective domain".into()))),
5667                Some(host) => host,
5668            };
5669
5670        // Step 6. If the surrounding agent's agent cluster's is origin-keyed is true, then return.
5671        // TODO
5672
5673        // Step 7. Set this's origin's domain to the result of parsing the given value.
5674        self.origin().set_domain(host);
5675
5676        Ok(())
5677    }
5678
5679    /// <https://html.spec.whatwg.org/multipage/#dom-document-referrer>
5680    fn Referrer(&self) -> DOMString {
5681        match self.referrer {
5682            Some(ref referrer) => DOMString::from(referrer.to_string()),
5683            None => DOMString::new(),
5684        }
5685    }
5686
5687    /// <https://dom.spec.whatwg.org/#dom-document-documenturi>
5688    fn DocumentURI(&self) -> USVString {
5689        self.URL()
5690    }
5691
5692    /// <https://dom.spec.whatwg.org/#dom-document-compatmode>
5693    fn CompatMode(&self) -> DOMString {
5694        DOMString::from(match self.quirks_mode.get() {
5695            QuirksMode::LimitedQuirks | QuirksMode::NoQuirks => "CSS1Compat",
5696            QuirksMode::Quirks => "BackCompat",
5697        })
5698    }
5699
5700    /// <https://dom.spec.whatwg.org/#dom-document-characterset>
5701    fn CharacterSet(&self) -> DOMString {
5702        DOMString::from_static(self.encoding.get().name())
5703    }
5704
5705    /// <https://dom.spec.whatwg.org/#dom-document-charset>
5706    fn Charset(&self) -> DOMString {
5707        self.CharacterSet()
5708    }
5709
5710    /// <https://dom.spec.whatwg.org/#dom-document-inputencoding>
5711    fn InputEncoding(&self) -> DOMString {
5712        self.CharacterSet()
5713    }
5714
5715    /// <https://dom.spec.whatwg.org/#dom-document-content_type>
5716    fn ContentType(&self) -> DOMString {
5717        DOMString::from(self.content_type.to_string())
5718    }
5719
5720    /// <https://dom.spec.whatwg.org/#dom-document-doctype>
5721    fn GetDoctype(&self) -> Option<DomRoot<DocumentType>> {
5722        self.upcast::<Node>().children().find_map(DomRoot::downcast)
5723    }
5724
5725    /// <https://dom.spec.whatwg.org/#dom-document-documentelement>
5726    fn GetDocumentElement(&self) -> Option<DomRoot<Element>> {
5727        self.upcast::<Node>().child_elements().next()
5728    }
5729
5730    /// <https://dom.spec.whatwg.org/#dom-document-getelementsbytagname>
5731    fn GetElementsByTagName(
5732        &self,
5733        cx: &mut JSContext,
5734        qualified_name: DOMString,
5735    ) -> DomRoot<HTMLCollection> {
5736        let qualified_name = LocalName::from(qualified_name);
5737        if let Some(entry) = self.tag_map.borrow_mut().get(&qualified_name) {
5738            return DomRoot::from_ref(entry);
5739        }
5740        let result = HTMLCollection::by_qualified_name(
5741            cx,
5742            &self.window,
5743            self.upcast(),
5744            qualified_name.clone(),
5745        );
5746        self.tag_map
5747            .borrow_mut()
5748            .insert(qualified_name, Dom::from_ref(&*result));
5749        result
5750    }
5751
5752    /// <https://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens>
5753    fn GetElementsByTagNameNS(
5754        &self,
5755        cx: &mut JSContext,
5756        maybe_ns: Option<DOMString>,
5757        tag_name: DOMString,
5758    ) -> DomRoot<HTMLCollection> {
5759        let ns = namespace_from_domstring(maybe_ns);
5760        let local = LocalName::from(tag_name);
5761        let qname = QualName::new(None, ns, local);
5762        if let Some(collection) = self.tagns_map.borrow().get(&qname) {
5763            return DomRoot::from_ref(collection);
5764        }
5765        let result =
5766            HTMLCollection::by_qual_tag_name(cx, &self.window, self.upcast(), qname.clone());
5767        self.tagns_map
5768            .borrow_mut()
5769            .insert(qname, Dom::from_ref(&*result));
5770        result
5771    }
5772
5773    /// <https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname>
5774    fn GetElementsByClassName(
5775        &self,
5776        cx: &mut JSContext,
5777        classes: DOMString,
5778    ) -> DomRoot<HTMLCollection> {
5779        let class_atoms: Vec<Atom> = split_html_space_chars(&classes.str())
5780            .map(Atom::from)
5781            .collect();
5782        if let Some(collection) = self.classes_map.borrow().get(&class_atoms) {
5783            return DomRoot::from_ref(collection);
5784        }
5785        let result = HTMLCollection::by_atomic_class_name(
5786            cx,
5787            &self.window,
5788            self.upcast(),
5789            class_atoms.clone(),
5790        );
5791        self.classes_map
5792            .borrow_mut()
5793            .insert(class_atoms, Dom::from_ref(&*result));
5794        result
5795    }
5796
5797    /// <https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid>
5798    fn GetElementById(
5799        &self,
5800        cx: &js::context::JSContext,
5801        id: DOMString,
5802    ) -> Option<DomRoot<Element>> {
5803        self.get_element_by_id(cx, &Atom::from(id))
5804    }
5805
5806    /// <https://dom.spec.whatwg.org/#dom-document-createelement>
5807    fn CreateElement(
5808        &self,
5809        cx: &mut JSContext,
5810        mut local_name: DOMString,
5811        options: StringOrElementCreationOptions,
5812    ) -> Fallible<DomRoot<Element>> {
5813        // Step 1. If localName is not a valid element local name, then throw an "InvalidCharacterError" DOMException.
5814        if !is_valid_element_local_name(&local_name.str()) {
5815            return Err(Error::InvalidCharacter(Some(
5816                "Provided element local name is invalid".into(),
5817            )));
5818        }
5819
5820        // Step 2. If this is an HTML document, then set localName to localName in ASCII lowercase.
5821        if self.is_html_document {
5822            local_name.make_ascii_lowercase();
5823        }
5824
5825        // Step 4. Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml"; otherwise null.
5826        let ns = if self.is_html_document || self.is_xhtml_document() {
5827            ns!(html)
5828        } else {
5829            ns!()
5830        };
5831        let name = QualName::new(None, ns, LocalName::from(local_name));
5832
5833        let is = match options {
5834            StringOrElementCreationOptions::String(_) => None,
5835            StringOrElementCreationOptions::ElementCreationOptions(options) => {
5836                options.is.as_ref().map(LocalName::from)
5837            },
5838        };
5839        Ok(Element::create(
5840            cx,
5841            name,
5842            is,
5843            self,
5844            ElementCreator::ScriptCreated,
5845            CustomElementCreationMode::Synchronous,
5846            None,
5847        ))
5848    }
5849
5850    /// <https://dom.spec.whatwg.org/#dom-document-createelementns>
5851    fn CreateElementNS(
5852        &self,
5853        cx: &mut JSContext,
5854        namespace: Option<DOMString>,
5855        qualified_name: DOMString,
5856        options: StringOrElementCreationOptions,
5857    ) -> Fallible<DomRoot<Element>> {
5858        // Step 1. Let (namespace, prefix, localName) be the result of
5859        //      validating and extracting namespace and qualifiedName given "element".
5860        let context = domname::Context::Element;
5861        let (namespace, prefix, local_name) =
5862            domname::validate_and_extract(namespace, &qualified_name, context)?;
5863
5864        // Step 2. Let is be null.
5865        // Step 3. If options is a dictionary and options["is"] exists, then set is to it.
5866        let name = QualName::new(prefix, namespace, local_name);
5867        let is = match options {
5868            StringOrElementCreationOptions::String(_) => None,
5869            StringOrElementCreationOptions::ElementCreationOptions(options) => {
5870                options.is.as_ref().map(LocalName::from)
5871            },
5872        };
5873
5874        // Step 4. Return the result of creating an element given document, localName, namespace, prefix, is, and true.
5875        Ok(Element::create(
5876            cx,
5877            name,
5878            is,
5879            self,
5880            ElementCreator::ScriptCreated,
5881            CustomElementCreationMode::Synchronous,
5882            None,
5883        ))
5884    }
5885
5886    /// <https://dom.spec.whatwg.org/#dom-document-createattribute>
5887    fn CreateAttribute(
5888        &self,
5889        cx: &mut JSContext,
5890        mut local_name: DOMString,
5891    ) -> Fallible<DomRoot<Attr>> {
5892        // Step 1. If localName is not a valid attribute local name, then throw an "InvalidCharacterError" DOMException
5893        if !is_valid_attribute_local_name(&local_name.str()) {
5894            return Err(Error::InvalidCharacter(Some(
5895                "Provided local name is invalid".into(),
5896            )));
5897        }
5898
5899        // Step 2. If this is an HTML document, then set localName to localName in ASCII lowercase.
5900        if self.is_html_document {
5901            local_name.make_ascii_lowercase();
5902        }
5903        let name = LocalName::from(local_name);
5904        let value = AttrValue::String(String::new());
5905
5906        Ok(Attr::new(
5907            cx,
5908            self,
5909            name.clone(),
5910            value,
5911            name,
5912            ns!(),
5913            None,
5914            None,
5915        ))
5916    }
5917
5918    /// <https://dom.spec.whatwg.org/#dom-document-createattributens>
5919    fn CreateAttributeNS(
5920        &self,
5921        cx: &mut JSContext,
5922        namespace: Option<DOMString>,
5923        qualified_name: DOMString,
5924    ) -> Fallible<DomRoot<Attr>> {
5925        // Step 1. Let (namespace, prefix, localName) be the result of validating and
5926        //      extracting namespace and qualifiedName given "attribute".
5927        let context = domname::Context::Attribute;
5928        let (namespace, prefix, local_name) =
5929            domname::validate_and_extract(namespace, &qualified_name, context)?;
5930        let value = AttrValue::String(String::new());
5931        let qualified_name = LocalName::from(qualified_name);
5932        Ok(Attr::new(
5933            cx,
5934            self,
5935            local_name,
5936            value,
5937            qualified_name,
5938            namespace,
5939            prefix,
5940            None,
5941        ))
5942    }
5943
5944    /// <https://dom.spec.whatwg.org/#dom-document-createdocumentfragment>
5945    fn CreateDocumentFragment(&self, cx: &mut JSContext) -> DomRoot<DocumentFragment> {
5946        DocumentFragment::new(cx, self)
5947    }
5948
5949    /// <https://dom.spec.whatwg.org/#dom-document-createtextnode>
5950    fn CreateTextNode(&self, cx: &mut JSContext, data: DOMString) -> DomRoot<Text> {
5951        Text::new(cx, data, self)
5952    }
5953
5954    /// <https://dom.spec.whatwg.org/#dom-document-createcdatasection>
5955    fn CreateCDATASection(
5956        &self,
5957        cx: &mut JSContext,
5958        data: DOMString,
5959    ) -> Fallible<DomRoot<CDATASection>> {
5960        // Step 1
5961        if self.is_html_document {
5962            return Err(Error::NotSupported(Some(
5963                "Document must be an XML document".into(),
5964            )));
5965        }
5966
5967        // Step 2
5968        if data.contains("]]>") {
5969            return Err(Error::InvalidCharacter(Some(
5970                "CDATA section cannot include `]]>`".into(),
5971            )));
5972        }
5973
5974        // Step 3
5975        Ok(CDATASection::new(cx, data, self))
5976    }
5977
5978    /// <https://dom.spec.whatwg.org/#dom-document-createcomment>
5979    fn CreateComment(&self, cx: &mut JSContext, data: DOMString) -> DomRoot<Comment> {
5980        Comment::new(cx, data, self, None)
5981    }
5982
5983    /// <https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction>
5984    fn CreateProcessingInstruction(
5985        &self,
5986        cx: &mut JSContext,
5987        target: DOMString,
5988        data: DOMString,
5989    ) -> Fallible<DomRoot<ProcessingInstruction>> {
5990        // Step 1. If target does not match the Name production, then throw an "InvalidCharacterError" DOMException.
5991        if !matches_name_production(&target.str()) {
5992            return Err(Error::InvalidCharacter(Some(
5993                "Target name provided is invalid".into(),
5994            )));
5995        }
5996
5997        // Step 2. If data contains the string "?>", then throw an "InvalidCharacterError" DOMException.
5998        if data.contains("?>") {
5999            return Err(Error::InvalidCharacter(Some(
6000                "Processing instruction's data cannot contain `>?`".into(),
6001            )));
6002        }
6003
6004        // Step 3.
6005        Ok(ProcessingInstruction::new(cx, target, data, self))
6006    }
6007
6008    /// <https://dom.spec.whatwg.org/#dom-document-importnode>
6009    fn ImportNode(
6010        &self,
6011        cx: &mut JSContext,
6012        node: &Node,
6013        options: BooleanOrImportNodeOptions,
6014    ) -> Fallible<DomRoot<Node>> {
6015        // Step 1. If node is a document or shadow root, then throw a "NotSupportedError" DOMException.
6016        if node.is::<Document>() || node.is::<ShadowRoot>() {
6017            return Err(Error::NotSupported(Some(
6018                "Node cannot be a document or shadow root".into(),
6019            )));
6020        }
6021        // Step 2. Let subtree be false.
6022        let (subtree, registry) = match options {
6023            // Step 3. Let registry be null.
6024            // Step 4. If options is a boolean, then set subtree to options.
6025            BooleanOrImportNodeOptions::Boolean(boolean) => (boolean.into(), None),
6026            // Step 5. Otherwise:
6027            BooleanOrImportNodeOptions::ImportNodeOptions(options) => {
6028                // Step 5.1. Set subtree to the negation of options["selfOnly"].
6029                let subtree = (!options.selfOnly).into();
6030                // Step 5.2. If options["customElementRegistry"] exists, then set registry to it.
6031                let registry = if let Some(registry) = options.customElementRegistry {
6032                    // Step 5.3. If registry's is scoped is false and registry
6033                    // is not this's custom element registry, then throw a "NotSupportedError" DOMException.
6034                    let this_registry = self
6035                        .custom_element_registry()
6036                        .expect("Document must have a custom element registry");
6037                    if !registry.is_scoped() && registry != this_registry {
6038                        return Err(Error::NotSupported(Some(
6039                            "Imported customElementRegistry is not scoped and does not match existing registry.".into()
6040                        )));
6041                    }
6042                    Some(registry)
6043                } else {
6044                    None
6045                };
6046                (subtree, registry)
6047            },
6048        };
6049        // Step 6. If registry is null, then set registry to the
6050        // result of looking up a custom element registry given this.
6051        let registry = registry
6052            .or_else(|| CustomElementRegistry::lookup_a_custom_element_registry(self.upcast()));
6053
6054        // Step 7. Return the result of cloning a node given node with
6055        // document set to this, subtree set to subtree, and fallbackRegistry set to registry.
6056        Ok(Node::clone(cx, node, Some(self), subtree, registry))
6057    }
6058
6059    /// <https://dom.spec.whatwg.org/#dom-document-adoptnode>
6060    fn AdoptNode(&self, cx: &mut JSContext, node: &Node) -> Fallible<DomRoot<Node>> {
6061        // Step 1.
6062        if node.is::<Document>() {
6063            return Err(Error::NotSupported(Some(
6064                "Node cannot be a document".into(),
6065            )));
6066        }
6067
6068        // Step 2.
6069        if node.is::<ShadowRoot>() {
6070            return Err(Error::HierarchyRequest(Some(
6071                "Node cannot be a shadow root".into(),
6072            )));
6073        }
6074
6075        // Step 3.
6076        Node::adopt(cx, node, self);
6077
6078        // Step 4.
6079        Ok(DomRoot::from_ref(node))
6080    }
6081
6082    /// <https://dom.spec.whatwg.org/#dom-document-createevent>
6083    fn CreateEvent(
6084        &self,
6085        cx: &mut JSContext,
6086        mut interface: DOMString,
6087    ) -> Fallible<DomRoot<Event>> {
6088        interface.make_ascii_lowercase();
6089        match &*interface.str() {
6090            "beforeunloadevent" => Ok(DomRoot::upcast(BeforeUnloadEvent::new_uninitialized(
6091                cx,
6092                &self.window,
6093            ))),
6094            "compositionevent" => Ok(DomRoot::upcast(CompositionEvent::new_uninitialized(
6095                cx,
6096                &self.window,
6097            ))),
6098            "customevent" => Ok(DomRoot::upcast(CustomEvent::new_uninitialized(
6099                cx,
6100                self.window.upcast(),
6101            ))),
6102            // FIXME(#25136): devicemotionevent, deviceorientationevent
6103            // FIXME(#7529): dragevent
6104            "events" | "event" | "htmlevents" | "svgevents" => {
6105                Ok(Event::new_uninitialized(cx, self.window.upcast()))
6106            },
6107            "focusevent" => Ok(DomRoot::upcast(FocusEvent::new_uninitialized(
6108                cx,
6109                &self.window,
6110            ))),
6111            "hashchangeevent" => Ok(DomRoot::upcast(HashChangeEvent::new_uninitialized(
6112                cx,
6113                &self.window,
6114            ))),
6115            "keyboardevent" => Ok(DomRoot::upcast(KeyboardEvent::new_uninitialized(
6116                cx,
6117                &self.window,
6118            ))),
6119            "messageevent" => Ok(DomRoot::upcast(MessageEvent::new_uninitialized(
6120                cx,
6121                self.window.upcast(),
6122            ))),
6123            "mouseevent" | "mouseevents" => Ok(DomRoot::upcast(MouseEvent::new_uninitialized(
6124                cx,
6125                &self.window,
6126            ))),
6127            "storageevent" => Ok(DomRoot::upcast(StorageEvent::new_uninitialized(
6128                cx,
6129                &self.window,
6130                DOMString::new(),
6131            ))),
6132            "textevent" => Ok(DomRoot::upcast(TextEvent::new_uninitialized(
6133                cx,
6134                &self.window,
6135            ))),
6136            "touchevent" => {
6137                let touches = TouchList::new(cx, &self.window, &[]);
6138                let changed_touches = TouchList::new(cx, &self.window, &[]);
6139                let target_touches = TouchList::new(cx, &self.window, &[]);
6140
6141                Ok(DomRoot::upcast(DomTouchEvent::new_uninitialized(
6142                    cx,
6143                    &self.window,
6144                    &touches,
6145                    &changed_touches,
6146                    &target_touches,
6147                )))
6148            },
6149            "uievent" | "uievents" => Ok(DomRoot::upcast(UIEvent::new_uninitialized(
6150                cx,
6151                &self.window,
6152            ))),
6153            _ => Err(Error::NotSupported(Some(
6154                "Interface is not supported".into(),
6155            ))),
6156        }
6157    }
6158
6159    /// <https://html.spec.whatwg.org/multipage/#dom-document-lastmodified>
6160    fn LastModified(&self) -> DOMString {
6161        DOMString::from(self.last_modified.as_ref().cloned().unwrap_or_else(|| {
6162            // Ideally this would get the local time using `time`, but `time` always fails to get the local
6163            // timezone on Unix unless the application is single threaded unless the library is explicitly
6164            // set to "unsound" mode. Maybe that's fine, but it needs more investigation. see
6165            // https://nvd.nist.gov/vuln/detail/CVE-2020-26235
6166            // When `time` supports a thread-safe way of getting the local time zone we could use it here.
6167            Local::now().format("%m/%d/%Y %H:%M:%S").to_string()
6168        }))
6169    }
6170
6171    /// <https://dom.spec.whatwg.org/#dom-document-createrange>
6172    fn CreateRange(&self, cx: &mut JSContext) -> DomRoot<Range> {
6173        Range::new_with_doc(cx, self, None)
6174    }
6175
6176    /// <https://dom.spec.whatwg.org/#dom-document-createnodeiteratorroot-whattoshow-filter>
6177    fn CreateNodeIterator(
6178        &self,
6179        cx: &mut js::context::JSContext,
6180        root: &Node,
6181        what_to_show: u32,
6182        filter: Option<RootedCallback<NodeFilter>>,
6183    ) -> DomRoot<NodeIterator> {
6184        NodeIterator::new(cx, self, root, what_to_show, filter)
6185    }
6186
6187    /// <https://dom.spec.whatwg.org/#dom-document-createtreewalker>
6188    fn CreateTreeWalker(
6189        &self,
6190        cx: &mut JSContext,
6191        root: &Node,
6192        what_to_show: u32,
6193        filter: Option<RootedCallback<NodeFilter>>,
6194    ) -> DomRoot<TreeWalker> {
6195        TreeWalker::new(cx, self, root, what_to_show, filter)
6196    }
6197
6198    /// <https://html.spec.whatwg.org/multipage/#document.title>
6199    fn Title(&self) -> DOMString {
6200        self.title().unwrap_or_default()
6201    }
6202
6203    /// <https://html.spec.whatwg.org/multipage/#document.title>
6204    fn SetTitle(&self, cx: &mut JSContext, title: DOMString) {
6205        let root = match self.GetDocumentElement() {
6206            Some(root) => root,
6207            None => return,
6208        };
6209
6210        // > On setting, the steps corresponding to the first matching condition in the following list must be run:
6211        // ↪ If the document element is an SVG svg element
6212        let node = if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") {
6213            // Step 1. If there is an SVG title element that is a child of the document element,
6214            // let element be the first such element.
6215            let elem = root
6216                .upcast::<Node>()
6217                .child_elements_unrooted(cx.no_gc())
6218                .find(|node| {
6219                    node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
6220                });
6221            match elem {
6222                Some(elem) => UnrootedDom::upcast::<Node>(elem).as_rooted(),
6223                // Step 2. Otherwise:
6224                None => {
6225                    // Step 2.1 Let element be the result of creating an element given the document element's
6226                    // node document, "title", and the SVG namespace.
6227                    let name = QualName::new(None, ns!(svg), local_name!("title"));
6228                    let elem = Element::create(
6229                        cx,
6230                        name,
6231                        None,
6232                        self,
6233                        ElementCreator::ScriptCreated,
6234                        CustomElementCreationMode::Synchronous,
6235                        None,
6236                    );
6237
6238                    // Step 2.2 Insert element as the first child of the document element.
6239                    let parent = root.upcast::<Node>();
6240                    let child = elem.upcast::<Node>();
6241                    parent
6242                        .InsertBefore(cx, child, parent.GetFirstChild().as_deref())
6243                        .unwrap()
6244                },
6245            }
6246        }
6247        // ↪ If the document element is in the HTML namespace
6248        else if root.namespace() == &ns!(html) {
6249            let elem = root
6250                .upcast::<Node>()
6251                .traverse_preorder_unrooted(cx.no_gc(), ShadowIncluding::No)
6252                .find(|node| node.is::<HTMLTitleElement>());
6253            match elem {
6254                // Step 2. If the title element is non-null, let element be the title element.
6255                Some(elem) => elem.as_rooted(),
6256                // Step 3. Otherwise:
6257                None => match self.GetHead() {
6258                    Some(head) => {
6259                        // Step 3.1 Let element be the result of creating an element given the
6260                        // document element's node document, "title", and the HTML namespace.
6261                        let name = QualName::new(None, ns!(html), local_name!("title"));
6262                        let elem = Element::create(
6263                            cx,
6264                            name,
6265                            None,
6266                            self,
6267                            ElementCreator::ScriptCreated,
6268                            CustomElementCreationMode::Synchronous,
6269                            None,
6270                        );
6271
6272                        // Step 3.2 Append element to the head element.
6273                        head.upcast::<Node>()
6274                            .AppendChild(cx, elem.upcast())
6275                            .unwrap()
6276                    },
6277                    // Step 1. If the title element is null and the head element is null, then return.
6278                    None => return,
6279                },
6280            }
6281        }
6282        // ↪ Otherwise
6283        else {
6284            // Do nothing.
6285            return;
6286        };
6287
6288        // Step 3. of "↪ If the document element is an SVG svg element"
6289        // Step 4. of "↪ If the document element is in the HTML namespace"
6290        //
6291        // > String replace all with the given value within element.
6292        node.set_text_content_for_element(cx, Some(title));
6293    }
6294
6295    /// <https://html.spec.whatwg.org/multipage/#dom-document-dir>
6296    fn Dir(&self) -> DOMString {
6297        self.get_html_element()
6298            .map(|html| html.upcast::<HTMLElement>().Dir())
6299            .unwrap_or_default()
6300    }
6301
6302    /// <https://html.spec.whatwg.org/multipage/#dom-document-dir>
6303    fn SetDir(&self, cx: &mut JSContext, dir: DOMString) {
6304        if let Some(html) = self.get_html_element() {
6305            html.upcast::<HTMLElement>().SetDir(cx, dir);
6306        }
6307    }
6308
6309    /// <https://html.spec.whatwg.org/multipage/#dom-document-head>
6310    fn GetHead(&self) -> Option<DomRoot<HTMLHeadElement>> {
6311        self.get_html_element()
6312            .and_then(|root| root.upcast::<Node>().children().find_map(DomRoot::downcast))
6313    }
6314
6315    /// <https://html.spec.whatwg.org/multipage/#dom-document-currentscript>
6316    fn GetCurrentScript(&self) -> Option<DomRoot<HTMLScriptElement>> {
6317        self.current_script.get()
6318    }
6319
6320    /// <https://html.spec.whatwg.org/multipage/#dom-document-body>
6321    fn GetBody(&self) -> Option<DomRoot<HTMLElement>> {
6322        // > The body element of a document is the first of the html element's children
6323        // > that is either a body element or a frameset element, or null if there is no such element.
6324        self.get_html_element().and_then(|root| {
6325            let node = root.upcast::<Node>();
6326            node.children()
6327                .find(|child| {
6328                    matches!(
6329                        child.type_id(),
6330                        NodeTypeId::Element(ElementTypeId::HTMLElement(
6331                            HTMLElementTypeId::HTMLBodyElement,
6332                        )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
6333                            HTMLElementTypeId::HTMLFrameSetElement,
6334                        ))
6335                    )
6336                })
6337                .map(|node| DomRoot::downcast(node).unwrap())
6338        })
6339    }
6340
6341    /// <https://html.spec.whatwg.org/multipage/#dom-document-body>
6342    fn SetBody(&self, cx: &mut JSContext, new_body: Option<&HTMLElement>) -> ErrorResult {
6343        // Step 1. If the new value is not a body or frameset element, then throw a "HierarchyRequestError" DOMException.
6344        let new_body = match new_body {
6345            Some(new_body) => new_body,
6346            None => {
6347                return Err(Error::HierarchyRequest(Some(
6348                    "HTML element provided is neither a body nor a frameset element".into(),
6349                )));
6350            },
6351        };
6352
6353        let node = new_body.upcast::<Node>();
6354        match node.type_id() {
6355            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) |
6356            NodeTypeId::Element(ElementTypeId::HTMLElement(
6357                HTMLElementTypeId::HTMLFrameSetElement,
6358            )) => {},
6359            _ => {
6360                return Err(Error::HierarchyRequest(Some(
6361                    "HTML element provided is neither a body nor a frameset element".into(),
6362                )));
6363            },
6364        }
6365
6366        // Step 2. Otherwise, if the new value is the same as the body element, return.
6367        let old_body = self.GetBody();
6368        if old_body.as_deref() == Some(new_body) {
6369            return Ok(());
6370        }
6371
6372        match (self.GetDocumentElement(), &old_body) {
6373            // Step 3. Otherwise, if the body element is not null,
6374            // then replace the body element with the new value within the body element's parent and return.
6375            (Some(ref root), Some(child)) => {
6376                let root = root.upcast::<Node>();
6377                root.ReplaceChild(cx, new_body.upcast(), child.upcast())
6378                    .map(|_| ())
6379            },
6380
6381            // Step 4. Otherwise, if there is no document element, throw a "HierarchyRequestError" DOMException.
6382            (None, _) => Err(Error::HierarchyRequest(Some(
6383                "Document element is missing".into(),
6384            ))),
6385
6386            // Step 5. Otherwise, the body element is null, but there's a document element.
6387            // Append the new value to the document element.
6388            (Some(ref root), &None) => {
6389                let root = root.upcast::<Node>();
6390                root.AppendChild(cx, new_body.upcast()).map(|_| ())
6391            },
6392        }
6393    }
6394
6395    /// <https://html.spec.whatwg.org/multipage/#dom-document-getelementsbyname>
6396    fn GetElementsByName(&self, cx: &mut JSContext, name: DOMString) -> DomRoot<NodeList> {
6397        NodeList::new_elements_by_name_list(cx, self.window(), self, name)
6398    }
6399
6400    /// <https://html.spec.whatwg.org/multipage/#dom-document-images>
6401    fn Images(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6402        self.images.or_init(|| {
6403            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6404                element.is::<HTMLImageElement>()
6405            })
6406        })
6407    }
6408
6409    /// <https://html.spec.whatwg.org/multipage/#dom-document-embeds>
6410    fn Embeds(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6411        self.embeds.or_init(|| {
6412            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6413                element.is::<HTMLEmbedElement>()
6414            })
6415        })
6416    }
6417
6418    /// <https://html.spec.whatwg.org/multipage/#dom-document-plugins>
6419    fn Plugins(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6420        self.Embeds(cx)
6421    }
6422
6423    /// <https://html.spec.whatwg.org/multipage/#dom-document-links>
6424    fn Links(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6425        self.links.or_init(|| {
6426            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6427                (element.is::<HTMLAnchorElement>() || element.is::<HTMLAreaElement>()) &&
6428                    element.has_attribute(&local_name!("href"))
6429            })
6430        })
6431    }
6432
6433    /// <https://html.spec.whatwg.org/multipage/#dom-document-forms>
6434    fn Forms(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6435        self.forms.or_init(|| {
6436            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6437                element.is::<HTMLFormElement>()
6438            })
6439        })
6440    }
6441
6442    /// <https://html.spec.whatwg.org/multipage/#dom-document-scripts>
6443    fn Scripts(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6444        self.scripts.or_init(|| {
6445            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6446                element.is::<HTMLScriptElement>()
6447            })
6448        })
6449    }
6450
6451    /// <https://html.spec.whatwg.org/multipage/#dom-document-anchors>
6452    fn Anchors(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6453        self.anchors.or_init(|| {
6454            HTMLCollection::new_with_filter_fn(cx, &self.window, self.upcast(), |element, _| {
6455                element.is::<HTMLAnchorElement>() && element.has_attribute(&local_name!("href"))
6456            })
6457        })
6458    }
6459
6460    /// <https://html.spec.whatwg.org/multipage/#dom-document-applets>
6461    fn Applets(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6462        self.applets
6463            .or_init(|| HTMLCollection::always_empty(cx, &self.window, self.upcast()))
6464    }
6465
6466    /// <https://html.spec.whatwg.org/multipage/#dom-document-location>
6467    fn GetLocation(&self, cx: &mut JSContext) -> Option<DomRoot<Location>> {
6468        if self.is_fully_active() {
6469            Some(self.window.Location(cx))
6470        } else {
6471            None
6472        }
6473    }
6474
6475    /// <https://dom.spec.whatwg.org/#dom-parentnode-children>
6476    fn Children(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
6477        HTMLCollection::children(cx, &self.window, self.upcast())
6478    }
6479
6480    /// <https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild>
6481    fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> {
6482        self.upcast::<Node>().child_elements().next()
6483    }
6484
6485    /// <https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild>
6486    fn GetLastElementChild(&self) -> Option<DomRoot<Element>> {
6487        self.upcast::<Node>()
6488            .rev_children()
6489            .find_map(DomRoot::downcast)
6490    }
6491
6492    /// <https://dom.spec.whatwg.org/#dom-parentnode-childelementcount>
6493    fn ChildElementCount(&self) -> u32 {
6494        self.upcast::<Node>().child_elements().count() as u32
6495    }
6496
6497    /// <https://dom.spec.whatwg.org/#dom-parentnode-prepend>
6498    fn Prepend(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
6499        self.upcast::<Node>().prepend(cx, nodes)
6500    }
6501
6502    /// <https://dom.spec.whatwg.org/#dom-parentnode-append>
6503    fn Append(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
6504        self.upcast::<Node>().append(cx, nodes)
6505    }
6506
6507    /// <https://dom.spec.whatwg.org/#dom-parentnode-replacechildren>
6508    fn ReplaceChildren(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
6509        self.upcast::<Node>().replace_children(cx, nodes)
6510    }
6511
6512    /// <https://dom.spec.whatwg.org/#dom-parentnode-movebefore>
6513    fn MoveBefore(&self, cx: &mut JSContext, node: &Node, child: Option<&Node>) -> ErrorResult {
6514        self.upcast::<Node>().move_before(cx, node, child)
6515    }
6516
6517    /// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
6518    fn QuerySelector(
6519        &self,
6520        cx: &mut JSContext,
6521        selectors: DOMString,
6522    ) -> Fallible<Option<DomRoot<Element>>> {
6523        self.upcast::<Node>().query_selector(cx.no_gc(), selectors)
6524    }
6525
6526    /// <https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall>
6527    fn QuerySelectorAll(
6528        &self,
6529        cx: &mut JSContext,
6530        selectors: DOMString,
6531    ) -> Fallible<DomRoot<NodeList>> {
6532        self.upcast::<Node>().query_selector_all(cx, selectors)
6533    }
6534
6535    /// <https://html.spec.whatwg.org/multipage/#dom-document-readystate>
6536    fn ReadyState(&self) -> DocumentReadyState {
6537        self.ready_state.get()
6538    }
6539
6540    /// <https://html.spec.whatwg.org/multipage/#dom-document-defaultview>
6541    fn GetDefaultView(&self) -> Option<DomRoot<Window>> {
6542        if self.has_browsing_context {
6543            Some(DomRoot::from_ref(&*self.window))
6544        } else {
6545            None
6546        }
6547    }
6548
6549    /// <https://html.spec.whatwg.org/multipage/#dom-document-cookie>
6550    fn GetCookie(&self) -> Fallible<DOMString> {
6551        if self.is_cookie_averse() {
6552            return Ok(DOMString::new());
6553        }
6554
6555        if !self.origin().is_tuple() {
6556            return Err(Error::Security(Some("Document's origin is opaque".into())));
6557        }
6558
6559        let url = self.url();
6560        let (tx, rx) =
6561            profile_generic_channel::channel(self.global().time_profiler_chan().clone()).unwrap();
6562        let _ = self
6563            .window
6564            .as_global_scope()
6565            .resource_threads()
6566            .send(GetCookieStringForUrl(url, tx, NonHTTP));
6567        let cookies = rx.recv().unwrap();
6568        Ok(cookies.map_or(DOMString::new(), DOMString::from))
6569    }
6570
6571    /// <https://html.spec.whatwg.org/multipage/#dom-document-cookie>
6572    fn SetCookie(&self, cookie: DOMString) -> ErrorResult {
6573        if self.is_cookie_averse() {
6574            return Ok(());
6575        }
6576
6577        if !self.origin().is_tuple() {
6578            return Err(Error::Security(Some("Document's origin is opaque".into())));
6579        }
6580
6581        if !cookie.is_valid_for_cookie() {
6582            return Ok(());
6583        }
6584
6585        let cookies = if let Some(cookie) = Cookie::parse(cookie.to_string()).ok().map(Serde) {
6586            vec![cookie]
6587        } else {
6588            vec![]
6589        };
6590
6591        let _ = self
6592            .window
6593            .as_global_scope()
6594            .resource_threads()
6595            .send(SetCookiesForUrl(self.url(), cookies, NonHTTP));
6596        Ok(())
6597    }
6598
6599    /// <https://html.spec.whatwg.org/multipage/#dom-document-bgcolor>
6600    fn BgColor(&self) -> DOMString {
6601        self.get_body_attribute(&local_name!("bgcolor"))
6602    }
6603
6604    /// <https://html.spec.whatwg.org/multipage/#dom-document-bgcolor>
6605    fn SetBgColor(&self, cx: &mut JSContext, value: DOMString) {
6606        self.set_body_attribute(cx, &local_name!("bgcolor"), value)
6607    }
6608
6609    /// <https://html.spec.whatwg.org/multipage/#dom-document-fgcolor>
6610    fn FgColor(&self) -> DOMString {
6611        self.get_body_attribute(&local_name!("text"))
6612    }
6613
6614    /// <https://html.spec.whatwg.org/multipage/#dom-document-fgcolor>
6615    fn SetFgColor(&self, cx: &mut JSContext, value: DOMString) {
6616        self.set_body_attribute(cx, &local_name!("text"), value)
6617    }
6618
6619    /// <https://html.spec.whatwg.org/multipage/#dom-tree-accessors:dom-document-nameditem-filter>
6620    fn NamedGetter(&self, cx: &mut JSContext, name: DOMString) -> Option<NamedPropertyValue> {
6621        if name.is_empty() {
6622            return None;
6623        }
6624        let name = Atom::from(name);
6625
6626        // Step 1. Let elements be the list of named elements with the name name that are in a document tree
6627        // with the Document as their root.
6628        let elements_with_name = self.get_elements_with_name(cx, &name);
6629        let name_iter = elements_with_name
6630            .iter()
6631            .filter(|elem| is_named_element_with_name_attribute(elem));
6632        let elements_with_id = self.id_map.get_all(cx.no_gc(), self.upcast(), &name);
6633        let id_iter = elements_with_id
6634            .iter()
6635            .filter(|elem| is_named_element_with_id_attribute(elem));
6636        let mut elements = name_iter.chain(id_iter);
6637
6638        // Step 2. If elements has only one element, and that element is an iframe element,
6639        // and that iframe element's content navigable is not null, then return the active
6640        // WindowProxy of the element's content navigable.
6641
6642        // NOTE: We have to check if all remaining elements are equal to the first, since
6643        // the same element may appear in both lists.
6644        let first = elements.next()?;
6645        if elements.all(|other| first == other) {
6646            if let Some(nested_window_proxy) = first
6647                .downcast::<HTMLIFrameElement>()
6648                .and_then(|iframe| iframe.GetContentWindow())
6649            {
6650                return Some(NamedPropertyValue::WindowProxy(nested_window_proxy));
6651            }
6652
6653            // Step 3. Otherwise, if elements has only one element, return that element.
6654            return Some(NamedPropertyValue::Element(DomRoot::from_ref(first)));
6655        }
6656
6657        // Step 4. Otherwise, return an HTMLCollection rooted at the Document node,
6658        // whose filter matches only named elements with the name name.
6659        #[derive(JSTraceable, MallocSizeOf)]
6660        struct DocumentNamedGetter {
6661            #[no_trace]
6662            name: Atom,
6663        }
6664        impl CollectionFilter for DocumentNamedGetter {
6665            fn filter(&self, elem: &Element, _root: &Node) -> bool {
6666                let type_ = match elem.upcast::<Node>().type_id() {
6667                    NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
6668                    _ => return false,
6669                };
6670                match type_ {
6671                    HTMLElementTypeId::HTMLFormElement | HTMLElementTypeId::HTMLIFrameElement => {
6672                        elem.get_name().as_ref() == Some(&self.name)
6673                    },
6674                    HTMLElementTypeId::HTMLImageElement => elem.get_name().is_some_and(|name| {
6675                        name == *self.name ||
6676                            !name.is_empty() && elem.get_id().as_ref() == Some(&self.name)
6677                    }),
6678                    // TODO handle <embed> and <object>; these depend on whether the element is
6679                    // “exposed”, a concept that doesn’t fully make sense until embed/object
6680                    // behaviour is actually implemented
6681                    _ => false,
6682                }
6683            }
6684        }
6685        let collection = HTMLCollection::create(
6686            cx,
6687            self.window(),
6688            self.upcast(),
6689            Box::new(DocumentNamedGetter { name }),
6690        );
6691        Some(NamedPropertyValue::HTMLCollection(collection))
6692    }
6693
6694    /// <https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names>
6695    fn SupportedPropertyNames(&self, no_gc: &NoGC) -> Vec<DOMString> {
6696        let mut names_with_first_named_element_map = HashMap::new();
6697        self.name_map
6698            .for_each(no_gc, self.upcast(), |name, elements| {
6699                if name.is_empty() {
6700                    return;
6701                }
6702                let mut name_iter = elements
6703                    .iter()
6704                    .filter(|elem| is_named_element_with_name_attribute(elem));
6705                if let Some(first) = name_iter.next() {
6706                    names_with_first_named_element_map.insert(name.clone(), first.as_rooted());
6707                }
6708            });
6709
6710        self.id_map.for_each(no_gc, self.upcast(), |id, elements| {
6711            if id.is_empty() {
6712                return;
6713            }
6714            let mut id_iter = elements
6715                .iter()
6716                .filter(|elem| is_named_element_with_id_attribute(elem));
6717            if let Some(first) = id_iter.next() {
6718                match names_with_first_named_element_map.entry(id.clone()) {
6719                    Vacant(entry) => drop(entry.insert(first.as_rooted())),
6720                    Occupied(mut entry) => {
6721                        if first
6722                            .upcast::<Node>()
6723                            .is_before(no_gc, entry.get().upcast())
6724                        {
6725                            *entry.get_mut() = first.as_rooted();
6726                        }
6727                    },
6728                }
6729            }
6730        });
6731
6732        let mut names_with_first_named_element_vec: Vec<_> =
6733            names_with_first_named_element_map.into_iter().collect();
6734        names_with_first_named_element_vec.sort_unstable_by(|a, b| {
6735            if a.1 == b.1 {
6736                // This can happen if an img has an id different from its name,
6737                // spec does not say which string to put first.
6738                a.0.cmp(&b.0)
6739            } else if a.1.upcast::<Node>().is_before(no_gc, b.1.upcast::<Node>()) {
6740                Ordering::Less
6741            } else {
6742                Ordering::Greater
6743            }
6744        });
6745
6746        names_with_first_named_element_vec
6747            .into_iter()
6748            .map(|(k, _)| DOMString::from(&*k))
6749            .collect()
6750    }
6751
6752    /// <https://html.spec.whatwg.org/multipage/#dom-document-clear>
6753    fn Clear(&self) {
6754        // This method intentionally does nothing
6755    }
6756
6757    /// <https://html.spec.whatwg.org/multipage/#dom-document-captureevents>
6758    fn CaptureEvents(&self) {
6759        // This method intentionally does nothing
6760    }
6761
6762    /// <https://html.spec.whatwg.org/multipage/#dom-document-releaseevents>
6763    fn ReleaseEvents(&self) {
6764        // This method intentionally does nothing
6765    }
6766
6767    // https://html.spec.whatwg.org/multipage/#globaleventhandlers
6768    global_event_handlers!();
6769
6770    // https://html.spec.whatwg.org/multipage/#handler-onreadystatechange
6771    event_handler!(
6772        readystatechange,
6773        GetOnreadystatechange,
6774        SetOnreadystatechange
6775    );
6776
6777    /// <https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint>
6778    fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> {
6779        self.document_or_shadow_root.element_from_point(
6780            self.upcast(),
6781            x,
6782            y,
6783            self.GetDocumentElement(),
6784            self.has_browsing_context,
6785        )
6786    }
6787
6788    /// <https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint>
6789    fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> {
6790        self.document_or_shadow_root.elements_from_point(
6791            self.upcast(),
6792            x,
6793            y,
6794            self.GetDocumentElement(),
6795            self.has_browsing_context,
6796        )
6797    }
6798
6799    /// <https://drafts.csswg.org/cssom-view/#dom-document-scrollingelement>
6800    fn GetScrollingElement(&self) -> Option<DomRoot<Element>> {
6801        // Step 1. If the Document is in quirks mode, follow these steps:
6802        if self.quirks_mode() == QuirksMode::Quirks {
6803            // Step 1.1. If the body element exists,
6804            if let Some(ref body) = self.GetBody() {
6805                let e = body.upcast::<Element>();
6806                // and it is not potentially scrollable, return the body element and abort these steps.
6807                // For this purpose, a value of overflow:clip on the body element’s parent element
6808                // must be treated as overflow:hidden.
6809                if !e.is_potentially_scrollable_body_for_scrolling_element() {
6810                    return Some(DomRoot::from_ref(e));
6811                }
6812            }
6813
6814            // Step 1.2. Return null and abort these steps.
6815            return None;
6816        }
6817
6818        // Step 2. If there is a root element, return the root element and abort these steps.
6819        // Step 3. Return null.
6820        self.GetDocumentElement()
6821    }
6822
6823    /// <https://html.spec.whatwg.org/multipage/#dom-document-open>
6824    fn Open(
6825        &self,
6826        cx: &mut JSContext,
6827        _unused1: Option<DOMString>,
6828        _unused2: Option<DOMString>,
6829    ) -> Fallible<DomRoot<Document>> {
6830        // Step 1. If document is an XML document, then throw an "InvalidStateError" DOMException.
6831        if !self.is_html_document() {
6832            return Err(Error::InvalidState(Some(
6833                "Document must be a HTML document".into(),
6834            )));
6835        }
6836
6837        // Step 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0,
6838        // then throw an "InvalidStateError" DOMException.
6839        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
6840            return Err(Error::InvalidState(Some(
6841                "A custom element constructor attempted to open, close or write to this document"
6842                    .into(),
6843            )));
6844        }
6845
6846        // Step 3. Let entryDocument be the entry global object's associated Document.
6847        let entry_responsible_document = GlobalScope::entry().as_window().Document();
6848
6849        // Step 4. If document's origin is not same origin to entryDocument's origin,
6850        // then throw a "SecurityError" DOMException.
6851        if !self
6852            .origin()
6853            .same_origin(&entry_responsible_document.origin())
6854        {
6855            return Err(Error::Security(Some(
6856                "Document's origin is not the same as entry global's document origin".into(),
6857            )));
6858        }
6859
6860        // Step 5. If document has an active parser whose script nesting level is greater than 0,
6861        // then return document.
6862        if self
6863            .active_parser()
6864            .is_some_and(|parser| parser.script_nesting_level() > 0)
6865        {
6866            return Ok(DomRoot::from_ref(self));
6867        }
6868
6869        // Step 6. Similarly, if document's unload counter is greater than 0, then return document.
6870        if self.is_prompting_or_unloading() {
6871            return Ok(DomRoot::from_ref(self));
6872        }
6873
6874        // Step 7. If document's active parser was aborted is true, then return document.
6875        if self.active_parser_was_aborted.get() {
6876            return Ok(DomRoot::from_ref(self));
6877        }
6878
6879        // TODO: prompt to unload.
6880        // TODO: set unload_event_start and unload_event_end
6881
6882        self.window().set_navigation_start();
6883
6884        // Step 8. If document's node navigable is non-null and document's node navigable's
6885        // ongoing navigation is a navigation ID, then stop loading document's node navigable.
6886        // TODO: https://github.com/servo/servo/issues/21937
6887        if self.has_browsing_context() {
6888            // spec says "stop document loading",
6889            // which is a process that does more than just abort
6890            self.abort(cx, AbortReason::DocumentOpen);
6891        }
6892
6893        // Step 9. For each shadow-including inclusive descendant node of document,
6894        // erase all event listeners and handlers given node.
6895        for node in self
6896            .upcast::<Node>()
6897            .traverse_preorder_unrooted(cx.no_gc(), ShadowIncluding::Yes)
6898        {
6899            node.upcast::<EventTarget>().remove_all_listeners(cx);
6900        }
6901
6902        // Step 10. If document is the associated Document of document's relevant global object,
6903        // then erase all event listeners and handlers given document's relevant global object.
6904        if self.window.Document() == DomRoot::from_ref(self) {
6905            self.window.upcast::<EventTarget>().remove_all_listeners(cx);
6906        }
6907
6908        // Step 11. Replace all with null within document.
6909        Node::replace_all(cx, None, self.upcast::<Node>());
6910
6911        // Specs and tests are in a state of flux about whether
6912        // we want to clear the selection when we remove the contents;
6913        // WPT selection/Document-open.html wants us to not clear it
6914        // as of Feb 1 2020
6915
6916        // Step 12. If document is fully active, then:
6917        if self.is_fully_active() {
6918            // Step 12.1. Let newURL be a copy of entryDocument's URL.
6919            let mut new_url = entry_responsible_document.url();
6920
6921            // Step 12.2. If entryDocument is not document, then set newURL's fragment to null.
6922            if entry_responsible_document != DomRoot::from_ref(self) {
6923                new_url.set_fragment(None);
6924            }
6925
6926            // Step 12.3. Run the URL and history update steps with document and newURL.
6927            // TODO: https://github.com/servo/servo/issues/21939
6928            self.set_url(new_url);
6929        }
6930
6931        // Step 13. Set document's is initial about:blank to false.
6932        self.is_initial_about_blank.set(false);
6933
6934        // Step 14. If document's iframe load in progress flag is set, then set document's mute
6935        // iframe load flag.
6936        if self.iframe_load_in_progress.get() {
6937            self.mute_iframe_load.set(true);
6938        }
6939
6940        // Step 15: Set document to no-quirks mode.
6941        self.set_quirks_mode(QuirksMode::NoQuirks);
6942
6943        // Step 16. Create a new HTML parser and associate it with document. This is a
6944        // script-created parser (meaning that it can be closed by the document.open() and
6945        // document.close() methods, and that the tokenizer will wait for an explicit call to
6946        // document.close() before emitting an end-of-file token). The encoding confidence is
6947        // irrelevant.
6948        let resource_threads = self.window.as_global_scope().resource_threads().clone();
6949        *self.loader.borrow_mut() =
6950            DocumentLoader::new_with_threads(resource_threads, Some(self.url()));
6951        ServoParser::parse_html_script_input(cx, self, self.url());
6952
6953        // Step 17. Set the insertion point to point at just before the end of the input stream
6954        // (which at this point will be empty).
6955        // Handled when creating the parser in step 16
6956
6957        // Step 18. Update the current document readiness of document to "loading".
6958        self.update_the_current_document_readiness(cx, DocumentReadyState::Loading);
6959
6960        // Step 19. Return document.
6961        Ok(DomRoot::from_ref(self))
6962    }
6963
6964    /// <https://html.spec.whatwg.org/multipage/#dom-document-open-window>
6965    fn Open_(
6966        &self,
6967        cx: &mut JSContext,
6968        url: USVString,
6969        target: DOMString,
6970        features: DOMString,
6971    ) -> Fallible<Option<DomRoot<WindowProxy>>> {
6972        self.browsing_context()
6973            .ok_or(Error::InvalidAccess(Some(
6974                "Document is not fully active".into(),
6975            )))?
6976            .open(cx, url, target, features)
6977    }
6978
6979    /// <https://html.spec.whatwg.org/multipage/#dom-document-write>
6980    fn Write(&self, cx: &mut JSContext, text: Vec<TrustedHTMLOrString>) -> ErrorResult {
6981        // The document.write(...text) method steps are to run the document write steps
6982        // with this, text, false, and "Document write".
6983        self.write(cx, text, false, "Document", "write")
6984    }
6985
6986    /// <https://html.spec.whatwg.org/multipage/#dom-document-writeln>
6987    fn Writeln(&self, cx: &mut JSContext, text: Vec<TrustedHTMLOrString>) -> ErrorResult {
6988        // The document.writeln(...text) method steps are to run the document write steps
6989        // with this, text, true, and "Document writeln".
6990        self.write(cx, text, true, "Document", "writeln")
6991    }
6992
6993    /// <https://html.spec.whatwg.org/multipage/#dom-document-close>
6994    fn Close(&self, cx: &mut JSContext) -> ErrorResult {
6995        if !self.is_html_document() {
6996            // Step 1. If this is an XML document, then throw an "InvalidStateError" DOMException.
6997            return Err(Error::InvalidState(Some(
6998                "Document must be a HTML document".into(),
6999            )));
7000        }
7001
7002        // Step 2. If this's throw-on-dynamic-markup-insertion counter is greater than zero,
7003        // then throw an "InvalidStateError" DOMException.
7004        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
7005            return Err(Error::InvalidState(Some(
7006                "A custom element constructor attempted to open, close or write to this document"
7007                    .into(),
7008            )));
7009        }
7010
7011        // Step 3. If there is no script-created parser associated with this, then return.
7012        let parser = match self.get_current_parser() {
7013            Some(ref parser) if parser.is_script_created() => DomRoot::from_ref(&**parser),
7014            _ => {
7015                return Ok(());
7016            },
7017        };
7018
7019        // parser.close implements the remainder of this algorithm
7020        parser.close(cx);
7021
7022        Ok(())
7023    }
7024
7025    /// <https://w3c.github.io/editing/docs/execCommand/#execcommand()>
7026    fn ExecCommand(
7027        &self,
7028        cx: &mut JSContext,
7029        command_id: DOMString,
7030        _show_ui: bool,
7031        value: TrustedHTMLOrString,
7032    ) -> Fallible<bool> {
7033        let value = if command_id == "insertHTML" {
7034            TrustedHTML::get_trusted_type_compliant_string(
7035                cx,
7036                self.window.as_global_scope(),
7037                value,
7038                "Document execCommand",
7039            )?
7040        } else {
7041            match value {
7042                TrustedHTMLOrString::TrustedHTML(trusted_html) => trusted_html.data().clone(),
7043                TrustedHTMLOrString::String(value) => value,
7044            }
7045        };
7046
7047        Ok(self.exec_command_for_command_id(cx, command_id, value))
7048    }
7049
7050    /// <https://w3c.github.io/editing/docs/execCommand/#querycommandenabled()>
7051    fn QueryCommandEnabled(&self, cx: &mut JSContext, command_id: DOMString) -> bool {
7052        // Step 2. Return true if command is both supported and enabled, false otherwise.
7053        self.check_support_and_enabled(cx, &command_id).is_some()
7054    }
7055
7056    /// <https://w3c.github.io/editing/docs/execCommand/#querycommandsupported()>
7057    fn QueryCommandSupported(&self, command_id: DOMString) -> bool {
7058        // > When the queryCommandSupported(command) method on the Document interface is invoked,
7059        // the user agent must return true if command is supported and available
7060        // within the current script on the current site, and false otherwise.
7061        self.is_command_supported(command_id)
7062    }
7063
7064    /// <https://w3c.github.io/editing/docs/execCommand/#querycommandindeterm()>
7065    fn QueryCommandIndeterm(&self, cx: &mut JSContext, command_id: DOMString) -> bool {
7066        self.is_command_indeterminate(cx, command_id)
7067    }
7068
7069    /// <https://w3c.github.io/editing/docs/execCommand/#querycommandstate()>
7070    fn QueryCommandState(&self, cx: &mut JSContext, command_id: DOMString) -> bool {
7071        self.command_state_for_command(cx, command_id)
7072    }
7073
7074    /// <https://w3c.github.io/editing/docs/execCommand/#querycommandvalue()>
7075    fn QueryCommandValue(&self, cx: &mut JSContext, command_id: DOMString) -> DOMString {
7076        self.command_value_for_command(cx, command_id)
7077    }
7078
7079    // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenerror
7080    event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror);
7081
7082    // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenchange
7083    event_handler!(
7084        fullscreenchange,
7085        GetOnfullscreenchange,
7086        SetOnfullscreenchange
7087    );
7088
7089    /// <https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled>
7090    fn FullscreenEnabled(&self) -> bool {
7091        self.get_allow_fullscreen()
7092    }
7093
7094    /// <https://fullscreen.spec.whatwg.org/#dom-document-fullscreen>
7095    fn Fullscreen(&self) -> bool {
7096        self.fullscreen_element.get().is_some()
7097    }
7098
7099    /// <https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement>
7100    fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> {
7101        DocumentOrShadowRoot::get_fullscreen_element(&self.node, self.fullscreen_element.get())
7102    }
7103
7104    /// <https://fullscreen.spec.whatwg.org/#dom-document-exitfullscreen>
7105    fn ExitFullscreen(&self, cx: &mut CurrentRealm) -> RootedPromise {
7106        self.exit_fullscreen(cx)
7107    }
7108
7109    // check-tidy: no specs after this line
7110    // Servo only API to get an instance of the controls of a specific
7111    // media element matching the given id.
7112    fn ServoGetMediaControls(&self, id: DOMString) -> Fallible<DomRoot<ShadowRoot>> {
7113        match self.media_controls.borrow().get(&*id.str()) {
7114            Some(m) => Ok(DomRoot::from_ref(m)),
7115            None => Err(Error::InvalidAccess(Some(
7116                "No registered media controls exist with provided id".into(),
7117            ))),
7118        }
7119    }
7120
7121    /// <https://w3c.github.io/selection-api/#dom-document-getselection>
7122    fn GetSelection(&self, cx: &mut JSContext) -> Option<DomRoot<Selection>> {
7123        if self.has_browsing_context {
7124            Some(self.selection.or_init(|| Selection::new(cx, self)))
7125        } else {
7126            None
7127        }
7128    }
7129
7130    /// <https://drafts.csswg.org/css-font-loading/#font-face-source>
7131    fn Fonts(&self, cx: &mut JSContext) -> DomRoot<FontFaceSet> {
7132        self.fonts
7133            .or_init(|| FontFaceSet::new(cx, &self.global(), None))
7134    }
7135
7136    /// <https://html.spec.whatwg.org/multipage/#dom-document-hidden>
7137    fn Hidden(&self) -> bool {
7138        self.visibility_state.get() == DocumentVisibilityState::Hidden
7139    }
7140
7141    /// <https://html.spec.whatwg.org/multipage/#dom-document-visibilitystate>
7142    fn VisibilityState(&self) -> DocumentVisibilityState {
7143        self.visibility_state.get()
7144    }
7145
7146    fn CreateExpression(
7147        &self,
7148        cx: &mut JSContext,
7149        expression: DOMString,
7150        resolver: Option<RootedCallback<XPathNSResolver>>,
7151    ) -> Fallible<DomRoot<crate::dom::types::XPathExpression>> {
7152        let parsed_expression =
7153            parse_expression(cx, &expression.str(), resolver, self.is_html_document())?;
7154        Ok(XPathExpression::new(
7155            cx,
7156            &self.window,
7157            None,
7158            parsed_expression,
7159        ))
7160    }
7161
7162    fn CreateNSResolver(&self, cx: &mut JSContext, node_resolver: &Node) -> DomRoot<Node> {
7163        let global = self.global();
7164        let window = global.as_window();
7165        let evaluator = XPathEvaluator::new(cx, window, None);
7166        XPathEvaluatorMethods::<crate::DomTypeHolder>::CreateNSResolver(&*evaluator, node_resolver)
7167    }
7168
7169    fn Evaluate(
7170        &self,
7171        cx: &mut JSContext,
7172        expression: DOMString,
7173        context_node: &Node,
7174        resolver: Option<RootedCallback<XPathNSResolver>>,
7175        result_type: u16,
7176        result: Option<&crate::dom::types::XPathResult>,
7177    ) -> Fallible<DomRoot<crate::dom::types::XPathResult>> {
7178        let parsed_expression =
7179            parse_expression(cx, &expression.str(), resolver, self.is_html_document())?;
7180        XPathExpression::new(cx, &self.window, None, parsed_expression).evaluate_internal(
7181            cx,
7182            context_node,
7183            result_type,
7184            result,
7185        )
7186    }
7187
7188    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
7189    fn AdoptedStyleSheets(&self, cx: &mut JSContext, retval: MutableHandleValue) {
7190        self.adopted_stylesheets_frozen_types.get_or_init(
7191            cx,
7192            || {
7193                self.adopted_stylesheets
7194                    .borrow()
7195                    .clone()
7196                    .iter()
7197                    .map(|sheet| sheet.as_rooted())
7198                    .collect()
7199            },
7200            retval,
7201        );
7202    }
7203
7204    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
7205    fn SetAdoptedStyleSheets(&self, cx: &mut JSContext, val: HandleValue) -> ErrorResult {
7206        let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval(
7207            cx,
7208            &self.adopted_stylesheets,
7209            val,
7210            &StyleSheetListOwner::Document(Dom::from_ref(self)),
7211        );
7212
7213        if result.is_ok() {
7214            self.adopted_stylesheets_frozen_types.clear()
7215        }
7216
7217        result
7218    }
7219
7220    fn Timeline(&self) -> DomRoot<DocumentTimeline> {
7221        self.timeline.as_rooted()
7222    }
7223}
7224
7225fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) {
7226    if marker.get().is_none() {
7227        marker.set(Some(CrossProcessInstant::now()))
7228    }
7229}
7230
7231#[derive(JSTraceable, MallocSizeOf)]
7232pub(crate) enum AnimationFrameCallback {
7233    DevtoolsFramerateTick {
7234        actor_name: String,
7235    },
7236    FrameRequestCallback {
7237        callback: TracedCallback<FrameRequestCallback>,
7238    },
7239}
7240
7241impl js::gc::Rootable for AnimationFrameCallback {}
7242
7243impl AnimationFrameCallback {
7244    fn call(&self, cx: &mut JSContext, document: &Document, now: f64) {
7245        match *self {
7246            AnimationFrameCallback::DevtoolsFramerateTick { ref actor_name } => {
7247                let msg = ScriptToDevtoolsControlMsg::FramerateTick(actor_name.clone(), now);
7248                let devtools_sender = document.window().as_global_scope().devtools_chan().unwrap();
7249                devtools_sender.send(msg).unwrap();
7250            },
7251            AnimationFrameCallback::FrameRequestCallback { ref callback } => {
7252                // TODO(jdm): The spec says that any exceptions should be suppressed:
7253                // https://github.com/servo/servo/issues/6928
7254                let _ = callback.Call__(cx, Finite::wrap(now), ExceptionHandling::Report);
7255            },
7256        }
7257    }
7258}
7259
7260#[derive(Default, JSTraceable, MallocSizeOf)]
7261#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
7262struct PendingInOrderScriptVec {
7263    scripts: DomRefCell<VecDeque<PendingScript>>,
7264}
7265
7266impl PendingInOrderScriptVec {
7267    fn is_empty(&self) -> bool {
7268        self.scripts.borrow().is_empty()
7269    }
7270
7271    fn push(&self, element: &HTMLScriptElement) {
7272        self.scripts
7273            .borrow_mut()
7274            .push_back(PendingScript::new(element));
7275    }
7276
7277    fn loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
7278        let mut scripts = self.scripts.borrow_mut();
7279        let entry = scripts
7280            .iter_mut()
7281            .find(|entry| &*entry.element == element)
7282            .unwrap();
7283        entry.loaded(result);
7284    }
7285
7286    fn take_next_ready_to_be_executed(&self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> {
7287        let mut scripts = self.scripts.borrow_mut();
7288        let pair = scripts.front_mut()?.take_result()?;
7289        scripts.pop_front();
7290        Some(pair)
7291    }
7292
7293    fn clear(&self) {
7294        *self.scripts.borrow_mut() = Default::default();
7295    }
7296}
7297
7298#[derive(JSTraceable, MallocSizeOf)]
7299#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
7300struct PendingScript {
7301    element: Dom<HTMLScriptElement>,
7302    // TODO(sagudev): could this be all no_trace?
7303    load: Option<ScriptResult>,
7304}
7305
7306impl PendingScript {
7307    fn new(element: &HTMLScriptElement) -> Self {
7308        Self {
7309            element: Dom::from_ref(element),
7310            load: None,
7311        }
7312    }
7313
7314    fn new_with_load(element: &HTMLScriptElement, load: Option<ScriptResult>) -> Self {
7315        Self {
7316            element: Dom::from_ref(element),
7317            load,
7318        }
7319    }
7320
7321    fn loaded(&mut self, result: ScriptResult) {
7322        assert!(self.load.is_none());
7323        self.load = Some(result);
7324    }
7325
7326    fn take_result(&mut self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> {
7327        self.load
7328            .take()
7329            .map(|result| (DomRoot::from_ref(&*self.element), result))
7330    }
7331}
7332
7333fn is_named_element_with_name_attribute(elem: &Element) -> bool {
7334    let type_ = match elem.upcast::<Node>().type_id() {
7335        NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
7336        _ => return false,
7337    };
7338    match type_ {
7339        HTMLElementTypeId::HTMLFormElement |
7340        HTMLElementTypeId::HTMLIFrameElement |
7341        HTMLElementTypeId::HTMLImageElement => true,
7342        // TODO handle <embed> and <object>; these depend on whether the element is
7343        // “exposed”, a concept that doesn’t fully make sense until embed/object
7344        // behaviour is actually implemented
7345        _ => false,
7346    }
7347}
7348
7349fn is_named_element_with_id_attribute(elem: &Element) -> bool {
7350    // TODO handle <embed> and <object>; these depend on whether the element is
7351    // “exposed”, a concept that doesn’t fully make sense until embed/object
7352    // behaviour is actually implemented
7353    elem.is::<HTMLImageElement>() && elem.get_name().is_some_and(|name| !name.is_empty())
7354}
7355
7356impl DocumentHelpers for Document {
7357    fn ensure_safe_to_run_script_or_layout(&self) {
7358        Document::ensure_safe_to_run_script_or_layout(self)
7359    }
7360}
7361
7362/// Iterator for same origin ancestor navigables, returning the active documents of the navigables.
7363/// <https://html.spec.whatwg.org/multipage/#ancestor-navigables>
7364// TODO: Find a way for something equivalent for cross origin document.
7365pub(crate) struct SameoriginAncestorNavigablesIterator {
7366    document: DomRoot<Document>,
7367}
7368
7369impl SameoriginAncestorNavigablesIterator {
7370    pub(crate) fn new(document: DomRoot<Document>) -> Self {
7371        Self { document }
7372    }
7373}
7374
7375impl Iterator for SameoriginAncestorNavigablesIterator {
7376    type Item = DomRoot<Document>;
7377
7378    fn next(&mut self) -> Option<Self::Item> {
7379        let window_proxy = self.document.browsing_context()?;
7380        self.document = window_proxy.parent()?.document()?;
7381        Some(self.document.clone())
7382    }
7383}
7384
7385/// Iterator for same origin descendant navigables in a shadow-including tree order, returning the
7386/// active documents of the navigables.
7387/// <https://html.spec.whatwg.org/multipage/#descendant-navigables>
7388// TODO: Find a way for something equivalent for cross origin document.
7389pub(crate) struct SameOriginDescendantNavigablesIterator {
7390    stack: Vec<Box<dyn Iterator<Item = DomRoot<HTMLIFrameElement>>>>,
7391}
7392
7393impl SameOriginDescendantNavigablesIterator {
7394    pub(crate) fn new(document: &Document) -> Self {
7395        let iframes: Vec<DomRoot<HTMLIFrameElement>> = document.iframes().iter().collect();
7396        Self {
7397            stack: vec![Box::new(iframes.into_iter())],
7398        }
7399    }
7400
7401    fn get_next_iframe(&mut self) -> Option<DomRoot<HTMLIFrameElement>> {
7402        let mut cur_iframe = self.stack.last_mut()?.next();
7403        while cur_iframe.is_none() {
7404            self.stack.pop();
7405            cur_iframe = self.stack.last_mut()?.next();
7406        }
7407        cur_iframe
7408    }
7409}
7410
7411impl Iterator for SameOriginDescendantNavigablesIterator {
7412    type Item = DomRoot<Document>;
7413
7414    fn next(&mut self) -> Option<Self::Item> {
7415        while let Some(iframe) = self.get_next_iframe() {
7416            let Some(pipeline_id) = iframe.pipeline_id() else {
7417                continue;
7418            };
7419
7420            if let Some(document) = ScriptThread::find_document(pipeline_id) {
7421                let child_iframes: Vec<DomRoot<HTMLIFrameElement>> =
7422                    document.iframes().iter().collect();
7423                self.stack.push(Box::new(child_iframes.into_iter()));
7424                return Some(document);
7425            } else {
7426                continue;
7427            };
7428        }
7429        None
7430    }
7431}