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