Skip to main content

script/dom/document/
document.rs

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