use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::cmp::Ordering;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet, VecDeque};
use std::default::Default;
use std::mem;
use std::rc::Rc;
use std::slice::from_ref;
use std::sync::{LazyLock, Mutex};
use std::time::{Duration, Instant};
use base::cross_process_instant::CrossProcessInstant;
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
use chrono::Local;
use content_security_policy::{self as csp, CspList};
use cookie::Cookie;
use cssparser::match_ignore_ascii_case;
use devtools_traits::ScriptToDevtoolsControlMsg;
use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use encoding_rs::{Encoding, UTF_8};
use euclid::default::{Point2D, Rect, Size2D};
use html5ever::{local_name, namespace_url, ns, LocalName, Namespace, QualName};
use hyper_serde::Serde;
use ipc_channel::ipc;
use js::rust::{HandleObject, HandleValue};
use keyboard_types::{Code, Key, KeyState};
use metrics::{
InteractiveFlag, InteractiveMetrics, InteractiveWindow, ProfilerMetadataFactory,
ProgressiveWebMetric,
};
use mime::{self, Mime};
use net_traits::policy_container::PolicyContainer;
use net_traits::pub_domains::is_pub_domain;
use net_traits::request::RequestBuilder;
use net_traits::response::HttpsState;
use net_traits::CookieSource::NonHTTP;
use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl};
use net_traits::{FetchResponseListener, IpcSend, ReferrerPolicy};
use num_traits::ToPrimitive;
use percent_encoding::percent_decode;
use profile_traits::ipc as profile_ipc;
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
use script_layout_interface::{PendingRestyle, TrustedNodeAddress};
use script_traits::{
AnimationState, AnimationTickType, CompositorEvent, DocumentActivity, MouseButton,
MouseEventType, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta,
};
use servo_arc::Arc;
use servo_atoms::Atom;
use servo_config::pref;
use servo_media::{ClientContextId, ServoMedia};
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use style::attr::AttrValue;
use style::context::QuirksMode;
use style::invalidation::element::restyle_hints::RestyleHint;
use style::selector_parser::Snapshot;
use style::shared_lock::SharedRwLock as StyleSharedRwLock;
use style::str::{split_html_space_chars, str_join};
use style::stylesheet_set::DocumentStylesheetSet;
use style::stylesheets::{Origin, OriginSet, Stylesheet};
use url::Host;
use uuid::Uuid;
#[cfg(feature = "webgpu")]
use webgpu::swapchain::WebGPUContextId;
use webrender_api::units::DeviceIntRect;
use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
use crate::animation_timeline::AnimationTimeline;
use crate::animations::Animations;
use crate::document_loader::{DocumentLoader, LoadType};
use crate::dom::attr::Attr;
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut};
use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEvent_Binding::BeforeUnloadEventMethods;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
DocumentMethods, DocumentReadyState, DocumentVisibilityState, NamedPropertyValue,
};
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElement_Binding::HTMLIFrameElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter;
use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::{
FrameRequestCallback, ScrollBehavior, WindowMethods,
};
use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions};
use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
use crate::dom::bindings::weakref::WeakRef;
use crate::dom::bindings::xmlname::XMLName::Invalid;
use crate::dom::bindings::xmlname::{
namespace_from_domstring, validate_and_extract, xml_name_type,
};
use crate::dom::cdatasection::CDATASection;
use crate::dom::comment::Comment;
use crate::dom::compositionevent::CompositionEvent;
use crate::dom::cssstylesheet::CSSStyleSheet;
use crate::dom::customelementregistry::CustomElementDefinition;
use crate::dom::customevent::CustomEvent;
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument};
use crate::dom::documenttype::DocumentType;
use crate::dom::domimplementation::DOMImplementation;
use crate::dom::element::{
CustomElementCreationMode, Element, ElementCreator, ElementPerformFullscreenEnter,
ElementPerformFullscreenExit,
};
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus};
use crate::dom::eventtarget::EventTarget;
use crate::dom::focusevent::FocusEvent;
use crate::dom::fontfaceset::FontFaceSet;
use crate::dom::globalscope::GlobalScope;
use crate::dom::hashchangeevent::HashChangeEvent;
use crate::dom::htmlanchorelement::HTMLAnchorElement;
use crate::dom::htmlareaelement::HTMLAreaElement;
use crate::dom::htmlbaseelement::HTMLBaseElement;
use crate::dom::htmlbodyelement::HTMLBodyElement;
use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection};
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlembedelement::HTMLEmbedElement;
use crate::dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
use crate::dom::htmlheadelement::HTMLHeadElement;
use crate::dom::htmlhtmlelement::HTMLHtmlElement;
use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::htmlinputelement::HTMLInputElement;
use crate::dom::htmlmetaelement::RefreshRedirectDue;
use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult};
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
use crate::dom::htmltitleelement::HTMLTitleElement;
use crate::dom::keyboardevent::KeyboardEvent;
use crate::dom::location::Location;
use crate::dom::messageevent::MessageEvent;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::{
self, document_from_node, window_from_node, CloneChildrenFlag, Node, NodeDamage, NodeFlags,
ShadowIncluding,
};
use crate::dom::nodeiterator::NodeIterator;
use crate::dom::nodelist::NodeList;
use crate::dom::pagetransitionevent::PageTransitionEvent;
use crate::dom::performanceentry::PerformanceEntry;
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::dom::promise::Promise;
use crate::dom::range::Range;
use crate::dom::resizeobserver::{ResizeObservationDepth, ResizeObserver};
use crate::dom::selection::Selection;
use crate::dom::servoparser::ServoParser;
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::storageevent::StorageEvent;
use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
use crate::dom::text::Text;
use crate::dom::touch::Touch;
use crate::dom::touchevent::TouchEvent;
use crate::dom::touchlist::TouchList;
use crate::dom::treewalker::TreeWalker;
use crate::dom::types::VisibilityStateEntry;
use crate::dom::uievent::UIEvent;
use crate::dom::virtualmethods::vtable_for;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
use crate::dom::wheelevent::WheelEvent;
use crate::dom::window::Window;
use crate::dom::windowproxy::WindowProxy;
use crate::dom::xpathevaluator::XPathEvaluator;
use crate::fetch::FetchCanceller;
use crate::iframe_collection::IFrameCollection;
use crate::network_listener::{NetworkListener, PreInvoke};
use crate::realms::{enter_realm, AlreadyInRealm, InRealm};
use crate::script_runtime::{CanGc, CommonScriptMsg, ScriptThreadEventCategory};
use crate::script_thread::{with_script_thread, MainThreadScriptMsg, ScriptThread};
use crate::stylesheet_set::StylesheetSetRef;
use crate::task::TaskBox;
use crate::task_source::{TaskSource, TaskSourceName};
use crate::timers::OneshotTimerCallback;
const SPURIOUS_ANIMATION_FRAME_THRESHOLD: u8 = 5;
const FAKE_REQUEST_ANIMATION_FRAME_DELAY: u64 = 16;
pub enum TouchEventResult {
Processed(bool),
Forwarded,
}
#[derive(Clone, Copy, PartialEq)]
pub enum FireMouseEventType {
Move,
Over,
Out,
Enter,
Leave,
}
impl FireMouseEventType {
pub fn as_str(&self) -> &str {
match *self {
FireMouseEventType::Move => "mousemove",
FireMouseEventType::Over => "mouseover",
FireMouseEventType::Out => "mouseout",
FireMouseEventType::Enter => "mouseenter",
FireMouseEventType::Leave => "mouseleave",
}
}
}
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub enum IsHTMLDocument {
HTMLDocument,
NonHTMLDocument,
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
enum FocusTransaction {
NotInTransaction,
InTransaction(Option<Dom<Element>>),
}
#[derive(JSTraceable, MallocSizeOf)]
pub enum DeclarativeRefresh {
PendingLoad {
#[no_trace]
url: ServoUrl,
time: u64,
},
CreatedAfterLoad,
}
#[cfg(feature = "webgpu")]
pub(crate) type WebGPUContextsMap =
Rc<RefCell<HashMapTracedValues<WebGPUContextId, WeakRef<GPUCanvasContext>>>>;
#[dom_struct]
pub struct Document {
node: Node,
document_or_shadow_root: DocumentOrShadowRoot,
window: Dom<Window>,
implementation: MutNullableDom<DOMImplementation>,
#[ignore_malloc_size_of = "type from external crate"]
#[no_trace]
content_type: Mime,
last_modified: Option<String>,
#[no_trace]
encoding: Cell<&'static Encoding>,
has_browsing_context: bool,
is_html_document: bool,
#[no_trace]
activity: Cell<DocumentActivity>,
#[no_trace]
url: DomRefCell<ServoUrl>,
#[ignore_malloc_size_of = "defined in selectors"]
#[no_trace]
quirks_mode: Cell<QuirksMode>,
id_map: DomRefCell<HashMapTracedValues<Atom, Vec<Dom<Element>>>>,
name_map: DomRefCell<HashMapTracedValues<Atom, Vec<Dom<Element>>>>,
tag_map: DomRefCell<HashMapTracedValues<LocalName, Dom<HTMLCollection>>>,
tagns_map: DomRefCell<HashMapTracedValues<QualName, Dom<HTMLCollection>>>,
classes_map: DomRefCell<HashMapTracedValues<Vec<Atom>, Dom<HTMLCollection>>>,
images: MutNullableDom<HTMLCollection>,
embeds: MutNullableDom<HTMLCollection>,
links: MutNullableDom<HTMLCollection>,
forms: MutNullableDom<HTMLCollection>,
scripts: MutNullableDom<HTMLCollection>,
anchors: MutNullableDom<HTMLCollection>,
applets: MutNullableDom<HTMLCollection>,
iframes: RefCell<IFrameCollection>,
#[no_trace]
style_shared_lock: StyleSharedRwLock,
#[custom_trace]
stylesheets: DomRefCell<DocumentStylesheetSet<StyleSheetInDocument>>,
stylesheet_list: MutNullableDom<StyleSheetList>,
ready_state: Cell<DocumentReadyState>,
domcontentloaded_dispatched: Cell<bool>,
focus_transaction: DomRefCell<FocusTransaction>,
focused: MutNullableDom<Element>,
current_script: MutNullableDom<HTMLScriptElement>,
pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>,
script_blocking_stylesheets_count: Cell<u32>,
deferred_scripts: PendingInOrderScriptVec,
asap_in_order_scripts_list: PendingInOrderScriptVec,
asap_scripts_set: DomRefCell<Vec<Dom<HTMLScriptElement>>>,
scripting_enabled: bool,
animation_frame_ident: Cell<u32>,
animation_frame_list: DomRefCell<Vec<(u32, Option<AnimationFrameCallback>)>>,
running_animation_callbacks: Cell<bool>,
loader: DomRefCell<DocumentLoader>,
current_parser: MutNullableDom<ServoParser>,
base_element: MutNullableDom<HTMLBaseElement>,
appropriate_template_contents_owner_document: MutNullableDom<Document>,
pending_restyles: DomRefCell<HashMap<Dom<Element>, NoTrace<PendingRestyle>>>,
needs_paint: Cell<bool>,
active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
#[no_trace]
dom_interactive: Cell<Option<CrossProcessInstant>>,
#[no_trace]
dom_content_loaded_event_start: Cell<Option<CrossProcessInstant>>,
#[no_trace]
dom_content_loaded_event_end: Cell<Option<CrossProcessInstant>>,
#[no_trace]
dom_complete: Cell<Option<CrossProcessInstant>>,
#[no_trace]
top_level_dom_complete: Cell<Option<CrossProcessInstant>>,
#[no_trace]
load_event_start: Cell<Option<CrossProcessInstant>>,
#[no_trace]
load_event_end: Cell<Option<CrossProcessInstant>>,
#[no_trace]
unload_event_start: Cell<Option<CrossProcessInstant>>,
#[no_trace]
unload_event_end: Cell<Option<CrossProcessInstant>>,
#[no_trace]
https_state: Cell<HttpsState>,
#[no_trace]
origin: MutableOrigin,
referrer: Option<String>,
target_element: MutNullableDom<Element>,
#[no_trace]
policy_container: DomRefCell<PolicyContainer>,
#[ignore_malloc_size_of = "Defined in std"]
#[no_trace]
last_click_info: DomRefCell<Option<(Instant, Point2D<f32>)>>,
ignore_destructive_writes_counter: Cell<u32>,
ignore_opens_during_unload_counter: Cell<u32>,
spurious_animation_frames: Cell<u8>,
dom_count: Cell<u32>,
fullscreen_element: MutNullableDom<Element>,
form_id_listener_map: DomRefCell<HashMapTracedValues<Atom, HashSet<Dom<Element>>>>,
#[no_trace]
interactive_time: DomRefCell<InteractiveMetrics>,
#[no_trace]
tti_window: DomRefCell<InteractiveWindow>,
canceller: FetchCanceller,
throw_on_dynamic_markup_insertion_counter: Cell<u64>,
page_showing: Cell<bool>,
salvageable: Cell<bool>,
active_parser_was_aborted: Cell<bool>,
fired_unload: Cell<bool>,
responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>,
redirect_count: Cell<u16>,
script_and_layout_blockers: Cell<u32>,
#[ignore_malloc_size_of = "Measuring trait objects is hard"]
delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>,
completely_loaded: Cell<bool>,
shadow_roots: DomRefCell<HashSet<Dom<ShadowRoot>>>,
shadow_roots_styles_changed: Cell<bool>,
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
dirty_webgl_contexts:
DomRefCell<HashMapTracedValues<WebGLContextId, Dom<WebGLRenderingContext>>>,
#[cfg(feature = "webgpu")]
#[ignore_malloc_size_of = "Rc are hard"]
webgpu_contexts: WebGPUContextsMap,
selection: MutNullableDom<Selection>,
animation_timeline: DomRefCell<AnimationTimeline>,
animations: DomRefCell<Animations>,
dirty_root: MutNullableDom<Element>,
declarative_refresh: DomRefCell<Option<DeclarativeRefresh>>,
#[no_trace]
#[ignore_malloc_size_of = "CompositorEvent contains data from outside crates"]
pending_compositor_events: DomRefCell<Vec<CompositorEvent>>,
mouse_move_event_index: DomRefCell<Option<usize>>,
#[no_trace]
#[ignore_malloc_size_of = "AnimationTickType contains data from an outside crate"]
pending_animation_ticks: DomRefCell<AnimationTickType>,
resize_observers: DomRefCell<Vec<Dom<ResizeObserver>>>,
fonts: MutNullableDom<FontFaceSet>,
visibility_state: Cell<DocumentVisibilityState>,
status_code: Option<u16>,
}
#[allow(non_snake_case)]
impl Document {
pub fn note_node_with_dirty_descendants(&self, node: &Node) {
debug_assert!(*node.owner_doc() == *self);
if !node.is_connected() {
return;
}
let parent = match node.inclusive_ancestors(ShadowIncluding::Yes).nth(1) {
Some(parent) => parent,
None => {
let document_element = match self.GetDocumentElement() {
Some(element) => element,
None => return,
};
if let Some(dirty_root) = self.dirty_root.get() {
for ancestor in dirty_root
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::Yes)
{
if ancestor.is::<Element>() {
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
}
}
}
self.dirty_root.set(Some(&document_element));
return;
},
};
if parent.is::<Element>() {
if !parent.is_styled() {
return;
}
if parent.is_display_none() {
return;
}
}
let element_parent: DomRoot<Element>;
let element = match node.downcast::<Element>() {
Some(element) => element,
None => {
match DomRoot::downcast::<Element>(parent) {
Some(parent) => {
element_parent = parent;
&element_parent
},
None => {
return;
},
}
},
};
let dirty_root = match self.dirty_root.get() {
None => {
element
.upcast::<Node>()
.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
self.dirty_root.set(Some(element));
return;
},
Some(root) => root,
};
for ancestor in element
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::Yes)
{
if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
return;
}
if ancestor.is::<Element>() {
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
}
}
let new_dirty_root = element
.upcast::<Node>()
.common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes)
.expect("Couldn't find common ancestor");
let mut has_dirty_descendants = true;
for ancestor in dirty_root
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::Yes)
{
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, has_dirty_descendants);
has_dirty_descendants &= *ancestor != *new_dirty_root;
}
self.dirty_root
.set(Some(new_dirty_root.downcast::<Element>().unwrap()));
}
pub fn take_dirty_root(&self) -> Option<DomRoot<Element>> {
self.dirty_root.take()
}
#[inline]
pub fn loader(&self) -> Ref<DocumentLoader> {
self.loader.borrow()
}
#[inline]
pub fn loader_mut(&self) -> RefMut<DocumentLoader> {
self.loader.borrow_mut()
}
#[inline]
pub fn has_browsing_context(&self) -> bool {
self.has_browsing_context
}
#[inline]
pub fn browsing_context(&self) -> Option<DomRoot<WindowProxy>> {
if self.has_browsing_context {
self.window.undiscarded_window_proxy()
} else {
None
}
}
#[inline]
pub fn window(&self) -> &Window {
&self.window
}
#[inline]
pub fn is_html_document(&self) -> bool {
self.is_html_document
}
pub fn is_xhtml_document(&self) -> bool {
self.content_type.type_() == mime::APPLICATION &&
self.content_type.subtype().as_str() == "xhtml" &&
self.content_type.suffix() == Some(mime::XML)
}
pub fn set_https_state(&self, https_state: HttpsState) {
self.https_state.set(https_state);
}
pub fn is_fully_active(&self) -> bool {
self.activity.get() == DocumentActivity::FullyActive
}
pub fn is_active(&self) -> bool {
self.activity.get() != DocumentActivity::Inactive
}
pub fn set_activity(&self, activity: DocumentActivity) {
assert!(self.has_browsing_context);
if activity == self.activity.get() {
return;
}
self.activity.set(activity);
let media = ServoMedia::get().unwrap();
let pipeline_id = self.window().pipeline_id();
let client_context_id =
ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
if activity != DocumentActivity::FullyActive {
self.window().suspend();
media.suspend(&client_context_id);
return;
}
self.title_changed();
self.dirty_all_nodes();
self.window().resume();
media.resume(&client_context_id);
if self.ready_state.get() != DocumentReadyState::Complete {
return;
}
let document = Trusted::new(self);
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_pageshow_event: move || {
let document = document.root();
let window = document.window();
if document.page_showing.get() {
return;
}
document.page_showing.set(true);
document.update_visibility_state(DocumentVisibilityState::Visible, CanGc::note());
let event = PageTransitionEvent::new(
window,
atom!("pageshow"),
false, false, true, CanGc::note(),
);
let event = event.upcast::<Event>();
event.set_trusted(true);
window.dispatch_event_with_target_override(event, CanGc::note());
}),
self.window.upcast(),
)
.unwrap();
}
pub fn origin(&self) -> &MutableOrigin {
&self.origin
}
pub fn url(&self) -> ServoUrl {
self.url.borrow().clone()
}
pub fn set_url(&self, url: ServoUrl) {
*self.url.borrow_mut() = url;
}
pub fn fallback_base_url(&self) -> ServoUrl {
let document_url = self.url();
if let Some(browsing_context) = self.browsing_context() {
let container_base_url = browsing_context
.parent()
.and_then(|parent| parent.document())
.map(|document| document.base_url());
if document_url.as_str() == "about:srcdoc" {
if let Some(base_url) = container_base_url {
return base_url;
}
}
if document_url.as_str() == "about:blank" && browsing_context.has_creator_base_url() {
return browsing_context.creator_base_url().unwrap();
}
}
document_url
}
pub fn base_url(&self) -> ServoUrl {
match self.base_element() {
None => self.fallback_base_url(),
Some(base) => base.frozen_base_url(),
}
}
pub(crate) fn set_needs_paint(&self, value: bool) {
self.needs_paint.set(value)
}
pub fn needs_reflow(&self) -> Option<ReflowTriggerCondition> {
if self.stylesheets.borrow().has_changed() {
return Some(ReflowTriggerCondition::StylesheetsChanged);
}
let root = self.GetDocumentElement()?;
if root.upcast::<Node>().has_dirty_descendants() {
return Some(ReflowTriggerCondition::DirtyDescendants);
}
if !self.pending_restyles.borrow().is_empty() {
return Some(ReflowTriggerCondition::PendingRestyles);
}
if self.needs_paint.get() {
return Some(ReflowTriggerCondition::PaintPostponed);
}
None
}
pub fn base_element(&self) -> Option<DomRoot<HTMLBaseElement>> {
self.base_element.get()
}
pub fn refresh_base_element(&self) {
let base = self
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLBaseElement>)
.find(|element| {
element
.upcast::<Element>()
.has_attribute(&local_name!("href"))
});
self.base_element.set(base.as_deref());
}
pub fn dom_count(&self) -> u32 {
self.dom_count.get()
}
pub fn increment_dom_count(&self) {
self.dom_count.set(self.dom_count.get() + 1);
}
pub fn decrement_dom_count(&self) {
self.dom_count.set(self.dom_count.get() - 1);
}
pub fn quirks_mode(&self) -> QuirksMode {
self.quirks_mode.get()
}
pub fn set_quirks_mode(&self, new_mode: QuirksMode) {
let old_mode = self.quirks_mode.replace(new_mode);
if old_mode != new_mode {
self.window.layout_mut().set_quirks_mode(new_mode);
}
}
pub fn encoding(&self) -> &'static Encoding {
self.encoding.get()
}
pub fn set_encoding(&self, encoding: &'static Encoding) {
self.encoding.set(encoding);
}
pub fn content_and_heritage_changed(&self, node: &Node) {
if node.is_connected() {
node.note_dirty_descendants();
}
node.dirty(NodeDamage::OtherNodeDamage);
}
pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) {
self.document_or_shadow_root
.unregister_named_element(&self.id_map, to_unregister, &id);
self.reset_form_owner_for_listeners(&id);
}
pub fn register_element_id(&self, element: &Element, id: Atom) {
let root = self.GetDocumentElement().expect(
"The element is in the document, so there must be a document \
element.",
);
self.document_or_shadow_root.register_named_element(
&self.id_map,
element,
&id,
DomRoot::from_ref(root.upcast::<Node>()),
);
self.reset_form_owner_for_listeners(&id);
}
pub fn unregister_element_name(&self, to_unregister: &Element, name: Atom) {
self.document_or_shadow_root
.unregister_named_element(&self.name_map, to_unregister, &name);
}
pub fn register_element_name(&self, element: &Element, name: Atom) {
let root = self.GetDocumentElement().expect(
"The element is in the document, so there must be a document \
element.",
);
self.document_or_shadow_root.register_named_element(
&self.name_map,
element,
&name,
DomRoot::from_ref(root.upcast::<Node>()),
);
}
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
let mut map = self.form_id_listener_map.borrow_mut();
let listener = listener.to_element();
let set = map.entry(Atom::from(id)).or_default();
set.insert(Dom::from_ref(listener));
}
pub fn unregister_form_id_listener<T: ?Sized + FormControl>(
&self,
id: DOMString,
listener: &T,
) {
let mut map = self.form_id_listener_map.borrow_mut();
if let Occupied(mut entry) = map.entry(Atom::from(id)) {
entry
.get_mut()
.remove(&Dom::from_ref(listener.to_element()));
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn find_fragment_node(&self, fragid: &str) -> Option<DomRoot<Element>> {
percent_decode(fragid.as_bytes())
.decode_utf8()
.ok()
.and_then(|decoded_fragid| self.get_element_by_id(&Atom::from(decoded_fragid)))
.or_else(|| self.get_anchor_by_name(fragid))
}
pub fn check_and_scroll_fragment(&self, fragment: &str, can_gc: CanGc) {
let target = self.find_fragment_node(fragment);
self.set_target_element(target.as_deref());
let point = target
.as_ref()
.map(|element| {
let rect = element
.upcast::<Node>()
.bounding_content_box_or_zero(can_gc);
let device_pixel_ratio = self.window.device_pixel_ratio().get();
(
rect.origin.x.to_nearest_pixel(device_pixel_ratio),
rect.origin.y.to_nearest_pixel(device_pixel_ratio),
)
})
.or_else(|| {
if fragment.is_empty() || fragment.eq_ignore_ascii_case("top") {
Some((0.0, 0.0))
} else {
None
}
});
if let Some((x, y)) = point {
self.window
.scroll(x as f64, y as f64, ScrollBehavior::Instant, can_gc)
}
}
fn get_anchor_by_name(&self, name: &str) -> Option<DomRoot<Element>> {
let name = Atom::from(name);
self.name_map.borrow().get(&name).and_then(|elements| {
elements
.iter()
.find(|e| e.is::<HTMLAnchorElement>())
.map(|e| DomRoot::from_ref(&**e))
})
}
pub fn set_ready_state(&self, state: DocumentReadyState, can_gc: CanGc) {
match state {
DocumentReadyState::Loading => {
if self.window().is_top_level() {
self.send_to_embedder(EmbedderMsg::LoadStart);
self.send_to_embedder(EmbedderMsg::Status(None));
}
},
DocumentReadyState::Complete => {
if self.window().is_top_level() {
self.send_to_embedder(EmbedderMsg::LoadComplete);
}
update_with_current_instant(&self.dom_complete);
},
DocumentReadyState::Interactive => update_with_current_instant(&self.dom_interactive),
};
self.ready_state.set(state);
self.upcast::<EventTarget>()
.fire_event(atom!("readystatechange"), can_gc);
}
pub fn is_scripting_enabled(&self) -> bool {
self.scripting_enabled
}
pub fn get_focused_element(&self) -> Option<DomRoot<Element>> {
self.focused.get()
}
fn begin_focus_transaction(&self) {
*self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default());
}
pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element, can_gc: CanGc) {
if Some(not_focusable) != self.focused.get().as_deref() {
return;
}
self.request_focus(
self.GetBody().as_ref().map(|e| e.upcast()),
FocusType::Element,
can_gc,
)
}
pub(crate) fn request_focus(
&self,
elem: Option<&Element>,
focus_type: FocusType,
can_gc: CanGc,
) {
let implicit_transaction = matches!(
*self.focus_transaction.borrow(),
FocusTransaction::NotInTransaction
);
if implicit_transaction {
self.begin_focus_transaction();
}
if elem.map_or(true, |e| e.is_focusable_area()) {
*self.focus_transaction.borrow_mut() =
FocusTransaction::InTransaction(elem.map(Dom::from_ref));
}
if implicit_transaction {
self.commit_focus_transaction(focus_type, can_gc);
}
}
fn commit_focus_transaction(&self, focus_type: FocusType, can_gc: CanGc) {
let possibly_focused = match *self.focus_transaction.borrow() {
FocusTransaction::NotInTransaction => unreachable!(),
FocusTransaction::InTransaction(ref elem) => {
elem.as_ref().map(|e| DomRoot::from_ref(&**e))
},
};
*self.focus_transaction.borrow_mut() = FocusTransaction::NotInTransaction;
if self.focused == possibly_focused.as_deref() {
return;
}
if let Some(ref elem) = self.focused.get() {
let node = elem.upcast::<Node>();
elem.set_focus_state(false);
self.fire_focus_event(FocusEventType::Blur, node, None, can_gc);
if elem.input_method_type().is_some() {
self.send_to_embedder(EmbedderMsg::HideIME);
}
}
self.focused.set(possibly_focused.as_deref());
if let Some(ref elem) = self.focused.get() {
elem.set_focus_state(true);
let node = elem.upcast::<Node>();
self.fire_focus_event(FocusEventType::Focus, node, None, can_gc);
if focus_type == FocusType::Element {
self.window().send_to_constellation(ScriptMsg::Focus);
}
if let Some(kind) = elem.input_method_type() {
let rect = elem.upcast::<Node>().bounding_content_box_or_zero(can_gc);
let rect = Rect::new(
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
);
let (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>() {
(
Some((
input.Value().to_string(),
input.GetSelectionEnd().unwrap_or(0) as i32,
)),
false,
)
} else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() {
(
Some((
textarea.Value().to_string(),
textarea.GetSelectionEnd().unwrap_or(0) as i32,
)),
true,
)
} else {
(None, false)
};
self.send_to_embedder(EmbedderMsg::ShowIME(
kind,
text,
multiline,
DeviceIntRect::from_untyped(&rect.to_box2d()),
));
}
}
}
pub fn title_changed(&self) {
if self.browsing_context().is_some() {
self.send_title_to_embedder();
let title = String::from(self.Title());
self.window.send_to_constellation(ScriptMsg::TitleChanged(
self.window.pipeline_id(),
title.clone(),
));
let global = self.window.upcast::<GlobalScope>();
if let Some(chan) = global.devtools_chan() {
let _ = chan.send(ScriptToDevtoolsControlMsg::TitleChanged(
global.pipeline_id(),
title,
));
}
}
}
fn title(&self) -> Option<DOMString> {
let title = self.GetDocumentElement().and_then(|root| {
if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") {
root.upcast::<Node>()
.child_elements()
.find(|node| {
node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
})
.map(DomRoot::upcast::<Node>)
} else {
root.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.find(|node| node.is::<HTMLTitleElement>())
}
});
title.map(|title| {
let value = title.child_text_content();
DOMString::from(str_join(split_html_space_chars(&value), " "))
})
}
pub fn send_title_to_embedder(&self) {
let window = self.window();
if window.is_top_level() {
let title = self.title().map(String::from);
self.send_to_embedder(EmbedderMsg::ChangePageTitle(title));
}
}
fn send_to_embedder(&self, msg: EmbedderMsg) {
let window = self.window();
window.send_to_embedder(msg);
}
pub fn dirty_all_nodes(&self) {
let root = match self.GetDocumentElement() {
Some(root) => root,
None => return,
};
for node in root
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
{
node.dirty(NodeDamage::OtherNodeDamage)
}
}
#[allow(unsafe_code)]
#[allow(clippy::too_many_arguments)]
pub unsafe fn handle_mouse_button_event(
&self,
button: MouseButton,
client_point: Point2D<f32>,
mouse_event_type: MouseEventType,
node_address: Option<UntrustedNodeAddress>,
point_in_node: Option<Point2D<f32>>,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
let mouse_event_type_string = match mouse_event_type {
MouseEventType::Click => "click".to_owned(),
MouseEventType::MouseUp => "mouseup".to_owned(),
MouseEventType::MouseDown => "mousedown".to_owned(),
};
debug!("{}: at {:?}", mouse_event_type_string, client_point);
let el = node_address.and_then(|address| {
let node = node::from_untrusted_node_address(address);
node.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.next()
});
let el = match el {
Some(el) => el,
None => return,
};
let node = el.upcast::<Node>();
debug!("{} on {:?}", mouse_event_type_string, node.debug_str());
if let MouseEventType::Click = mouse_event_type {
if el.is_actually_disabled() {
return;
}
self.begin_focus_transaction();
self.request_focus(Some(&*el), FocusType::Element, can_gc);
}
let client_x = client_point.x as i32;
let client_y = client_point.y as i32;
let click_count = 1;
let event = MouseEvent::new(
&self.window,
DOMString::from(mouse_event_type_string),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
Some(&self.window),
click_count,
client_x,
client_y,
client_x,
client_y, false,
false,
false,
false,
match &button {
MouseButton::Left => 0i16,
MouseButton::Middle => 1i16,
MouseButton::Right => 2i16,
},
pressed_mouse_buttons,
None,
point_in_node,
can_gc,
);
let event = event.upcast::<Event>();
event.set_trusted(true);
let activatable = el.as_maybe_activatable();
match mouse_event_type {
MouseEventType::Click => {
el.set_click_in_progress(true);
event.fire(node.upcast(), can_gc);
el.set_click_in_progress(false);
},
MouseEventType::MouseDown => {
if let Some(a) = activatable {
a.enter_formal_activation_state();
}
let target = node.upcast();
event.fire(target, can_gc);
},
MouseEventType::MouseUp => {
if let Some(a) = activatable {
a.exit_formal_activation_state();
}
let target = node.upcast();
event.fire(target, can_gc);
},
}
if let MouseEventType::Click = mouse_event_type {
self.commit_focus_transaction(FocusType::Element, can_gc);
self.maybe_fire_dblclick(client_point, node, pressed_mouse_buttons, can_gc);
}
}
fn maybe_fire_dblclick(
&self,
click_pos: Point2D<f32>,
target: &Node,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
let now = Instant::now();
let opt = self.last_click_info.borrow_mut().take();
if let Some((last_time, last_pos)) = opt {
let DBL_CLICK_TIMEOUT =
Duration::from_millis(pref!(dom.document.dblclick_timeout) as u64);
let DBL_CLICK_DIST_THRESHOLD = pref!(dom.document.dblclick_dist) as u64;
let line = click_pos - last_pos;
let dist = (line.dot(line) as f64).sqrt();
if now.duration_since(last_time) < DBL_CLICK_TIMEOUT &&
dist < DBL_CLICK_DIST_THRESHOLD as f64
{
let click_count = 2;
let client_x = click_pos.x as i32;
let client_y = click_pos.y as i32;
let event = MouseEvent::new(
&self.window,
DOMString::from("dblclick"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
Some(&self.window),
click_count,
client_x,
client_y,
client_x,
client_y,
false,
false,
false,
false,
0i16,
pressed_mouse_buttons,
None,
None,
can_gc,
);
event.upcast::<Event>().fire(target.upcast(), can_gc);
return;
}
}
*self.last_click_info.borrow_mut() = Some((now, click_pos));
}
#[allow(clippy::too_many_arguments)]
pub fn fire_mouse_event(
&self,
client_point: Point2D<f32>,
target: &EventTarget,
event_name: FireMouseEventType,
can_bubble: EventBubbles,
cancelable: EventCancelable,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
let client_x = client_point.x.to_i32().unwrap_or(0);
let client_y = client_point.y.to_i32().unwrap_or(0);
let mouse_event = MouseEvent::new(
&self.window,
DOMString::from(event_name.as_str()),
can_bubble,
cancelable,
Some(&self.window),
0i32,
client_x,
client_y,
client_x,
client_y,
false,
false,
false,
false,
0i16,
pressed_mouse_buttons,
None,
None,
can_gc,
);
let event = mouse_event.upcast::<Event>();
event.fire(target, can_gc);
}
#[allow(unsafe_code)]
pub unsafe fn handle_mouse_move_event(
&self,
client_point: Point2D<f32>,
prev_mouse_over_target: &MutNullableDom<Element>,
node_address: Option<UntrustedNodeAddress>,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
let maybe_new_target = node_address.and_then(|address| {
let node = node::from_untrusted_node_address(address);
node.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.next()
});
let new_target = match maybe_new_target {
Some(ref target) => target,
None => return,
};
let target_has_changed = prev_mouse_over_target
.get()
.as_ref()
.map_or(true, |old_target| old_target != new_target);
if target_has_changed {
if let Some(old_target) = prev_mouse_over_target.get() {
let old_target_is_ancestor_of_new_target = old_target
.upcast::<Node>()
.is_ancestor_of(new_target.upcast::<Node>());
if !old_target_is_ancestor_of_new_target {
for element in old_target
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
{
element.set_hover_state(false);
element.set_active_state(false);
}
}
self.fire_mouse_event(
client_point,
old_target.upcast(),
FireMouseEventType::Out,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
can_gc,
);
if !old_target_is_ancestor_of_new_target {
let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
self.handle_mouse_enter_leave_event(
client_point,
FireMouseEventType::Leave,
moving_into,
event_target,
pressed_mouse_buttons,
can_gc,
);
}
}
for element in new_target
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
{
if element.hover_state() {
break;
}
element.set_hover_state(true);
}
self.fire_mouse_event(
client_point,
new_target.upcast(),
FireMouseEventType::Over,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
can_gc,
);
let moving_from = prev_mouse_over_target
.get()
.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
self.handle_mouse_enter_leave_event(
client_point,
FireMouseEventType::Enter,
moving_from,
event_target,
pressed_mouse_buttons,
can_gc,
);
}
self.fire_mouse_event(
client_point,
new_target.upcast(),
FireMouseEventType::Move,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
can_gc,
);
if target_has_changed {
prev_mouse_over_target.set(maybe_new_target.as_deref());
}
}
fn handle_mouse_enter_leave_event(
&self,
client_point: Point2D<f32>,
event_type: FireMouseEventType,
related_target: Option<DomRoot<Node>>,
event_target: DomRoot<Node>,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
assert!(matches!(
event_type,
FireMouseEventType::Enter | FireMouseEventType::Leave
));
let common_ancestor = match related_target.as_ref() {
Some(related_target) => event_target
.common_ancestor(related_target, ShadowIncluding::No)
.unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
None => DomRoot::from_ref(&*event_target),
};
let mut targets = vec![];
let mut current = Some(event_target);
while let Some(node) = current {
if node == common_ancestor {
break;
}
current = node.GetParentNode();
targets.push(node);
}
if event_type == FireMouseEventType::Enter {
targets = targets.into_iter().rev().collect();
}
for target in targets {
self.fire_mouse_event(
client_point,
target.upcast(),
event_type,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
pressed_mouse_buttons,
can_gc,
);
}
}
#[allow(unsafe_code)]
pub unsafe fn handle_wheel_event(
&self,
delta: WheelDelta,
client_point: Point2D<f32>,
node_address: Option<UntrustedNodeAddress>,
can_gc: CanGc,
) {
let wheel_event_type_string = "wheel".to_owned();
debug!("{}: at {:?}", wheel_event_type_string, client_point);
let el = node_address.and_then(|address| {
let node = node::from_untrusted_node_address(address);
node.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.next()
});
let el = match el {
Some(el) => el,
None => return,
};
let node = el.upcast::<Node>();
debug!("{}: on {:?}", wheel_event_type_string, node.debug_str());
let event = WheelEvent::new(
&self.window,
DOMString::from(wheel_event_type_string),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
Some(&self.window),
0i32,
Finite::wrap(delta.x),
Finite::wrap(delta.y),
Finite::wrap(delta.z),
delta.mode as u32,
can_gc,
);
let event = event.upcast::<Event>();
event.set_trusted(true);
let target = node.upcast();
event.fire(target, can_gc);
}
#[allow(unsafe_code)]
pub unsafe fn handle_touch_event(
&self,
event_type: TouchEventType,
touch_id: TouchId,
point: Point2D<f32>,
node_address: Option<UntrustedNodeAddress>,
can_gc: CanGc,
) -> TouchEventResult {
let TouchId(identifier) = touch_id;
let event_name = match event_type {
TouchEventType::Down => "touchstart",
TouchEventType::Move => "touchmove",
TouchEventType::Up => "touchend",
TouchEventType::Cancel => "touchcancel",
};
let el = node_address.and_then(|address| {
let node = node::from_untrusted_node_address(address);
node.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.next()
});
let el = match el {
Some(el) => el,
None => return TouchEventResult::Forwarded,
};
let target = DomRoot::upcast::<EventTarget>(el);
let window = &*self.window;
let client_x = Finite::wrap(point.x as f64);
let client_y = Finite::wrap(point.y as f64);
let page_x = Finite::wrap(point.x as f64 + window.PageXOffset() as f64);
let page_y = Finite::wrap(point.y as f64 + window.PageYOffset() as f64);
let touch = Touch::new(
window, identifier, &target, client_x,
client_y, client_x, client_y, page_x, page_y,
);
match event_type {
TouchEventType::Down => {
self.active_touch_points
.borrow_mut()
.push(Dom::from_ref(&*touch));
},
TouchEventType::Move => {
let mut active_touch_points = self.active_touch_points.borrow_mut();
match active_touch_points
.iter_mut()
.find(|t| t.Identifier() == identifier)
{
Some(t) => *t = Dom::from_ref(&*touch),
None => warn!("Got a touchmove event for a non-active touch point"),
}
},
TouchEventType::Up | TouchEventType::Cancel => {
let mut active_touch_points = self.active_touch_points.borrow_mut();
match active_touch_points
.iter()
.position(|t| t.Identifier() == identifier)
{
Some(i) => {
active_touch_points.swap_remove(i);
},
None => warn!("Got a touchend event for a non-active touch point"),
}
},
}
rooted_vec!(let mut target_touches);
let touches = {
let touches = self.active_touch_points.borrow();
target_touches.extend(touches.iter().filter(|t| t.Target() == target).cloned());
TouchList::new(window, touches.r())
};
let event = TouchEvent::new(
window,
DOMString::from(event_name),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
Some(window),
0i32,
&touches,
&TouchList::new(window, from_ref(&&*touch)),
&TouchList::new(window, target_touches.r()),
false,
false,
false,
false,
);
let event = event.upcast::<Event>();
let result = event.fire(&target, can_gc);
match result {
EventStatus::Canceled => TouchEventResult::Processed(false),
EventStatus::NotCanceled => TouchEventResult::Processed(true),
}
}
pub fn dispatch_key_event(
&self,
keyboard_event: ::keyboard_types::KeyboardEvent,
can_gc: CanGc,
) {
let focused = self.get_focused_element();
let body = self.GetBody();
let target = match (&focused, &body) {
(Some(focused), _) => focused.upcast(),
(&None, Some(body)) => body.upcast(),
(&None, &None) => self.window.upcast(),
};
let keyevent = KeyboardEvent::new(
&self.window,
DOMString::from(keyboard_event.state.to_string()),
true,
true,
Some(&self.window),
0,
keyboard_event.key.clone(),
DOMString::from(keyboard_event.code.to_string()),
keyboard_event.location as u32,
keyboard_event.repeat,
keyboard_event.is_composing,
keyboard_event.modifiers,
0,
keyboard_event.key.legacy_keycode(),
can_gc,
);
let event = keyevent.upcast::<Event>();
event.fire(target, can_gc);
let mut cancel_state = event.get_cancel_state();
if keyboard_event.state == KeyState::Down &&
is_character_value_key(&(keyboard_event.key)) &&
!keyboard_event.is_composing &&
cancel_state != EventDefault::Prevented
{
let event = KeyboardEvent::new(
&self.window,
DOMString::from("keypress"),
true,
true,
Some(&self.window),
0,
keyboard_event.key.clone(),
DOMString::from(keyboard_event.code.to_string()),
keyboard_event.location as u32,
keyboard_event.repeat,
keyboard_event.is_composing,
keyboard_event.modifiers,
keyboard_event.key.legacy_charcode(),
0,
can_gc,
);
let ev = event.upcast::<Event>();
ev.fire(target, can_gc);
cancel_state = ev.get_cancel_state();
}
if cancel_state == EventDefault::Allowed {
let msg = EmbedderMsg::Keyboard(keyboard_event.clone());
self.send_to_embedder(msg);
if (keyboard_event.key == Key::Enter || keyboard_event.code == Code::Space) &&
keyboard_event.state == KeyState::Up
{
if let Some(elem) = target.downcast::<Element>() {
elem.upcast::<Node>()
.fire_synthetic_mouse_event_not_trusted(DOMString::from("click"), can_gc);
}
}
}
}
pub fn ime_dismissed(&self, can_gc: CanGc) {
self.request_focus(
self.GetBody().as_ref().map(|e| e.upcast()),
FocusType::Element,
can_gc,
)
}
pub fn dispatch_composition_event(
&self,
composition_event: ::keyboard_types::CompositionEvent,
can_gc: CanGc,
) {
let focused = self.get_focused_element();
let target = if let Some(elem) = &focused {
elem.upcast()
} else {
return;
};
let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
let compositionevent = CompositionEvent::new(
&self.window,
DOMString::from(composition_event.state.to_string()),
true,
cancelable,
Some(&self.window),
0,
DOMString::from(composition_event.data),
can_gc,
);
let event = compositionevent.upcast::<Event>();
event.fire(target, can_gc);
}
pub fn node_from_nodes_and_strings(
&self,
mut nodes: Vec<NodeOrString>,
can_gc: CanGc,
) -> Fallible<DomRoot<Node>> {
if nodes.len() == 1 {
Ok(match nodes.pop().unwrap() {
NodeOrString::Node(node) => node,
NodeOrString::String(string) => {
DomRoot::upcast(self.CreateTextNode(string, can_gc))
},
})
} else {
let fragment = DomRoot::upcast::<Node>(self.CreateDocumentFragment(can_gc));
for node in nodes {
match node {
NodeOrString::Node(node) => {
fragment.AppendChild(&node)?;
},
NodeOrString::String(string) => {
let node = DomRoot::upcast::<Node>(self.CreateTextNode(string, can_gc));
fragment.AppendChild(&node).unwrap();
},
}
}
Ok(fragment)
}
}
pub fn get_body_attribute(&self, local_name: &LocalName) -> DOMString {
match self
.GetBody()
.and_then(DomRoot::downcast::<HTMLBodyElement>)
{
Some(ref body) => body.upcast::<Element>().get_string_attribute(local_name),
None => DOMString::new(),
}
}
pub fn set_body_attribute(&self, local_name: &LocalName, value: DOMString, can_gc: CanGc) {
if let Some(ref body) = self
.GetBody()
.and_then(DomRoot::downcast::<HTMLBodyElement>)
{
let body = body.upcast::<Element>();
let value = body.parse_attribute(&ns!(), local_name, value);
body.set_attribute(local_name, value, can_gc);
}
}
pub fn set_current_script(&self, script: Option<&HTMLScriptElement>) {
self.current_script.set(script);
}
pub fn get_script_blocking_stylesheets_count(&self) -> u32 {
self.script_blocking_stylesheets_count.get()
}
pub fn increment_script_blocking_stylesheet_count(&self) {
let count_cell = &self.script_blocking_stylesheets_count;
count_cell.set(count_cell.get() + 1);
}
pub fn decrement_script_blocking_stylesheet_count(&self) {
let count_cell = &self.script_blocking_stylesheets_count;
assert!(count_cell.get() > 0);
count_cell.set(count_cell.get() - 1);
}
pub fn invalidate_stylesheets(&self) {
self.stylesheets.borrow_mut().force_dirty(OriginSet::all());
if let Some(element) = self.GetDocumentElement() {
element.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged);
}
}
pub(crate) fn has_active_request_animation_frame_callbacks(&self) -> bool {
!self.animation_frame_list.borrow().is_empty()
}
pub(crate) fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 {
let ident = self.animation_frame_ident.get() + 1;
self.animation_frame_ident.set(ident);
self.animation_frame_list
.borrow_mut()
.push((ident, Some(callback)));
if self.is_faking_animation_frames() && !self.window().throttled() {
warn!("Scheduling fake animation frame. Animation frames tick too fast.");
let callback = FakeRequestAnimationFrameCallback {
document: Trusted::new(self),
};
self.global().schedule_callback(
OneshotTimerCallback::FakeRequestAnimationFrame(callback),
Duration::from_millis(FAKE_REQUEST_ANIMATION_FRAME_DELAY),
);
} else if !self.running_animation_callbacks.get() {
let event =
ScriptMsg::ChangeRunningAnimationsState(AnimationState::AnimationCallbacksPresent);
self.window().send_to_constellation(event);
}
ident
}
pub(crate) fn cancel_animation_frame(&self, ident: u32) {
let mut list = self.animation_frame_list.borrow_mut();
if let Some(pair) = list.iter_mut().find(|pair| pair.0 == ident) {
pair.1 = None;
}
}
pub(crate) fn run_the_animation_frame_callbacks(&self) {
let _realm = enter_realm(self);
rooted_vec!(let mut animation_frame_list);
mem::swap(
&mut *animation_frame_list,
&mut *self.animation_frame_list.borrow_mut(),
);
self.running_animation_callbacks.set(true);
let was_faking_animation_frames = self.is_faking_animation_frames();
let timing = self.global().performance().Now();
for (_, callback) in animation_frame_list.drain(..) {
if let Some(callback) = callback {
callback.call(self, *timing);
}
}
self.running_animation_callbacks.set(false);
let callbacks_did_not_trigger_reflow = self.needs_reflow().is_none();
let is_empty = self.animation_frame_list.borrow().is_empty();
if !is_empty && callbacks_did_not_trigger_reflow && !was_faking_animation_frames {
self.set_needs_paint(true);
}
if is_empty || (!was_faking_animation_frames && self.is_faking_animation_frames()) {
if is_empty {
mem::swap(
&mut *self.animation_frame_list.borrow_mut(),
&mut *animation_frame_list,
);
}
let event = ScriptMsg::ChangeRunningAnimationsState(
AnimationState::NoAnimationCallbacksPresent,
);
self.window().send_to_constellation(event);
}
if callbacks_did_not_trigger_reflow {
if self.spurious_animation_frames.get() < SPURIOUS_ANIMATION_FRAME_THRESHOLD {
self.spurious_animation_frames
.set(self.spurious_animation_frames.get() + 1)
}
} else {
self.spurious_animation_frames.set(0)
}
}
pub fn policy_container(&self) -> Ref<PolicyContainer> {
self.policy_container.borrow()
}
pub(crate) fn prepare_request(&self, request: RequestBuilder) -> RequestBuilder {
request
.policy_container(self.policy_container().to_owned())
.https_state(self.https_state.get())
}
pub(crate) fn fetch<Listener: FetchResponseListener + PreInvoke + Send + 'static>(
&self,
load: LoadType,
request: RequestBuilder,
listener: Listener,
) {
let (task_source, canceller) = self
.window()
.task_manager()
.networking_task_source_with_canceller();
let callback = NetworkListener {
context: std::sync::Arc::new(Mutex::new(listener)),
task_source,
canceller: Some(canceller),
}
.into_callback();
self.loader_mut()
.fetch_async_with_callback(load, request, callback);
}
pub(crate) fn fetch_background<Listener: FetchResponseListener + PreInvoke + Send + 'static>(
&self,
request: RequestBuilder,
listener: Listener,
cancel_override: Option<ipc::IpcReceiver<()>>,
) {
let (task_source, canceller) = self
.window()
.task_manager()
.networking_task_source_with_canceller();
let callback = NetworkListener {
context: std::sync::Arc::new(Mutex::new(listener)),
task_source,
canceller: Some(canceller),
}
.into_callback();
self.loader_mut()
.fetch_async_background(request, callback, cancel_override);
}
pub fn finish_load(&self, load: LoadType, can_gc: CanGc) {
debug!("Document got finish_load: {:?}", load);
self.loader.borrow_mut().finish_load(&load);
match load {
LoadType::Stylesheet(_) => {
self.process_pending_parsing_blocking_script(can_gc);
self.process_deferred_scripts();
},
LoadType::PageSource(_) => {
if self.has_browsing_context && self.is_fully_active() {
self.window().allow_layout_if_necessary(can_gc);
}
self.process_deferred_scripts();
},
_ => {},
}
let loader = self.loader.borrow();
if self.top_level_dom_complete.get().is_none() && loader.is_only_blocked_by_iframes() {
update_with_current_instant(&self.top_level_dom_complete);
}
if loader.is_blocked() || loader.events_inhibited() {
return;
}
ScriptThread::mark_document_with_no_blocked_loads(self);
}
pub fn prompt_to_unload(&self, recursive_flag: bool, can_gc: CanGc) -> bool {
self.incr_ignore_opens_during_unload_counter();
let beforeunload_event = BeforeUnloadEvent::new(
&self.window,
atom!("beforeunload"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
);
let event = beforeunload_event.upcast::<Event>();
event.set_trusted(true);
let event_target = self.window.upcast::<EventTarget>();
let has_listeners = event_target.has_listeners_for(&atom!("beforeunload"));
self.window
.dispatch_event_with_target_override(event, can_gc);
if has_listeners {
self.salvageable.set(false);
}
let mut can_unload = true;
let default_prevented = event.DefaultPrevented();
let return_value_not_empty = !event
.downcast::<BeforeUnloadEvent>()
.unwrap()
.ReturnValue()
.is_empty();
if default_prevented || return_value_not_empty {
let (chan, port) = ipc::channel().expect("Failed to create IPC channel!");
let msg = EmbedderMsg::AllowUnload(chan);
self.send_to_embedder(msg);
can_unload = port.recv().unwrap();
}
if !recursive_flag {
let iframes: Vec<_> = self.iframes().iter().collect();
for iframe in &iframes {
let document = document_from_node(&**iframe);
can_unload = document.prompt_to_unload(true, can_gc);
if !document.salvageable() {
self.salvageable.set(false);
}
if !can_unload {
break;
}
}
}
self.decr_ignore_opens_during_unload_counter();
can_unload
}
pub fn unload(&self, recursive_flag: bool, can_gc: CanGc) {
self.incr_ignore_opens_during_unload_counter();
if self.page_showing.get() {
self.page_showing.set(false);
let event = PageTransitionEvent::new(
&self.window,
atom!("pagehide"),
false, false, self.salvageable.get(), can_gc,
);
let event = event.upcast::<Event>();
event.set_trusted(true);
let _ = self
.window
.dispatch_event_with_target_override(event, can_gc);
self.update_visibility_state(DocumentVisibilityState::Hidden, can_gc);
}
if !self.fired_unload.get() {
let event = Event::new(
self.window.upcast(),
atom!("unload"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
can_gc,
);
event.set_trusted(true);
let event_target = self.window.upcast::<EventTarget>();
let has_listeners = event_target.has_listeners_for(&atom!("unload"));
let _ = self
.window
.dispatch_event_with_target_override(&event, can_gc);
self.fired_unload.set(true);
if has_listeners {
self.salvageable.set(false);
}
}
if !recursive_flag {
let iframes: Vec<_> = self.iframes().iter().collect();
for iframe in &iframes {
let document = document_from_node(&**iframe);
document.unload(true, can_gc);
if !document.salvageable() {
self.salvageable.set(false);
}
}
}
let global_scope = self.window.upcast::<GlobalScope>();
if !self.salvageable.get() {
global_scope.close_event_sources();
let msg = ScriptMsg::DiscardDocument;
let _ = global_scope.script_to_constellation_chan().send(msg);
}
global_scope.clean_up_all_file_resources();
self.decr_ignore_opens_during_unload_counter();
}
pub fn maybe_queue_document_completion(&self) {
let is_in_delaying_load_events_mode = match self.window.undiscarded_window_proxy() {
Some(window_proxy) => window_proxy.is_delaying_load_events_mode(),
None => false,
};
let not_ready_for_load = self.loader.borrow().is_blocked() ||
!self.is_fully_active() ||
is_in_delaying_load_events_mode;
if not_ready_for_load {
return;
}
assert!(!self.loader.borrow().events_inhibited());
self.loader.borrow_mut().inhibit_events();
debug!("Document loads are complete.");
let document = Trusted::new(self);
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_load_event: move || {
let document = document.root();
let window = document.window();
if !window.is_alive() {
return;
}
document.set_ready_state(DocumentReadyState::Complete, CanGc::note());
if document.browsing_context().is_none() {
return;
}
let event = Event::new(
window.upcast(),
atom!("load"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
CanGc::note(),
);
event.set_trusted(true);
update_with_current_instant(&document.load_event_start);
debug!("About to dispatch load for {:?}", document.url());
window.dispatch_event_with_target_override(&event, CanGc::note());
update_with_current_instant(&document.load_event_end);
if let Some(fragment) = document.url().fragment() {
document.check_and_scroll_fragment(fragment, CanGc::note());
}
}),
self.window.upcast(),
)
.unwrap();
let document = Trusted::new(self);
if document.root().browsing_context().is_some() {
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_pageshow_event: move || {
let document = document.root();
let window = document.window();
if document.page_showing.get() || !window.is_alive() {
return;
}
document.page_showing.set(true);
let event = PageTransitionEvent::new(
window,
atom!("pageshow"),
false, false, false, CanGc::note(),
);
let event = event.upcast::<Event>();
event.set_trusted(true);
window.dispatch_event_with_target_override(event, CanGc::note());
}),
self.window.upcast(),
)
.unwrap();
}
#[cfg(feature = "webxr")]
if pref!(dom.webxr.sessionavailable) && self.window.is_top_level() {
self.window.Navigator().Xr().dispatch_sessionavailable();
}
let document = Trusted::new(self);
if document.root().browsing_context().is_some() {
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(completely_loaded: move || {
let document = document.root();
document.completely_loaded.set(true);
if let Some(DeclarativeRefresh::PendingLoad {
url,
time
}) = &*document.declarative_refresh.borrow() {
document.window.upcast::<GlobalScope>().schedule_callback(
OneshotTimerCallback::RefreshRedirectDue(RefreshRedirectDue {
window: window_from_node(&*document),
url: url.clone(),
}),
Duration::from_secs(*time),
);
}
document.notify_constellation_load();
}),
self.window.upcast(),
)
.unwrap();
}
}
pub fn completely_loaded(&self) -> bool {
self.completely_loaded.get()
}
pub fn set_pending_parsing_blocking_script(
&self,
script: &HTMLScriptElement,
load: Option<ScriptResult>,
) {
assert!(!self.has_pending_parsing_blocking_script());
*self.pending_parsing_blocking_script.borrow_mut() =
Some(PendingScript::new_with_load(script, load));
}
pub fn has_pending_parsing_blocking_script(&self) -> bool {
self.pending_parsing_blocking_script.borrow().is_some()
}
pub fn pending_parsing_blocking_script_loaded(
&self,
element: &HTMLScriptElement,
result: ScriptResult,
can_gc: CanGc,
) {
{
let mut blocking_script = self.pending_parsing_blocking_script.borrow_mut();
let entry = blocking_script.as_mut().unwrap();
assert!(&*entry.element == element);
entry.loaded(result);
}
self.process_pending_parsing_blocking_script(can_gc);
}
fn process_pending_parsing_blocking_script(&self, can_gc: CanGc) {
if self.script_blocking_stylesheets_count.get() > 0 {
return;
}
let pair = self
.pending_parsing_blocking_script
.borrow_mut()
.as_mut()
.and_then(PendingScript::take_result);
if let Some((element, result)) = pair {
*self.pending_parsing_blocking_script.borrow_mut() = None;
self.get_current_parser()
.unwrap()
.resume_with_pending_parsing_blocking_script(&element, result, can_gc);
}
}
pub fn add_asap_script(&self, script: &HTMLScriptElement) {
self.asap_scripts_set
.borrow_mut()
.push(Dom::from_ref(script));
}
pub fn asap_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
{
let mut scripts = self.asap_scripts_set.borrow_mut();
let idx = scripts
.iter()
.position(|entry| &**entry == element)
.unwrap();
scripts.swap_remove(idx);
}
element.execute(result);
}
pub fn push_asap_in_order_script(&self, script: &HTMLScriptElement) {
self.asap_in_order_scripts_list.push(script);
}
pub fn asap_in_order_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
self.asap_in_order_scripts_list.loaded(element, result);
while let Some((element, result)) = self
.asap_in_order_scripts_list
.take_next_ready_to_be_executed()
{
element.execute(result);
}
}
pub fn add_deferred_script(&self, script: &HTMLScriptElement) {
self.deferred_scripts.push(script);
}
pub fn deferred_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
self.deferred_scripts.loaded(element, result);
self.process_deferred_scripts();
}
fn process_deferred_scripts(&self) {
if self.ready_state.get() != DocumentReadyState::Interactive {
return;
}
loop {
if self.script_blocking_stylesheets_count.get() > 0 {
return;
}
if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed()
{
element.execute(result);
} else {
break;
}
}
if self.deferred_scripts.is_empty() {
self.maybe_dispatch_dom_content_loaded();
}
}
pub fn maybe_dispatch_dom_content_loaded(&self) {
if self.domcontentloaded_dispatched.get() {
return;
}
self.domcontentloaded_dispatched.set(true);
assert_ne!(
self.ReadyState(),
DocumentReadyState::Complete,
"Complete before DOMContentLoaded?"
);
update_with_current_instant(&self.dom_content_loaded_event_start);
let window = self.window();
let document = Trusted::new(self);
window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_dom_content_loaded_event: move || {
let document = document.root();
document.upcast::<EventTarget>().fire_bubbling_event(atom!("DOMContentLoaded"), CanGc::note());
update_with_current_instant(&document.dom_content_loaded_event_end);
}),
window.upcast(),
)
.unwrap();
self.interactive_time
.borrow()
.maybe_set_tti(self, InteractiveFlag::DOMContentLoaded);
}
pub fn abort(&self, can_gc: CanGc) {
self.loader.borrow_mut().inhibit_events();
for iframe in self.iframes().iter() {
if let Some(document) = iframe.GetContentDocument() {
document.abort(can_gc);
}
}
self.script_blocking_stylesheets_count.set(0);
*self.pending_parsing_blocking_script.borrow_mut() = None;
*self.asap_scripts_set.borrow_mut() = vec![];
self.asap_in_order_scripts_list.clear();
self.deferred_scripts.clear();
let global_scope = self.window.upcast::<GlobalScope>();
let loads_cancelled = self.loader.borrow_mut().cancel_all_loads();
let event_sources_canceled = global_scope.close_event_sources();
if loads_cancelled || event_sources_canceled {
self.salvageable.set(false);
};
self.window
.cancel_all_tasks_from_source(TaskSourceName::Networking);
if let Some(parser) = self.get_current_parser() {
self.active_parser_was_aborted.set(true);
parser.abort(can_gc);
self.salvageable.set(false);
}
}
pub fn notify_constellation_load(&self) {
self.window().send_to_constellation(ScriptMsg::LoadComplete);
}
pub fn set_current_parser(&self, script: Option<&ServoParser>) {
self.current_parser.set(script);
}
pub fn get_current_parser(&self) -> Option<DomRoot<ServoParser>> {
self.current_parser.get()
}
pub(crate) fn iframes(&self) -> Ref<IFrameCollection> {
self.iframes.borrow_mut().validate(self);
self.iframes.borrow()
}
pub(crate) fn iframes_mut(&self) -> RefMut<IFrameCollection> {
self.iframes.borrow_mut().validate(self);
self.iframes.borrow_mut()
}
pub fn get_dom_interactive(&self) -> Option<CrossProcessInstant> {
self.dom_interactive.get()
}
pub fn set_navigation_start(&self, navigation_start: CrossProcessInstant) {
self.interactive_time
.borrow_mut()
.set_navigation_start(navigation_start);
}
pub fn get_interactive_metrics(&self) -> Ref<InteractiveMetrics> {
self.interactive_time.borrow()
}
pub fn has_recorded_tti_metric(&self) -> bool {
self.get_interactive_metrics().get_tti().is_some()
}
pub fn get_dom_content_loaded_event_start(&self) -> Option<CrossProcessInstant> {
self.dom_content_loaded_event_start.get()
}
pub fn get_dom_content_loaded_event_end(&self) -> Option<CrossProcessInstant> {
self.dom_content_loaded_event_end.get()
}
pub fn get_dom_complete(&self) -> Option<CrossProcessInstant> {
self.dom_complete.get()
}
pub fn get_top_level_dom_complete(&self) -> Option<CrossProcessInstant> {
self.top_level_dom_complete.get()
}
pub fn get_load_event_start(&self) -> Option<CrossProcessInstant> {
self.load_event_start.get()
}
pub fn get_load_event_end(&self) -> Option<CrossProcessInstant> {
self.load_event_end.get()
}
pub fn get_unload_event_start(&self) -> Option<CrossProcessInstant> {
self.unload_event_start.get()
}
pub fn get_unload_event_end(&self) -> Option<CrossProcessInstant> {
self.unload_event_end.get()
}
pub fn start_tti(&self) {
if self.get_interactive_metrics().needs_tti() {
self.tti_window.borrow_mut().start_window();
}
}
pub fn record_tti_if_necessary(&self) {
if self.has_recorded_tti_metric() {
return;
}
if self.tti_window.borrow().needs_check() {
self.get_interactive_metrics().maybe_set_tti(
self,
InteractiveFlag::TimeToInteractive(self.tti_window.borrow().get_start()),
);
}
}
fn fire_focus_event(
&self,
focus_event_type: FocusEventType,
node: &Node,
related_target: Option<&EventTarget>,
can_gc: CanGc,
) {
let (event_name, does_bubble) = match focus_event_type {
FocusEventType::Focus => (DOMString::from("focus"), EventBubbles::DoesNotBubble),
FocusEventType::Blur => (DOMString::from("blur"), EventBubbles::DoesNotBubble),
};
let event = FocusEvent::new(
&self.window,
event_name,
does_bubble,
EventCancelable::NotCancelable,
Some(&self.window),
0i32,
related_target,
can_gc,
);
let event = event.upcast::<Event>();
event.set_trusted(true);
let target = node.upcast();
event.fire(target, can_gc);
}
pub fn is_cookie_averse(&self) -> bool {
!self.has_browsing_context || !url_has_network_scheme(&self.url())
}
pub fn lookup_custom_element_definition(
&self,
namespace: &Namespace,
local_name: &LocalName,
is: Option<&LocalName>,
) -> Option<Rc<CustomElementDefinition>> {
if !pref!(dom.custom_elements.enabled) {
return None;
}
if *namespace != ns!(html) {
return None;
}
if !self.has_browsing_context {
return None;
}
let registry = self.window.CustomElements();
registry.lookup_definition(local_name, is)
}
pub fn increment_throw_on_dynamic_markup_insertion_counter(&self) {
let counter = self.throw_on_dynamic_markup_insertion_counter.get();
self.throw_on_dynamic_markup_insertion_counter
.set(counter + 1);
}
pub fn decrement_throw_on_dynamic_markup_insertion_counter(&self) {
let counter = self.throw_on_dynamic_markup_insertion_counter.get();
self.throw_on_dynamic_markup_insertion_counter
.set(counter - 1);
}
pub fn react_to_environment_changes(&self) {
for image in self.responsive_images.borrow().iter() {
image.react_to_environment_changes();
}
}
pub fn register_responsive_image(&self, img: &HTMLImageElement) {
self.responsive_images.borrow_mut().push(Dom::from_ref(img));
}
pub fn unregister_responsive_image(&self, img: &HTMLImageElement) {
let index = self
.responsive_images
.borrow()
.iter()
.position(|x| **x == *img);
if let Some(i) = index {
self.responsive_images.borrow_mut().remove(i);
}
}
pub fn register_media_controls(&self, controls: &ShadowRoot) -> String {
let id = Uuid::new_v4().to_string();
self.media_controls
.borrow_mut()
.insert(id.clone(), Dom::from_ref(controls));
id
}
pub fn unregister_media_controls(&self, id: &str) {
if let Some(ref media_controls) = self.media_controls.borrow_mut().remove(id) {
let media_controls = DomRoot::from_ref(&**media_controls);
media_controls.Host().detach_shadow();
} else {
debug_assert!(false, "Trying to unregister unknown media controls");
}
}
pub fn add_dirty_webgl_canvas(&self, context: &WebGLRenderingContext) {
self.dirty_webgl_contexts
.borrow_mut()
.entry(context.context_id())
.or_insert_with(|| Dom::from_ref(context));
}
pub fn flush_dirty_webgl_canvases(&self) {
let dirty_context_ids: Vec<_> = self
.dirty_webgl_contexts
.borrow_mut()
.drain()
.filter(|(_, context)| context.onscreen())
.map(|(id, _)| id)
.collect();
if dirty_context_ids.is_empty() {
return;
}
#[allow(unused)]
let mut time = 0;
let (sender, receiver) = webgl::webgl_channel().unwrap();
self.window
.webgl_chan()
.expect("Where's the WebGL channel?")
.send(WebGLMsg::SwapBuffers(dirty_context_ids, sender, time))
.unwrap();
receiver.recv().unwrap();
}
#[cfg(feature = "webgpu")]
pub fn webgpu_contexts(&self) -> WebGPUContextsMap {
self.webgpu_contexts.clone()
}
#[allow(crown::unrooted_must_root)]
#[cfg(feature = "webgpu")]
pub fn update_rendering_of_webgpu_canvases(&self) {
self.webgpu_contexts
.borrow_mut()
.iter()
.filter_map(|(_, context)| context.root())
.filter(|context| context.onscreen())
.for_each(|context| context.update_rendering_of_webgpu_canvas());
}
pub fn id_map(&self) -> Ref<HashMapTracedValues<Atom, Vec<Dom<Element>>>> {
self.id_map.borrow()
}
pub fn name_map(&self) -> Ref<HashMapTracedValues<Atom, Vec<Dom<Element>>>> {
self.name_map.borrow()
}
pub(crate) fn add_resize_observer(&self, resize_observer: &ResizeObserver) {
self.resize_observers
.borrow_mut()
.push(Dom::from_ref(resize_observer));
}
pub(crate) fn gather_active_resize_observations_at_depth(
&self,
depth: &ResizeObservationDepth,
can_gc: CanGc,
) -> bool {
let mut has_active_resize_observations = false;
for observer in self.resize_observers.borrow_mut().iter_mut() {
observer.gather_active_resize_observations_at_depth(
depth,
&mut has_active_resize_observations,
can_gc,
);
}
has_active_resize_observations
}
pub(crate) fn broadcast_active_resize_observations(
&self,
can_gc: CanGc,
) -> ResizeObservationDepth {
let mut shallowest = ResizeObservationDepth::max();
for observer in self
.resize_observers
.borrow()
.iter()
.map(|obs| DomRoot::from_ref(&**obs))
{
observer.broadcast_active_resize_observations(&mut shallowest, can_gc);
}
shallowest
}
pub(crate) fn has_skipped_resize_observations(&self) -> bool {
self.resize_observers
.borrow()
.iter()
.any(|observer| observer.has_skipped_resize_observations())
}
pub(crate) fn deliver_resize_loop_error_notification(&self, can_gc: CanGc) {
let global_scope = self.window.upcast::<GlobalScope>();
let error_info: ErrorInfo = crate::dom::bindings::error::ErrorInfo {
message: "ResizeObserver loop completed with undelivered notifications.".to_string(),
..Default::default()
};
global_scope.report_an_error(error_info, HandleValue::null(), can_gc);
}
pub(crate) fn status_code(&self) -> Option<u16> {
self.status_code
}
pub fn encoding_parse_a_url(&self, url: &str) -> Result<ServoUrl, url::ParseError> {
let encoding = self.encoding.get();
let base_url = self.base_url();
url::Url::options()
.base_url(Some(base_url.as_url()))
.encoding_override(Some(&|s| encoding.encode(s).0))
.parse(url)
.map(ServoUrl::from)
}
}
fn is_character_value_key(key: &Key) -> bool {
matches!(key, Key::Character(_) | Key::Enter)
}
#[derive(MallocSizeOf, PartialEq)]
pub enum DocumentSource {
FromParser,
NotFromParser,
}
#[allow(unsafe_code)]
pub trait LayoutDocumentHelpers<'dom> {
fn is_html_document_for_layout(&self) -> bool;
fn quirks_mode(self) -> QuirksMode;
fn style_shared_lock(self) -> &'dom StyleSharedRwLock;
fn shadow_roots(self) -> Vec<LayoutDom<'dom, ShadowRoot>>;
fn shadow_roots_styles_changed(self) -> bool;
fn flush_shadow_roots_stylesheets(self);
}
#[allow(unsafe_code)]
impl<'dom> LayoutDocumentHelpers<'dom> for LayoutDom<'dom, Document> {
#[inline]
fn is_html_document_for_layout(&self) -> bool {
self.unsafe_get().is_html_document
}
#[inline]
fn quirks_mode(self) -> QuirksMode {
self.unsafe_get().quirks_mode.get()
}
#[inline]
fn style_shared_lock(self) -> &'dom StyleSharedRwLock {
self.unsafe_get().style_shared_lock()
}
#[inline]
fn shadow_roots(self) -> Vec<LayoutDom<'dom, ShadowRoot>> {
unsafe {
self.unsafe_get()
.shadow_roots
.borrow_for_layout()
.iter()
.map(|sr| sr.to_layout())
.collect()
}
}
#[inline]
fn shadow_roots_styles_changed(self) -> bool {
self.unsafe_get().shadow_roots_styles_changed.get()
}
#[inline]
fn flush_shadow_roots_stylesheets(self) {
(*self.unsafe_get()).flush_shadow_roots_stylesheets()
}
}
fn get_registrable_domain_suffix_of_or_is_equal_to(
host_suffix_string: &str,
original_host: Host,
) -> Option<Host> {
if host_suffix_string.is_empty() {
return None;
}
let host = match Host::parse(host_suffix_string) {
Ok(host) => host,
Err(_) => return None,
};
if host != original_host {
let host = match host {
Host::Domain(ref host) => host,
_ => return None,
};
let original_host = match original_host {
Host::Domain(ref original_host) => original_host,
_ => return None,
};
let index = original_host.len().checked_sub(host.len())?;
let (prefix, suffix) = original_host.split_at(index);
if !prefix.ends_with('.') {
return None;
}
if suffix != host {
return None;
}
if is_pub_domain(host) {
return None;
}
}
Some(host)
}
fn url_has_network_scheme(url: &ServoUrl) -> bool {
matches!(url.scheme(), "ftp" | "http" | "https")
}
#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
pub enum HasBrowsingContext {
No,
Yes,
}
impl Document {
#[allow(clippy::too_many_arguments)]
pub fn new_inherited(
window: &Window,
has_browsing_context: HasBrowsingContext,
url: Option<ServoUrl>,
origin: MutableOrigin,
is_html_document: IsHTMLDocument,
content_type: Option<Mime>,
last_modified: Option<String>,
activity: DocumentActivity,
source: DocumentSource,
doc_loader: DocumentLoader,
referrer: Option<String>,
status_code: Option<u16>,
canceller: FetchCanceller,
) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
(DocumentReadyState::Loading, false)
} else {
(DocumentReadyState::Complete, true)
};
let interactive_time =
InteractiveMetrics::new(window.time_profiler_chan().clone(), url.clone());
let content_type = content_type.unwrap_or_else(|| {
match is_html_document {
IsHTMLDocument::HTMLDocument => mime::TEXT_HTML,
IsHTMLDocument::NonHTMLDocument => "application/xml".parse().unwrap(),
}
});
let encoding = content_type
.get_param(mime::CHARSET)
.and_then(|charset| Encoding::for_label(charset.as_str().as_bytes()))
.unwrap_or(UTF_8);
let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes;
Document {
node: Node::new_document_node(),
document_or_shadow_root: DocumentOrShadowRoot::new(window),
window: Dom::from_ref(window),
has_browsing_context,
implementation: Default::default(),
content_type,
last_modified,
url: DomRefCell::new(url),
quirks_mode: Cell::new(QuirksMode::NoQuirks),
id_map: DomRefCell::new(HashMapTracedValues::new()),
name_map: DomRefCell::new(HashMapTracedValues::new()),
encoding: Cell::new(encoding),
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
activity: Cell::new(activity),
tag_map: DomRefCell::new(HashMapTracedValues::new()),
tagns_map: DomRefCell::new(HashMapTracedValues::new()),
classes_map: DomRefCell::new(HashMapTracedValues::new()),
images: Default::default(),
embeds: Default::default(),
links: Default::default(),
forms: Default::default(),
scripts: Default::default(),
anchors: Default::default(),
applets: Default::default(),
iframes: Default::default(),
style_shared_lock: {
static PER_PROCESS_AUTHOR_SHARED_LOCK: LazyLock<StyleSharedRwLock> =
LazyLock::new(StyleSharedRwLock::new);
PER_PROCESS_AUTHOR_SHARED_LOCK.clone()
},
stylesheets: DomRefCell::new(DocumentStylesheetSet::new()),
stylesheet_list: MutNullableDom::new(None),
ready_state: Cell::new(ready_state),
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction),
focused: Default::default(),
current_script: Default::default(),
pending_parsing_blocking_script: Default::default(),
script_blocking_stylesheets_count: Cell::new(0u32),
deferred_scripts: Default::default(),
asap_in_order_scripts_list: Default::default(),
asap_scripts_set: Default::default(),
scripting_enabled: has_browsing_context,
animation_frame_ident: Cell::new(0),
animation_frame_list: DomRefCell::new(vec![]),
running_animation_callbacks: Cell::new(false),
loader: DomRefCell::new(doc_loader),
current_parser: Default::default(),
base_element: Default::default(),
appropriate_template_contents_owner_document: Default::default(),
pending_restyles: DomRefCell::new(HashMap::new()),
needs_paint: Cell::new(false),
active_touch_points: DomRefCell::new(Vec::new()),
dom_interactive: Cell::new(Default::default()),
dom_content_loaded_event_start: Cell::new(Default::default()),
dom_content_loaded_event_end: Cell::new(Default::default()),
dom_complete: Cell::new(Default::default()),
top_level_dom_complete: Cell::new(Default::default()),
load_event_start: Cell::new(Default::default()),
load_event_end: Cell::new(Default::default()),
unload_event_start: Cell::new(Default::default()),
unload_event_end: Cell::new(Default::default()),
https_state: Cell::new(HttpsState::None),
origin,
referrer,
target_element: MutNullableDom::new(None),
policy_container: DomRefCell::new(PolicyContainer::default()),
last_click_info: DomRefCell::new(None),
ignore_destructive_writes_counter: Default::default(),
ignore_opens_during_unload_counter: Default::default(),
spurious_animation_frames: Cell::new(0),
dom_count: Cell::new(1),
fullscreen_element: MutNullableDom::new(None),
form_id_listener_map: Default::default(),
interactive_time: DomRefCell::new(interactive_time),
tti_window: DomRefCell::new(InteractiveWindow::default()),
canceller,
throw_on_dynamic_markup_insertion_counter: Cell::new(0),
page_showing: Cell::new(false),
salvageable: Cell::new(true),
active_parser_was_aborted: Cell::new(false),
fired_unload: Cell::new(false),
responsive_images: Default::default(),
redirect_count: Cell::new(0),
completely_loaded: Cell::new(false),
script_and_layout_blockers: Cell::new(0),
delayed_tasks: Default::default(),
shadow_roots: DomRefCell::new(HashSet::new()),
shadow_roots_styles_changed: Cell::new(false),
media_controls: DomRefCell::new(HashMap::new()),
dirty_webgl_contexts: DomRefCell::new(HashMapTracedValues::new()),
#[cfg(feature = "webgpu")]
webgpu_contexts: Rc::new(RefCell::new(HashMapTracedValues::new())),
selection: MutNullableDom::new(None),
animation_timeline: if pref!(layout.animations.test.enabled) {
DomRefCell::new(AnimationTimeline::new_for_testing())
} else {
DomRefCell::new(AnimationTimeline::new())
},
animations: DomRefCell::new(Animations::new()),
dirty_root: Default::default(),
declarative_refresh: Default::default(),
pending_animation_ticks: Default::default(),
pending_compositor_events: Default::default(),
mouse_move_event_index: Default::default(),
resize_observers: Default::default(),
fonts: Default::default(),
visibility_state: Cell::new(DocumentVisibilityState::Hidden),
status_code,
}
}
pub fn note_pending_compositor_event(&self, event: CompositorEvent) {
let mut pending_compositor_events = self.pending_compositor_events.borrow_mut();
if matches!(event, CompositorEvent::MouseMoveEvent { .. }) {
if let Some(mouse_move_event) = self
.mouse_move_event_index
.borrow()
.and_then(|index| pending_compositor_events.get_mut(index))
{
*mouse_move_event = event;
return;
}
*self.mouse_move_event_index.borrow_mut() = Some(pending_compositor_events.len());
}
pending_compositor_events.push(event);
}
pub fn take_pending_compositor_events(&self) -> Vec<CompositorEvent> {
*self.mouse_move_event_index.borrow_mut() = None;
mem::take(&mut *self.pending_compositor_events.borrow_mut())
}
pub fn set_csp_list(&self, csp_list: Option<CspList>) {
self.policy_container.borrow_mut().set_csp_list(csp_list);
}
pub fn get_csp_list(&self) -> Option<CspList> {
self.policy_container.borrow().csp_list.clone()
}
pub fn should_elements_inline_type_behavior_be_blocked(
&self,
el: &Element,
type_: csp::InlineCheckType,
source: &str,
) -> csp::CheckResult {
let element = csp::Element {
nonce: el
.get_attribute(&ns!(), &local_name!("nonce"))
.map(|attr| Cow::Owned(attr.value().to_string())),
};
self.get_csp_list()
.map(|c| {
c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
.0
})
.unwrap_or(csp::CheckResult::Allowed)
}
pub fn add_script_and_layout_blocker(&self) {
self.script_and_layout_blockers
.set(self.script_and_layout_blockers.get() + 1);
}
pub fn remove_script_and_layout_blocker(&self) {
assert!(self.script_and_layout_blockers.get() > 0);
self.script_and_layout_blockers
.set(self.script_and_layout_blockers.get() - 1);
while self.script_and_layout_blockers.get() == 0 && !self.delayed_tasks.borrow().is_empty()
{
let task = self.delayed_tasks.borrow_mut().remove(0);
task.run_box();
}
}
pub fn add_delayed_task<T: 'static + TaskBox>(&self, task: T) {
self.delayed_tasks.borrow_mut().push(Box::new(task));
}
pub fn ensure_safe_to_run_script_or_layout(&self) {
assert_eq!(
self.script_and_layout_blockers.get(),
0,
"Attempt to use script or layout while DOM not in a stable state"
);
}
#[allow(clippy::too_many_arguments)]
pub fn new(
window: &Window,
has_browsing_context: HasBrowsingContext,
url: Option<ServoUrl>,
origin: MutableOrigin,
doctype: IsHTMLDocument,
content_type: Option<Mime>,
last_modified: Option<String>,
activity: DocumentActivity,
source: DocumentSource,
doc_loader: DocumentLoader,
referrer: Option<String>,
status_code: Option<u16>,
canceller: FetchCanceller,
can_gc: CanGc,
) -> DomRoot<Document> {
Self::new_with_proto(
window,
None,
has_browsing_context,
url,
origin,
doctype,
content_type,
last_modified,
activity,
source,
doc_loader,
referrer,
status_code,
canceller,
can_gc,
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
has_browsing_context: HasBrowsingContext,
url: Option<ServoUrl>,
origin: MutableOrigin,
doctype: IsHTMLDocument,
content_type: Option<Mime>,
last_modified: Option<String>,
activity: DocumentActivity,
source: DocumentSource,
doc_loader: DocumentLoader,
referrer: Option<String>,
status_code: Option<u16>,
canceller: FetchCanceller,
can_gc: CanGc,
) -> DomRoot<Document> {
let document = reflect_dom_object_with_proto(
Box::new(Document::new_inherited(
window,
has_browsing_context,
url,
origin,
doctype,
content_type,
last_modified,
activity,
source,
doc_loader,
referrer,
status_code,
canceller,
)),
window,
proto,
can_gc,
);
{
let node = document.upcast::<Node>();
node.set_owner_doc(&document);
}
document
}
pub fn get_redirect_count(&self) -> u16 {
self.redirect_count.get()
}
pub fn set_redirect_count(&self, count: u16) {
self.redirect_count.set(count)
}
pub fn elements_by_name_count(&self, name: &DOMString) -> u32 {
if name.is_empty() {
return 0;
}
self.count_node_list(|n| Document::is_element_in_get_by_name(n, name))
}
pub fn nth_element_by_name(&self, index: u32, name: &DOMString) -> Option<DomRoot<Node>> {
if name.is_empty() {
return None;
}
self.nth_in_node_list(index, |n| Document::is_element_in_get_by_name(n, name))
}
fn is_element_in_get_by_name(node: &Node, name: &DOMString) -> bool {
let element = match node.downcast::<Element>() {
Some(element) => element,
None => return false,
};
if element.namespace() != &ns!(html) {
return false;
}
element.get_name().is_some_and(|n| *n == **name)
}
fn count_node_list<F: Fn(&Node) -> bool>(&self, callback: F) -> u32 {
let doc = self.GetDocumentElement();
let maybe_node = doc.as_deref().map(Castable::upcast::<Node>);
maybe_node
.iter()
.flat_map(|node| node.traverse_preorder(ShadowIncluding::No))
.filter(|node| callback(node))
.count() as u32
}
fn nth_in_node_list<F: Fn(&Node) -> bool>(
&self,
index: u32,
callback: F,
) -> Option<DomRoot<Node>> {
let doc = self.GetDocumentElement();
let maybe_node = doc.as_deref().map(Castable::upcast::<Node>);
maybe_node
.iter()
.flat_map(|node| node.traverse_preorder(ShadowIncluding::No))
.filter(|node| callback(node))
.nth(index as usize)
.map(|n| DomRoot::from_ref(&*n))
}
fn get_html_element(&self) -> Option<DomRoot<HTMLHtmlElement>> {
self.GetDocumentElement().and_then(DomRoot::downcast)
}
pub fn style_shared_lock(&self) -> &StyleSharedRwLock {
&self.style_shared_lock
}
pub fn flush_stylesheets_for_reflow(&self) -> bool {
let mut stylesheets = self.stylesheets.borrow_mut();
let have_changed = stylesheets.has_changed();
stylesheets.flush_without_invalidation();
have_changed
}
pub fn salvageable(&self) -> bool {
self.salvageable.get()
}
pub fn appropriate_template_contents_owner_document(&self, can_gc: CanGc) -> DomRoot<Document> {
self.appropriate_template_contents_owner_document
.or_init(|| {
let doctype = if self.is_html_document {
IsHTMLDocument::HTMLDocument
} else {
IsHTMLDocument::NonHTMLDocument
};
let new_doc = Document::new(
self.window(),
HasBrowsingContext::No,
None,
MutableOrigin::new(ImmutableOrigin::new_opaque()),
doctype,
None,
None,
DocumentActivity::Inactive,
DocumentSource::NotFromParser,
DocumentLoader::new(&self.loader()),
None,
None,
Default::default(),
can_gc,
);
new_doc
.appropriate_template_contents_owner_document
.set(Some(&new_doc));
new_doc
})
}
pub fn get_element_by_id(&self, id: &Atom) -> Option<DomRoot<Element>> {
self.id_map
.borrow()
.get(id)
.map(|elements| DomRoot::from_ref(&*elements[0]))
}
pub fn ensure_pending_restyle(&self, el: &Element) -> RefMut<PendingRestyle> {
let map = self.pending_restyles.borrow_mut();
RefMut::map(map, |m| {
&mut m
.entry(Dom::from_ref(el))
.or_insert_with(|| NoTrace(PendingRestyle::default()))
.0
})
}
pub fn element_state_will_change(&self, el: &Element) {
let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() {
entry.snapshot = Some(Snapshot::new());
}
let snapshot = entry.snapshot.as_mut().unwrap();
if snapshot.state.is_none() {
snapshot.state = Some(el.state());
}
}
pub fn element_attr_will_change(&self, el: &Element, attr: &Attr) {
let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() {
entry.snapshot = Some(Snapshot::new());
}
if attr.local_name() == &local_name!("style") {
entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
}
if vtable_for(el.upcast()).attribute_affects_presentational_hints(attr) {
entry.hint.insert(RestyleHint::RESTYLE_SELF);
}
let snapshot = entry.snapshot.as_mut().unwrap();
if attr.local_name() == &local_name!("id") {
if snapshot.id_changed {
return;
}
snapshot.id_changed = true;
} else if attr.local_name() == &local_name!("class") {
if snapshot.class_changed {
return;
}
snapshot.class_changed = true;
} else {
snapshot.other_attributes_changed = true;
}
let local_name = style::LocalName::cast(attr.local_name());
if !snapshot.changed_attrs.contains(local_name) {
snapshot.changed_attrs.push(local_name.clone());
}
if snapshot.attrs.is_none() {
let attrs = el
.attrs()
.iter()
.map(|attr| (attr.identifier().clone(), attr.value().clone()))
.collect();
snapshot.attrs = Some(attrs);
}
}
pub fn set_referrer_policy(&self, policy: ReferrerPolicy) {
self.policy_container
.borrow_mut()
.set_referrer_policy(policy);
}
pub fn get_referrer_policy(&self) -> ReferrerPolicy {
self.policy_container.borrow().get_referrer_policy()
}
pub fn set_target_element(&self, node: Option<&Element>) {
if let Some(ref element) = self.target_element.get() {
element.set_target_state(false);
}
self.target_element.set(node);
if let Some(ref element) = self.target_element.get() {
element.set_target_state(true);
}
}
pub fn incr_ignore_destructive_writes_counter(&self) {
self.ignore_destructive_writes_counter
.set(self.ignore_destructive_writes_counter.get() + 1);
}
pub fn decr_ignore_destructive_writes_counter(&self) {
self.ignore_destructive_writes_counter
.set(self.ignore_destructive_writes_counter.get() - 1);
}
pub fn is_prompting_or_unloading(&self) -> bool {
self.ignore_opens_during_unload_counter.get() > 0
}
fn incr_ignore_opens_during_unload_counter(&self) {
self.ignore_opens_during_unload_counter
.set(self.ignore_opens_during_unload_counter.get() + 1);
}
fn decr_ignore_opens_during_unload_counter(&self) {
self.ignore_opens_during_unload_counter
.set(self.ignore_opens_during_unload_counter.get() - 1);
}
fn is_faking_animation_frames(&self) -> bool {
self.spurious_animation_frames.get() >= SPURIOUS_ANIMATION_FRAME_THRESHOLD
}
pub fn enter_fullscreen(&self, pending: &Element, can_gc: CanGc) -> Rc<Promise> {
let in_realm_proof = AlreadyInRealm::assert();
let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
let mut error = false;
match *pending.namespace() {
ns!(mathml) => {
if pending.local_name().as_ref() != "math" {
error = true;
}
},
ns!(svg) => {
if pending.local_name().as_ref() != "svg" {
error = true;
}
},
ns!(html) => (),
_ => error = true,
}
if !pending.fullscreen_element_ready_check() {
error = true;
}
if pref!(dom.fullscreen.test) {
info!("Tests don't really enter fullscreen.");
} else {
warn!("Fullscreen not supported yet");
}
let window = self.window();
if !error {
let event = EmbedderMsg::SetFullscreenState(true);
self.send_to_embedder(event);
}
let pipeline_id = self.window().pipeline_id();
let trusted_pending = Trusted::new(pending);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenEnter::new(trusted_pending, trusted_promise, error);
let script_msg = CommonScriptMsg::Task(
ScriptThreadEventCategory::EnterFullscreen,
handler,
Some(pipeline_id),
TaskSourceName::DOMManipulation,
);
let msg = MainThreadScriptMsg::Common(script_msg);
window.main_thread_script_chan().send(msg).unwrap();
promise
}
pub fn exit_fullscreen(&self, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let in_realm_proof = AlreadyInRealm::assert();
let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
if self.fullscreen_element.get().is_none() {
promise.reject_error(Error::Type(String::from("fullscreen is null")));
return promise;
}
let element = self.fullscreen_element.get().unwrap();
let window = self.window();
let event = EmbedderMsg::SetFullscreenState(false);
self.send_to_embedder(event);
let trusted_element = Trusted::new(&*element);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
let pipeline_id = Some(global.pipeline_id());
let script_msg = CommonScriptMsg::Task(
ScriptThreadEventCategory::ExitFullscreen,
handler,
pipeline_id,
TaskSourceName::DOMManipulation,
);
let msg = MainThreadScriptMsg::Common(script_msg);
window.main_thread_script_chan().send(msg).unwrap();
promise
}
pub fn set_fullscreen_element(&self, element: Option<&Element>) {
self.fullscreen_element.set(element);
}
pub fn get_allow_fullscreen(&self) -> bool {
match self.browsing_context() {
None => false,
Some(_) => {
let window = self.window();
if window.is_top_level() {
true
} else {
window
.GetFrameElement()
.is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
}
},
}
}
fn reset_form_owner_for_listeners(&self, id: &Atom) {
let map = self.form_id_listener_map.borrow();
if let Some(listeners) = map.get(id) {
for listener in listeners {
listener
.as_maybe_form_control()
.expect("Element must be a form control")
.reset_form_owner();
}
}
}
pub fn register_shadow_root(&self, shadow_root: &ShadowRoot) {
self.shadow_roots
.borrow_mut()
.insert(Dom::from_ref(shadow_root));
self.invalidate_shadow_roots_stylesheets();
}
pub fn unregister_shadow_root(&self, shadow_root: &ShadowRoot) {
let mut shadow_roots = self.shadow_roots.borrow_mut();
shadow_roots.remove(&Dom::from_ref(shadow_root));
}
pub fn invalidate_shadow_roots_stylesheets(&self) {
self.shadow_roots_styles_changed.set(true);
}
pub fn shadow_roots_styles_changed(&self) -> bool {
self.shadow_roots_styles_changed.get()
}
pub fn flush_shadow_roots_stylesheets(&self) {
if !self.shadow_roots_styles_changed.get() {
return;
}
self.shadow_roots_styles_changed.set(false);
}
pub fn stylesheet_count(&self) -> usize {
self.stylesheets.borrow().len()
}
pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> {
let stylesheets = self.stylesheets.borrow();
stylesheets
.get(Origin::Author, index)
.and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet())
}
#[allow(crown::unrooted_must_root)] pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) {
let stylesheets = &mut *self.stylesheets.borrow_mut();
let insertion_point = stylesheets
.iter()
.map(|(sheet, _origin)| sheet)
.find(|sheet_in_doc| {
owner
.upcast::<Node>()
.is_before(sheet_in_doc.owner.upcast())
})
.cloned();
let cloned_stylesheet = sheet.clone();
let insertion_point2 = insertion_point.clone();
self.window.layout_mut().add_stylesheet(
cloned_stylesheet,
insertion_point2.as_ref().map(|s| s.sheet.clone()),
);
DocumentOrShadowRoot::add_stylesheet(
owner,
StylesheetSetRef::Document(stylesheets),
sheet,
insertion_point,
self.style_shared_lock(),
);
}
pub fn load_web_fonts_from_stylesheet(&self, stylesheet: Arc<Stylesheet>) {
self.window
.layout()
.load_web_fonts_from_stylesheet(stylesheet);
}
#[allow(crown::unrooted_must_root)] pub fn remove_stylesheet(&self, owner: &Element, stylesheet: &Arc<Stylesheet>) {
let cloned_stylesheet = stylesheet.clone();
self.window
.layout_mut()
.remove_stylesheet(cloned_stylesheet);
DocumentOrShadowRoot::remove_stylesheet(
owner,
stylesheet,
StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()),
)
}
pub fn get_elements_with_id(&self, id: &Atom) -> Ref<[Dom<Element>]> {
Ref::map(self.id_map.borrow(), |map| {
map.get(id).map(|vec| &**vec).unwrap_or_default()
})
}
pub fn get_elements_with_name(&self, name: &Atom) -> Ref<[Dom<Element>]> {
Ref::map(self.name_map.borrow(), |map| {
map.get(name).map(|vec| &**vec).unwrap_or_default()
})
}
#[allow(crown::unrooted_must_root)]
pub fn drain_pending_restyles(&self) -> Vec<(TrustedNodeAddress, PendingRestyle)> {
self.pending_restyles
.borrow_mut()
.drain()
.filter_map(|(elem, restyle)| {
let node = elem.upcast::<Node>();
if !node.get_flag(NodeFlags::IS_CONNECTED) {
return None;
}
node.note_dirty_descendants();
Some((node.to_trusted_node_address(), restyle.0))
})
.collect()
}
pub(crate) fn note_pending_animation_tick(&self, tick_type: AnimationTickType) {
self.pending_animation_ticks.borrow_mut().extend(tick_type);
}
pub(crate) fn has_received_raf_tick(&self) -> bool {
self.pending_animation_ticks
.borrow()
.contains(AnimationTickType::REQUEST_ANIMATION_FRAME)
}
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
self.animation_timeline.borrow_mut().advance_specific(delta);
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
.update_for_new_timeline_value(&self.window, current_timeline_value);
}
pub(crate) fn maybe_mark_animating_nodes_as_dirty(&self) {
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
.mark_animating_nodes_as_dirty(current_timeline_value);
}
pub(crate) fn current_animation_timeline_value(&self) -> f64 {
self.animation_timeline.borrow().current_value()
}
pub(crate) fn animations(&self) -> Ref<Animations> {
self.animations.borrow()
}
pub(crate) fn update_animations_post_reflow(&self) {
self.animations
.borrow()
.do_post_reflow_update(&self.window, self.current_animation_timeline_value());
}
pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
self.animations.borrow().cancel_animations_for_node(node);
}
pub(crate) fn update_animations_and_send_events(&self, can_gc: CanGc) {
if !pref!(layout.animations.test.enabled) {
self.animation_timeline.borrow_mut().update();
}
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
.update_for_new_timeline_value(&self.window, current_timeline_value);
self.maybe_mark_animating_nodes_as_dirty();
self.window()
.upcast::<GlobalScope>()
.perform_a_microtask_checkpoint(can_gc);
let _realm = enter_realm(self);
self.animations().send_pending_events(self.window(), can_gc);
}
pub(crate) fn will_declaratively_refresh(&self) -> bool {
self.declarative_refresh.borrow().is_some()
}
pub(crate) fn set_declarative_refresh(&self, refresh: DeclarativeRefresh) {
*self.declarative_refresh.borrow_mut() = Some(refresh);
}
fn update_visibility_state(&self, visibility_state: DocumentVisibilityState, can_gc: CanGc) {
if self.visibility_state.get() == visibility_state {
return;
}
self.visibility_state.set(visibility_state);
let entry =
VisibilityStateEntry::new(&self.global(), visibility_state, CrossProcessInstant::now());
self.window
.Performance()
.queue_entry(entry.upcast::<PerformanceEntry>(), can_gc);
if visibility_state == DocumentVisibilityState::Hidden {
self.window
.Navigator()
.GetGamepads()
.iter_mut()
.for_each(|gamepad| {
if let Some(g) = gamepad {
g.vibration_actuator().handle_visibility_change();
}
});
}
self.upcast::<EventTarget>()
.fire_bubbling_event(atom!("visibilitychange"), can_gc);
}
}
impl ProfilerMetadataFactory for Document {
fn new_metadata(&self) -> Option<TimerMetadata> {
Some(TimerMetadata {
url: String::from(self.url().as_str()),
iframe: TimerMetadataFrameType::RootWindow,
incremental: TimerMetadataReflowType::Incremental,
})
}
}
#[allow(non_snake_case)]
impl DocumentMethods<crate::DomTypeHolder> for Document {
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<Document>> {
let doc = window.Document();
let docloader = DocumentLoader::new(&doc.loader());
Ok(Document::new_with_proto(
window,
proto,
HasBrowsingContext::No,
None,
doc.origin().clone(),
IsHTMLDocument::NonHTMLDocument,
None,
None,
DocumentActivity::Inactive,
DocumentSource::NotFromParser,
docloader,
None,
None,
Default::default(),
can_gc,
))
}
fn QueryCommandSupported(&self, _command: DOMString) -> bool {
false
}
fn StyleSheets(&self) -> DomRoot<StyleSheetList> {
self.stylesheet_list.or_init(|| {
StyleSheetList::new(
&self.window,
StyleSheetListOwner::Document(Dom::from_ref(self)),
)
})
}
fn Implementation(&self) -> DomRoot<DOMImplementation> {
self.implementation.or_init(|| DOMImplementation::new(self))
}
fn URL(&self) -> USVString {
USVString(String::from(self.url().as_str()))
}
fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
self.document_or_shadow_root.get_active_element(
self.get_focused_element(),
self.GetBody(),
self.GetDocumentElement(),
)
}
fn HasFocus(&self) -> bool {
if self.window().parent_info().is_none() && self.is_fully_active() {
return true;
}
false
}
fn Domain(&self) -> DOMString {
if !self.has_browsing_context {
return DOMString::new();
}
match self.origin.effective_domain() {
None => DOMString::new(),
Some(Host::Domain(domain)) => DOMString::from(domain),
Some(host) => DOMString::from(host.to_string()),
}
}
fn SetDomain(&self, value: DOMString) -> ErrorResult {
if !self.has_browsing_context {
return Err(Error::Security);
}
let effective_domain = match self.origin.effective_domain() {
Some(effective_domain) => effective_domain,
None => return Err(Error::Security),
};
let host = match get_registrable_domain_suffix_of_or_is_equal_to(&value, effective_domain) {
None => return Err(Error::Security),
Some(host) => host,
};
self.origin.set_domain(host);
Ok(())
}
fn Referrer(&self) -> DOMString {
match self.referrer {
Some(ref referrer) => DOMString::from(referrer.to_string()),
None => DOMString::new(),
}
}
fn DocumentURI(&self) -> USVString {
self.URL()
}
fn CompatMode(&self) -> DOMString {
DOMString::from(match self.quirks_mode.get() {
QuirksMode::LimitedQuirks | QuirksMode::NoQuirks => "CSS1Compat",
QuirksMode::Quirks => "BackCompat",
})
}
fn CharacterSet(&self) -> DOMString {
DOMString::from(self.encoding.get().name())
}
fn Charset(&self) -> DOMString {
self.CharacterSet()
}
fn InputEncoding(&self) -> DOMString {
self.CharacterSet()
}
fn ContentType(&self) -> DOMString {
DOMString::from(self.content_type.to_string())
}
fn GetDoctype(&self) -> Option<DomRoot<DocumentType>> {
self.upcast::<Node>()
.children()
.filter_map(DomRoot::downcast)
.next()
}
fn GetDocumentElement(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>().child_elements().next()
}
fn GetElementsByTagName(&self, qualified_name: DOMString) -> DomRoot<HTMLCollection> {
let qualified_name = LocalName::from(&*qualified_name);
if let Some(entry) = self.tag_map.borrow_mut().get(&qualified_name) {
return DomRoot::from_ref(entry);
}
let result =
HTMLCollection::by_qualified_name(&self.window, self.upcast(), qualified_name.clone());
self.tag_map
.borrow_mut()
.insert(qualified_name, Dom::from_ref(&*result));
result
}
fn GetElementsByTagNameNS(
&self,
maybe_ns: Option<DOMString>,
tag_name: DOMString,
) -> DomRoot<HTMLCollection> {
let ns = namespace_from_domstring(maybe_ns);
let local = LocalName::from(tag_name);
let qname = QualName::new(None, ns, local);
if let Some(collection) = self.tagns_map.borrow().get(&qname) {
return DomRoot::from_ref(collection);
}
let result = HTMLCollection::by_qual_tag_name(&self.window, self.upcast(), qname.clone());
self.tagns_map
.borrow_mut()
.insert(qname, Dom::from_ref(&*result));
result
}
fn GetElementsByClassName(&self, classes: DOMString) -> DomRoot<HTMLCollection> {
let class_atoms: Vec<Atom> = split_html_space_chars(&classes).map(Atom::from).collect();
if let Some(collection) = self.classes_map.borrow().get(&class_atoms) {
return DomRoot::from_ref(collection);
}
let result =
HTMLCollection::by_atomic_class_name(&self.window, self.upcast(), class_atoms.clone());
self.classes_map
.borrow_mut()
.insert(class_atoms, Dom::from_ref(&*result));
result
}
fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> {
self.get_element_by_id(&Atom::from(id))
}
fn CreateElement(
&self,
mut local_name: DOMString,
options: StringOrElementCreationOptions,
can_gc: CanGc,
) -> Fallible<DomRoot<Element>> {
if xml_name_type(&local_name) == Invalid {
debug!("Not a valid element name");
return Err(Error::InvalidCharacter);
}
if self.is_html_document {
local_name.make_ascii_lowercase();
}
let ns = if self.is_html_document || self.is_xhtml_document() {
ns!(html)
} else {
ns!()
};
let name = QualName::new(None, ns, LocalName::from(local_name));
let is = match options {
StringOrElementCreationOptions::String(_) => None,
StringOrElementCreationOptions::ElementCreationOptions(options) => {
options.is.as_ref().map(|is| LocalName::from(&**is))
},
};
Ok(Element::create(
name,
is,
self,
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
None,
can_gc,
))
}
fn CreateElementNS(
&self,
namespace: Option<DOMString>,
qualified_name: DOMString,
options: StringOrElementCreationOptions,
can_gc: CanGc,
) -> Fallible<DomRoot<Element>> {
let (namespace, prefix, local_name) = validate_and_extract(namespace, &qualified_name)?;
let name = QualName::new(prefix, namespace, local_name);
let is = match options {
StringOrElementCreationOptions::String(_) => None,
StringOrElementCreationOptions::ElementCreationOptions(options) => {
options.is.as_ref().map(|is| LocalName::from(&**is))
},
};
Ok(Element::create(
name,
is,
self,
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
None,
can_gc,
))
}
fn CreateAttribute(&self, mut local_name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<Attr>> {
if xml_name_type(&local_name) == Invalid {
debug!("Not a valid element name");
return Err(Error::InvalidCharacter);
}
if self.is_html_document {
local_name.make_ascii_lowercase();
}
let name = LocalName::from(local_name);
let value = AttrValue::String("".to_owned());
Ok(Attr::new(
self,
name.clone(),
value,
name,
ns!(),
None,
None,
can_gc,
))
}
fn CreateAttributeNS(
&self,
namespace: Option<DOMString>,
qualified_name: DOMString,
can_gc: CanGc,
) -> Fallible<DomRoot<Attr>> {
let (namespace, prefix, local_name) = validate_and_extract(namespace, &qualified_name)?;
let value = AttrValue::String("".to_owned());
let qualified_name = LocalName::from(qualified_name);
Ok(Attr::new(
self,
local_name,
value,
qualified_name,
namespace,
prefix,
None,
can_gc,
))
}
fn CreateDocumentFragment(&self, can_gc: CanGc) -> DomRoot<DocumentFragment> {
DocumentFragment::new(self, can_gc)
}
fn CreateTextNode(&self, data: DOMString, can_gc: CanGc) -> DomRoot<Text> {
Text::new(data, self, can_gc)
}
fn CreateCDATASection(
&self,
data: DOMString,
can_gc: CanGc,
) -> Fallible<DomRoot<CDATASection>> {
if self.is_html_document {
return Err(Error::NotSupported);
}
if data.contains("]]>") {
return Err(Error::InvalidCharacter);
}
Ok(CDATASection::new(data, self, can_gc))
}
fn CreateComment(&self, data: DOMString, can_gc: CanGc) -> DomRoot<Comment> {
Comment::new(data, self, None, can_gc)
}
fn CreateProcessingInstruction(
&self,
target: DOMString,
data: DOMString,
can_gc: CanGc,
) -> Fallible<DomRoot<ProcessingInstruction>> {
if xml_name_type(&target) == Invalid {
return Err(Error::InvalidCharacter);
}
if data.contains("?>") {
return Err(Error::InvalidCharacter);
}
Ok(ProcessingInstruction::new(target, data, self, can_gc))
}
fn ImportNode(&self, node: &Node, deep: bool, can_gc: CanGc) -> Fallible<DomRoot<Node>> {
if node.is::<Document>() || node.is::<ShadowRoot>() {
return Err(Error::NotSupported);
}
let clone_children = if deep {
CloneChildrenFlag::CloneChildren
} else {
CloneChildrenFlag::DoNotCloneChildren
};
Ok(Node::clone(node, Some(self), clone_children, can_gc))
}
fn AdoptNode(&self, node: &Node) -> Fallible<DomRoot<Node>> {
if node.is::<Document>() {
return Err(Error::NotSupported);
}
if node.is::<ShadowRoot>() {
return Err(Error::HierarchyRequest);
}
Node::adopt(node, self);
Ok(DomRoot::from_ref(node))
}
fn CreateEvent(&self, mut interface: DOMString, can_gc: CanGc) -> Fallible<DomRoot<Event>> {
interface.make_ascii_lowercase();
match &*interface {
"beforeunloadevent" => Ok(DomRoot::upcast(BeforeUnloadEvent::new_uninitialized(
&self.window,
))),
"compositionevent" | "textevent" => Ok(DomRoot::upcast(
CompositionEvent::new_uninitialized(&self.window),
)),
"customevent" => Ok(DomRoot::upcast(CustomEvent::new_uninitialized(
self.window.upcast(),
can_gc,
))),
"events" | "event" | "htmlevents" | "svgevents" => {
Ok(Event::new_uninitialized(self.window.upcast(), can_gc))
},
"focusevent" => Ok(DomRoot::upcast(FocusEvent::new_uninitialized(
&self.window,
can_gc,
))),
"hashchangeevent" => Ok(DomRoot::upcast(HashChangeEvent::new_uninitialized(
&self.window,
can_gc,
))),
"keyboardevent" => Ok(DomRoot::upcast(KeyboardEvent::new_uninitialized(
&self.window,
can_gc,
))),
"messageevent" => Ok(DomRoot::upcast(MessageEvent::new_uninitialized(
self.window.upcast(),
can_gc,
))),
"mouseevent" | "mouseevents" => Ok(DomRoot::upcast(MouseEvent::new_uninitialized(
&self.window,
can_gc,
))),
"storageevent" => Ok(DomRoot::upcast(StorageEvent::new_uninitialized(
&self.window,
"".into(),
can_gc,
))),
"touchevent" => Ok(DomRoot::upcast(TouchEvent::new_uninitialized(
&self.window,
&TouchList::new(&self.window, &[]),
&TouchList::new(&self.window, &[]),
&TouchList::new(&self.window, &[]),
))),
"uievent" | "uievents" => Ok(DomRoot::upcast(UIEvent::new_uninitialized(
&self.window,
can_gc,
))),
_ => Err(Error::NotSupported),
}
}
fn LastModified(&self) -> DOMString {
DOMString::from(self.last_modified.as_ref().cloned().unwrap_or_else(|| {
Local::now().format("%m/%d/%Y %H:%M:%S").to_string()
}))
}
fn CreateRange(&self, can_gc: CanGc) -> DomRoot<Range> {
Range::new_with_doc(self, None, can_gc)
}
fn CreateNodeIterator(
&self,
root: &Node,
what_to_show: u32,
filter: Option<Rc<NodeFilter>>,
) -> DomRoot<NodeIterator> {
NodeIterator::new(self, root, what_to_show, filter)
}
fn CreateTreeWalker(
&self,
root: &Node,
what_to_show: u32,
filter: Option<Rc<NodeFilter>>,
) -> DomRoot<TreeWalker> {
TreeWalker::new(self, root, what_to_show, filter)
}
fn Title(&self) -> DOMString {
self.title().unwrap_or_else(|| DOMString::from(""))
}
fn SetTitle(&self, title: DOMString, can_gc: CanGc) {
let root = match self.GetDocumentElement() {
Some(root) => root,
None => return,
};
let elem = if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") {
let elem = root.upcast::<Node>().child_elements().find(|node| {
node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
});
match elem {
Some(elem) => DomRoot::upcast::<Node>(elem),
None => {
let name = QualName::new(None, ns!(svg), local_name!("title"));
let elem = Element::create(
name,
None,
self,
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
None,
can_gc,
);
let parent = root.upcast::<Node>();
let child = elem.upcast::<Node>();
parent
.InsertBefore(child, parent.GetFirstChild().as_deref())
.unwrap()
},
}
} else if root.namespace() == &ns!(html) {
let elem = root
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.find(|node| node.is::<HTMLTitleElement>());
match elem {
Some(elem) => elem,
None => match self.GetHead() {
Some(head) => {
let name = QualName::new(None, ns!(html), local_name!("title"));
let elem = Element::create(
name,
None,
self,
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
None,
can_gc,
);
head.upcast::<Node>().AppendChild(elem.upcast()).unwrap()
},
None => return,
},
}
} else {
return;
};
elem.SetTextContent(Some(title), can_gc);
}
fn GetHead(&self) -> Option<DomRoot<HTMLHeadElement>> {
self.get_html_element().and_then(|root| {
root.upcast::<Node>()
.children()
.filter_map(DomRoot::downcast)
.next()
})
}
fn GetCurrentScript(&self) -> Option<DomRoot<HTMLScriptElement>> {
self.current_script.get()
}
fn GetBody(&self) -> Option<DomRoot<HTMLElement>> {
self.get_html_element().and_then(|root| {
let node = root.upcast::<Node>();
node.children()
.find(|child| {
matches!(
child.type_id(),
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLBodyElement,
)) | NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLFrameSetElement,
))
)
})
.map(|node| DomRoot::downcast(node).unwrap())
})
}
fn SetBody(&self, new_body: Option<&HTMLElement>) -> ErrorResult {
let new_body = match new_body {
Some(new_body) => new_body,
None => return Err(Error::HierarchyRequest),
};
let node = new_body.upcast::<Node>();
match node.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) |
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLFrameSetElement,
)) => {},
_ => return Err(Error::HierarchyRequest),
}
let old_body = self.GetBody();
if old_body.as_deref() == Some(new_body) {
return Ok(());
}
match (self.GetDocumentElement(), &old_body) {
(Some(ref root), Some(child)) => {
let root = root.upcast::<Node>();
root.ReplaceChild(new_body.upcast(), child.upcast())
.unwrap();
},
(None, _) => return Err(Error::HierarchyRequest),
(Some(ref root), &None) => {
let root = root.upcast::<Node>();
root.AppendChild(new_body.upcast()).unwrap();
},
}
Ok(())
}
fn GetElementsByName(&self, name: DOMString) -> DomRoot<NodeList> {
NodeList::new_elements_by_name_list(self.window(), self, name)
}
fn Images(&self) -> DomRoot<HTMLCollection> {
self.images.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
element.is::<HTMLImageElement>()
})
})
}
fn Embeds(&self) -> DomRoot<HTMLCollection> {
self.embeds.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
element.is::<HTMLEmbedElement>()
})
})
}
fn Plugins(&self) -> DomRoot<HTMLCollection> {
self.Embeds()
}
fn Links(&self) -> DomRoot<HTMLCollection> {
self.links.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
(element.is::<HTMLAnchorElement>() || element.is::<HTMLAreaElement>()) &&
element.has_attribute(&local_name!("href"))
})
})
}
fn Forms(&self) -> DomRoot<HTMLCollection> {
self.forms.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
element.is::<HTMLFormElement>()
})
})
}
fn Scripts(&self) -> DomRoot<HTMLCollection> {
self.scripts.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
element.is::<HTMLScriptElement>()
})
})
}
fn Anchors(&self) -> DomRoot<HTMLCollection> {
self.anchors.or_init(|| {
HTMLCollection::new_with_filter_fn(&self.window, self.upcast(), |element, _| {
element.is::<HTMLAnchorElement>() && element.has_attribute(&local_name!("href"))
})
})
}
fn Applets(&self) -> DomRoot<HTMLCollection> {
self.applets
.or_init(|| HTMLCollection::always_empty(&self.window, self.upcast()))
}
fn GetLocation(&self) -> Option<DomRoot<Location>> {
if self.is_fully_active() {
Some(self.window.Location())
} else {
None
}
}
fn Children(&self) -> DomRoot<HTMLCollection> {
HTMLCollection::children(&self.window, self.upcast())
}
fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>().child_elements().next()
}
fn GetLastElementChild(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>()
.rev_children()
.filter_map(DomRoot::downcast)
.next()
}
fn ChildElementCount(&self) -> u32 {
self.upcast::<Node>().child_elements().count() as u32
}
fn Prepend(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().prepend(nodes, can_gc)
}
fn Append(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().append(nodes, can_gc)
}
fn ReplaceChildren(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().replace_children(nodes, can_gc)
}
fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> {
let root = self.upcast::<Node>();
root.query_selector(selectors)
}
fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> {
let root = self.upcast::<Node>();
root.query_selector_all(selectors)
}
fn ReadyState(&self) -> DocumentReadyState {
self.ready_state.get()
}
fn GetDefaultView(&self) -> Option<DomRoot<Window>> {
if self.has_browsing_context {
Some(DomRoot::from_ref(&*self.window))
} else {
None
}
}
fn GetCookie(&self) -> Fallible<DOMString> {
if self.is_cookie_averse() {
return Ok(DOMString::new());
}
if !self.origin.is_tuple() {
return Err(Error::Security);
}
let url = self.url();
let (tx, rx) = profile_ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
let _ = self
.window
.upcast::<GlobalScope>()
.resource_threads()
.send(GetCookiesForUrl(url, tx, NonHTTP));
let cookies = rx.recv().unwrap();
Ok(cookies.map_or(DOMString::new(), DOMString::from))
}
fn SetCookie(&self, cookie: DOMString) -> ErrorResult {
if self.is_cookie_averse() {
return Ok(());
}
if !self.origin.is_tuple() {
return Err(Error::Security);
}
let cookies = if let Some(cookie) = Cookie::parse(cookie.to_string()).ok().map(Serde) {
vec![cookie]
} else {
vec![]
};
let _ = self
.window
.upcast::<GlobalScope>()
.resource_threads()
.send(SetCookiesForUrl(self.url(), cookies, NonHTTP));
Ok(())
}
fn BgColor(&self) -> DOMString {
self.get_body_attribute(&local_name!("bgcolor"))
}
fn SetBgColor(&self, value: DOMString, can_gc: CanGc) {
self.set_body_attribute(&local_name!("bgcolor"), value, can_gc)
}
fn FgColor(&self) -> DOMString {
self.get_body_attribute(&local_name!("text"))
}
fn SetFgColor(&self, value: DOMString, can_gc: CanGc) {
self.set_body_attribute(&local_name!("text"), value, can_gc)
}
#[allow(unsafe_code)]
fn NamedGetter(&self, name: DOMString) -> Option<NamedPropertyValue> {
if name.is_empty() {
return None;
}
let name = Atom::from(name);
let elements_with_name = self.get_elements_with_name(&name);
let name_iter = elements_with_name
.iter()
.filter(|elem| is_named_element_with_name_attribute(elem));
let elements_with_id = self.get_elements_with_id(&name);
let id_iter = elements_with_id
.iter()
.filter(|elem| is_named_element_with_id_attribute(elem));
let mut elements = name_iter.chain(id_iter);
let first = elements.next()?;
if elements.next().is_none() {
if let Some(nested_window_proxy) = first
.downcast::<HTMLIFrameElement>()
.and_then(|iframe| iframe.GetContentWindow())
{
return Some(NamedPropertyValue::WindowProxy(nested_window_proxy));
}
return Some(NamedPropertyValue::Element(DomRoot::from_ref(first)));
}
#[derive(JSTraceable, MallocSizeOf)]
struct DocumentNamedGetter {
#[no_trace]
name: Atom,
}
impl CollectionFilter for DocumentNamedGetter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
let type_ = match elem.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
_ => return false,
};
match type_ {
HTMLElementTypeId::HTMLFormElement | HTMLElementTypeId::HTMLIFrameElement => {
elem.get_name().as_ref() == Some(&self.name)
},
HTMLElementTypeId::HTMLImageElement => elem.get_name().is_some_and(|name| {
name == *self.name ||
!name.is_empty() && elem.get_id().as_ref() == Some(&self.name)
}),
_ => false,
}
}
}
let collection = HTMLCollection::create(
self.window(),
self.upcast(),
Box::new(DocumentNamedGetter { name }),
);
Some(NamedPropertyValue::HTMLCollection(collection))
}
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
let mut names_with_first_named_element_map: HashMap<&Atom, &Element> = HashMap::new();
let name_map = self.name_map.borrow();
for (name, elements) in &(name_map).0 {
if name.is_empty() {
continue;
}
let mut name_iter = elements
.iter()
.filter(|elem| is_named_element_with_name_attribute(elem));
if let Some(first) = name_iter.next() {
names_with_first_named_element_map.insert(name, first);
}
}
let id_map = self.id_map.borrow();
for (id, elements) in &(id_map).0 {
if id.is_empty() {
continue;
}
let mut id_iter = elements
.iter()
.filter(|elem| is_named_element_with_id_attribute(elem));
if let Some(first) = id_iter.next() {
match names_with_first_named_element_map.entry(id) {
Vacant(entry) => drop(entry.insert(first)),
Occupied(mut entry) => {
if first.upcast::<Node>().is_before(entry.get().upcast()) {
*entry.get_mut() = first;
}
},
}
}
}
let mut names_with_first_named_element_vec: Vec<(&Atom, &Element)> =
names_with_first_named_element_map
.iter()
.map(|(k, v)| (*k, *v))
.collect();
names_with_first_named_element_vec.sort_unstable_by(|a, b| {
if a.1 == b.1 {
a.0.cmp(b.0)
} else if a.1.upcast::<Node>().is_before(b.1.upcast::<Node>()) {
Ordering::Less
} else {
Ordering::Greater
}
});
names_with_first_named_element_vec
.iter()
.map(|(k, _v)| DOMString::from(&***k))
.collect()
}
fn Clear(&self) {
}
fn CaptureEvents(&self) {
}
fn ReleaseEvents(&self) {
}
global_event_handlers!();
event_handler!(
readystatechange,
GetOnreadystatechange,
SetOnreadystatechange
);
fn ElementFromPoint(
&self,
x: Finite<f64>,
y: Finite<f64>,
can_gc: CanGc,
) -> Option<DomRoot<Element>> {
self.document_or_shadow_root.element_from_point(
x,
y,
self.GetDocumentElement(),
self.has_browsing_context,
can_gc,
)
}
fn ElementsFromPoint(
&self,
x: Finite<f64>,
y: Finite<f64>,
can_gc: CanGc,
) -> Vec<DomRoot<Element>> {
self.document_or_shadow_root.elements_from_point(
x,
y,
self.GetDocumentElement(),
self.has_browsing_context,
can_gc,
)
}
fn Open(
&self,
_unused1: Option<DOMString>,
_unused2: Option<DOMString>,
can_gc: CanGc,
) -> Fallible<DomRoot<Document>> {
if !self.is_html_document() {
return Err(Error::InvalidState);
}
if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
return Err(Error::InvalidState);
}
let entry_responsible_document = GlobalScope::entry().as_window().Document();
if !self.origin.same_origin(&entry_responsible_document.origin) {
return Err(Error::Security);
}
if self
.get_current_parser()
.is_some_and(|parser| parser.is_active())
{
return Ok(DomRoot::from_ref(self));
}
if self.is_prompting_or_unloading() {
return Ok(DomRoot::from_ref(self));
}
if self.active_parser_was_aborted.get() {
return Ok(DomRoot::from_ref(self));
}
window_from_node(self).set_navigation_start();
if self.has_browsing_context() {
self.abort(can_gc);
}
for node in self
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
{
node.upcast::<EventTarget>().remove_all_listeners();
}
if self.window.Document() == DomRoot::from_ref(self) {
self.window.upcast::<EventTarget>().remove_all_listeners();
}
Node::replace_all(None, self.upcast::<Node>());
if self.is_fully_active() {
let mut new_url = entry_responsible_document.url();
if entry_responsible_document != DomRoot::from_ref(self) {
new_url.set_fragment(None);
}
self.set_url(new_url);
}
self.set_quirks_mode(QuirksMode::NoQuirks);
let resource_threads = self
.window
.upcast::<GlobalScope>()
.resource_threads()
.clone();
*self.loader.borrow_mut() =
DocumentLoader::new_with_threads(resource_threads, Some(self.url()));
ServoParser::parse_html_script_input(self, self.url());
self.ready_state.set(DocumentReadyState::Loading);
Ok(DomRoot::from_ref(self))
}
fn Open_(
&self,
url: USVString,
target: DOMString,
features: DOMString,
can_gc: CanGc,
) -> Fallible<Option<DomRoot<WindowProxy>>> {
self.browsing_context()
.ok_or(Error::InvalidAccess)?
.open(url, target, features, can_gc)
}
fn Write(&self, text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
if !self.is_html_document() {
return Err(Error::InvalidState);
}
if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
return Err(Error::InvalidState);
}
if !self.is_active() || self.active_parser_was_aborted.get() {
return Ok(());
}
let parser = match self.get_current_parser() {
Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser),
_ => {
if self.is_prompting_or_unloading() ||
self.ignore_destructive_writes_counter.get() > 0
{
return Ok(());
}
self.Open(None, None, can_gc)?;
self.get_current_parser().unwrap()
},
};
parser.write(text, can_gc);
Ok(())
}
fn Writeln(&self, mut text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
text.push("\n".into());
self.Write(text, can_gc)
}
fn Close(&self, can_gc: CanGc) -> ErrorResult {
if !self.is_html_document() {
return Err(Error::InvalidState);
}
if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
return Err(Error::InvalidState);
}
let parser = match self.get_current_parser() {
Some(ref parser) if parser.is_script_created() => DomRoot::from_ref(&**parser),
_ => {
return Ok(());
},
};
parser.close(can_gc);
Ok(())
}
document_and_element_event_handlers!();
event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror);
event_handler!(
fullscreenchange,
GetOnfullscreenchange,
SetOnfullscreenchange
);
fn FullscreenEnabled(&self) -> bool {
self.get_allow_fullscreen()
}
fn Fullscreen(&self) -> bool {
self.fullscreen_element.get().is_some()
}
fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> {
self.fullscreen_element.get()
}
fn ExitFullscreen(&self, can_gc: CanGc) -> Rc<Promise> {
self.exit_fullscreen(can_gc)
}
fn ServoGetMediaControls(&self, id: DOMString) -> Fallible<DomRoot<ShadowRoot>> {
match self.media_controls.borrow().get(&*id) {
Some(m) => Ok(DomRoot::from_ref(m)),
None => Err(Error::InvalidAccess),
}
}
fn GetSelection(&self) -> Option<DomRoot<Selection>> {
if self.has_browsing_context {
Some(self.selection.or_init(|| Selection::new(self)))
} else {
None
}
}
fn Fonts(&self, can_gc: CanGc) -> DomRoot<FontFaceSet> {
self.fonts
.or_init(|| FontFaceSet::new(&self.global(), None, can_gc))
}
fn Hidden(&self) -> bool {
self.visibility_state.get() == DocumentVisibilityState::Hidden
}
fn VisibilityState(&self) -> DocumentVisibilityState {
self.visibility_state.get()
}
fn CreateExpression(
&self,
expression: DOMString,
resolver: Option<Rc<XPathNSResolver>>,
can_gc: CanGc,
) -> Fallible<DomRoot<super::types::XPathExpression>> {
let global = self.global();
let window = global.as_window();
let evaluator = XPathEvaluator::new(window, None, can_gc);
XPathEvaluatorMethods::<crate::DomTypeHolder>::CreateExpression(
&*evaluator,
expression,
resolver,
can_gc,
)
}
fn CreateNSResolver(&self, node_resolver: &Node, can_gc: CanGc) -> DomRoot<Node> {
let global = self.global();
let window = global.as_window();
let evaluator = XPathEvaluator::new(window, None, can_gc);
XPathEvaluatorMethods::<crate::DomTypeHolder>::CreateNSResolver(&*evaluator, node_resolver)
}
fn Evaluate(
&self,
expression: DOMString,
context_node: &Node,
resolver: Option<Rc<XPathNSResolver>>,
type_: u16,
result: Option<&super::types::XPathResult>,
can_gc: CanGc,
) -> Fallible<DomRoot<super::types::XPathResult>> {
let global = self.global();
let window = global.as_window();
let evaluator = XPathEvaluator::new(window, None, can_gc);
XPathEvaluatorMethods::<crate::DomTypeHolder>::Evaluate(
&*evaluator,
expression,
context_node,
resolver,
type_,
result,
can_gc,
)
}
}
fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) {
if marker.get().is_none() {
marker.set(Some(CrossProcessInstant::now()))
}
}
pub fn determine_policy_for_token(token: &str) -> ReferrerPolicy {
match_ignore_ascii_case! { token,
"never" | "no-referrer" => ReferrerPolicy::NoReferrer,
"no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
"origin" => ReferrerPolicy::Origin,
"same-origin" => ReferrerPolicy::SameOrigin,
"strict-origin" => ReferrerPolicy::StrictOrigin,
"default" | "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
"origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
"always" | "unsafe-url" => ReferrerPolicy::UnsafeUrl,
_ => ReferrerPolicy::EmptyString,
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum FocusType {
Element, Parent, }
pub enum FocusEventType {
Focus, Blur, }
#[derive(JSTraceable, MallocSizeOf)]
pub struct FakeRequestAnimationFrameCallback {
#[ignore_malloc_size_of = "non-owning"]
document: Trusted<Document>,
}
impl FakeRequestAnimationFrameCallback {
pub fn invoke(self, can_gc: CanGc) {
self.document
.root()
.note_pending_animation_tick(AnimationTickType::REQUEST_ANIMATION_FRAME);
with_script_thread(|script_thread| script_thread.update_the_rendering(false, can_gc))
}
}
#[derive(JSTraceable, MallocSizeOf)]
pub enum AnimationFrameCallback {
DevtoolsFramerateTick {
actor_name: String,
},
FrameRequestCallback {
#[ignore_malloc_size_of = "Rc is hard"]
callback: Rc<FrameRequestCallback>,
},
}
impl AnimationFrameCallback {
fn call(&self, document: &Document, now: f64) {
match *self {
AnimationFrameCallback::DevtoolsFramerateTick { ref actor_name } => {
let msg = ScriptToDevtoolsControlMsg::FramerateTick(actor_name.clone(), now);
let devtools_sender = document
.window()
.upcast::<GlobalScope>()
.devtools_chan()
.unwrap();
devtools_sender.send(msg).unwrap();
},
AnimationFrameCallback::FrameRequestCallback { ref callback } => {
let _ = callback.Call__(Finite::wrap(now), ExceptionHandling::Report);
},
}
}
}
#[derive(Default, JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
struct PendingInOrderScriptVec {
scripts: DomRefCell<VecDeque<PendingScript>>,
}
impl PendingInOrderScriptVec {
fn is_empty(&self) -> bool {
self.scripts.borrow().is_empty()
}
fn push(&self, element: &HTMLScriptElement) {
self.scripts
.borrow_mut()
.push_back(PendingScript::new(element));
}
fn loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
let mut scripts = self.scripts.borrow_mut();
let entry = scripts
.iter_mut()
.find(|entry| &*entry.element == element)
.unwrap();
entry.loaded(result);
}
fn take_next_ready_to_be_executed(&self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> {
let mut scripts = self.scripts.borrow_mut();
let pair = scripts.front_mut()?.take_result()?;
scripts.pop_front();
Some(pair)
}
fn clear(&self) {
*self.scripts.borrow_mut() = Default::default();
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
struct PendingScript {
element: Dom<HTMLScriptElement>,
load: Option<ScriptResult>,
}
impl PendingScript {
fn new(element: &HTMLScriptElement) -> Self {
Self {
element: Dom::from_ref(element),
load: None,
}
}
fn new_with_load(element: &HTMLScriptElement, load: Option<ScriptResult>) -> Self {
Self {
element: Dom::from_ref(element),
load,
}
}
fn loaded(&mut self, result: ScriptResult) {
assert!(self.load.is_none());
self.load = Some(result);
}
fn take_result(&mut self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> {
self.load
.take()
.map(|result| (DomRoot::from_ref(&*self.element), result))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ReflowTriggerCondition {
StylesheetsChanged,
DirtyDescendants,
PendingRestyles,
PaintPostponed,
}
fn is_named_element_with_name_attribute(elem: &Element) -> bool {
let type_ = match elem.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
_ => return false,
};
match type_ {
HTMLElementTypeId::HTMLFormElement |
HTMLElementTypeId::HTMLIFrameElement |
HTMLElementTypeId::HTMLImageElement => true,
_ => false,
}
}
fn is_named_element_with_id_attribute(elem: &Element) -> bool {
elem.is::<HTMLImageElement>() && elem.get_name().is_some_and(|name| !name.is_empty())
}