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