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