script/dom/document/
document.rs

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