Skip to main content

script/dom/document/
document.rs

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