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