Skip to main content

script/dom/document/
document.rs

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