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