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