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