Skip to main content

script/dom/document/
document.rs

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