Skip to main content

script/dom/document/
document.rs

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