aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/document.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/document.rs')
-rw-r--r--components/script/dom/document.rs5054
1 files changed, 3231 insertions, 1823 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index f5ffd973936..28b820f8919 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -1,148 +1,179 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use cookie_rs;
-use core::nonzero::NonZero;
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+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::{ref_filter_map, DomRefCell, Ref, RefMut};
+use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventBinding::BeforeUnloadEventMethods;
+use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
+ DocumentMethods, DocumentReadyState,
+};
+use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
+use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::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::UnionTypes::{NodeOrString, StringOrElementCreationOptions};
+use crate::dom::bindings::error::{Error, 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, DomObject};
+use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom};
+use crate::dom::bindings::str::{DOMString, USVString};
+use crate::dom::bindings::xmlname::XMLName::InvalidXMLName;
+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;
+use crate::dom::element::{
+ 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::globalscope::GlobalScope;
+use crate::dom::gpucanvascontext::{GPUCanvasContext, WebGPUContextId};
+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::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};
+use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding};
+use crate::dom::nodeiterator::NodeIterator;
+use crate::dom::nodelist::NodeList;
+use crate::dom::pagetransitionevent::PageTransitionEvent;
+use crate::dom::processinginstruction::ProcessingInstruction;
+use crate::dom::promise::Promise;
+use crate::dom::range::Range;
+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::uievent::UIEvent;
+use crate::dom::virtualmethods::vtable_for;
+use crate::dom::webglrenderingcontext::WebGLRenderingContext;
+use crate::dom::wheelevent::WheelEvent;
+use crate::dom::window::{ReflowReason, Window};
+use crate::dom::windowproxy::WindowProxy;
+use crate::fetch::FetchCanceller;
+use crate::realms::{AlreadyInRealm, InRealm};
+use crate::script_runtime::JSContext;
+use crate::script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
+use crate::script_thread::{MainThreadScriptMsg, ScriptThread};
+use crate::stylesheet_set::StylesheetSetRef;
+use crate::task::TaskBox;
+use crate::task_source::{TaskSource, TaskSourceName};
+use crate::timers::OneshotTimerCallback;
+use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
+use content_security_policy::{self as csp, CspList};
+use cookie::Cookie;
use devtools_traits::ScriptToDevtoolsControlMsg;
-use document_loader::{DocumentLoader, LoadType};
-use dom::activation::{ActivationSource, synthetic_click_activation};
-use dom::attr::Attr;
-use dom::beforeunloadevent::BeforeUnloadEvent;
-use dom::bindings::callback::ExceptionHandling;
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
-use dom::bindings::codegen::Bindings::DocumentBinding;
-use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
-use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
-use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
-use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
-use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
-use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
-use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter;
-use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
-use dom::bindings::codegen::Bindings::TouchBinding::TouchMethods;
-use dom::bindings::codegen::Bindings::WindowBinding::{FrameRequestCallback, ScrollBehavior, WindowMethods};
-use dom::bindings::codegen::UnionTypes::NodeOrString;
-use dom::bindings::error::{Error, ErrorResult, Fallible};
-use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
-use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root};
-use dom::bindings::js::RootedReference;
-use dom::bindings::num::Finite;
-use dom::bindings::refcounted::{Trusted, TrustedPromise};
-use dom::bindings::reflector::{DomObject, reflect_dom_object};
-use dom::bindings::str::{DOMString, USVString};
-use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type};
-use dom::bindings::xmlname::XMLName::InvalidXMLName;
-use dom::browsingcontext::BrowsingContext;
-use dom::closeevent::CloseEvent;
-use dom::comment::Comment;
-use dom::customevent::CustomEvent;
-use dom::documentfragment::DocumentFragment;
-use dom::documenttype::DocumentType;
-use dom::domimplementation::DOMImplementation;
-use dom::element::{Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit};
-use dom::errorevent::ErrorEvent;
-use dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus};
-use dom::eventtarget::EventTarget;
-use dom::focusevent::FocusEvent;
-use dom::forcetouchevent::ForceTouchEvent;
-use dom::globalscope::GlobalScope;
-use dom::hashchangeevent::HashChangeEvent;
-use dom::htmlanchorelement::HTMLAnchorElement;
-use dom::htmlappletelement::HTMLAppletElement;
-use dom::htmlareaelement::HTMLAreaElement;
-use dom::htmlbaseelement::HTMLBaseElement;
-use dom::htmlbodyelement::HTMLBodyElement;
-use dom::htmlcollection::{CollectionFilter, HTMLCollection};
-use dom::htmlelement::HTMLElement;
-use dom::htmlembedelement::HTMLEmbedElement;
-use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
-use dom::htmlheadelement::HTMLHeadElement;
-use dom::htmlhtmlelement::HTMLHtmlElement;
-use dom::htmliframeelement::HTMLIFrameElement;
-use dom::htmlimageelement::HTMLImageElement;
-use dom::htmlscriptelement::{HTMLScriptElement, ScriptResult};
-use dom::htmltitleelement::HTMLTitleElement;
-use dom::keyboardevent::KeyboardEvent;
-use dom::location::Location;
-use dom::messageevent::MessageEvent;
-use dom::mouseevent::MouseEvent;
-use dom::node::{self, CloneChildrenFlag, Node, NodeDamage, window_from_node, IS_IN_DOC, LayoutNodeHelpers};
-use dom::node::VecPreOrderInsertionHelper;
-use dom::nodeiterator::NodeIterator;
-use dom::nodelist::NodeList;
-use dom::pagetransitionevent::PageTransitionEvent;
-use dom::popstateevent::PopStateEvent;
-use dom::processinginstruction::ProcessingInstruction;
-use dom::progressevent::ProgressEvent;
-use dom::promise::Promise;
-use dom::range::Range;
-use dom::servoparser::ServoParser;
-use dom::storageevent::StorageEvent;
-use dom::stylesheetlist::StyleSheetList;
-use dom::text::Text;
-use dom::touch::Touch;
-use dom::touchevent::TouchEvent;
-use dom::touchlist::TouchList;
-use dom::treewalker::TreeWalker;
-use dom::uievent::UIEvent;
-use dom::webglcontextevent::WebGLContextEvent;
-use dom::window::{ReflowReason, Window};
use dom_struct::dom_struct;
-use encoding::EncodingRef;
-use encoding::all::UTF_8;
-use euclid::point::Point2D;
-use html5ever_atoms::{LocalName, QualName};
-use hyper::header::{Header, SetCookie};
+use embedder_traits::EmbedderMsg;
+use encoding_rs::{Encoding, UTF_8};
+use euclid::default::{Point2D, Rect, Size2D};
+use html5ever::{LocalName, Namespace, QualName};
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender};
-use js::jsapi::{JSContext, JSObject, JSRuntime};
-use js::jsapi::JS_GetRuntime;
-use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
-use msg::constellation_msg::{FrameId, Key, KeyModifiers, KeyState};
-use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy};
-use net_traits::CookieSource::NonHTTP;
-use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl};
+use js::jsapi::JSObject;
+use keyboard_types::{Code, Key, KeyState};
+use metrics::{
+ InteractiveFlag, InteractiveMetrics, InteractiveWindow, ProfilerMetadataFactory,
+ ProgressiveWebMetric,
+};
+use mime::{self, Mime};
+use msg::constellation_msg::BrowsingContextId;
use net_traits::pub_domains::is_pub_domain;
-use net_traits::request::RequestInit;
+use net_traits::request::RequestBuilder;
use net_traits::response::HttpsState;
+use net_traits::CookieSource::NonHTTP;
+use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl};
+use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy};
use num_traits::ToPrimitive;
-use script_layout_interface::message::{Msg, ReflowQueryType};
-use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
-use script_thread::{MainThreadScriptMsg, Runnable, ScriptThread};
-use script_traits::{AnimationState, CompositorEvent, DocumentActivity};
-use script_traits::{MouseButton, MouseEventType, MozBrowserEvent};
-use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
-use script_traits::{TouchEventType, TouchId};
-use script_traits::UntrustedNodeAddress;
+use percent_encoding::percent_decode;
+use profile_traits::ipc as profile_ipc;
+use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
+use script_layout_interface::message::{Msg, PendingRestyle, ReflowGoal};
+use script_layout_interface::TrustedNodeAddress;
+use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
+use script_traits::{
+ MsDuration, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta,
+};
+use servo_arc::Arc;
use servo_atoms::Atom;
-use servo_config::prefs::PREFS;
+use servo_config::pref;
+use servo_media::{ClientContextId, ServoMedia};
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
-use std::ascii::AsciiExt;
-use std::borrow::ToOwned;
-use std::cell::{Cell, Ref, RefMut};
-use std::collections::{HashMap, HashSet, VecDeque};
+use std::borrow::Cow;
+use std::cell::Cell;
+use std::cmp::Ordering;
use std::collections::hash_map::Entry::{Occupied, Vacant};
+use std::collections::{HashMap, HashSet, VecDeque};
use std::default::Default;
-use std::iter::once;
use std::mem;
+use std::ptr::NonNull;
use std::rc::Rc;
-use std::sync::Arc;
+use std::slice::from_ref;
use std::time::{Duration, Instant};
use style::attr::AttrValue;
-use style::context::{QuirksMode, ReflowGoal};
-use style::restyle_hints::{RestyleHint, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE};
-use style::selector_parser::{RestyleDamage, Snapshot};
+use style::context::QuirksMode;
+use style::invalidation::element::restyle_hints::RestyleHint;
+use style::media_queries::{Device, MediaType};
+use style::selector_parser::Snapshot;
use style::shared_lock::SharedRwLock as StyleSharedRwLock;
-use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join};
-use style::stylesheets::Stylesheet;
-use task_source::TaskSource;
-use time;
-use timers::OneshotTimerCallback;
+use style::str::{split_html_space_chars, str_join};
+use style::stylesheet_set::DocumentStylesheetSet;
+use style::stylesheets::{Origin, OriginSet, Stylesheet};
use url::Host;
-use url::percent_encoding::percent_decode;
-use webrender_traits::ClipId;
+use uuid::Uuid;
+use webrender_api::units::DeviceIntRect;
/// The number of times we are allowed to see spurious `requestAnimationFrame()` calls before
/// falling back to fake ones.
@@ -158,155 +189,160 @@ pub enum TouchEventResult {
Forwarded,
}
-#[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable, PartialEq)]
-pub enum IsHTMLDocument {
- HTMLDocument,
- NonHTMLDocument,
+#[derive(Clone, Copy, PartialEq)]
+pub enum FireMouseEventType {
+ Move,
+ Over,
+ Out,
+ Enter,
+ Leave,
}
-#[derive(JSTraceable, HeapSizeOf)]
-#[must_root]
-pub struct StylesheetInDocument {
- pub node: JS<Node>,
- #[ignore_heap_size_of = "Arc"]
- pub stylesheet: Arc<Stylesheet>,
+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(Debug, HeapSizeOf)]
-pub struct PendingRestyle {
- /// If this element had a state or attribute change since the last restyle, track
- /// the original condition of the element.
- pub snapshot: Option<Snapshot>,
-
- /// Any explicit restyles hints that have been accumulated for this element.
- pub hint: RestyleHint,
-
- /// Any explicit restyles damage that have been accumulated for this element.
- pub damage: RestyleDamage,
+#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
+pub enum IsHTMLDocument {
+ HTMLDocument,
+ NonHTMLDocument,
}
-impl PendingRestyle {
- pub fn new() -> Self {
- PendingRestyle {
- snapshot: None,
- hint: RestyleHint::empty(),
- damage: RestyleDamage::empty(),
- }
- }
+#[derive(JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
+enum FocusTransaction {
+ /// No focus operation is in effect.
+ NotInTransaction,
+ /// A focus operation is in effect.
+ /// Contains the element that has most recently requested focus for itself.
+ InTransaction(Option<Dom<Element>>),
}
-/// https://dom.spec.whatwg.org/#document
+/// <https://dom.spec.whatwg.org/#document>
#[dom_struct]
pub struct Document {
node: Node,
- window: JS<Window>,
- implementation: MutNullableJS<DOMImplementation>,
- location: MutNullableJS<Location>,
- content_type: DOMString,
+ document_or_shadow_root: DocumentOrShadowRoot,
+ window: Dom<Window>,
+ implementation: MutNullableDom<DOMImplementation>,
+ #[ignore_malloc_size_of = "type from external crate"]
+ content_type: Mime,
last_modified: Option<String>,
- encoding: Cell<EncodingRef>,
+ encoding: Cell<&'static Encoding>,
has_browsing_context: bool,
is_html_document: bool,
activity: Cell<DocumentActivity>,
- url: DOMRefCell<ServoUrl>,
+ url: DomRefCell<ServoUrl>,
+ #[ignore_malloc_size_of = "defined in selectors"]
quirks_mode: Cell<QuirksMode>,
/// Caches for the getElement methods
- id_map: DOMRefCell<HashMap<Atom, Vec<JS<Element>>>>,
- tag_map: DOMRefCell<HashMap<LocalName, JS<HTMLCollection>>>,
- tagns_map: DOMRefCell<HashMap<QualName, JS<HTMLCollection>>>,
- classes_map: DOMRefCell<HashMap<Vec<Atom>, JS<HTMLCollection>>>,
- images: MutNullableJS<HTMLCollection>,
- embeds: MutNullableJS<HTMLCollection>,
- links: MutNullableJS<HTMLCollection>,
- forms: MutNullableJS<HTMLCollection>,
- scripts: MutNullableJS<HTMLCollection>,
- anchors: MutNullableJS<HTMLCollection>,
- applets: MutNullableJS<HTMLCollection>,
+ id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
+ name_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>,
+ tag_map: DomRefCell<HashMap<LocalName, Dom<HTMLCollection>>>,
+ tagns_map: DomRefCell<HashMap<QualName, Dom<HTMLCollection>>>,
+ classes_map: DomRefCell<HashMap<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>,
/// Lock use for style attributes and author-origin stylesheet objects in this document.
/// Can be acquired once for accessing many objects.
style_shared_lock: StyleSharedRwLock,
/// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed.
- stylesheets: DOMRefCell<Option<Vec<StylesheetInDocument>>>,
- /// Whether the list of stylesheets has changed since the last reflow was triggered.
- stylesheets_changed_since_reflow: Cell<bool>,
- stylesheet_list: MutNullableJS<StyleSheetList>,
+ stylesheets: DomRefCell<DocumentStylesheetSet<StyleSheetInDocument>>,
+ stylesheet_list: MutNullableDom<StyleSheetList>,
ready_state: Cell<DocumentReadyState>,
/// Whether the DOMContentLoaded event has already been dispatched.
domcontentloaded_dispatched: Cell<bool>,
- /// The element that has most recently requested focus for itself.
- possibly_focused: MutNullableJS<Element>,
+ /// The state of this document's focus transaction.
+ focus_transaction: DomRefCell<FocusTransaction>,
/// The element that currently has the document focus context.
- focused: MutNullableJS<Element>,
+ focused: MutNullableDom<Element>,
/// The script element that is currently executing.
- current_script: MutNullableJS<HTMLScriptElement>,
- /// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
- pending_parsing_blocking_script: DOMRefCell<Option<PendingScript>>,
+ current_script: MutNullableDom<HTMLScriptElement>,
+ /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script>
+ pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>,
/// Number of stylesheets that block executing the next parser-inserted script
script_blocking_stylesheets_count: Cell<u32>,
/// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing
deferred_scripts: PendingInOrderScriptVec,
- /// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible
+ /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible>
asap_in_order_scripts_list: PendingInOrderScriptVec,
- /// https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible
- asap_scripts_set: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
- /// https://html.spec.whatwg.org/multipage/#concept-n-noscript
+ /// <https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible>
+ asap_scripts_set: DomRefCell<Vec<Dom<HTMLScriptElement>>>,
+ /// <https://html.spec.whatwg.org/multipage/#concept-n-noscript>
/// True if scripting is enabled for all scripts in this document
scripting_enabled: bool,
- /// https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier
+ /// <https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier>
/// Current identifier of animation frame callback
animation_frame_ident: Cell<u32>,
- /// https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks
+ /// <https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks>
/// List of animation frame callbacks
- animation_frame_list: DOMRefCell<Vec<(u32, Option<AnimationFrameCallback>)>>,
+ animation_frame_list: DomRefCell<Vec<(u32, Option<AnimationFrameCallback>)>>,
/// Whether we're in the process of running animation callbacks.
///
/// Tracking this is not necessary for correctness. Instead, it is an optimization to avoid
/// sending needless `ChangeRunningAnimationsState` messages to the compositor.
running_animation_callbacks: Cell<bool>,
/// Tracks all outstanding loads related to this document.
- loader: DOMRefCell<DocumentLoader>,
+ loader: DomRefCell<DocumentLoader>,
/// The current active HTML parser, to allow resuming after interruptions.
- current_parser: MutNullableJS<ServoParser>,
+ current_parser: MutNullableDom<ServoParser>,
/// When we should kick off a reflow. This happens during parsing.
reflow_timeout: Cell<Option<u64>>,
/// The cached first `base` element with an `href` attribute.
- base_element: MutNullableJS<HTMLBaseElement>,
+ base_element: MutNullableDom<HTMLBaseElement>,
/// This field is set to the document itself for inert documents.
- /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
- appropriate_template_contents_owner_document: MutNullableJS<Document>,
+ /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document>
+ appropriate_template_contents_owner_document: MutNullableDom<Document>,
/// Information on elements needing restyle to ship over to the layout thread when the
/// time comes.
- pending_restyles: DOMRefCell<HashMap<JS<Element>, PendingRestyle>>,
+ pending_restyles: DomRefCell<HashMap<Dom<Element>, PendingRestyle>>,
/// This flag will be true if layout suppressed a reflow attempt that was
/// needed in order for the page to be painted.
needs_paint: Cell<bool>,
- /// http://w3c.github.io/touch-events/#dfn-active-touch-point
- active_touch_points: DOMRefCell<Vec<JS<Touch>>>,
+ /// <http://w3c.github.io/touch-events/#dfn-active-touch-point>
+ active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
/// Navigation Timing properties:
- /// https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming
+ /// <https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming>
dom_loading: Cell<u64>,
dom_interactive: Cell<u64>,
dom_content_loaded_event_start: Cell<u64>,
dom_content_loaded_event_end: Cell<u64>,
dom_complete: Cell<u64>,
+ top_level_dom_complete: Cell<u64>,
load_event_start: Cell<u64>,
load_event_end: Cell<u64>,
- /// https://html.spec.whatwg.org/multipage/#concept-document-https-state
+ unload_event_start: Cell<u64>,
+ unload_event_end: Cell<u64>,
+ /// <https://html.spec.whatwg.org/multipage/#concept-document-https-state>
https_state: Cell<HttpsState>,
- touchpad_pressure_phase: Cell<TouchpadPressurePhase>,
/// The document's origin.
origin: MutableOrigin,
/// https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states
referrer_policy: Cell<Option<ReferrerPolicy>>,
- /// https://html.spec.whatwg.org/multipage/#dom-document-referrer
+ /// <https://html.spec.whatwg.org/multipage/#dom-document-referrer>
referrer: Option<String>,
- /// https://html.spec.whatwg.org/multipage/#target-element
- target_element: MutNullableJS<Element>,
- /// https://w3c.github.io/uievents/#event-type-dblclick
- #[ignore_heap_size_of = "Defined in std"]
- last_click_info: DOMRefCell<Option<(Instant, Point2D<f32>)>>,
- /// https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter
+ /// <https://html.spec.whatwg.org/multipage/#target-element>
+ target_element: MutNullableDom<Element>,
+ /// <https://w3c.github.io/uievents/#event-type-dblclick>
+ #[ignore_malloc_size_of = "Defined in std"]
+ last_click_info: DomRefCell<Option<(Instant, Point2D<f32>)>>,
+ /// <https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter>
ignore_destructive_writes_counter: Cell<u32>,
+ /// <https://html.spec.whatwg.org/multipage/#ignore-opens-during-unload-counter>
+ ignore_opens_during_unload_counter: Cell<u32>,
/// The number of spurious `requestAnimationFrame()` requests we've received.
///
/// A rAF request is considered spurious if nothing was actually reflowed.
@@ -319,16 +355,67 @@ pub struct Document {
/// See also: https://github.com/servo/servo/issues/10110
dom_count: Cell<u32>,
/// Entry node for fullscreen.
- fullscreen_element: MutNullableJS<Element>,
+ fullscreen_element: MutNullableDom<Element>,
/// Map from ID to set of form control elements that have that ID as
/// their 'form' content attribute. Used to reset form controls
/// whenever any element with the same ID as the form attribute
/// is inserted or removed from the document.
/// See https://html.spec.whatwg.org/multipage/#form-owner
- form_id_listener_map: DOMRefCell<HashMap<Atom, HashSet<JS<Element>>>>,
+ form_id_listener_map: DomRefCell<HashMap<Atom, HashSet<Dom<Element>>>>,
+ interactive_time: DomRefCell<InteractiveMetrics>,
+ tti_window: DomRefCell<InteractiveWindow>,
+ /// RAII canceller for Fetch
+ canceller: FetchCanceller,
+ /// https://html.spec.whatwg.org/multipage/#throw-on-dynamic-markup-insertion-counter
+ throw_on_dynamic_markup_insertion_counter: Cell<u64>,
+ /// https://html.spec.whatwg.org/multipage/#page-showing
+ page_showing: Cell<bool>,
+ /// Whether the document is salvageable.
+ salvageable: Cell<bool>,
+ /// Whether the document was aborted with an active parser
+ active_parser_was_aborted: Cell<bool>,
+ /// Whether the unload event has already been fired.
+ fired_unload: Cell<bool>,
+ /// List of responsive images
+ responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>,
+ /// Number of redirects for the document load
+ redirect_count: Cell<u16>,
+ /// Number of outstanding requests to prevent JS or layout from running.
+ script_and_layout_blockers: Cell<u32>,
+ /// List of tasks to execute as soon as last script/layout blocker is removed.
+ #[ignore_malloc_size_of = "Measuring trait objects is hard"]
+ delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>,
+ /// https://html.spec.whatwg.org/multipage/#completely-loaded
+ completely_loaded: Cell<bool>,
+ /// Set of shadow roots connected to the document tree.
+ shadow_roots: DomRefCell<HashSet<Dom<ShadowRoot>>>,
+ /// Whether any of the shadow roots need the stylesheets flushed.
+ shadow_roots_styles_changed: Cell<bool>,
+ /// List of registered media controls.
+ /// We need to keep this list to allow the media controls to
+ /// access the "privileged" document.servoGetMediaControls(id) API,
+ /// where `id` needs to match any of the registered ShadowRoots
+ /// hosting the media controls UI.
+ media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
+ /// List of all WebGL context IDs that need flushing.
+ dirty_webgl_contexts: DomRefCell<HashMap<WebGLContextId, Dom<WebGLRenderingContext>>>,
+ /// List of all WebGPU context IDs that need flushing.
+ dirty_webgpu_contexts: DomRefCell<HashMap<WebGPUContextId, Dom<GPUCanvasContext>>>,
+ /// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ csp_list: DomRefCell<Option<CspList>>,
+ /// https://w3c.github.io/slection-api/#dfn-selection
+ selection: MutNullableDom<Selection>,
+ /// A timeline for animations which is used for synchronizing animations.
+ /// https://drafts.csswg.org/web-animations/#timeline
+ animation_timeline: DomRefCell<AnimationTimeline>,
+ /// Animations for this Document
+ animations: DomRefCell<Animations>,
+ /// The nearest inclusive ancestors to all the nodes that require a restyle.
+ dirty_root: MutNullableDom<Element>,
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct ImagesFilter;
impl CollectionFilter for ImagesFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
@@ -336,7 +423,7 @@ impl CollectionFilter for ImagesFilter {
}
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct EmbedsFilter;
impl CollectionFilter for EmbedsFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
@@ -344,16 +431,16 @@ impl CollectionFilter for EmbedsFilter {
}
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct LinksFilter;
impl CollectionFilter for LinksFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
(elem.is::<HTMLAnchorElement>() || elem.is::<HTMLAreaElement>()) &&
- elem.has_attribute(&local_name!("href"))
+ elem.has_attribute(&local_name!("href"))
}
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct FormsFilter;
impl CollectionFilter for FormsFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
@@ -361,7 +448,7 @@ impl CollectionFilter for FormsFilter {
}
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct ScriptsFilter;
impl CollectionFilter for ScriptsFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
@@ -369,7 +456,7 @@ impl CollectionFilter for ScriptsFilter {
}
}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
struct AnchorsFilter;
impl CollectionFilter for AnchorsFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
@@ -377,33 +464,141 @@ impl CollectionFilter for AnchorsFilter {
}
}
-#[derive(JSTraceable, HeapSizeOf)]
-struct AppletsFilter;
-impl CollectionFilter for AppletsFilter {
- fn filter(&self, elem: &Element, _root: &Node) -> bool {
- elem.is::<HTMLAppletElement>()
- }
+enum ElementLookupResult {
+ None,
+ One(DomRoot<Element>),
+ Many,
}
+#[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 => {
+ // There is no parent so this is the Document node, so we
+ // behave as if we were called with the document element.
+ let document_element = match self.GetDocumentElement() {
+ Some(element) => element,
+ None => return,
+ };
+ if let Some(dirty_root) = self.dirty_root.get() {
+ // There was an existing dirty root so we mark its
+ // ancestors as dirty until the document element.
+ 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 => {
+ // Current node is not an element, it's probably a text node,
+ // we try to get its element parent.
+ match DomRoot::downcast::<Element>(parent) {
+ Some(parent) => {
+ element_parent = parent;
+ &element_parent
+ },
+ None => {
+ // Parent is not an element so it must be a document,
+ // and this is not an element either, so there is
+ // nothing to do.
+ 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 mut_loader(&self) -> RefMut<DocumentLoader> {
+ pub fn loader_mut(&self) -> RefMut<DocumentLoader> {
self.loader.borrow_mut()
}
#[inline]
- pub fn has_browsing_context(&self) -> bool { self.has_browsing_context }
+ pub fn has_browsing_context(&self) -> bool {
+ self.has_browsing_context
+ }
- /// https://html.spec.whatwg.org/multipage/#concept-document-bc
+ /// <https://html.spec.whatwg.org/multipage/#concept-document-bc>
#[inline]
- pub fn browsing_context(&self) -> Option<Root<BrowsingContext>> {
+ pub fn browsing_context(&self) -> Option<DomRoot<WindowProxy>> {
if self.has_browsing_context {
- self.window.maybe_browsing_context()
+ self.window.undiscarded_window_proxy()
} else {
None
}
@@ -421,7 +616,6 @@ impl Document {
pub fn set_https_state(&self, https_state: HttpsState) {
self.https_state.set(https_state);
- self.trigger_mozbrowser_event(MozBrowserEvent::SecurityChange(https_state));
}
pub fn is_fully_active(&self) -> bool {
@@ -435,22 +629,68 @@ impl Document {
pub fn set_activity(&self, activity: DocumentActivity) {
// This function should only be called on documents with a browsing context
assert!(self.has_browsing_context);
+ if activity == self.activity.get() {
+ return;
+ }
+
// Set the document's activity level, reflow if necessary, and suspend or resume timers.
- if activity != self.activity.get() {
- self.activity.set(activity);
- if activity == DocumentActivity::FullyActive {
- self.title_changed();
- self.dirty_all_nodes();
- self.window().reflow(
- ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::CachedPageNeededReflow
- );
- self.window().resume();
- } else {
- self.window().suspend();
- }
+ 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()
+ .reflow(ReflowGoal::Full, ReflowReason::CachedPageNeededReflow);
+ self.window().resume();
+ media.resume(&client_context_id);
+
+ if self.ready_state.get() != DocumentReadyState::Complete {
+ return;
}
+
+ // html.spec.whatwg.org/multipage/#history-traversal
+ // Step 4.6
+ 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();
+ // Step 4.6.1
+ if document.page_showing.get() {
+ return;
+ }
+ // Step 4.6.2
+ document.page_showing.set(true);
+ // Step 4.6.4
+ let event = PageTransitionEvent::new(
+ window,
+ atom!("pageshow"),
+ false, // bubbles
+ false, // cancelable
+ true, // persisted
+ );
+ let event = event.upcast::<Event>();
+ event.set_trusted(true);
+ // FIXME(nox): Why are errors silenced here?
+ let _ = window.dispatch_event_with_target_override(
+ &event,
+ );
+ }),
+ self.window.upcast(),
+ )
+ .unwrap();
}
pub fn origin(&self) -> &MutableOrigin {
@@ -468,10 +708,25 @@ impl Document {
// https://html.spec.whatwg.org/multipage/#fallback-base-url
pub fn fallback_base_url(&self) -> ServoUrl {
- // Step 1: iframe srcdoc (#4767).
- // Step 2: about:blank with a creator browsing context.
- // Step 3.
- self.url()
+ let document_url = self.url();
+ if let Some(browsing_context) = self.browsing_context() {
+ // Step 1: If document is an iframe srcdoc document, then return the
+ // document base URL of document's browsing context's container document.
+ let container_base_url = browsing_context
+ .parent()
+ .and_then(|parent| parent.document())
+ .map(|document| document.base_url());
+ if document_url.as_str() == "about:srcdoc" && container_base_url.is_some() {
+ return container_base_url.unwrap();
+ }
+ // Step 2: If document's URL is about:blank, and document's browsing
+ // context's creator base URL is non-null, then return that creator base URL.
+ if document_url.as_str() == "about:blank" && browsing_context.has_creator_base_url() {
+ return browsing_context.creator_base_url().unwrap();
+ }
+ }
+ // Step 3: Return document's URL.
+ document_url
}
// https://html.spec.whatwg.org/multipage/#document-base-url
@@ -488,33 +743,48 @@ impl Document {
self.needs_paint.get()
}
- pub fn needs_reflow(&self) -> bool {
+ pub fn needs_reflow(&self) -> Option<ReflowTriggerCondition> {
// FIXME: This should check the dirty bit on the document,
// not the document element. Needs some layout changes to make
// that workable.
- match self.GetDocumentElement() {
- Some(root) => {
- root.upcast::<Node>().has_dirty_descendants() ||
- !self.pending_restyles.borrow().is_empty() ||
- self.needs_paint()
- }
- None => false,
+ 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() {
+ return Some(ReflowTriggerCondition::PaintPostponed);
+ }
+
+ None
}
/// Returns the first `base` element in the DOM that has an `href` attribute.
- pub fn base_element(&self) -> Option<Root<HTMLBaseElement>> {
+ pub fn base_element(&self) -> Option<DomRoot<HTMLBaseElement>> {
self.base_element.get()
}
/// Refresh the cached first base element in the DOM.
- /// https://github.com/w3c/web-platform-tests/issues/2122
+ /// <https://github.com/w3c/web-platform-tests/issues/2122>
pub fn refresh_base_element(&self) {
- let base = self.upcast::<Node>()
- .traverse_preorder()
- .filter_map(Root::downcast::<HTMLBaseElement>)
- .find(|element| element.upcast::<Element>().has_attribute(&local_name!("href")));
- self.base_element.set(base.r());
+ 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 {
@@ -541,20 +811,29 @@ impl Document {
self.quirks_mode.set(mode);
if mode == QuirksMode::Quirks {
- self.window.layout_chan().send(Msg::SetQuirksMode).unwrap();
+ match self.window.layout_chan() {
+ Some(chan) => chan.send(Msg::SetQuirksMode(mode)).unwrap(),
+ None => warn!("Layout channel unavailable"),
+ }
}
}
- pub fn encoding(&self) -> EncodingRef {
+ pub fn encoding(&self) -> &'static Encoding {
self.encoding.get()
}
- pub fn set_encoding(&self, encoding: EncodingRef) {
+ pub fn set_encoding(&self, encoding: &'static Encoding) {
self.encoding.set(encoding);
}
- pub fn content_and_heritage_changed(&self, node: &Node, damage: NodeDamage) {
- node.dirty(damage);
+ pub fn content_and_heritage_changed(&self, node: &Node) {
+ if node.is_connected() {
+ node.note_dirty_descendants();
+ }
+
+ // FIXME(emilio): This is very inefficient, ideally the flag above would
+ // be enough and incremental layout could figure out from there.
+ node.dirty(NodeDamage::OtherNodeDamage);
}
/// Reflows and disarms the timer if the reflow timer has expired.
@@ -565,9 +844,8 @@ impl Document {
}
self.reflow_timeout.set(None);
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::RefreshTick);
+ self.window
+ .reflow(ReflowGoal::Full, ReflowReason::RefreshTick);
}
}
@@ -584,68 +862,64 @@ impl Document {
}
/// Remove any existing association between the provided id and any elements in this document.
- pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) {
- debug!("Removing named element from document {:p}: {:p} id={}",
- self,
- to_unregister,
- id);
- // Limit the scope of the borrow because id_map might be borrowed again by
- // GetElementById through the following sequence of calls
- // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
- {
- let mut id_map = self.id_map.borrow_mut();
- let is_empty = match id_map.get_mut(&id) {
- None => false,
- Some(elements) => {
- let position = elements.iter()
- .position(|element| &**element == to_unregister)
- .expect("This element should be in registered.");
- elements.remove(position);
- elements.is_empty()
- }
- };
- if is_empty {
- id_map.remove(&id);
- }
- }
+ 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);
}
/// Associate an element present in this document with the provided id.
- pub fn register_named_element(&self, element: &Element, id: Atom) {
- debug!("Adding named element to document {:p}: {:p} id={}",
- self,
- element,
- id);
- assert!(element.upcast::<Node>().is_in_doc());
- assert!(!id.is_empty());
-
- let root = self.GetDocumentElement()
- .expect("The element is in the document, so there must be a document \
- element.");
-
- // Limit the scope of the borrow because id_map might be borrowed again by
- // GetElementById through the following sequence of calls
- // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
- {
- let mut id_map = self.id_map.borrow_mut();
- let mut elements = id_map.entry(id.clone()).or_insert(Vec::new());
- elements.insert_pre_order(element, root.r().upcast::<Node>());
- }
+ 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);
}
+ /// Remove any existing association between the provided name and any elements in this document.
+ 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);
+ }
+
+ /// Associate an element present in this document with the provided 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 mut set = map.entry(Atom::from(id)).or_insert(HashSet::new());
- set.insert(JS::from_ref(listener));
+ let set = map.entry(Atom::from(id)).or_insert(HashSet::new());
+ set.insert(Dom::from_ref(listener));
}
- pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
+ 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(&JS::from_ref(listener.to_element()));
+ entry
+ .get_mut()
+ .remove(&Dom::from_ref(listener.to_element()));
if entry.get().is_empty() {
entry.remove();
}
@@ -653,86 +927,97 @@ impl Document {
}
/// Attempt to find a named element in this page's document.
- /// https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document
- pub fn find_fragment_node(&self, fragid: &str) -> Option<Root<Element>> {
+ /// <https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document>
+ pub fn find_fragment_node(&self, fragid: &str) -> Option<DomRoot<Element>> {
// Step 1 is not handled here; the fragid is already obtained by the calling function
// Step 2: Simply use None to indicate the top of the document.
// Step 3 & 4
- percent_decode(fragid.as_bytes()).decode_utf8().ok()
- // Step 5
+ percent_decode(fragid.as_bytes())
+ .decode_utf8()
+ .ok()
+ // Step 5
.and_then(|decoded_fragid| self.get_element_by_id(&Atom::from(decoded_fragid)))
- // Step 6
+ // Step 6
.or_else(|| self.get_anchor_by_name(fragid))
// Step 7 & 8
}
/// Scroll to the target element, and when we do not find a target
/// and the fragment is empty or "top", scroll to the top.
- /// https://html.spec.whatwg.org/multipage/#scroll-to-the-fragment-identifier
+ /// <https://html.spec.whatwg.org/multipage/#scroll-to-the-fragment-identifier>
pub fn check_and_scroll_fragment(&self, fragment: &str) {
let target = self.find_fragment_node(fragment);
// Step 1
- self.set_target_element(target.r());
-
- let point = target.r().map(|element| {
- // FIXME(#8275, pcwalton): This is pretty bogus when multiple layers are involved.
- // Really what needs to happen is that this needs to go through layout to ask which
- // layer the element belongs to, and have it send the scroll message to the
- // compositor.
- let rect = element.upcast::<Node>().bounding_content_box_or_zero();
-
- // In order to align with element edges, we snap to unscaled pixel boundaries, since
- // the paint thread currently does the same for drawing elements. This is important
- // for pages that require pixel perfect scroll positioning for proper display
- // (like Acid2). Since we don't have the device pixel ratio here, this might not be
- // accurate, but should work as long as the ratio is a whole number. Once #8275 is
- // fixed this should actually take into account the real device pixel ratio.
- (rect.origin.x.to_nearest_px() as f32, rect.origin.y.to_nearest_px() as f32)
- }).or_else(|| if fragment.is_empty() || fragment.eq_ignore_ascii_case("top") {
- // FIXME(stshine): this should be the origin of the stacking context space,
- // which may differ under the influence of writing mode.
- Some((0.0, 0.0))
- } else {
- None
- });
+ self.set_target_element(target.as_deref());
+
+ let point = target
+ .as_ref()
+ .map(|element| {
+ // FIXME(#8275, pcwalton): This is pretty bogus when multiple layers are involved.
+ // Really what needs to happen is that this needs to go through layout to ask which
+ // layer the element belongs to, and have it send the scroll message to the
+ // compositor.
+ let rect = element.upcast::<Node>().bounding_content_box_or_zero();
+
+ // In order to align with element edges, we snap to unscaled pixel boundaries, since
+ // the paint thread currently does the same for drawing elements. This is important
+ // for pages that require pixel perfect scroll positioning for proper display
+ // (like Acid2). Since we don't have the device pixel ratio here, this might not be
+ // accurate, but should work as long as the ratio is a whole number. Once #8275 is
+ // fixed this should actually take into account the real device pixel ratio.
+ (
+ rect.origin.x.to_nearest_px() as f32,
+ rect.origin.y.to_nearest_px() as f32,
+ )
+ })
+ .or_else(|| {
+ if fragment.is_empty() || fragment.eq_ignore_ascii_case("top") {
+ // FIXME(stshine): this should be the origin of the stacking context space,
+ // which may differ under the influence of writing mode.
+ Some((0.0, 0.0))
+ } else {
+ None
+ }
+ });
if let Some((x, y)) = point {
// Step 3
let global_scope = self.window.upcast::<GlobalScope>();
- let webrender_pipeline_id = global_scope.pipeline_id().to_webrender();
- self.window.perform_a_scroll(x,
- y,
- ClipId::root_scroll_node(webrender_pipeline_id),
- ScrollBehavior::Instant,
- target.r());
+ self.window.update_viewport_for_scroll(x, y);
+ self.window.perform_a_scroll(
+ x,
+ y,
+ global_scope.pipeline_id().root_scroll_id(),
+ ScrollBehavior::Instant,
+ target.as_deref(),
+ );
}
}
- fn get_anchor_by_name(&self, name: &str) -> Option<Root<Element>> {
- let check_anchor = |node: &HTMLAnchorElement| {
- let elem = node.upcast::<Element>();
- elem.get_attribute(&ns!(), &local_name!("name"))
- .map_or(false, |attr| &**attr.value() == name)
- };
- let doc_node = self.upcast::<Node>();
- doc_node.traverse_preorder()
- .filter_map(Root::downcast)
- .find(|node| check_anchor(&node))
- .map(Root::upcast)
+ 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))
+ })
}
// https://html.spec.whatwg.org/multipage/#current-document-readiness
pub fn set_ready_state(&self, state: DocumentReadyState) {
match state {
DocumentReadyState::Loading => {
- // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserconnected
- self.trigger_mozbrowser_event(MozBrowserEvent::Connected);
+ if self.window().is_top_level() {
+ self.send_to_embedder(EmbedderMsg::LoadStart);
+ }
update_with_current_time_ms(&self.dom_loading);
},
DocumentReadyState::Complete => {
- // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadend
- self.trigger_mozbrowser_event(MozBrowserEvent::LoadEnd);
+ if self.window().is_top_level() {
+ self.send_to_embedder(EmbedderMsg::LoadComplete);
+ }
update_with_current_time_ms(&self.dom_complete);
},
DocumentReadyState::Interactive => update_with_current_time_ms(&self.dom_interactive),
@@ -740,7 +1025,8 @@ impl Document {
self.ready_state.set(state);
- self.upcast::<EventTarget>().fire_event(atom!("readystatechange"));
+ self.upcast::<EventTarget>()
+ .fire_event(atom!("readystatechange"));
}
/// Return whether scripting is enabled or not
@@ -750,37 +1036,73 @@ impl Document {
/// Return the element that currently has focus.
// https://w3c.github.io/uievents/#events-focusevent-doc-focus
- pub fn get_focused_element(&self) -> Option<Root<Element>> {
+ pub fn get_focused_element(&self) -> Option<DomRoot<Element>> {
self.focused.get()
}
/// Initiate a new round of checking for elements requesting focus. The last element to call
/// `request_focus` before `commit_focus_transaction` is called will receive focus.
- pub fn begin_focus_transaction(&self) {
- self.possibly_focused.set(None);
+ fn begin_focus_transaction(&self) {
+ *self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default());
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule>
+ pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element) {
+ if Some(not_focusable) != self.focused.get().as_ref().map(|e| &**e) {
+ return;
+ }
+ self.request_focus(
+ self.GetBody().as_ref().map(|e| &*e.upcast()),
+ FocusType::Element,
+ )
}
/// Request that the given element receive focus once the current transaction is complete.
- pub fn request_focus(&self, elem: &Element) {
- if elem.is_focusable_area() {
- self.possibly_focused.set(Some(elem))
+ /// If None is passed, then whatever element is currently focused will no longer be focused
+ /// once the transaction is complete.
+ pub(crate) fn request_focus(&self, elem: Option<&Element>, focus_type: FocusType) {
+ 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);
}
}
/// Reassign the focus context to the element that last requested focus during this
/// transaction, or none if no elements requested it.
- pub fn commit_focus_transaction(&self, focus_type: FocusType) {
- if self.focused == self.possibly_focused.get().r() {
- return
+ fn commit_focus_transaction(&self, focus_type: FocusType) {
+ 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_ref().map(|e| &**e) {
+ return;
}
if let Some(ref elem) = self.focused.get() {
let node = elem.upcast::<Node>();
elem.set_focus_state(false);
// FIXME: pass appropriate relatedTarget
self.fire_focus_event(FocusEventType::Blur, node, None);
+
+ // Notify the embedder to hide the input method.
+ if elem.input_method_type().is_some() {
+ self.send_to_embedder(EmbedderMsg::HideIME);
+ }
}
- self.focused.set(self.possibly_focused.get().r());
+ self.focused.set(possibly_focused.as_ref().map(|e| &**e));
if let Some(ref elem) = self.focused.get() {
elem.set_focus_state(true);
@@ -790,9 +1112,41 @@ impl Document {
// Update the focus state for all elements in the focus chain.
// https://html.spec.whatwg.org/multipage/#focus-chain
if focus_type == FocusType::Element {
- let global_scope = self.window.upcast::<GlobalScope>();
- let event = ConstellationMsg::Focus(global_scope.pipeline_id());
- global_scope.constellation_chan().send(event).unwrap();
+ self.window().send_to_constellation(ScriptMsg::Focus);
+ }
+
+ // Notify the embedder to display an input method.
+ if let Some(kind) = elem.input_method_type() {
+ let rect = elem.upcast::<Node>().bounding_content_box_or_zero();
+ 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),
+ ));
}
}
}
@@ -800,36 +1154,59 @@ impl Document {
/// Handles any updates when the document's title has changed.
pub fn title_changed(&self) {
if self.browsing_context().is_some() {
- // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsertitlechange
- self.trigger_mozbrowser_event(MozBrowserEvent::TitleChange(String::from(self.Title())));
+ 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(ref chan) = global.devtools_chan() {
+ let _ = chan.send(ScriptToDevtoolsControlMsg::TitleChanged(
+ global.pipeline_id(),
+ title,
+ ));
+ }
+ }
+ }
- self.send_title_to_compositor();
+ /// Sends this document's title to the constellation.
+ pub fn send_title_to_embedder(&self) {
+ let window = self.window();
+ if window.is_top_level() {
+ let title = Some(String::from(self.Title()));
+ self.send_to_embedder(EmbedderMsg::ChangePageTitle(title));
}
}
- /// Sends this document's title to the compositor.
- pub fn send_title_to_compositor(&self) {
+ fn send_to_embedder(&self, msg: EmbedderMsg) {
let window = self.window();
- let global_scope = window.upcast::<GlobalScope>();
- global_scope
- .constellation_chan()
- .send(ConstellationMsg::SetTitle(global_scope.pipeline_id(),
- Some(String::from(self.Title()))))
- .unwrap();
+ window.send_to_embedder(msg);
}
pub fn dirty_all_nodes(&self) {
- let root = self.upcast::<Node>();
- for node in root.traverse_preorder() {
+ let root = match self.GetDocumentElement() {
+ Some(root) => root,
+ None => return,
+ };
+ for node in root
+ .upcast::<Node>()
+ .traverse_preorder(ShadowIncluding::Yes)
+ {
node.dirty(NodeDamage::OtherNodeDamage)
}
}
- pub fn handle_mouse_event(&self,
- js_runtime: *mut JSRuntime,
- button: MouseButton,
- client_point: Point2D<f32>,
- mouse_event_type: MouseEventType) {
+ #[allow(unsafe_code)]
+ pub unsafe fn handle_mouse_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,
+ ) {
let mouse_event_type_string = match mouse_event_type {
MouseEventType::Click => "click".to_owned(),
MouseEventType::MouseUp => "mouseup".to_owned(),
@@ -837,39 +1214,17 @@ impl Document {
};
debug!("{}: at {:?}", mouse_event_type_string, client_point);
- let node = match self.window.hit_test_query(client_point, false) {
- Some(node_address) => {
- debug!("node address is {:?}", node_address);
- node::from_untrusted_node_address(js_runtime, node_address)
- },
+ 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 el = match node.downcast::<Element>() {
- Some(el) => Root::from_ref(el),
- None => {
- let parent = node.GetParentNode();
- match parent.and_then(Root::downcast::<Element>) {
- Some(parent) => parent,
- None => return,
- }
- },
- };
-
- // If the target is an iframe, forward the event to the child document.
- if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
- if let Some(pipeline_id) = iframe.pipeline_id() {
- let rect = iframe.upcast::<Element>().GetBoundingClientRect();
- let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32);
- let child_point = client_point - child_origin;
-
- let event = CompositorEvent::MouseButtonEvent(mouse_event_type, button, child_point);
- let event = ConstellationMsg::ForwardEvent(pipeline_id, event);
- self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
- }
- return;
- }
-
let node = el.upcast::<Node>();
debug!("{} on {:?}", mouse_event_type_string, node.debug_str());
// Prevent click event if form control element is disabled.
@@ -879,28 +1234,37 @@ impl Document {
}
self.begin_focus_transaction();
+ self.request_focus(Some(&*el), FocusType::Element);
}
// https://w3c.github.io/uievents/#event-type-click
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, // TODO: Get real screen coordinates?
- false,
- false,
- false,
- false,
- 0i16,
- None);
+ 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, // TODO: Get real screen coordinates?
+ false,
+ false,
+ false,
+ false,
+ match &button {
+ MouseButton::Left => 0i16,
+ MouseButton::Middle => 1i16,
+ MouseButton::Right => 2i16,
+ },
+ pressed_mouse_buttons,
+ None,
+ point_in_node,
+ );
let event = event.upcast::<Event>();
// https://w3c.github.io/uievents/#trusted-events
@@ -908,7 +1272,11 @@ impl Document {
// https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps
let activatable = el.as_maybe_activatable();
match mouse_event_type {
- MouseEventType::Click => el.authentic_click_activation(event),
+ MouseEventType::Click => {
+ el.set_click_in_progress(true);
+ event.fire(node.upcast());
+ el.set_click_in_progress(false);
+ },
MouseEventType::MouseDown => {
if let Some(a) = activatable {
a.enter_formal_activation_state();
@@ -929,52 +1297,61 @@ impl Document {
if let MouseEventType::Click = mouse_event_type {
self.commit_focus_transaction(FocusType::Element);
- self.maybe_fire_dblclick(client_point, node);
+ self.maybe_fire_dblclick(client_point, node, pressed_mouse_buttons);
}
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::MouseEvent);
+ self.window
+ .reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
}
- fn maybe_fire_dblclick(&self, click_pos: Point2D<f32>, target: &Node) {
+ fn maybe_fire_dblclick(
+ &self,
+ click_pos: Point2D<f32>,
+ target: &Node,
+ pressed_mouse_buttons: u16,
+ ) {
// https://w3c.github.io/uievents/#event-type-dblclick
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(PREFS.get("dom.document.dblclick_timeout").as_u64()
- .unwrap_or(300));
- let DBL_CLICK_DIST_THRESHOLD = PREFS.get("dom.document.dblclick_dist").as_u64().unwrap_or(1);
+ 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;
// Calculate distance between this click and the previous click.
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 {
+ if now.duration_since(last_time) < DBL_CLICK_TIMEOUT &&
+ dist < DBL_CLICK_DIST_THRESHOLD as f64
+ {
// A double click has occurred if this click is within a certain time and dist. of previous click.
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,
- None);
+ 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,
+ );
event.upcast::<Event>().fire(target.upcast());
// When a double click occurs, self.last_click_info is left as None so that a
@@ -987,210 +1364,268 @@ impl Document {
*self.last_click_info.borrow_mut() = Some((now, click_pos));
}
- pub fn handle_touchpad_pressure_event(&self,
- js_runtime: *mut JSRuntime,
- client_point: Point2D<f32>,
- pressure: f32,
- phase_now: TouchpadPressurePhase) {
- let node = match self.window.hit_test_query(client_point, false) {
- Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address),
- None => return
- };
-
- let el = match node.downcast::<Element>() {
- Some(el) => Root::from_ref(el),
- None => {
- let parent = node.GetParentNode();
- match parent.and_then(Root::downcast::<Element>) {
- Some(parent) => parent,
- None => return
- }
- },
- };
-
- // If the target is an iframe, forward the event to the child document.
- if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
- if let Some(pipeline_id) = iframe.pipeline_id() {
- let rect = iframe.upcast::<Element>().GetBoundingClientRect();
- let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32);
- let child_point = client_point - child_origin;
-
- let event = CompositorEvent::TouchpadPressureEvent(child_point,
- pressure,
- phase_now);
- let event = ConstellationMsg::ForwardEvent(pipeline_id, event);
- self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
- }
- return;
- }
-
- let phase_before = self.touchpad_pressure_phase.get();
- self.touchpad_pressure_phase.set(phase_now);
-
- if phase_before == TouchpadPressurePhase::BeforeClick &&
- phase_now == TouchpadPressurePhase::BeforeClick {
- return;
- }
-
- let node = el.upcast::<Node>();
- let target = node.upcast();
-
- let force = match phase_now {
- TouchpadPressurePhase::BeforeClick => pressure,
- TouchpadPressurePhase::AfterFirstClick => 1. + pressure,
- TouchpadPressurePhase::AfterSecondClick => 2. + pressure,
- };
-
- if phase_now != TouchpadPressurePhase::BeforeClick {
- self.fire_forcetouch_event("servomouseforcechanged".to_owned(), target, force);
- }
-
- if phase_before != TouchpadPressurePhase::AfterSecondClick &&
- phase_now == TouchpadPressurePhase::AfterSecondClick {
- self.fire_forcetouch_event("servomouseforcedown".to_owned(), target, force);
- }
-
- if phase_before == TouchpadPressurePhase::AfterSecondClick &&
- phase_now != TouchpadPressurePhase::AfterSecondClick {
- self.fire_forcetouch_event("servomouseforceup".to_owned(), target, force);
- }
- }
-
- fn fire_forcetouch_event(&self, event_name: String, target: &EventTarget, force: f32) {
- let force_event = ForceTouchEvent::new(&self.window,
- DOMString::from(event_name),
- force);
- let event = force_event.upcast::<Event>();
- event.fire(target);
- }
-
- pub fn fire_mouse_event(&self, client_point: Point2D<f32>, target: &EventTarget, event_name: String) {
+ pub fn fire_mouse_event(
+ &self,
+ client_point: Point2D<f32>,
+ target: &EventTarget,
+ event_name: FireMouseEventType,
+ can_bubble: EventBubbles,
+ cancelable: EventCancelable,
+ pressed_mouse_buttons: u16,
+ ) {
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),
- EventBubbles::Bubbles,
- EventCancelable::Cancelable,
- Some(&self.window),
- 0i32,
- client_x,
- client_y,
- client_x,
- client_y,
- false,
- false,
- false,
- false,
- 0i16,
- None);
+ 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,
+ );
let event = mouse_event.upcast::<Event>();
event.fire(target);
}
- pub fn handle_mouse_move_event(&self,
- js_runtime: *mut JSRuntime,
- client_point: Option<Point2D<f32>>,
- prev_mouse_over_target: &MutNullableJS<Element>) {
- let client_point = match client_point {
- None => {
- // If there's no point, there's no target under the mouse
- // FIXME: dispatch mouseout here. We have no point.
- prev_mouse_over_target.set(None);
- return;
- }
- Some(client_point) => client_point,
- };
-
- let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| {
- let node = node::from_untrusted_node_address(js_runtime, address);
- node.inclusive_ancestors()
- .filter_map(Root::downcast::<Element>)
+ #[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,
+ ) {
+ 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()
});
- // Send mousemove event to topmost target, and forward it if it's an iframe
- if let Some(ref new_target) = maybe_new_target {
- // If the target is an iframe, forward the event to the child document.
- if let Some(iframe) = new_target.downcast::<HTMLIFrameElement>() {
- if let Some(pipeline_id) = iframe.pipeline_id() {
- let rect = iframe.upcast::<Element>().GetBoundingClientRect();
- let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32);
- let child_point = client_point - child_origin;
-
- let event = CompositorEvent::MouseMoveEvent(Some(child_point));
- let event = ConstellationMsg::ForwardEvent(pipeline_id, event);
- self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
- }
- return;
- }
-
- self.fire_mouse_event(client_point, new_target.upcast(), "mousemove".to_owned());
- }
-
- // Nothing more to do here, mousemove is sent,
- // and the element under the mouse hasn't changed.
- if maybe_new_target == prev_mouse_over_target.get() {
- return;
- }
-
- let old_target_is_ancestor_of_new_target = match (prev_mouse_over_target.get(), maybe_new_target.as_ref()) {
- (Some(old_target), Some(new_target))
- => old_target.upcast::<Node>().is_ancestor_of(new_target.upcast::<Node>()),
- _ => false,
+ 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);
+
// Here we know the target has changed, so we must update the state,
- // dispatch mouseout to the previous one, mouseover to the new one,
- if let Some(old_target) = prev_mouse_over_target.get() {
- // If the old target is an ancestor of the new target, this can be skipped
- // completely, since the node's hover state will be reseted below.
- if !old_target_is_ancestor_of_new_target {
- for element in old_target.upcast::<Node>()
- .inclusive_ancestors()
- .filter_map(Root::downcast::<Element>) {
- element.set_hover_state(false);
- element.set_active_state(false);
+ // dispatch mouseout to the previous one, mouseover to the new one.
+ if target_has_changed {
+ // Dispatch mouseout and mouseleave to previous target.
+ 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 the old target is an ancestor of the new target, this can be skipped
+ // completely, since the node's hover state will be reset below.
+ 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);
+ }
}
- }
- // Remove hover state to old target and its parents
- self.fire_mouse_event(client_point, old_target.upcast(), "mouseout".to_owned());
+ self.fire_mouse_event(
+ client_point,
+ old_target.upcast(),
+ FireMouseEventType::Out,
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable,
+ pressed_mouse_buttons,
+ );
- // TODO: Fire mouseleave here only if the old target is
- // not an ancestor of the new target.
- }
+ 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,
+ );
+ }
+ }
- if let Some(ref new_target) = maybe_new_target {
- for element in new_target.upcast::<Node>()
- .inclusive_ancestors()
- .filter_map(Root::downcast::<Element>) {
+ // Dispatch mouseover and mouseenter to new target.
+ 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(), "mouseover".to_owned());
+ self.fire_mouse_event(
+ client_point,
+ new_target.upcast(),
+ FireMouseEventType::Over,
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable,
+ pressed_mouse_buttons,
+ );
+
+ 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,
+ );
+ }
+
+ // Send mousemove event to topmost target, unless it's an iframe, in which case the
+ // compositor should have also sent an event to the inner document.
+ self.fire_mouse_event(
+ client_point,
+ new_target.upcast(),
+ FireMouseEventType::Move,
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable,
+ pressed_mouse_buttons,
+ );
+
+ // If the target has changed then store the current mouse over target for next frame.
+ if target_has_changed {
+ prev_mouse_over_target.set(maybe_new_target.as_deref());
+ self.window
+ .reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
+ }
+ }
+
+ 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,
+ ) {
+ 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),
+ };
- // TODO: Fire mouseenter here.
+ // We need to create a target chain in case the event target shares
+ // its boundaries with its ancestors.
+ 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);
+ }
+
+ // The order for dispatching mouseenter events starts from the topmost
+ // common ancestor of the event target and the related target.
+ if event_type == FireMouseEventType::Enter {
+ targets = targets.into_iter().rev().collect();
}
- // Store the current mouse over target for next frame.
- prev_mouse_over_target.set(maybe_new_target.r());
+ for target in targets {
+ self.fire_mouse_event(
+ client_point,
+ target.upcast(),
+ event_type,
+ EventBubbles::DoesNotBubble,
+ EventCancelable::NotCancelable,
+ pressed_mouse_buttons,
+ );
+ }
+ }
+
+ #[allow(unsafe_code)]
+ pub unsafe fn handle_wheel_event(
+ &self,
+ delta: WheelDelta,
+ client_point: Point2D<f32>,
+ node_address: Option<UntrustedNodeAddress>,
+ ) {
+ 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());
+
+ // https://w3c.github.io/uievents/#event-wheelevents
+ 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,
+ );
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::MouseEvent);
+ let event = event.upcast::<Event>();
+ event.set_trusted(true);
+
+ let target = node.upcast();
+ event.fire(target);
}
- pub fn handle_touch_event(&self,
- js_runtime: *mut JSRuntime,
- event_type: TouchEventType,
- touch_id: TouchId,
- point: Point2D<f32>)
- -> TouchEventResult {
+ #[allow(unsafe_code)]
+ pub unsafe fn handle_touch_event(
+ &self,
+ event_type: TouchEventType,
+ touch_id: TouchId,
+ point: Point2D<f32>,
+ node_address: Option<UntrustedNodeAddress>,
+ ) -> TouchEventResult {
let TouchId(identifier) = touch_id;
let event_name = match event_type {
@@ -1200,36 +1635,18 @@ impl Document {
TouchEventType::Cancel => "touchcancel",
};
- let node = match self.window.hit_test_query(point, false) {
- Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address),
- None => return TouchEventResult::Processed(false),
- };
- let el = match node.downcast::<Element>() {
- Some(el) => Root::from_ref(el),
- None => {
- let parent = node.GetParentNode();
- match parent.and_then(Root::downcast::<Element>) {
- Some(parent) => parent,
- None => return TouchEventResult::Processed(false),
- }
- },
+ 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,
};
- // If the target is an iframe, forward the event to the child document.
- if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
- if let Some(pipeline_id) = iframe.pipeline_id() {
- let rect = iframe.upcast::<Element>().GetBoundingClientRect();
- let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32);
- let child_point = point - child_origin;
-
- let event = CompositorEvent::TouchEvent(event_type, touch_id, child_point);
- let event = ConstellationMsg::ForwardEvent(pipeline_id, event);
- self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
- }
- return TouchEventResult::Forwarded;
- }
-
- let target = Root::upcast::<EventTarget>(el);
+ let target = DomRoot::upcast::<EventTarget>(el);
let window = &*self.window;
let client_x = Finite::wrap(point.x as f64);
@@ -1237,72 +1654,72 @@ impl Document {
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, // TODO: Get real screen coordinates?
- client_x,
- client_y,
- page_x,
- page_y);
+ let touch = Touch::new(
+ window, identifier, &target, client_x,
+ client_y, // TODO: Get real screen coordinates?
+ client_x, client_y, page_x, page_y,
+ );
match event_type {
TouchEventType::Down => {
// Add a new touch point
- self.active_touch_points.borrow_mut().push(JS::from_ref(&*touch));
- }
+ self.active_touch_points
+ .borrow_mut()
+ .push(Dom::from_ref(&*touch));
+ },
TouchEventType::Move => {
// Replace an existing touch point
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 = JS::from_ref(&*touch),
+ 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 => {
+ },
+ TouchEventType::Up | TouchEventType::Cancel => {
// Remove an existing touch point
let mut active_touch_points = self.active_touch_points.borrow_mut();
- match active_touch_points.iter().position(|t| t.Identifier() == identifier) {
+ 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 touches);
- touches.extend(self.active_touch_points.borrow().iter().cloned());
rooted_vec!(let mut target_touches);
- target_touches.extend(self.active_touch_points
- .borrow()
- .iter()
- .filter(|t| t.Target() == target)
- .cloned());
- rooted_vec!(let changed_touches <- once(touch));
-
- let event = TouchEvent::new(window,
- DOMString::from(event_name),
- EventBubbles::Bubbles,
- EventCancelable::Cancelable,
- Some(window),
- 0i32,
- &TouchList::new(window, touches.r()),
- &TouchList::new(window, changed_touches.r()),
- &TouchList::new(window, target_touches.r()),
- // FIXME: modifier keys
- false,
- false,
- false,
- false);
+ 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()),
+ // FIXME: modifier keys
+ false,
+ false,
+ false,
+ false,
+ );
let event = event.upcast::<Event>();
let result = event.fire(&target);
- window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::MouseEvent);
+ window.reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
match result {
EventStatus::Canceled => TouchEventResult::Processed(false),
@@ -1311,12 +1728,7 @@ impl Document {
}
/// The entry point for all key processing for web content
- pub fn dispatch_key_event(&self,
- ch: Option<char>,
- key: Key,
- state: KeyState,
- modifiers: KeyModifiers,
- constellation: &IpcSender<ConstellationMsg>) {
+ pub fn dispatch_key_event(&self, keyboard_event: ::keyboard_types::KeyboardEvent) {
let focused = self.get_focused_element();
let body = self.GetBody();
@@ -1326,130 +1738,137 @@ impl Document {
(&None, &None) => self.window.upcast(),
};
- let ctrl = modifiers.contains(CONTROL);
- let alt = modifiers.contains(ALT);
- let shift = modifiers.contains(SHIFT);
- let meta = modifiers.contains(SUPER);
-
- let is_composing = false;
- let is_repeating = state == KeyState::Repeated;
- let ev_type = DOMString::from(match state {
- KeyState::Pressed | KeyState::Repeated => "keydown",
- KeyState::Released => "keyup",
- }
- .to_owned());
-
- let props = KeyboardEvent::key_properties(ch, key, modifiers);
-
- let keyevent = KeyboardEvent::new(&self.window,
- ev_type,
- true,
- true,
- Some(&self.window),
- 0,
- ch,
- Some(key),
- DOMString::from(props.key_string.clone()),
- DOMString::from(props.code),
- props.location,
- is_repeating,
- is_composing,
- ctrl,
- alt,
- shift,
- meta,
- None,
- props.key_code);
+ 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(),
+ );
let event = keyevent.upcast::<Event>();
event.fire(target);
let mut cancel_state = event.get_cancel_state();
// https://w3c.github.io/uievents/#keys-cancelable-keys
- if state != KeyState::Released && props.is_printable() && cancel_state != EventDefault::Prevented {
+ if keyboard_event.state == KeyState::Down &&
+ is_character_value_key(&(keyboard_event.key)) &&
+ !keyboard_event.is_composing &&
+ cancel_state != EventDefault::Prevented
+ {
// https://w3c.github.io/uievents/#keypress-event-order
- let event = KeyboardEvent::new(&self.window,
- DOMString::from("keypress"),
- true,
- true,
- Some(&self.window),
- 0,
- ch,
- Some(key),
- DOMString::from(props.key_string),
- DOMString::from(props.code),
- props.location,
- is_repeating,
- is_composing,
- ctrl,
- alt,
- shift,
- meta,
- props.char_code,
- 0);
+ 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,
+ );
let ev = event.upcast::<Event>();
ev.fire(target);
cancel_state = ev.get_cancel_state();
}
if cancel_state == EventDefault::Allowed {
- constellation.send(ConstellationMsg::SendKeyEvent(ch, key, state, modifiers)).unwrap();
+ let msg = EmbedderMsg::Keyboard(keyboard_event.clone());
+ self.send_to_embedder(msg);
// This behavior is unspecced
// We are supposed to dispatch synthetic click activation for Space and/or Return,
// however *when* we do it is up to us.
// Here, we're dispatching it after the key event so the script has a chance to cancel it
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337
- match key {
- Key::Space if state == KeyState::Released => {
- let maybe_elem = target.downcast::<Element>();
- if let Some(el) = maybe_elem {
- synthetic_click_activation(el,
- false,
- false,
- false,
- false,
- ActivationSource::NotFromClick)
- }
+ 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"));
}
- Key::Enter if state == KeyState::Released => {
- let maybe_elem = target.downcast::<Element>();
- if let Some(el) = maybe_elem {
- if let Some(a) = el.as_maybe_activatable() {
- a.implicit_submission(ctrl, alt, shift, meta);
- }
- }
- }
- _ => (),
}
}
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::KeyEvent);
+ self.window.reflow(ReflowGoal::Full, ReflowReason::KeyEvent);
+ }
+
+ pub fn ime_dismissed(&self) {
+ self.request_focus(
+ self.GetBody().as_ref().map(|e| &*e.upcast()),
+ FocusType::Element,
+ )
+ }
+
+ pub fn dispatch_composition_event(
+ &self,
+ composition_event: ::keyboard_types::CompositionEvent,
+ ) {
+ // spec: https://w3c.github.io/uievents/#compositionstart
+ // spec: https://w3c.github.io/uievents/#compositionupdate
+ // spec: https://w3c.github.io/uievents/#compositionend
+ // > Event.target : focused element processing the composition
+ let focused = self.get_focused_element();
+ let target = if let Some(elem) = &focused {
+ elem.upcast()
+ } else {
+ // Event is only dispatched if there is a focused element.
+ 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),
+ );
+ let event = compositionevent.upcast::<Event>();
+ event.fire(target);
}
// https://dom.spec.whatwg.org/#converting-nodes-into-a-node
- pub fn node_from_nodes_and_strings(&self,
- mut nodes: Vec<NodeOrString>)
- -> Fallible<Root<Node>> {
+ pub fn node_from_nodes_and_strings(
+ &self,
+ mut nodes: Vec<NodeOrString>,
+ ) -> Fallible<DomRoot<Node>> {
if nodes.len() == 1 {
Ok(match nodes.pop().unwrap() {
NodeOrString::Node(node) => node,
- NodeOrString::String(string) => Root::upcast(self.CreateTextNode(string)),
+ NodeOrString::String(string) => DomRoot::upcast(self.CreateTextNode(string)),
})
} else {
- let fragment = Root::upcast::<Node>(self.CreateDocumentFragment());
+ let fragment = DomRoot::upcast::<Node>(self.CreateDocumentFragment());
for node in nodes {
match node {
NodeOrString::Node(node) => {
- try!(fragment.AppendChild(&node));
+ fragment.AppendChild(&node)?;
},
NodeOrString::String(string) => {
- let node = Root::upcast::<Node>(self.CreateTextNode(string));
+ let node = DomRoot::upcast::<Node>(self.CreateTextNode(string));
// No try!() here because appending a text node
// should not fail.
fragment.AppendChild(&node).unwrap();
- }
+ },
}
}
Ok(fragment)
@@ -1457,16 +1876,20 @@ impl Document {
}
pub fn get_body_attribute(&self, local_name: &LocalName) -> DOMString {
- match self.GetBody().and_then(Root::downcast::<HTMLBodyElement>) {
- Some(ref body) => {
- body.upcast::<Element>().get_string_attribute(local_name)
- },
+ 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) {
- if let Some(ref body) = self.GetBody().and_then(Root::downcast::<HTMLBodyElement>) {
+ 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);
@@ -1493,84 +1916,70 @@ impl Document {
}
pub fn invalidate_stylesheets(&self) {
- self.stylesheets_changed_since_reflow.set(true);
- *self.stylesheets.borrow_mut() = None;
+ self.stylesheets.borrow_mut().force_dirty(OriginSet::all());
+
// Mark the document element dirty so a reflow will be performed.
+ //
+ // FIXME(emilio): Use the DocumentStylesheetSet invalidation stuff.
if let Some(element) = self.GetDocumentElement() {
element.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged);
}
}
- pub fn get_and_reset_stylesheets_changed_since_reflow(&self) -> bool {
- let changed = self.stylesheets_changed_since_reflow.get();
- self.stylesheets_changed_since_reflow.set(false);
- changed
- }
-
- pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) {
- if PREFS.is_mozbrowser_enabled() {
- if let Some((parent_pipeline_id, _)) = self.window.parent_info() {
- let global_scope = self.window.upcast::<GlobalScope>();
- let event = ConstellationMsg::MozBrowserEvent(parent_pipeline_id,
- global_scope.pipeline_id(),
- event);
- global_scope.constellation_chan().send(event).unwrap();
- }
- }
- }
-
- /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe
+ /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe>
pub 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)));
-
- // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks:
- // we're guaranteed to already be in the "animation callbacks present" state.
- //
- // This reduces CPU usage by avoiding needless thread wakeups in the common case of
- // repeated rAF.
- //
- // TODO: Should tick animation only when document is visible
- if !self.running_animation_callbacks.get() {
- if !self.is_faking_animation_frames() {
- let global_scope = self.window.upcast::<GlobalScope>();
- let event = ConstellationMsg::ChangeRunningAnimationsState(
- global_scope.pipeline_id(),
- AnimationState::AnimationCallbacksPresent);
- global_scope.constellation_chan().send(event).unwrap();
- } else {
- let callback = FakeRequestAnimationFrameCallback {
- document: Trusted::new(self),
- };
- self.global()
- .schedule_callback(OneshotTimerCallback::FakeRequestAnimationFrame(callback),
- MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY));
- }
+ self.animation_frame_list
+ .borrow_mut()
+ .push((ident, Some(callback)));
+
+ // If we are running 'fake' animation frames, we unconditionally
+ // set up a one-shot timer for script to execute the rAF callbacks.
+ if self.is_faking_animation_frames() && self.window().visible() {
+ warn!("Scheduling fake animation frame. Animation frames tick too fast.");
+ let callback = FakeRequestAnimationFrameCallback {
+ document: Trusted::new(self),
+ };
+ self.global().schedule_callback(
+ OneshotTimerCallback::FakeRequestAnimationFrame(callback),
+ MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY),
+ );
+ } else if !self.running_animation_callbacks.get() {
+ // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks:
+ // we're guaranteed to already be in the "animation callbacks present" state.
+ //
+ // This reduces CPU usage by avoiding needless thread wakeups in the common case of
+ // repeated rAF.
+
+ let event =
+ ScriptMsg::ChangeRunningAnimationsState(AnimationState::AnimationCallbacksPresent);
+ self.window().send_to_constellation(event);
}
ident
}
- /// https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe
+ /// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe>
pub fn cancel_animation_frame(&self, ident: u32) {
let mut list = self.animation_frame_list.borrow_mut();
- if let Some(mut pair) = list.iter_mut().find(|pair| pair.0 == ident) {
+ if let Some(pair) = list.iter_mut().find(|pair| pair.0 == ident) {
pair.1 = None;
}
}
- /// https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks
+ /// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks>
pub fn run_the_animation_frame_callbacks(&self) {
rooted_vec!(let mut animation_frame_list);
mem::swap(
&mut *animation_frame_list,
- &mut *self.animation_frame_list.borrow_mut());
+ &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.window.Performance().Now();
+ let timing = self.global().performance().Now();
for (_, callback) in animation_frame_list.drain(..) {
if let Some(callback) = callback {
@@ -1580,9 +1989,24 @@ impl Document {
self.running_animation_callbacks.set(false);
- let spurious = !self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::RequestAnimationFrame);
+ let spurious = !self
+ .window
+ .reflow(ReflowGoal::Full, ReflowReason::RequestAnimationFrame);
+
+ if spurious && !was_faking_animation_frames {
+ // If the rAF callbacks did not mutate the DOM, then the
+ // reflow call above means that layout will not be invoked,
+ // and therefore no new frame will be sent to the compositor.
+ // If this happens, the compositor will not tick the animation
+ // and the next rAF will never be called! When this happens
+ // for several frames, then the spurious rAF detection below
+ // will kick in and use a timer to tick the callbacks. However,
+ // for the interim frames where we are deciding whether this rAF
+ // is considered spurious, we need to ensure that the layout
+ // and compositor *do* tick the animation.
+ self.window
+ .force_reflow(ReflowGoal::Full, ReflowReason::RequestAnimationFrame, None);
+ }
// Only send the animation change state message after running any callbacks.
// This means that if the animation callback adds a new callback for
@@ -1593,30 +2017,43 @@ impl Document {
// constellation to stop giving us video refresh callbacks, to save energy. (A spurious
// animation frame is one in which the callback did not mutate the DOM—that is, an
// animation frame that wasn't actually used for animation.)
- if self.animation_frame_list.borrow().is_empty() ||
- (!was_faking_animation_frames && self.is_faking_animation_frames()) {
- mem::swap(&mut *self.animation_frame_list.borrow_mut(),
- &mut *animation_frame_list);
- let global_scope = self.window.upcast::<GlobalScope>();
- let event = ConstellationMsg::ChangeRunningAnimationsState(
- global_scope.pipeline_id(),
- AnimationState::NoAnimationCallbacksPresent);
- global_scope.constellation_chan().send(event).unwrap();
+ let is_empty = self.animation_frame_list.borrow().is_empty();
+ if is_empty || (!was_faking_animation_frames && self.is_faking_animation_frames()) {
+ if is_empty {
+ // If the current animation frame list in the DOM instance is empty,
+ // we can reuse the original `Vec<T>` that we put on the stack to
+ // avoid allocating a new one next time an animation callback
+ // is queued.
+ 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);
}
// Update the counter of spurious animation frames.
if spurious {
if self.spurious_animation_frames.get() < SPURIOUS_ANIMATION_FRAME_THRESHOLD {
- self.spurious_animation_frames.set(self.spurious_animation_frames.get() + 1)
+ self.spurious_animation_frames
+ .set(self.spurious_animation_frames.get() + 1)
}
} else {
self.spurious_animation_frames.set(0)
}
}
- pub fn fetch_async(&self, load: LoadType,
- request: RequestInit,
- fetch_target: IpcSender<FetchResponseMsg>) {
+ pub fn fetch_async(
+ &self,
+ load: LoadType,
+ mut request: RequestBuilder,
+ fetch_target: IpcSender<FetchResponseMsg>,
+ ) {
+ request.csp_list = self.get_csp_list().map(|x| x.clone());
+ request.https_state = self.https_state.get();
let mut loader = self.loader.borrow_mut();
loader.fetch_async(load, request, fetch_target);
}
@@ -1638,13 +2075,16 @@ impl Document {
self.process_deferred_scripts();
},
LoadType::PageSource(_) => {
- if self.has_browsing_context {
+ if self.has_browsing_context && self.is_fully_active() {
+ // Note: if the document is not fully active, the layout thread will have exited already.
+ // The underlying problem might actually be that layout exits while it should be kept alive.
+ // See https://github.com/servo/servo/issues/22507
+
// Disarm the reflow timer and trigger the initial reflow.
self.reflow_timeout.set(None);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::FirstLoad);
+ self.window
+ .reflow(ReflowGoal::Full, ReflowReason::FirstLoad);
}
// Deferred scripts have to wait for page to finish loading,
@@ -1663,6 +2103,12 @@ impl Document {
// asap_in_order_script_loaded.
let loader = self.loader.borrow();
+
+ // Servo measures when the top-level content (not iframes) is loaded.
+ if (self.top_level_dom_complete.get() == 0) && loader.is_only_blocked_by_iframes() {
+ update_with_current_time_ms(&self.top_level_dom_complete);
+ }
+
if loader.is_blocked() || loader.events_inhibited() {
// Step 6.
return;
@@ -1671,9 +2117,146 @@ impl Document {
ScriptThread::mark_document_with_no_blocked_loads(self);
}
+ // https://html.spec.whatwg.org/multipage/#prompt-to-unload-a-document
+ pub fn prompt_to_unload(&self, recursive_flag: bool) -> bool {
+ // TODO: Step 1, increase the event loop's termination nesting level by 1.
+ // Step 2
+ self.incr_ignore_opens_during_unload_counter();
+ //Step 3-5.
+ 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.has_listeners_for(&event_target, &atom!("beforeunload"));
+ self.window.dispatch_event_with_target_override(&event);
+ // TODO: Step 6, decrease the event loop's termination nesting level by 1.
+ // Step 7
+ if has_listeners {
+ self.salvageable.set(false);
+ }
+ let mut can_unload = true;
+ // TODO: Step 8, also check sandboxing modals flag.
+ 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();
+ }
+ // Step 9
+ if !recursive_flag {
+ for iframe in self.iter_iframes() {
+ // TODO: handle the case of cross origin iframes.
+ let document = document_from_node(&*iframe);
+ can_unload = document.prompt_to_unload(true);
+ if !document.salvageable() {
+ self.salvageable.set(false);
+ }
+ if !can_unload {
+ break;
+ }
+ }
+ }
+ // Step 10
+ self.decr_ignore_opens_during_unload_counter();
+ can_unload
+ }
+
+ // https://html.spec.whatwg.org/multipage/#unload-a-document
+ pub fn unload(&self, recursive_flag: bool) {
+ // TODO: Step 1, increase the event loop's termination nesting level by 1.
+ // Step 2
+ self.incr_ignore_opens_during_unload_counter();
+ // Step 3-6
+ if self.page_showing.get() {
+ self.page_showing.set(false);
+ let event = PageTransitionEvent::new(
+ &self.window,
+ atom!("pagehide"),
+ false, // bubbles
+ false, // cancelable
+ self.salvageable.get(), // persisted
+ );
+ let event = event.upcast::<Event>();
+ event.set_trusted(true);
+ let _ = self.window.dispatch_event_with_target_override(&event);
+ // TODO Step 6, document visibility steps.
+ }
+ // Step 7
+ if !self.fired_unload.get() {
+ let event = Event::new(
+ &self.window.upcast(),
+ atom!("unload"),
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable,
+ );
+ event.set_trusted(true);
+ let event_target = self.window.upcast::<EventTarget>();
+ let has_listeners = event.has_listeners_for(&event_target, &atom!("unload"));
+ let _ = self.window.dispatch_event_with_target_override(&event);
+ self.fired_unload.set(true);
+ // Step 9
+ if has_listeners {
+ self.salvageable.set(false);
+ }
+ }
+ // TODO: Step 8, decrease the event loop's termination nesting level by 1.
+
+ // Step 13
+ if !recursive_flag {
+ for iframe in self.iter_iframes() {
+ // TODO: handle the case of cross origin iframes.
+ let document = document_from_node(&*iframe);
+ document.unload(true);
+ if !document.salvageable() {
+ self.salvageable.set(false);
+ }
+ }
+ }
+
+ let global_scope = self.window.upcast::<GlobalScope>();
+ // Step 10, 14
+ // https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps
+ if !self.salvageable.get() {
+ // Step 1 of clean-up steps.
+ global_scope.close_event_sources();
+ let msg = ScriptMsg::DiscardDocument;
+ let _ = global_scope.script_to_constellation_chan().send(msg);
+ }
+ // https://w3c.github.io/FileAPI/#lifeTime
+ global_scope.clean_up_all_file_resources();
+
+ // Step 15, End
+ self.decr_ignore_opens_during_unload_counter();
+ }
+
// https://html.spec.whatwg.org/multipage/#the-end
pub fn maybe_queue_document_completion(&self) {
- if self.loader.borrow().is_blocked() {
+ // https://html.spec.whatwg.org/multipage/#delaying-load-events-mode
+ 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,
+ };
+
+ // Note: if the document is not fully active, the layout thread will have exited already,
+ // and this method will panic.
+ // The underlying problem might actually be that layout exits while it should be kept alive.
+ // See https://github.com/servo/servo/issues/22507
+ 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 {
// Step 6.
return;
}
@@ -1684,11 +2267,90 @@ impl Document {
// The rest will ever run only once per document.
// Step 7.
debug!("Document loads are complete.");
- let handler = box DocumentProgressHandler::new(Trusted::new(self));
- self.window.dom_manipulation_task_source().queue(handler, self.window.upcast()).unwrap();
+ 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;
+ }
+
+ // Step 7.1.
+ document.set_ready_state(DocumentReadyState::Complete);
+
+ // Step 7.2.
+ if document.browsing_context().is_none() {
+ return;
+ }
+ let event = Event::new(
+ window.upcast(),
+ atom!("load"),
+ EventBubbles::DoesNotBubble,
+ EventCancelable::NotCancelable,
+ );
+ event.set_trusted(true);
+
+ // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart
+ update_with_current_time_ms(&document.load_event_start);
+
+ debug!("About to dispatch load for {:?}", document.url());
+ // FIXME(nox): Why are errors silenced here?
+ let _ = window.dispatch_event_with_target_override(
+ &event,
+ );
+
+ // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd
+ update_with_current_time_ms(&document.load_event_end);
+
+ window.reflow(ReflowGoal::Full, ReflowReason::DocumentLoaded);
+
+ if let Some(fragment) = document.url().fragment() {
+ document.check_and_scroll_fragment(fragment);
+ }
+ }),
+ self.window.upcast(),
+ )
+ .unwrap();
// Step 8.
- // TODO: pageshow event.
+ 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, // bubbles
+ false, // cancelable
+ false, // persisted
+ );
+ let event = event.upcast::<Event>();
+ event.set_trusted(true);
+
+ // FIXME(nox): Why are errors silenced here?
+ let _ = window.dispatch_event_with_target_override(
+ &event,
+ );
+ }),
+ self.window.upcast(),
+ )
+ .unwrap();
+ }
// Step 9.
// TODO: pending application cache download process tasks.
@@ -1699,16 +2361,48 @@ impl Document {
// Step 11.
// TODO: ready for post-load tasks.
- // Step 12.
- // TODO: completely loaded.
+ // The dom.webxr.sessionavailable pref allows webxr
+ // content to immediately begin a session without waiting for a user gesture.
+ // TODO: should this only happen on the first document loaded?
+ // https://immersive-web.github.io/webxr/#user-intention
+ // https://github.com/immersive-web/navigation/issues/10
+ if pref!(dom.webxr.sessionavailable) {
+ if self.window.is_top_level() {
+ self.window.Navigator().Xr().dispatch_sessionavailable();
+ }
+ }
+
+ // Step 12: completely loaded.
+ // https://html.spec.whatwg.org/multipage/#completely-loaded
+ // TODO: fully implement "completely loaded".
+ 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);
+ // Note: this will, among others, result in the "iframe-load-event-steps" being run.
+ // https://html.spec.whatwg.org/multipage/#iframe-load-event-steps
+ document.notify_constellation_load();
+ }),
+ self.window.upcast(),
+ )
+ .unwrap();
+ }
}
// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
- pub fn set_pending_parsing_blocking_script(&self,
- script: &HTMLScriptElement,
- load: Option<ScriptResult>) {
+ 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));
+ *self.pending_parsing_blocking_script.borrow_mut() =
+ Some(PendingScript::new_with_load(script, load));
}
// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
@@ -1717,7 +2411,11 @@ impl Document {
}
/// https://html.spec.whatwg.org/multipage/#prepare-a-script step 22.d.
- pub fn pending_parsing_blocking_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
+ pub fn pending_parsing_blocking_script_loaded(
+ &self,
+ element: &HTMLScriptElement,
+ result: ScriptResult,
+ ) {
{
let mut blocking_script = self.pending_parsing_blocking_script.borrow_mut();
let entry = blocking_script.as_mut().unwrap();
@@ -1731,19 +2429,24 @@ impl Document {
if self.script_blocking_stylesheets_count.get() > 0 {
return;
}
- let pair = self.pending_parsing_blocking_script
+ 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);
+ self.get_current_parser()
+ .unwrap()
+ .resume_with_pending_parsing_blocking_script(&element, result);
}
}
// https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible
pub fn add_asap_script(&self, script: &HTMLScriptElement) {
- self.asap_scripts_set.borrow_mut().push(JS::from_ref(script));
+ self.asap_scripts_set
+ .borrow_mut()
+ .push(Dom::from_ref(script));
}
/// https://html.spec.whatwg.org/multipage/#the-end step 5.
@@ -1751,7 +2454,10 @@ impl Document {
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();
+ let idx = scripts
+ .iter()
+ .position(|entry| &**entry == element)
+ .unwrap();
scripts.swap_remove(idx);
}
element.execute(result);
@@ -1764,11 +2470,12 @@ impl Document {
/// https://html.spec.whatwg.org/multipage/#the-end step 5.
/// https://html.spec.whatwg.org/multipage/#prepare-a-script step 22.c.
- pub fn asap_in_order_script_loaded(&self,
- element: &HTMLScriptElement,
- result: ScriptResult) {
+ 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() {
+ while let Some((element, result)) = self
+ .asap_in_order_scripts_list
+ .take_next_ready_to_be_executed()
+ {
element.execute(result);
}
}
@@ -1795,7 +2502,8 @@ impl Document {
if self.script_blocking_stylesheets_count.get() > 0 {
return;
}
- if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed() {
+ if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed()
+ {
element.execute(result);
} else {
break;
@@ -1813,27 +2521,41 @@ impl Document {
return;
}
self.domcontentloaded_dispatched.set(true);
- assert!(self.ReadyState() != DocumentReadyState::Complete,
- "Complete before DOMContentLoaded?");
+ assert_ne!(
+ self.ReadyState(),
+ DocumentReadyState::Complete,
+ "Complete before DOMContentLoaded?"
+ );
update_with_current_time_ms(&self.dom_content_loaded_event_start);
// Step 4.1.
let window = self.window();
- window.dom_manipulation_task_source().queue_event(self.upcast(), atom!("DOMContentLoaded"),
- EventBubbles::Bubbles, EventCancelable::NotCancelable, 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"));
+ update_with_current_time_ms(&document.dom_content_loaded_event_end);
+ }),
+ window.upcast(),
+ )
+ .unwrap();
- window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::DOMContentLoaded);
- update_with_current_time_ms(&self.dom_content_loaded_event_end);
+ // html parsing has finished - set dom content loaded
+ self.interactive_time
+ .borrow()
+ .maybe_set_tti(self, InteractiveFlag::DOMContentLoaded);
// Step 4.2.
// TODO: client message queue.
}
// https://html.spec.whatwg.org/multipage/#abort-a-document
- fn abort(&self) {
+ pub fn abort(&self) {
// We need to inhibit the loader before anything else.
self.loader.borrow_mut().inhibit_events();
@@ -1852,43 +2574,67 @@ impl Document {
*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 {
+ // If any loads were canceled.
+ self.salvageable.set(false);
+ };
- // TODO: https://github.com/servo/servo/issues/15236
- self.window.cancel_all_tasks();
+ // Also Step 2.
+ // Note: the spec says to discard any tasks queued for fetch.
+ // This cancels all tasks on the networking task source, which might be too broad.
+ // See https://github.com/whatwg/html/issues/3837
+ self.window
+ .cancel_all_tasks_from_source(TaskSourceName::Networking);
// Step 3.
if let Some(parser) = self.get_current_parser() {
+ self.active_parser_was_aborted.set(true);
parser.abort();
- // TODO: salvageable flag.
+ self.salvageable.set(false);
}
}
pub fn notify_constellation_load(&self) {
- let global_scope = self.window.upcast::<GlobalScope>();
- let pipeline_id = global_scope.pipeline_id();
- let load_event = ConstellationMsg::LoadComplete(pipeline_id);
- global_scope.constellation_chan().send(load_event).unwrap();
+ 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<Root<ServoParser>> {
+ pub fn get_current_parser(&self) -> Option<DomRoot<ServoParser>> {
self.current_parser.get()
}
+ pub fn can_invoke_script(&self) -> bool {
+ match self.get_current_parser() {
+ Some(parser) => {
+ // It is safe to run script if the parser is not actively parsing,
+ // or if it is impossible to interact with the token stream.
+ parser.parser_is_not_active() ||
+ self.throw_on_dynamic_markup_insertion_counter.get() > 0
+ },
+ None => true,
+ }
+ }
+
/// Iterate over all iframes in the document.
- pub fn iter_iframes(&self) -> impl Iterator<Item=Root<HTMLIFrameElement>> {
+ pub fn iter_iframes(&self) -> impl Iterator<Item = DomRoot<HTMLIFrameElement>> {
self.upcast::<Node>()
- .traverse_preorder()
- .filter_map(Root::downcast::<HTMLIFrameElement>)
+ .traverse_preorder(ShadowIncluding::Yes)
+ .filter_map(DomRoot::downcast::<HTMLIFrameElement>)
}
/// Find an iframe element in the document.
- pub fn find_iframe(&self, frame_id: FrameId) -> Option<Root<HTMLIFrameElement>> {
+ pub fn find_iframe(
+ &self,
+ browsing_context_id: BrowsingContextId,
+ ) -> Option<DomRoot<HTMLIFrameElement>> {
self.iter_iframes()
- .find(|node| node.frame_id() == frame_id)
+ .find(|node| node.browsing_context_id() == Some(browsing_context_id))
}
pub fn get_dom_loading(&self) -> u64 {
@@ -1899,6 +2645,20 @@ impl Document {
self.dom_interactive.get()
}
+ pub fn set_navigation_start(&self, navigation_start: u64) {
+ 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) -> u64 {
self.dom_content_loaded_event_start.get()
}
@@ -1911,6 +2671,10 @@ impl Document {
self.dom_complete.get()
}
+ pub fn get_top_level_dom_complete(&self) -> u64 {
+ self.top_level_dom_complete.get()
+ }
+
pub fn get_load_event_start(&self) -> u64 {
self.load_event_start.get()
}
@@ -1919,106 +2683,349 @@ impl Document {
self.load_event_end.get()
}
+ pub fn get_unload_event_start(&self) -> u64 {
+ self.unload_event_start.get()
+ }
+
+ pub fn get_unload_event_end(&self) -> u64 {
+ self.unload_event_end.get()
+ }
+
+ pub fn start_tti(&self) {
+ if self.get_interactive_metrics().needs_tti() {
+ self.tti_window.borrow_mut().start_window();
+ }
+ }
+
+ /// check tti for this document
+ /// if it's been 10s since this doc encountered a task over 50ms, then we consider the
+ /// main thread available and try to set tti
+ 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()),
+ );
+ }
+ }
+
// https://html.spec.whatwg.org/multipage/#fire-a-focus-event
- fn fire_focus_event(&self, focus_event_type: FocusEventType, node: &Node, related_target: Option<&EventTarget>) {
+ fn fire_focus_event(
+ &self,
+ focus_event_type: FocusEventType,
+ node: &Node,
+ related_target: Option<&EventTarget>,
+ ) {
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);
+ let event = FocusEvent::new(
+ &self.window,
+ event_name,
+ does_bubble,
+ EventCancelable::NotCancelable,
+ Some(&self.window),
+ 0i32,
+ related_target,
+ );
let event = event.upcast::<Event>();
event.set_trusted(true);
let target = node.upcast();
event.fire(target);
}
- /// https://html.spec.whatwg.org/multipage/#cookie-averse-document-object
+ /// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object>
pub fn is_cookie_averse(&self) -> bool {
!self.has_browsing_context || !url_has_network_scheme(&self.url())
}
- pub fn nodes_from_point(&self, client_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
- let page_point =
- Point2D::new(client_point.x + self.window.PageXOffset() as f32,
- client_point.y + self.window.PageYOffset() as f32);
+ /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition>
+ 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 !self.window.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::NodesFromPoint(page_point, *client_point),
- ReflowReason::Query) {
- return vec!();
- };
+ // Step 1
+ if *namespace != ns!(html) {
+ return None;
+ }
+
+ // Step 2
+ if !self.has_browsing_context {
+ return None;
+ }
- self.window.layout().nodes_from_point_response()
+ // Step 3
+ 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;
+ #[cfg(feature = "xr-profile")]
+ {
+ time = time::precise_time_ns();
+ }
+ 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();
+ }
+
+ pub fn add_dirty_webgpu_canvas(&self, context: &GPUCanvasContext) {
+ self.dirty_webgpu_contexts
+ .borrow_mut()
+ .entry(context.context_id())
+ .or_insert_with(|| Dom::from_ref(context));
+ }
+
+ #[allow(unrooted_must_root)]
+ pub fn flush_dirty_webgpu_canvases(&self) {
+ self.dirty_webgpu_contexts
+ .borrow_mut()
+ .drain()
+ .for_each(|(_, context)| context.send_swap_chain_present());
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
+ // (This takes the filter as a method so the window named getter can use it too)
+ pub fn supported_property_names_impl(
+ &self,
+ nameditem_filter: fn(&Node, &Atom) -> bool,
+ ) -> Vec<DOMString> {
+ // The tricky part here is making sure we return the names in
+ // tree order, without just resorting to a full tree walkthrough.
+
+ let mut first_elements_with_name: HashMap<&Atom, &Dom<Element>> = HashMap::new();
+
+ // Get the first-in-tree-order element for each name in the name_map
+ let name_map = self.name_map.borrow();
+ name_map.iter().for_each(|(name, value)| {
+ if let Some(first) = value
+ .iter()
+ .find(|n| nameditem_filter((***n).upcast::<Node>(), &name))
+ {
+ first_elements_with_name.insert(name, first);
+ }
+ });
+
+ // Get the first-in-tree-order element for each name in the id_map;
+ // if we already had one from the name_map, figure out which of
+ // the two is first.
+ let id_map = self.id_map.borrow();
+ id_map.iter().for_each(|(name, value)| {
+ if let Some(first) = value
+ .iter()
+ .find(|n| nameditem_filter((***n).upcast::<Node>(), &name))
+ {
+ match first_elements_with_name.get(&name) {
+ None => {
+ first_elements_with_name.insert(name, first);
+ },
+ Some(el) => {
+ if *el != first && first.upcast::<Node>().is_before(el.upcast::<Node>()) {
+ first_elements_with_name.insert(name, first);
+ }
+ },
+ }
+ }
+ });
+
+ // first_elements_with_name now has our supported property names
+ // as keys, and the elements to order on as values.
+ let mut sortable_vec: Vec<(&Atom, &Dom<Element>)> = first_elements_with_name
+ .iter()
+ .map(|(k, v)| (*k, *v))
+ .collect();
+ sortable_vec.sort_unstable_by(|a, b| {
+ if a.1 == b.1 {
+ // This can happen if an img has an id different from its name,
+ // spec does not say which string to put first.
+ a.0.cmp(&b.0)
+ } else if a.1.upcast::<Node>().is_before(b.1.upcast::<Node>()) {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ });
+
+ // And now that they're sorted, we can return the keys
+ sortable_vec
+ .iter()
+ .map(|(k, _v)| DOMString::from(&***k))
+ .collect()
+ }
+}
+
+fn is_character_value_key(key: &Key) -> bool {
+ match key {
+ Key::Character(_) | Key::Enter => true,
+ _ => false,
}
}
-#[derive(PartialEq, HeapSizeOf)]
+#[derive(MallocSizeOf, PartialEq)]
pub enum DocumentSource {
FromParser,
NotFromParser,
}
#[allow(unsafe_code)]
-pub trait LayoutDocumentHelpers {
- unsafe fn is_html_document_for_layout(&self) -> bool;
- unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)>;
- unsafe fn needs_paint_from_layout(&self);
- unsafe fn will_paint(&self);
- unsafe fn quirks_mode(&self) -> QuirksMode;
- unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock;
+pub trait LayoutDocumentHelpers<'dom> {
+ fn is_html_document_for_layout(self) -> bool;
+ unsafe fn needs_paint_from_layout(self);
+ unsafe fn will_paint(self);
+ 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;
+ unsafe fn flush_shadow_roots_stylesheets(self);
}
#[allow(unsafe_code)]
-impl LayoutDocumentHelpers for LayoutJS<Document> {
+impl<'dom> LayoutDocumentHelpers<'dom> for LayoutDom<'dom, Document> {
#[inline]
- unsafe fn is_html_document_for_layout(&self) -> bool {
- (*self.unsafe_get()).is_html_document
+ fn is_html_document_for_layout(self) -> bool {
+ unsafe { self.unsafe_get().is_html_document }
}
#[inline]
- #[allow(unrooted_must_root)]
- unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)> {
- let mut elements = (*self.unsafe_get()).pending_restyles.borrow_mut_for_layout();
- // Elements were in a document when they were adding to this list, but that
- // may no longer be true when the next layout occurs.
- let result = elements.drain()
- .map(|(k, v)| (k.to_layout(), v))
- .filter(|&(ref k, _)| k.upcast::<Node>().get_flag(IS_IN_DOC))
- .collect();
- result
+ unsafe fn needs_paint_from_layout(self) {
+ (*self.unsafe_get()).needs_paint.set(true)
}
#[inline]
- unsafe fn needs_paint_from_layout(&self) {
- (*self.unsafe_get()).needs_paint.set(true)
+ unsafe fn will_paint(self) {
+ (*self.unsafe_get()).needs_paint.set(false)
}
#[inline]
- unsafe fn will_paint(&self) {
- (*self.unsafe_get()).needs_paint.set(false)
+ fn quirks_mode(self) -> QuirksMode {
+ unsafe { self.unsafe_get().quirks_mode.get() }
}
#[inline]
- unsafe fn quirks_mode(&self) -> QuirksMode {
- (*self.unsafe_get()).quirks_mode()
+ fn style_shared_lock(self) -> &'dom StyleSharedRwLock {
+ unsafe { self.unsafe_get().style_shared_lock() }
}
#[inline]
- unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock {
- (*self.unsafe_get()).style_shared_lock()
+ fn shadow_roots(self) -> Vec<LayoutDom<'dom, ShadowRoot>> {
+ // FIXME(nox): We should just return a
+ // &'dom HashSet<LayoutDom<'dom, ShadowRoot>> here but not until
+ // I rework the ToLayout trait as mentioned in
+ // LayoutDom::to_layout_slice.
+ unsafe {
+ self.unsafe_get()
+ .shadow_roots
+ .borrow_for_layout()
+ .iter()
+ .map(|sr| sr.to_layout())
+ .collect()
+ }
+ }
+
+ #[inline]
+ fn shadow_roots_styles_changed(self) -> bool {
+ unsafe { self.unsafe_get().shadow_roots_styles_changed.get() }
+ }
+
+ #[inline]
+ unsafe fn flush_shadow_roots_stylesheets(self) {
+ (*self.unsafe_get()).flush_shadow_roots_stylesheets()
}
}
// https://html.spec.whatwg.org/multipage/#is-a-registrable-domain-suffix-of-or-is-equal-to
// The spec says to return a bool, we actually return an Option<Host> containing
// the parsed host in the successful case, to avoid having to re-parse the host.
-fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, original_host: Host) -> Option<Host> {
+fn get_registrable_domain_suffix_of_or_is_equal_to(
+ host_suffix_string: &str,
+ original_host: Host,
+) -> Option<Host> {
// Step 1
if host_suffix_string.is_empty() {
return None;
@@ -2043,10 +3050,9 @@ fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, ori
};
// Step 4.2
- let (prefix, suffix) = match original_host.len().checked_sub(host.len()) {
- Some(index) => original_host.split_at(index),
- None => 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;
}
@@ -2064,7 +3070,7 @@ fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, ori
Some(host)
}
-/// https://url.spec.whatwg.org/#network-scheme
+/// <https://url.spec.whatwg.org/#network-scheme>
fn url_has_network_scheme(url: &ServoUrl) -> bool {
match url.scheme() {
"ftp" | "http" | "https" => true,
@@ -2072,26 +3078,28 @@ fn url_has_network_scheme(url: &ServoUrl) -> bool {
}
}
-#[derive(Copy, Clone, HeapSizeOf, JSTraceable, PartialEq, Eq)]
+#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
pub enum HasBrowsingContext {
No,
Yes,
}
impl Document {
- pub fn new_inherited(window: &Window,
- has_browsing_context: HasBrowsingContext,
- url: Option<ServoUrl>,
- origin: MutableOrigin,
- is_html_document: IsHTMLDocument,
- content_type: Option<DOMString>,
- last_modified: Option<String>,
- activity: DocumentActivity,
- source: DocumentSource,
- doc_loader: DocumentLoader,
- referrer: Option<String>,
- referrer_policy: Option<ReferrerPolicy>)
- -> Document {
+ 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>,
+ referrer_policy: Option<ReferrerPolicy>,
+ canceller: FetchCanceller,
+ ) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
@@ -2100,33 +3108,44 @@ impl Document {
(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 {
+ // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
+ IsHTMLDocument::HTMLDocument => mime::TEXT_HTML,
+ // https://dom.spec.whatwg.org/#concept-document-content-type
+ 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(),
- window: JS::from_ref(window),
- has_browsing_context: has_browsing_context == HasBrowsingContext::Yes,
+ document_or_shadow_root: DocumentOrShadowRoot::new(window),
+ window: Dom::from_ref(window),
+ has_browsing_context,
implementation: Default::default(),
- location: Default::default(),
- content_type: match content_type {
- Some(string) => string,
- None => DOMString::from(match is_html_document {
- // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
- IsHTMLDocument::HTMLDocument => "text/html",
- // https://dom.spec.whatwg.org/#concept-document-content-type
- IsHTMLDocument::NonHTMLDocument => "application/xml",
- }),
- },
+ content_type,
last_modified: last_modified,
- url: DOMRefCell::new(url),
+ url: DomRefCell::new(url),
// https://dom.spec.whatwg.org/#concept-document-quirks
quirks_mode: Cell::new(QuirksMode::NoQuirks),
+ id_map: DomRefCell::new(HashMap::new()),
+ name_map: DomRefCell::new(HashMap::new()),
// https://dom.spec.whatwg.org/#concept-document-encoding
- encoding: Cell::new(UTF_8),
+ encoding: Cell::new(encoding),
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
activity: Cell::new(activity),
- id_map: DOMRefCell::new(HashMap::new()),
- tag_map: DOMRefCell::new(HashMap::new()),
- tagns_map: DOMRefCell::new(HashMap::new()),
- classes_map: DOMRefCell::new(HashMap::new()),
+ tag_map: DomRefCell::new(HashMap::new()),
+ tagns_map: DomRefCell::new(HashMap::new()),
+ classes_map: DomRefCell::new(HashMap::new()),
images: Default::default(),
embeds: Default::default(),
links: Default::default(),
@@ -2139,7 +3158,7 @@ impl Document {
/// Per-process shared lock for author-origin stylesheets
///
/// FIXME: make it per-document or per-pipeline instead:
- /// https://github.com/servo/servo/issues/16027
+ /// <https://github.com/servo/servo/issues/16027>
/// (Need to figure out what to do with the style attribute
/// of elements adopted into another document.)
static ref PER_PROCESS_AUTHOR_SHARED_LOCK: StyleSharedRwLock = {
@@ -2149,12 +3168,11 @@ impl Document {
PER_PROCESS_AUTHOR_SHARED_LOCK.clone()
//StyleSharedRwLock::new()
},
- stylesheets: DOMRefCell::new(None),
- stylesheets_changed_since_reflow: Cell::new(false),
- stylesheet_list: MutNullableJS::new(None),
+ stylesheets: DomRefCell::new(DocumentStylesheetSet::new()),
+ stylesheet_list: MutNullableDom::new(None),
ready_state: Cell::new(ready_state),
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
- possibly_focused: Default::default(),
+ focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction),
focused: Default::default(),
current_script: Default::default(),
pending_parsing_blocking_script: Default::default(),
@@ -2162,85 +3180,194 @@ impl Document {
deferred_scripts: Default::default(),
asap_in_order_scripts_list: Default::default(),
asap_scripts_set: Default::default(),
- scripting_enabled: has_browsing_context == HasBrowsingContext::Yes,
+ scripting_enabled: has_browsing_context,
animation_frame_ident: Cell::new(0),
- animation_frame_list: DOMRefCell::new(vec![]),
+ animation_frame_list: DomRefCell::new(vec![]),
running_animation_callbacks: Cell::new(false),
- loader: DOMRefCell::new(doc_loader),
+ loader: DomRefCell::new(doc_loader),
current_parser: Default::default(),
reflow_timeout: Cell::new(None),
base_element: Default::default(),
appropriate_template_contents_owner_document: Default::default(),
- pending_restyles: DOMRefCell::new(HashMap::new()),
+ pending_restyles: DomRefCell::new(HashMap::new()),
needs_paint: Cell::new(false),
- active_touch_points: DOMRefCell::new(Vec::new()),
+ active_touch_points: DomRefCell::new(Vec::new()),
dom_loading: Cell::new(Default::default()),
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),
- touchpad_pressure_phase: Cell::new(TouchpadPressurePhase::BeforeClick),
origin: origin,
referrer: referrer,
referrer_policy: Cell::new(referrer_policy),
- target_element: MutNullableJS::new(None),
- last_click_info: DOMRefCell::new(None),
+ target_element: MutNullableDom::new(None),
+ 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: MutNullableJS::new(None),
+ fullscreen_element: MutNullableDom::new(None),
form_id_listener_map: Default::default(),
+ interactive_time: DomRefCell::new(interactive_time),
+ tti_window: DomRefCell::new(InteractiveWindow::new()),
+ canceller: 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(HashMap::new()),
+ dirty_webgpu_contexts: DomRefCell::new(HashMap::new()),
+ csp_list: DomRefCell::new(None),
+ 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(),
}
}
+ pub fn set_csp_list(&self, csp_list: Option<CspList>) {
+ *self.csp_list.borrow_mut() = csp_list;
+ }
+
+ pub fn get_csp_list(&self) -> Option<Ref<CspList>> {
+ ref_filter_map(self.csp_list.borrow(), Option::as_ref)
+ }
+
+ /// https://www.w3.org/TR/CSP/#should-block-inline
+ 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())),
+ };
+ // TODO: Instead of ignoring violations, report them.
+ self.get_csp_list()
+ .map(|c| {
+ c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
+ .0
+ })
+ .unwrap_or(csp::CheckResult::Allowed)
+ }
+
+ /// Prevent any JS or layout from running until the corresponding call to
+ /// `remove_script_and_layout_blocker`. Used to isolate periods in which
+ /// the DOM is in an unstable state and should not be exposed to arbitrary
+ /// web content. Any attempts to invoke content JS or query layout during
+ /// that time will trigger a panic. `add_delayed_task` will cause the
+ /// provided task to be executed as soon as the last blocker is removed.
+ pub fn add_script_and_layout_blocker(&self) {
+ self.script_and_layout_blockers
+ .set(self.script_and_layout_blockers.get() + 1);
+ }
+
+ /// Terminate the period in which JS or layout is disallowed from running.
+ /// If no further blockers remain, any delayed tasks in the queue will
+ /// be executed in queue order until the queue is empty.
+ 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();
+ }
+ }
+
+ /// Enqueue a task to run as soon as any JS and layout blockers are removed.
+ pub fn add_delayed_task<T: 'static + TaskBox>(&self, task: T) {
+ self.delayed_tasks.borrow_mut().push(Box::new(task));
+ }
+
+ /// Assert that the DOM is in a state that will allow running content JS or
+ /// performing a layout operation.
+ 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"
+ );
+ }
+
// https://dom.spec.whatwg.org/#dom-document-document
- pub fn Constructor(window: &Window) -> Fallible<Root<Document>> {
+ #[allow(non_snake_case)]
+ pub fn Constructor(window: &Window) -> Fallible<DomRoot<Document>> {
let doc = window.Document();
let docloader = DocumentLoader::new(&*doc.loader());
- Ok(Document::new(window,
- HasBrowsingContext::No,
- None,
- doc.origin().clone(),
- IsHTMLDocument::NonHTMLDocument,
- None,
- None,
- DocumentActivity::Inactive,
- DocumentSource::NotFromParser,
- docloader,
- None,
- None))
- }
-
- pub fn new(window: &Window,
- has_browsing_context: HasBrowsingContext,
- url: Option<ServoUrl>,
- origin: MutableOrigin,
- doctype: IsHTMLDocument,
- content_type: Option<DOMString>,
- last_modified: Option<String>,
- activity: DocumentActivity,
- source: DocumentSource,
- doc_loader: DocumentLoader,
- referrer: Option<String>,
- referrer_policy: Option<ReferrerPolicy>)
- -> Root<Document> {
- let document = reflect_dom_object(box Document::new_inherited(window,
- has_browsing_context,
- url,
- origin,
- doctype,
- content_type,
- last_modified,
- activity,
- source,
- doc_loader,
- referrer,
- referrer_policy),
- window,
- DocumentBinding::Wrap);
+ Ok(Document::new(
+ window,
+ HasBrowsingContext::No,
+ None,
+ doc.origin().clone(),
+ IsHTMLDocument::NonHTMLDocument,
+ None,
+ None,
+ DocumentActivity::Inactive,
+ DocumentSource::NotFromParser,
+ docloader,
+ None,
+ None,
+ Default::default(),
+ ))
+ }
+
+ 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>,
+ referrer_policy: Option<ReferrerPolicy>,
+ canceller: FetchCanceller,
+ ) -> DomRoot<Document> {
+ let document = reflect_dom_object(
+ Box::new(Document::new_inherited(
+ window,
+ has_browsing_context,
+ url,
+ origin,
+ doctype,
+ content_type,
+ last_modified,
+ activity,
+ source,
+ doc_loader,
+ referrer,
+ referrer_policy,
+ canceller,
+ )),
+ window,
+ );
{
let node = document.upcast::<Node>();
node.set_owner_doc(&document);
@@ -2248,34 +3375,68 @@ impl Document {
document
}
- fn create_node_list<F: Fn(&Node) -> bool>(&self, callback: F) -> Root<NodeList> {
- let doc = self.GetDocumentElement();
- let maybe_node = doc.r().map(Castable::upcast::<Node>);
- let iter = maybe_node.iter()
- .flat_map(|node| node.traverse_preorder())
- .filter(|node| callback(&node));
- NodeList::new_simple_list(&self.window, iter)
+ pub fn get_redirect_count(&self) -> u16 {
+ self.redirect_count.get()
}
- fn get_html_element(&self) -> Option<Root<HTMLHtmlElement>> {
- self.GetDocumentElement().and_then(Root::downcast)
+ pub fn set_redirect_count(&self, count: u16) {
+ self.redirect_count.set(count)
}
- // Ensure that the stylesheets vector is populated
- fn ensure_stylesheets(&self) {
- let mut stylesheets = self.stylesheets.borrow_mut();
- if stylesheets.is_none() {
- *stylesheets = Some(self.upcast::<Node>()
- .traverse_preorder()
- .filter_map(|node| {
- node.get_stylesheet()
- .map(|stylesheet| StylesheetInDocument {
- node: JS::from_ref(&*node),
- stylesheet: stylesheet,
- })
- })
- .collect());
+ 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))
+ }
+
+ // Note that document.getByName does not match on the same conditions
+ // as the document named getter.
+ 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().map_or(false, |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)
}
/// Return a reference to the per-document shared lock used in stylesheets.
@@ -2283,61 +3444,94 @@ impl Document {
&self.style_shared_lock
}
- /// Returns the list of stylesheets associated with nodes in the document.
- pub fn stylesheets(&self) -> Vec<Arc<Stylesheet>> {
- self.ensure_stylesheets();
- self.stylesheets.borrow().as_ref().unwrap().iter()
- .map(|s| s.stylesheet.clone())
- .collect()
- }
-
- pub fn with_style_sheets_in_document<F, T>(&self, mut f: F) -> T
- where F: FnMut(&[StylesheetInDocument]) -> T {
- self.ensure_stylesheets();
- f(&self.stylesheets.borrow().as_ref().unwrap())
+ /// Flushes the stylesheet list, and returns whether any stylesheet changed.
+ pub fn flush_stylesheets_for_reflow(&self) -> bool {
+ // NOTE(emilio): The invalidation machinery is used on the replicated
+ // list on the layout thread.
+ //
+ // FIXME(emilio): This really should differentiate between CSSOM changes
+ // and normal stylesheets additions / removals, because in the last case
+ // the layout thread already has that information and we could avoid
+ // dirtying the whole thing.
+ let mut stylesheets = self.stylesheets.borrow_mut();
+ let have_changed = stylesheets.has_changed();
+ stylesheets.flush_without_invalidation();
+ have_changed
}
- /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
- pub fn appropriate_template_contents_owner_document(&self) -> Root<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,
- // https://github.com/whatwg/html/issues/2109
- MutableOrigin::new(ImmutableOrigin::new_opaque()),
- doctype,
- None,
- None,
- DocumentActivity::Inactive,
- DocumentSource::NotFromParser,
- DocumentLoader::new(&self.loader()),
- None,
- None);
- new_doc.appropriate_template_contents_owner_document.set(Some(&new_doc));
- new_doc
- })
+ /// Returns a `Device` suitable for media query evaluation.
+ ///
+ /// FIXME(emilio): This really needs to be somehow more in sync with layout.
+ /// Feels like a hack.
+ pub fn device(&self) -> Device {
+ let window_size = self.window().window_size();
+ let viewport_size = window_size.initial_viewport;
+ let device_pixel_ratio = window_size.device_pixel_ratio;
+ Device::new(
+ MediaType::screen(),
+ self.quirks_mode(),
+ viewport_size,
+ device_pixel_ratio,
+ )
+ }
+
+ pub fn salvageable(&self) -> bool {
+ self.salvageable.get()
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document>
+ pub fn appropriate_template_contents_owner_document(&self) -> 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,
+ // https://github.com/whatwg/html/issues/2109
+ MutableOrigin::new(ImmutableOrigin::new_opaque()),
+ doctype,
+ None,
+ None,
+ DocumentActivity::Inactive,
+ DocumentSource::NotFromParser,
+ DocumentLoader::new(&self.loader()),
+ None,
+ None,
+ Default::default(),
+ );
+ new_doc
+ .appropriate_template_contents_owner_document
+ .set(Some(&new_doc));
+ new_doc
+ })
}
- pub fn get_element_by_id(&self, id: &Atom) -> Option<Root<Element>> {
- self.id_map.borrow().get(&id).map(|ref elements| Root::from_ref(&*(*elements)[0]))
+ pub fn get_element_by_id(&self, id: &Atom) -> Option<DomRoot<Element>> {
+ self.id_map
+ .borrow()
+ .get(&id)
+ .map(|ref 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| m.entry(JS::from_ref(el)).or_insert_with(PendingRestyle::new))
+ RefMut::map(map, |m| {
+ m.entry(Dom::from_ref(el))
+ .or_insert_with(PendingRestyle::new)
+ })
}
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(el.html_element_in_html_document()));
+ entry.snapshot = Some(Snapshot::new());
}
- let mut snapshot = entry.snapshot.as_mut().unwrap();
+ let snapshot = entry.snapshot.as_mut().unwrap();
if snapshot.state.is_none() {
snapshot.state = Some(el.state());
}
@@ -2351,25 +3545,40 @@ impl Document {
// could in theory do it in the DOM I think.
let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() {
- entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document()));
+ entry.snapshot = Some(Snapshot::new());
}
if attr.local_name() == &local_name!("style") {
- entry.hint |= RESTYLE_STYLE_ATTRIBUTE;
+ entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
}
- // FIXME(emilio): This should become something like
- // element.is_attribute_mapped(attr.local_name()).
- if attr.local_name() == &local_name!("width") ||
- attr.local_name() == &local_name!("height") {
- entry.hint |= RESTYLE_SELF;
+ if vtable_for(el.upcast()).attribute_affects_presentational_hints(attr) {
+ entry.hint.insert(RestyleHint::RESTYLE_SELF);
}
- let mut snapshot = entry.snapshot.as_mut().unwrap();
+ 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();
+ let attrs = el
+ .attrs()
+ .iter()
+ .map(|attr| (attr.identifier().clone(), attr.value().clone()))
+ .collect();
snapshot.attrs = Some(attrs);
}
}
@@ -2394,19 +3603,32 @@ impl Document {
element.set_target_state(true);
}
- self.window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::ElementStateChanged);
+ self.window
+ .reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged);
}
pub fn incr_ignore_destructive_writes_counter(&self) {
- self.ignore_destructive_writes_counter.set(
- self.ignore_destructive_writes_counter.get() + 1);
+ 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);
+ 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);
}
/// Whether we've seen so many spurious animation frames (i.e. animation frames that didn't
@@ -2416,10 +3638,11 @@ impl Document {
}
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
- #[allow(unrooted_must_root)]
pub fn enter_fullscreen(&self, pending: &Element) -> Rc<Promise> {
// Step 1
- let promise = Promise::new(self.global().r());
+ let in_realm_proof = AlreadyInRealm::assert(&self.global());
+ let promise =
+ Promise::new_in_current_realm(&self.global(), InRealm::Already(&in_realm_proof));
let mut error = false;
// Step 4
@@ -2429,12 +3652,12 @@ impl Document {
if pending.local_name().as_ref() != "math" {
error = true;
}
- }
+ },
ns!(svg) => {
if pending.local_name().as_ref() != "svg" {
error = true;
}
- }
+ },
ns!(html) => (),
_ => error = true,
}
@@ -2442,23 +3665,40 @@ impl Document {
if !pending.fullscreen_element_ready_check() {
error = true;
}
- // TODO fullscreen is supported
- // TODO This algorithm is allowed to request fullscreen.
+
+ if pref!(dom.fullscreen.test) {
+ // For reftests we just take over the current window,
+ // and don't try to really enter fullscreen.
+ info!("Tests don't really enter fullscreen.");
+ } else {
+ // TODO fullscreen is supported
+ // TODO This algorithm is allowed to request fullscreen.
+ warn!("Fullscreen not supported yet");
+ }
// Step 5 Parallel start
let window = self.window();
// Step 6
if !error {
- let event = ConstellationMsg::SetFullscreenState(true);
- window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
+ let event = EmbedderMsg::SetFullscreenState(true);
+ self.send_to_embedder(event);
}
+ let pipeline_id = self.window().pipeline_id();
+
// Step 7
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::RunnableMsg(ScriptThreadEventCategory::EnterFullscreen, handler);
+ // NOTE: This steps should be running in parallel
+ // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
+ 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();
@@ -2466,15 +3706,15 @@ impl Document {
}
// https://fullscreen.spec.whatwg.org/#exit-fullscreen
- #[allow(unrooted_must_root)]
pub fn exit_fullscreen(&self) -> Rc<Promise> {
let global = self.global();
// Step 1
- let promise = Promise::new(global.r());
+ let in_realm_proof = AlreadyInRealm::assert(&global);
+ let promise = Promise::new_in_current_realm(&global, InRealm::Already(&in_realm_proof));
// Step 2
if self.fullscreen_element.get().is_none() {
- promise.reject_error(global.get_cx(), Error::Type(String::from("fullscreen is null")));
- return promise
+ promise.reject_error(Error::Type(String::from("fullscreen is null")));
+ return promise;
}
// TODO Step 3-6
let element = self.fullscreen_element.get().unwrap();
@@ -2483,14 +3723,22 @@ impl Document {
let window = self.window();
// Step 8
- let event = ConstellationMsg::SetFullscreenState(false);
- window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
+ let event = EmbedderMsg::SetFullscreenState(false);
+ self.send_to_embedder(event);
// Step 9
- let trusted_element = Trusted::new(element.r());
+ let trusted_element = Trusted::new(&*element);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
- let script_msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::ExitFullscreen, handler);
+ let pipeline_id = Some(global.pipeline_id());
+ // NOTE: This steps should be running in parallel
+ // https://fullscreen.spec.whatwg.org/#exit-fullscreen
+ 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();
@@ -2513,9 +3761,11 @@ impl Document {
true
} else {
// Step 3
- window.GetFrameElement().map_or(false, |el| el.has_attribute(&local_name!("allowfullscreen")))
+ window.GetFrameElement().map_or(false, |el| {
+ el.has_attribute(&local_name!("allowfullscreen"))
+ })
}
- }
+ },
}
}
@@ -2523,14 +3773,233 @@ impl Document {
let map = self.form_id_listener_map.borrow();
if let Some(listeners) = map.get(id) {
for listener in listeners {
- listener.r().as_maybe_form_control()
- .expect("Element must be a form control")
- .reset_form_owner();
+ 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())
+ }
+
+ /// Add a stylesheet owned by `owner` to the list of document sheets, in the
+ /// correct tree position.
+ #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily.
+ 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();
+
+ match self.window.layout_chan() {
+ Some(chan) => chan
+ .send(Msg::AddStylesheet(
+ sheet.clone(),
+ insertion_point.as_ref().map(|s| s.sheet.clone()),
+ ))
+ .unwrap(),
+ None => return warn!("Layout channel unavailable"),
+ }
+
+ DocumentOrShadowRoot::add_stylesheet(
+ owner,
+ StylesheetSetRef::Document(stylesheets),
+ sheet,
+ insertion_point,
+ self.style_shared_lock(),
+ );
+ }
+
+ /// Remove a stylesheet owned by `owner` from the list of document sheets.
+ #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily.
+ pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) {
+ match self.window.layout_chan() {
+ Some(chan) => chan.send(Msg::RemoveStylesheet(s.clone())).unwrap(),
+ None => return warn!("Layout channel unavailable"),
+ }
+
+ DocumentOrShadowRoot::remove_stylesheet(
+ owner,
+ s,
+ StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()),
+ )
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:determine-the-value-of-a-named-property
+ // Support method for steps 1-3:
+ // Count if there are 0, 1, or >1 elements that match the name.
+ // (This takes the filter as a method so the window named getter can use it too)
+ fn look_up_named_elements(
+ &self,
+ name: &Atom,
+ nameditem_filter: fn(&Node, &Atom) -> bool,
+ ) -> ElementLookupResult {
+ // We might match because of either id==name or name==name, so there
+ // are two sets of nodes to look through, but we don't need a
+ // full tree traversal.
+ let id_map = self.id_map.borrow();
+ let name_map = self.name_map.borrow();
+ let id_vec = id_map.get(&name);
+ let name_vec = name_map.get(&name);
+
+ // If nothing can possibly have the name, exit fast
+ if id_vec.is_none() && name_vec.is_none() {
+ return ElementLookupResult::None;
+ }
+
+ let one_from_id_map = if let Some(id_vec) = id_vec {
+ let mut elements = id_vec
+ .iter()
+ .filter(|n| nameditem_filter((***n).upcast::<Node>(), &name))
+ .peekable();
+ if let Some(first) = elements.next() {
+ if elements.peek().is_none() {
+ Some(first)
+ } else {
+ return ElementLookupResult::Many;
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ let one_from_name_map = if let Some(name_vec) = name_vec {
+ let mut elements = name_vec
+ .iter()
+ .filter(|n| nameditem_filter((***n).upcast::<Node>(), &name))
+ .peekable();
+ if let Some(first) = elements.next() {
+ if elements.peek().is_none() {
+ Some(first)
+ } else {
+ return ElementLookupResult::Many;
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ // We now have two elements, or one element, or the same
+ // element twice, or no elements.
+ match (one_from_id_map, one_from_name_map) {
+ (Some(one), None) | (None, Some(one)) => {
+ ElementLookupResult::One(DomRoot::from_ref(&one))
+ },
+ (Some(one), Some(other)) => {
+ if one == other {
+ ElementLookupResult::One(DomRoot::from_ref(&one))
+ } else {
+ ElementLookupResult::Many
+ }
+ },
+ (None, None) => ElementLookupResult::None,
+ }
+ }
+
+ #[allow(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))
+ })
+ .collect()
+ }
+
+ 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 update_animation_timeline(&self) {
+ // Only update the time if it isn't being managed by a test.
+ if !pref!(layout.animations.test.enabled) {
+ self.animation_timeline.borrow_mut().update();
+ }
+
+ // We still want to update the animations, because our timeline
+ // value might have been advanced previously via the TestBinding.
+ 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 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);
+ }
+}
impl Element {
fn click_event_filter_by_disabled_state(&self) -> bool {
@@ -2548,14 +4017,34 @@ impl Element {
}
}
+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,
+ })
+ }
+}
+
impl DocumentMethods for Document {
+ // https://w3c.github.io/editing/ActiveDocuments/execCommand.html#querycommandsupported()
+ fn QueryCommandSupported(&self, _command: DOMString) -> bool {
+ false
+ }
+
// https://drafts.csswg.org/cssom/#dom-document-stylesheets
- fn StyleSheets(&self) -> Root<StyleSheetList> {
- self.stylesheet_list.or_init(|| StyleSheetList::new(&self.window, JS::from_ref(&self)))
+ fn StyleSheets(&self) -> DomRoot<StyleSheetList> {
+ self.stylesheet_list.or_init(|| {
+ StyleSheetList::new(
+ &self.window,
+ StyleSheetListOwner::Document(Dom::from_ref(self)),
+ )
+ })
}
// https://dom.spec.whatwg.org/#dom-document-implementation
- fn Implementation(&self) -> Root<DOMImplementation> {
+ fn Implementation(&self) -> DomRoot<DOMImplementation> {
self.implementation.or_init(|| DOMImplementation::new(self))
}
@@ -2565,16 +4054,12 @@ impl DocumentMethods for Document {
}
// https://html.spec.whatwg.org/multipage/#dom-document-activeelement
- fn GetActiveElement(&self) -> Option<Root<Element>> {
- // TODO: Step 2.
-
- match self.get_focused_element() {
- Some(element) => Some(element), // Step 3. and 4.
- None => match self.GetBody() { // Step 5.
- Some(body) => Some(Root::upcast(body)),
- None => self.GetDocumentElement(),
- },
- }
+ fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
+ self.document_or_shadow_root.get_active_element(
+ self.get_focused_element(),
+ self.GetBody(),
+ self.GetDocumentElement(),
+ )
}
// https://html.spec.whatwg.org/multipage/#dom-document-hasfocus
@@ -2622,7 +4107,8 @@ impl DocumentMethods for Document {
};
// Step 5
- let host = match get_registrable_domain_suffix_of_or_is_equal_to(&*value, effective_domain) {
+ let host = match get_registrable_domain_suffix_of_or_is_equal_to(&*value, effective_domain)
+ {
None => return Err(Error::Security),
Some(host) => host,
};
@@ -2637,7 +4123,7 @@ impl DocumentMethods for Document {
fn Referrer(&self) -> DOMString {
match self.referrer {
Some(ref referrer) => DOMString::from(referrer.to_string()),
- None => DOMString::new()
+ None => DOMString::new(),
}
}
@@ -2656,34 +4142,7 @@ impl DocumentMethods for Document {
// https://dom.spec.whatwg.org/#dom-document-characterset
fn CharacterSet(&self) -> DOMString {
- DOMString::from(match self.encoding.get().name() {
- "utf-8" => "UTF-8",
- "ibm866" => "IBM866",
- "iso-8859-2" => "ISO-8859-2",
- "iso-8859-3" => "ISO-8859-3",
- "iso-8859-4" => "ISO-8859-4",
- "iso-8859-5" => "ISO-8859-5",
- "iso-8859-6" => "ISO-8859-6",
- "iso-8859-7" => "ISO-8859-7",
- "iso-8859-8" => "ISO-8859-8",
- "iso-8859-8-i" => "ISO-8859-8-I",
- "iso-8859-10" => "ISO-8859-10",
- "iso-8859-13" => "ISO-8859-13",
- "iso-8859-14" => "ISO-8859-14",
- "iso-8859-15" => "ISO-8859-15",
- "iso-8859-16" => "ISO-8859-16",
- "koi8-r" => "KOI8-R",
- "koi8-u" => "KOI8-U",
- "gbk" => "GBK",
- "big5" => "Big5",
- "euc-jp" => "EUC-JP",
- "iso-2022-jp" => "ISO-2022-JP",
- "shift_jis" => "Shift_JIS",
- "euc-kr" => "EUC-KR",
- "utf-16be" => "UTF-16BE",
- "utf-16le" => "UTF-16LE",
- name => name
- })
+ DOMString::from(self.encoding.get().name())
}
// https://dom.spec.whatwg.org/#dom-document-charset
@@ -2698,75 +4157,80 @@ impl DocumentMethods for Document {
// https://dom.spec.whatwg.org/#dom-document-content_type
fn ContentType(&self) -> DOMString {
- self.content_type.clone()
+ DOMString::from(self.content_type.to_string())
}
// https://dom.spec.whatwg.org/#dom-document-doctype
- fn GetDoctype(&self) -> Option<Root<DocumentType>> {
- self.upcast::<Node>().children().filter_map(Root::downcast).next()
+ fn GetDoctype(&self) -> Option<DomRoot<DocumentType>> {
+ self.upcast::<Node>()
+ .children()
+ .filter_map(DomRoot::downcast)
+ .next()
}
// https://dom.spec.whatwg.org/#dom-document-documentelement
- fn GetDocumentElement(&self) -> Option<Root<Element>> {
+ fn GetDocumentElement(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>().child_elements().next()
}
// https://dom.spec.whatwg.org/#dom-document-getelementsbytagname
- fn GetElementsByTagName(&self, qualified_name: DOMString) -> Root<HTMLCollection> {
+ fn GetElementsByTagName(&self, qualified_name: DOMString) -> DomRoot<HTMLCollection> {
let qualified_name = LocalName::from(&*qualified_name);
match self.tag_map.borrow_mut().entry(qualified_name.clone()) {
- Occupied(entry) => Root::from_ref(entry.get()),
+ Occupied(entry) => DomRoot::from_ref(entry.get()),
Vacant(entry) => {
- let result = HTMLCollection::by_qualified_name(
- &self.window, self.upcast(), qualified_name);
- entry.insert(JS::from_ref(&*result));
+ let result =
+ HTMLCollection::by_qualified_name(&self.window, self.upcast(), qualified_name);
+ entry.insert(Dom::from_ref(&*result));
result
- }
+ },
}
}
// https://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens
- fn GetElementsByTagNameNS(&self,
- maybe_ns: Option<DOMString>,
- tag_name: DOMString)
- -> Root<HTMLCollection> {
+ 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(ns, local);
+ let qname = QualName::new(None, ns, local);
match self.tagns_map.borrow_mut().entry(qname.clone()) {
- Occupied(entry) => Root::from_ref(entry.get()),
+ Occupied(entry) => DomRoot::from_ref(entry.get()),
Vacant(entry) => {
let result = HTMLCollection::by_qual_tag_name(&self.window, self.upcast(), qname);
- entry.insert(JS::from_ref(&*result));
+ entry.insert(Dom::from_ref(&*result));
result
- }
+ },
}
}
// https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname
- fn GetElementsByClassName(&self, classes: DOMString) -> Root<HTMLCollection> {
- let class_atoms: Vec<Atom> = split_html_space_chars(&classes)
- .map(Atom::from)
- .collect();
+ fn GetElementsByClassName(&self, classes: DOMString) -> DomRoot<HTMLCollection> {
+ let class_atoms: Vec<Atom> = split_html_space_chars(&classes).map(Atom::from).collect();
match self.classes_map.borrow_mut().entry(class_atoms.clone()) {
- Occupied(entry) => Root::from_ref(entry.get()),
+ Occupied(entry) => DomRoot::from_ref(entry.get()),
Vacant(entry) => {
- let result = HTMLCollection::by_atomic_class_name(&self.window,
- self.upcast(),
- class_atoms);
- entry.insert(JS::from_ref(&*result));
+ let result =
+ HTMLCollection::by_atomic_class_name(&self.window, self.upcast(), class_atoms);
+ entry.insert(Dom::from_ref(&*result));
result
- }
+ },
}
}
// https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
- fn GetElementById(&self, id: DOMString) -> Option<Root<Element>> {
+ fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> {
self.get_element_by_id(&Atom::from(id))
}
// https://dom.spec.whatwg.org/#dom-document-createelement
- fn CreateElement(&self, mut local_name: DOMString) -> Fallible<Root<Element>> {
+ fn CreateElement(
+ &self,
+ mut local_name: DOMString,
+ options: StringOrElementCreationOptions,
+ ) -> Fallible<DomRoot<Element>> {
if xml_name_type(&local_name) == InvalidXMLName {
debug!("Not a valid element name");
return Err(Error::InvalidCharacter);
@@ -2775,29 +4239,58 @@ impl DocumentMethods for Document {
local_name.make_ascii_lowercase();
}
- let ns = if self.is_html_document || self.content_type == "application/xhtml+xml" {
+ let is_xhtml = self.content_type.type_() == mime::APPLICATION &&
+ self.content_type.subtype().as_str() == "xhtml" &&
+ self.content_type.suffix() == Some(mime::XML);
+
+ let ns = if self.is_html_document || is_xhtml {
ns!(html)
} else {
ns!()
};
- let name = QualName::new(ns, LocalName::from(local_name));
- Ok(Element::create(name, None, self, ElementCreator::ScriptCreated))
+ 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,
+ ))
}
// https://dom.spec.whatwg.org/#dom-document-createelementns
- fn CreateElementNS(&self,
- namespace: Option<DOMString>,
- qualified_name: DOMString)
- -> Fallible<Root<Element>> {
- let (namespace, prefix, local_name) = try!(validate_and_extract(namespace,
- &qualified_name));
- let name = QualName::new(namespace, local_name);
- Ok(Element::create(name, prefix, self, ElementCreator::ScriptCreated))
+ fn CreateElementNS(
+ &self,
+ namespace: Option<DOMString>,
+ qualified_name: DOMString,
+ options: StringOrElementCreationOptions,
+ ) -> 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,
+ ))
}
// https://dom.spec.whatwg.org/#dom-document-createattribute
- fn CreateAttribute(&self, mut local_name: DOMString) -> Fallible<Root<Attr>> {
+ fn CreateAttribute(&self, mut local_name: DOMString) -> Fallible<DomRoot<Attr>> {
if xml_name_type(&local_name) == InvalidXMLName {
debug!("Not a valid element name");
return Err(Error::InvalidCharacter);
@@ -2808,47 +4301,74 @@ impl DocumentMethods for Document {
let name = LocalName::from(local_name);
let value = AttrValue::String("".to_owned());
- Ok(Attr::new(&self.window, name.clone(), value, name, ns!(), None, None))
+ Ok(Attr::new(
+ &self,
+ name.clone(),
+ value,
+ name,
+ ns!(),
+ None,
+ None,
+ ))
}
// https://dom.spec.whatwg.org/#dom-document-createattributens
- fn CreateAttributeNS(&self,
- namespace: Option<DOMString>,
- qualified_name: DOMString)
- -> Fallible<Root<Attr>> {
- let (namespace, prefix, local_name) = try!(validate_and_extract(namespace,
- &qualified_name));
+ fn CreateAttributeNS(
+ &self,
+ namespace: Option<DOMString>,
+ qualified_name: DOMString,
+ ) -> 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.window,
- local_name,
- value,
- qualified_name,
- namespace,
- prefix,
- None))
+ Ok(Attr::new(
+ &self,
+ local_name,
+ value,
+ qualified_name,
+ namespace,
+ prefix,
+ None,
+ ))
}
// https://dom.spec.whatwg.org/#dom-document-createdocumentfragment
- fn CreateDocumentFragment(&self) -> Root<DocumentFragment> {
+ fn CreateDocumentFragment(&self) -> DomRoot<DocumentFragment> {
DocumentFragment::new(self)
}
// https://dom.spec.whatwg.org/#dom-document-createtextnode
- fn CreateTextNode(&self, data: DOMString) -> Root<Text> {
+ fn CreateTextNode(&self, data: DOMString) -> DomRoot<Text> {
Text::new(data, self)
}
+ // https://dom.spec.whatwg.org/#dom-document-createcdatasection
+ fn CreateCDATASection(&self, data: DOMString) -> Fallible<DomRoot<CDATASection>> {
+ // Step 1
+ if self.is_html_document {
+ return Err(Error::NotSupported);
+ }
+
+ // Step 2
+ if data.contains("]]>") {
+ return Err(Error::InvalidCharacter);
+ }
+
+ // Step 3
+ Ok(CDATASection::new(data, self))
+ }
+
// https://dom.spec.whatwg.org/#dom-document-createcomment
- fn CreateComment(&self, data: DOMString) -> Root<Comment> {
+ fn CreateComment(&self, data: DOMString) -> DomRoot<Comment> {
Comment::new(data, self)
}
// https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
- fn CreateProcessingInstruction(&self,
- target: DOMString,
- data: DOMString)
- -> Fallible<Root<ProcessingInstruction>> {
+ fn CreateProcessingInstruction(
+ &self,
+ target: DOMString,
+ data: DOMString,
+ ) -> Fallible<DomRoot<ProcessingInstruction>> {
// Step 1.
if xml_name_type(&target) == InvalidXMLName {
return Err(Error::InvalidCharacter);
@@ -2864,9 +4384,9 @@ impl DocumentMethods for Document {
}
// https://dom.spec.whatwg.org/#dom-document-importnode
- fn ImportNode(&self, node: &Node, deep: bool) -> Fallible<Root<Node>> {
+ fn ImportNode(&self, node: &Node, deep: bool) -> Fallible<DomRoot<Node>> {
// Step 1.
- if node.is::<Document>() {
+ if node.is::<Document>() || node.is::<ShadowRoot>() {
return Err(Error::NotSupported);
}
@@ -2881,66 +4401,67 @@ impl DocumentMethods for Document {
}
// https://dom.spec.whatwg.org/#dom-document-adoptnode
- fn AdoptNode(&self, node: &Node) -> Fallible<Root<Node>> {
+ fn AdoptNode(&self, node: &Node) -> Fallible<DomRoot<Node>> {
// Step 1.
if node.is::<Document>() {
return Err(Error::NotSupported);
}
// Step 2.
- Node::adopt(node, self);
+ if node.is::<ShadowRoot>() {
+ return Err(Error::HierarchyRequest);
+ }
// Step 3.
- Ok(Root::from_ref(node))
+ Node::adopt(node, self);
+
+ // Step 4.
+ Ok(DomRoot::from_ref(node))
}
// https://dom.spec.whatwg.org/#dom-document-createevent
- fn CreateEvent(&self, mut interface: DOMString) -> Fallible<Root<Event>> {
+ fn CreateEvent(&self, mut interface: DOMString) -> Fallible<DomRoot<Event>> {
interface.make_ascii_lowercase();
match &*interface {
- "beforeunloadevent" =>
- Ok(Root::upcast(BeforeUnloadEvent::new_uninitialized(&self.window))),
- "closeevent" =>
- Ok(Root::upcast(CloseEvent::new_uninitialized(self.window.upcast()))),
- "customevent" =>
- Ok(Root::upcast(CustomEvent::new_uninitialized(self.window.upcast()))),
- "errorevent" =>
- Ok(Root::upcast(ErrorEvent::new_uninitialized(self.window.upcast()))),
- "events" | "event" | "htmlevents" | "svgevents" =>
- Ok(Event::new_uninitialized(&self.window.upcast())),
- "focusevent" =>
- Ok(Root::upcast(FocusEvent::new_uninitialized(&self.window))),
- "hashchangeevent" =>
- Ok(Root::upcast(HashChangeEvent::new_uninitialized(&self.window))),
- "keyboardevent" =>
- Ok(Root::upcast(KeyboardEvent::new_uninitialized(&self.window))),
- "messageevent" =>
- Ok(Root::upcast(MessageEvent::new_uninitialized(self.window.upcast()))),
- "mouseevent" | "mouseevents" =>
- Ok(Root::upcast(MouseEvent::new_uninitialized(&self.window))),
- "pagetransitionevent" =>
- Ok(Root::upcast(PageTransitionEvent::new_uninitialized(&self.window))),
- "popstateevent" =>
- Ok(Root::upcast(PopStateEvent::new_uninitialized(&self.window))),
- "progressevent" =>
- Ok(Root::upcast(ProgressEvent::new_uninitialized(self.window.upcast()))),
- "storageevent" => {
- Ok(Root::upcast(StorageEvent::new_uninitialized(&self.window, "".into())))
+ "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(),
+ ))),
+ // FIXME(#25136): devicemotionevent, deviceorientationevent
+ // FIXME(#7529): dragevent
+ "events" | "event" | "htmlevents" | "svgevents" => {
+ Ok(Event::new_uninitialized(&self.window.upcast()))
},
- "touchevent" =>
- Ok(Root::upcast(
- TouchEvent::new_uninitialized(&self.window,
- &TouchList::new(&self.window, &[]),
- &TouchList::new(&self.window, &[]),
- &TouchList::new(&self.window, &[]),
- )
- )),
- "uievent" | "uievents" =>
- Ok(Root::upcast(UIEvent::new_uninitialized(&self.window))),
- "webglcontextevent" =>
- Ok(Root::upcast(WebGLContextEvent::new_uninitialized(&self.window))),
- _ =>
- Err(Error::NotSupported),
+ "focusevent" => Ok(DomRoot::upcast(FocusEvent::new_uninitialized(&self.window))),
+ "hashchangeevent" => Ok(DomRoot::upcast(HashChangeEvent::new_uninitialized(
+ &self.window,
+ ))),
+ "keyboardevent" => Ok(DomRoot::upcast(KeyboardEvent::new_uninitialized(
+ &self.window,
+ ))),
+ "messageevent" => Ok(DomRoot::upcast(MessageEvent::new_uninitialized(
+ self.window.upcast(),
+ ))),
+ "mouseevent" | "mouseevents" => {
+ Ok(DomRoot::upcast(MouseEvent::new_uninitialized(&self.window)))
+ },
+ "storageevent" => Ok(DomRoot::upcast(StorageEvent::new_uninitialized(
+ &self.window,
+ "".into(),
+ ))),
+ "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))),
+ _ => Err(Error::NotSupported),
}
}
@@ -2948,58 +4469,37 @@ impl DocumentMethods for Document {
fn LastModified(&self) -> DOMString {
match self.last_modified {
Some(ref t) => DOMString::from(t.clone()),
- None => DOMString::from(time::now().strftime("%m/%d/%Y %H:%M:%S").unwrap().to_string()),
+ None => DOMString::from(
+ time::now()
+ .strftime("%m/%d/%Y %H:%M:%S")
+ .unwrap()
+ .to_string(),
+ ),
}
}
// https://dom.spec.whatwg.org/#dom-document-createrange
- fn CreateRange(&self) -> Root<Range> {
+ fn CreateRange(&self) -> DomRoot<Range> {
Range::new_with_doc(self)
}
// https://dom.spec.whatwg.org/#dom-document-createnodeiteratorroot-whattoshow-filter
- fn CreateNodeIterator(&self,
- root: &Node,
- what_to_show: u32,
- filter: Option<Rc<NodeFilter>>)
- -> Root<NodeIterator> {
+ fn CreateNodeIterator(
+ &self,
+ root: &Node,
+ what_to_show: u32,
+ filter: Option<Rc<NodeFilter>>,
+ ) -> DomRoot<NodeIterator> {
NodeIterator::new(self, root, what_to_show, filter)
}
- // https://w3c.github.io/touch-events/#idl-def-Document
- fn CreateTouch(&self,
- window: &Window,
- target: &EventTarget,
- identifier: i32,
- page_x: Finite<f64>,
- page_y: Finite<f64>,
- screen_x: Finite<f64>,
- screen_y: Finite<f64>)
- -> Root<Touch> {
- let client_x = Finite::wrap(*page_x - window.PageXOffset() as f64);
- let client_y = Finite::wrap(*page_y - window.PageYOffset() as f64);
- Touch::new(window,
- identifier,
- target,
- screen_x,
- screen_y,
- client_x,
- client_y,
- page_x,
- page_y)
- }
-
- // https://w3c.github.io/touch-events/#idl-def-document-createtouchlist(touch...)
- fn CreateTouchList(&self, touches: &[&Touch]) -> Root<TouchList> {
- TouchList::new(&self.window, &touches)
- }
-
// https://dom.spec.whatwg.org/#dom-document-createtreewalker
- fn CreateTreeWalker(&self,
- root: &Node,
- what_to_show: u32,
- filter: Option<Rc<NodeFilter>>)
- -> Root<TreeWalker> {
+ fn CreateTreeWalker(
+ &self,
+ root: &Node,
+ what_to_show: u32,
+ filter: Option<Rc<NodeFilter>>,
+ ) -> DomRoot<TreeWalker> {
TreeWalker::new(self, root, what_to_show, filter)
}
@@ -3013,11 +4513,11 @@ impl DocumentMethods for Document {
.find(|node| {
node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
})
- .map(Root::upcast::<Node>)
+ .map(DomRoot::upcast::<Node>)
} else {
// Step 2.
root.upcast::<Node>()
- .traverse_preorder()
+ .traverse_preorder(ShadowIncluding::No)
.find(|node| node.is::<HTMLTitleElement>())
}
});
@@ -3044,37 +4544,44 @@ impl DocumentMethods for Document {
node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
});
match elem {
- Some(elem) => Root::upcast::<Node>(elem),
+ Some(elem) => DomRoot::upcast::<Node>(elem),
None => {
- let name = QualName::new(ns!(svg), local_name!("title"));
- let elem = Element::create(name, None, self, ElementCreator::ScriptCreated);
+ let name = QualName::new(None, ns!(svg), local_name!("title"));
+ let elem = Element::create(
+ name,
+ None,
+ self,
+ ElementCreator::ScriptCreated,
+ CustomElementCreationMode::Synchronous,
+ );
let parent = root.upcast::<Node>();
let child = elem.upcast::<Node>();
- parent.InsertBefore(child, parent.GetFirstChild().r())
- .unwrap()
- }
+ parent
+ .InsertBefore(child, parent.GetFirstChild().as_deref())
+ .unwrap()
+ },
}
} else if root.namespace() == &ns!(html) {
- let elem = root.upcast::<Node>()
- .traverse_preorder()
- .find(|node| node.is::<HTMLTitleElement>());
+ 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(ns!(html), local_name!("title"));
- let elem = Element::create(name,
- None,
- self,
- ElementCreator::ScriptCreated);
- head.upcast::<Node>()
- .AppendChild(elem.upcast())
- .unwrap()
- },
- None => return,
- }
- }
+ 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,
+ );
+ head.upcast::<Node>().AppendChild(elem.upcast()).unwrap()
+ },
+ None => return,
+ },
}
} else {
return;
@@ -3084,27 +4591,35 @@ impl DocumentMethods for Document {
}
// https://html.spec.whatwg.org/multipage/#dom-document-head
- fn GetHead(&self) -> Option<Root<HTMLHeadElement>> {
- self.get_html_element()
- .and_then(|root| root.upcast::<Node>().children().filter_map(Root::downcast).next())
+ fn GetHead(&self) -> Option<DomRoot<HTMLHeadElement>> {
+ self.get_html_element().and_then(|root| {
+ root.upcast::<Node>()
+ .children()
+ .filter_map(DomRoot::downcast)
+ .next()
+ })
}
// https://html.spec.whatwg.org/multipage/#dom-document-currentscript
- fn GetCurrentScript(&self) -> Option<Root<HTMLScriptElement>> {
+ fn GetCurrentScript(&self) -> Option<DomRoot<HTMLScriptElement>> {
self.current_script.get()
}
// https://html.spec.whatwg.org/multipage/#dom-document-body
- fn GetBody(&self) -> Option<Root<HTMLElement>> {
+ fn GetBody(&self) -> Option<DomRoot<HTMLElement>> {
self.get_html_element().and_then(|root| {
let node = root.upcast::<Node>();
- node.children().find(|child| {
- match child.type_id() {
- NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) |
- NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFrameSetElement)) => true,
- _ => false
- }
- }).map(|node| Root::downcast(node).unwrap())
+ node.children()
+ .find(|child| match child.type_id() {
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLBodyElement,
+ )) |
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLFrameSetElement,
+ )) => true,
+ _ => false,
+ })
+ .map(|node| DomRoot::downcast(node).unwrap())
})
}
@@ -3119,21 +4634,24 @@ impl DocumentMethods for Document {
let node = new_body.upcast::<Node>();
match node.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) |
- NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFrameSetElement)) => {}
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLFrameSetElement,
+ )) => {},
_ => return Err(Error::HierarchyRequest),
}
// Step 2.
let old_body = self.GetBody();
- if old_body.r() == Some(new_body) {
+ if old_body.as_deref() == Some(new_body) {
return Ok(());
}
- match (self.get_html_element(), &old_body) {
+ match (self.GetDocumentElement(), &old_body) {
// Step 3.
(Some(ref root), &Some(ref child)) => {
let root = root.upcast::<Node>();
- root.ReplaceChild(new_body.upcast(), child.upcast()).unwrap();
+ root.ReplaceChild(new_body.upcast(), child.upcast())
+ .unwrap();
},
// Step 4.
@@ -3143,106 +4661,100 @@ impl DocumentMethods for Document {
(Some(ref root), &None) => {
let root = root.upcast::<Node>();
root.AppendChild(new_body.upcast()).unwrap();
- }
+ },
}
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-document-getelementsbyname
- fn GetElementsByName(&self, name: DOMString) -> Root<NodeList> {
- self.create_node_list(|node| {
- let element = match node.downcast::<Element>() {
- Some(element) => element,
- None => return false,
- };
- if element.namespace() != &ns!(html) {
- return false;
- }
- element.get_attribute(&ns!(), &local_name!("name"))
- .map_or(false, |attr| &**attr.value() == &*name)
- })
+ fn GetElementsByName(&self, name: DOMString) -> DomRoot<NodeList> {
+ NodeList::new_elements_by_name_list(self.window(), self, name)
}
// https://html.spec.whatwg.org/multipage/#dom-document-images
- fn Images(&self) -> Root<HTMLCollection> {
+ fn Images(&self) -> DomRoot<HTMLCollection> {
self.images.or_init(|| {
- let filter = box ImagesFilter;
+ let filter = Box::new(ImagesFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-embeds
- fn Embeds(&self) -> Root<HTMLCollection> {
+ fn Embeds(&self) -> DomRoot<HTMLCollection> {
self.embeds.or_init(|| {
- let filter = box EmbedsFilter;
+ let filter = Box::new(EmbedsFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-plugins
- fn Plugins(&self) -> Root<HTMLCollection> {
+ fn Plugins(&self) -> DomRoot<HTMLCollection> {
self.Embeds()
}
// https://html.spec.whatwg.org/multipage/#dom-document-links
- fn Links(&self) -> Root<HTMLCollection> {
+ fn Links(&self) -> DomRoot<HTMLCollection> {
self.links.or_init(|| {
- let filter = box LinksFilter;
+ let filter = Box::new(LinksFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-forms
- fn Forms(&self) -> Root<HTMLCollection> {
+ fn Forms(&self) -> DomRoot<HTMLCollection> {
self.forms.or_init(|| {
- let filter = box FormsFilter;
+ let filter = Box::new(FormsFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-scripts
- fn Scripts(&self) -> Root<HTMLCollection> {
+ fn Scripts(&self) -> DomRoot<HTMLCollection> {
self.scripts.or_init(|| {
- let filter = box ScriptsFilter;
+ let filter = Box::new(ScriptsFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-anchors
- fn Anchors(&self) -> Root<HTMLCollection> {
+ fn Anchors(&self) -> DomRoot<HTMLCollection> {
self.anchors.or_init(|| {
- let filter = box AnchorsFilter;
+ let filter = Box::new(AnchorsFilter);
HTMLCollection::create(&self.window, self.upcast(), filter)
})
}
// https://html.spec.whatwg.org/multipage/#dom-document-applets
- fn Applets(&self) -> Root<HTMLCollection> {
- // FIXME: This should be return OBJECT elements containing applets.
- self.applets.or_init(|| {
- let filter = box AppletsFilter;
- HTMLCollection::create(&self.window, self.upcast(), filter)
- })
+ fn Applets(&self) -> DomRoot<HTMLCollection> {
+ self.applets
+ .or_init(|| HTMLCollection::always_empty(&self.window, self.upcast()))
}
// https://html.spec.whatwg.org/multipage/#dom-document-location
- fn GetLocation(&self) -> Option<Root<Location>> {
- self.browsing_context().map(|_| self.location.or_init(|| Location::new(&self.window)))
+ fn GetLocation(&self) -> Option<DomRoot<Location>> {
+ if self.is_fully_active() {
+ Some(self.window.Location())
+ } else {
+ None
+ }
}
// https://dom.spec.whatwg.org/#dom-parentnode-children
- fn Children(&self) -> Root<HTMLCollection> {
+ fn Children(&self) -> DomRoot<HTMLCollection> {
HTMLCollection::children(&self.window, self.upcast())
}
// https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild
- fn GetFirstElementChild(&self) -> Option<Root<Element>> {
+ fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>().child_elements().next()
}
// https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild
- fn GetLastElementChild(&self) -> Option<Root<Element>> {
- self.upcast::<Node>().rev_children().filter_map(Root::downcast).next()
+ fn GetLastElementChild(&self) -> Option<DomRoot<Element>> {
+ self.upcast::<Node>()
+ .rev_children()
+ .filter_map(DomRoot::downcast)
+ .next()
}
// https://dom.spec.whatwg.org/#dom-parentnode-childelementcount
@@ -3260,14 +4772,19 @@ impl DocumentMethods for Document {
self.upcast::<Node>().append(nodes)
}
+ // https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
+ fn ReplaceChildren(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
+ self.upcast::<Node>().replace_children(nodes)
+ }
+
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
- fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
+ fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> {
let root = self.upcast::<Node>();
root.query_selector(selectors)
}
// https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
- fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Root<NodeList>> {
+ fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> {
let root = self.upcast::<Node>();
root.query_selector_all(selectors)
}
@@ -3278,9 +4795,9 @@ impl DocumentMethods for Document {
}
// https://html.spec.whatwg.org/multipage/#dom-document-defaultview
- fn GetDefaultView(&self) -> Option<Root<Window>> {
+ fn GetDefaultView(&self) -> Option<DomRoot<Window>> {
if self.has_browsing_context {
- Some(Root::from_ref(&*self.window))
+ Some(DomRoot::from_ref(&*self.window))
} else {
None
}
@@ -3297,8 +4814,9 @@ impl DocumentMethods for Document {
}
let url = self.url();
- let (tx, rx) = ipc::channel().unwrap();
- let _ = self.window
+ 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));
@@ -3316,15 +4834,17 @@ impl DocumentMethods for Document {
return Err(Error::Security);
}
- if let Ok(cookie_header) = SetCookie::parse_header(&vec![cookie.to_string().into_bytes()]) {
- let cookies = cookie_header.0.into_iter().filter_map(|cookie| {
- cookie_rs::Cookie::parse(cookie).ok().map(Serde)
- }).collect();
- let _ = self.window
- .upcast::<GlobalScope>()
- .resource_threads()
- .send(SetCookiesForUrl(self.url(), cookies, NonHTTP));
- }
+ 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(())
}
@@ -3350,92 +4870,56 @@ impl DocumentMethods for Document {
#[allow(unsafe_code)]
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:dom-document-nameditem-filter
- unsafe fn NamedGetter(&self, _cx: *mut JSContext, name: DOMString) -> Option<NonZero<*mut JSObject>> {
- #[derive(JSTraceable, HeapSizeOf)]
+ fn NamedGetter(&self, _cx: JSContext, name: DOMString) -> Option<NonNull<JSObject>> {
+ #[derive(JSTraceable, MallocSizeOf)]
struct NamedElementFilter {
name: Atom,
}
impl CollectionFilter for NamedElementFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
- filter_by_name(&self.name, elem.upcast())
- }
- }
- // https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter
- fn filter_by_name(name: &Atom, node: &Node) -> bool {
- let html_elem_type = match node.type_id() {
- NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
- _ => return false,
- };
- let elem = match node.downcast::<Element>() {
- Some(elem) => elem,
- None => return false,
- };
- match html_elem_type {
- HTMLElementTypeId::HTMLAppletElement => {
- match elem.get_attribute(&ns!(), &local_name!("name")) {
- Some(ref attr) if attr.value().as_atom() == name => true,
- _ => {
- match elem.get_attribute(&ns!(), &local_name!("id")) {
- Some(ref attr) => attr.value().as_atom() == name,
- None => false,
- }
- },
- }
- },
- HTMLElementTypeId::HTMLFormElement => {
- match elem.get_attribute(&ns!(), &local_name!("name")) {
- Some(ref attr) => attr.value().as_atom() == name,
- None => false,
- }
- },
- HTMLElementTypeId::HTMLImageElement => {
- match elem.get_attribute(&ns!(), &local_name!("name")) {
- Some(ref attr) => {
- if attr.value().as_atom() == name {
- true
- } else {
- match elem.get_attribute(&ns!(), &local_name!("id")) {
- Some(ref attr) => attr.value().as_atom() == name,
- None => false,
- }
- }
- },
- None => false,
- }
- },
- // TODO: Handle <embed>, <iframe> and <object>.
- _ => false,
+ elem.upcast::<Node>().is_document_named_item(&self.name)
}
}
+
let name = Atom::from(name);
- let root = self.upcast::<Node>();
- {
- // Step 1.
- let mut elements = root.traverse_preorder()
- .filter(|node| filter_by_name(&name, &node))
- .peekable();
- if let Some(first) = elements.next() {
- if elements.peek().is_none() {
- // TODO: Step 2.
- // Step 3.
- return Some(NonZero::new(first.reflector().get_jsobject().get()));
- }
- } else {
+
+ match self.look_up_named_elements(&name, Node::is_document_named_item) {
+ ElementLookupResult::None => {
return None;
- }
- }
- // Step 4.
- let filter = NamedElementFilter {
- name: name,
+ },
+ ElementLookupResult::One(element) => {
+ if let Some(nested_proxy) = element
+ .downcast::<HTMLIFrameElement>()
+ .and_then(|iframe| iframe.GetContentWindow())
+ {
+ unsafe {
+ return Some(NonNull::new_unchecked(
+ nested_proxy.reflector().get_jsobject().get(),
+ ));
+ }
+ }
+ unsafe {
+ return Some(NonNull::new_unchecked(
+ element.reflector().get_jsobject().get(),
+ ));
+ }
+ },
+ ElementLookupResult::Many => {},
};
- let collection = HTMLCollection::create(self.window(), root, box filter);
- Some(NonZero::new(collection.reflector().get_jsobject().get()))
+
+ // Step 4.
+ let filter = NamedElementFilter { name: name };
+ let collection = HTMLCollection::create(self.window(), self.upcast(), Box::new(filter));
+ unsafe {
+ Some(NonNull::new_unchecked(
+ collection.reflector().get_jsobject().get(),
+ ))
+ }
}
// https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
- // FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
- vec![]
+ self.supported_property_names_impl(Node::is_document_named_item)
}
// https://html.spec.whatwg.org/multipage/#dom-document-clear
@@ -3457,223 +4941,158 @@ impl DocumentMethods for Document {
global_event_handlers!();
// https://html.spec.whatwg.org/multipage/#handler-onreadystatechange
- event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange);
+ event_handler!(
+ readystatechange,
+ GetOnreadystatechange,
+ SetOnreadystatechange
+ );
- #[allow(unsafe_code)]
// https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint
- fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<Root<Element>> {
- let x = *x as f32;
- let y = *y as f32;
- let point = &Point2D::new(x, y);
- let window = window_from_node(self);
- let viewport = window.window_size().unwrap().initial_viewport;
-
- if self.browsing_context().is_none() {
- return None;
- }
-
- if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
- return None;
- }
-
- match self.window.hit_test_query(*point, false) {
- Some(untrusted_node_address) => {
- let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
-
- let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address);
- let parent_node = node.GetParentNode().unwrap();
- let element_ref = node.downcast::<Element>().unwrap_or_else(|| {
- parent_node.downcast::<Element>().unwrap()
- });
-
- Some(Root::from_ref(element_ref))
- },
- None => self.GetDocumentElement()
- }
+ fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> {
+ self.document_or_shadow_root.element_from_point(
+ x,
+ y,
+ self.GetDocumentElement(),
+ self.has_browsing_context,
+ )
}
- #[allow(unsafe_code)]
// https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint
- fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<Root<Element>> {
- let x = *x as f32;
- let y = *y as f32;
- let point = &Point2D::new(x, y);
- let window = window_from_node(self);
- let viewport = window.window_size().unwrap().initial_viewport;
-
- if self.browsing_context().is_none() {
- return vec!();
- }
-
- // Step 2
- if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
- return vec!();
- }
-
- let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
-
- // Step 1 and Step 3
- let mut elements: Vec<Root<Element>> = self.nodes_from_point(point).iter()
- .flat_map(|&untrusted_node_address| {
- let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address);
- Root::downcast::<Element>(node)
- }).collect();
-
- // Step 4
- if let Some(root_element) = self.GetDocumentElement() {
- if elements.last() != Some(&root_element) {
- elements.push(root_element);
- }
- }
-
- // Step 5
- elements
+ fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> {
+ self.document_or_shadow_root.elements_from_point(
+ x,
+ y,
+ self.GetDocumentElement(),
+ self.has_browsing_context,
+ )
}
// https://html.spec.whatwg.org/multipage/#dom-document-open
- fn Open(&self, type_: DOMString, replace: DOMString) -> Fallible<Root<Document>> {
+ fn Open(
+ &self,
+ _unused1: Option<DOMString>,
+ _unused2: Option<DOMString>,
+ ) -> Fallible<DomRoot<Document>> {
+ // Step 1
if !self.is_html_document() {
- // Step 1.
return Err(Error::InvalidState);
}
- // Step 2.
- // TODO: handle throw-on-dynamic-markup-insertion counter.
-
- if !self.is_active() {
- // Step 3.
- return Ok(Root::from_ref(self));
+ // Step 2
+ if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
+ return Err(Error::InvalidState);
}
+ // Step 3
let entry_responsible_document = GlobalScope::entry().as_window().Document();
+ // Step 4
// This check is same-origin not same-origin-domain.
// https://github.com/whatwg/html/issues/2282
// https://github.com/whatwg/html/pull/2288
if !self.origin.same_origin(&entry_responsible_document.origin) {
- // Step 4.
return Err(Error::Security);
}
- if self.get_current_parser().map_or(false, |parser| parser.script_nesting_level() > 0) {
- // Step 5.
- return Ok(Root::from_ref(self));
+ // Step 5
+ if self
+ .get_current_parser()
+ .map_or(false, |parser| parser.is_active())
+ {
+ return Ok(DomRoot::from_ref(self));
}
- // Step 6.
- // TODO: ignore-opens-during-unload counter check.
-
- // Step 7: first argument already bound to `type_`.
-
- // Step 8.
- // TODO: check session history's state.
- let replace = replace.eq_ignore_ascii_case("replace");
+ // Step 6
+ if self.is_prompting_or_unloading() {
+ return Ok(DomRoot::from_ref(self));
+ }
- // Step 9.
- // TODO: salvageable flag.
+ // Step 7
+ if self.active_parser_was_aborted.get() {
+ return Ok(DomRoot::from_ref(self));
+ }
- // Step 10.
// TODO: prompt to unload.
+ // TODO: set unload_event_start and unload_event_end
- // Step 11.
- // TODO: unload.
+ window_from_node(self).set_navigation_start();
- // Step 12.
- self.abort();
+ // Step 8
+ // TODO: https://github.com/servo/servo/issues/21937
+ if self.has_browsing_context() {
+ // spec says "stop document loading",
+ // which is a process that does more than just abort
+ self.abort();
+ }
- // Step 13.
- for node in self.upcast::<Node>().traverse_preorder() {
+ // Step 9
+ for node in self
+ .upcast::<Node>()
+ .traverse_preorder(ShadowIncluding::Yes)
+ {
node.upcast::<EventTarget>().remove_all_listeners();
}
- // Step 14.
- // TODO: remove any tasks associated with the Document in any task source.
+ // Step 10
+ if self.window.Document() == DomRoot::from_ref(self) {
+ self.window.upcast::<EventTarget>().remove_all_listeners();
+ }
- // Step 15.
+ // Step 11
+ // TODO: https://github.com/servo/servo/issues/21936
Node::replace_all(None, self.upcast::<Node>());
- // Steps 16-18.
- // Let's not?
- // TODO: https://github.com/whatwg/html/issues/1698
-
- // Step 19.
- self.implementation.set(None);
- self.location.set(None);
- self.images.set(None);
- self.embeds.set(None);
- self.links.set(None);
- self.forms.set(None);
- self.scripts.set(None);
- self.anchors.set(None);
- self.applets.set(None);
- *self.stylesheets.borrow_mut() = None;
- self.stylesheets_changed_since_reflow.set(true);
- self.animation_frame_ident.set(0);
- self.animation_frame_list.borrow_mut().clear();
- self.pending_restyles.borrow_mut().clear();
- self.target_element.set(None);
- *self.last_click_info.borrow_mut() = None;
-
- // Step 20.
- self.set_encoding(UTF_8);
-
- // Step 21.
- // TODO: reload override buffer.
-
- // Step 22.
- // TODO: salvageable flag.
-
- let url = entry_responsible_document.url();
-
- // Step 23.
- self.set_url(url.clone());
-
- // Step 24.
- // TODO: mute iframe load.
-
- // Step 27.
- let type_ = if type_.eq_ignore_ascii_case("replace") {
- "text/html"
- } else if let Some(position) = type_.find(';') {
- &type_[0..position]
- } else {
- &*type_
- };
- let type_ = type_.trim_matches(HTML_SPACE_CHARACTERS);
+ // Specs and tests are in a state of flux about whether
+ // we want to clear the selection when we remove the contents;
+ // WPT selection/Document-open.html wants us to not clear it
+ // as of Feb 1 2020
- // Step 25.
- let resource_threads =
- self.window.upcast::<GlobalScope>().resource_threads().clone();
- *self.loader.borrow_mut() =
- DocumentLoader::new_with_threads(resource_threads, Some(url.clone()));
- ServoParser::parse_html_script_input(self, url, type_);
-
- // Step 26.
- self.ready_state.set(DocumentReadyState::Interactive);
-
- // Step 28 is handled when creating the parser in step 25.
+ // Step 12
+ 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);
+ }
+ // TODO: https://github.com/servo/servo/issues/21939
+ self.set_url(new_url);
+ }
- // Step 29.
- // TODO: truncate session history.
+ // Step 13
+ // TODO: https://github.com/servo/servo/issues/21938
- // Step 30.
- // TODO: remove history traversal tasks.
+ // Step 14
+ self.set_quirks_mode(QuirksMode::NoQuirks);
- // Step 31.
- // TODO: remove earlier entries.
+ // Step 15
+ 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());
- if !replace {
- // Step 32.
- // TODO: add history entry.
- }
+ // Step 16
+ self.ready_state.set(DocumentReadyState::Loading);
- // Step 33.
- // TODO: clear fired unload flag.
+ // Step 17
+ // Handled when creating the parser in step 15
- // Step 34 is handled when creating the parser in step 25.
+ // Step 18
+ Ok(DomRoot::from_ref(self))
+ }
- // Step 35.
- Ok(Root::from_ref(self))
+ // https://html.spec.whatwg.org/multipage/#dom-document-open-window
+ fn Open_(
+ &self,
+ url: USVString,
+ target: DOMString,
+ features: DOMString,
+ ) -> Fallible<Option<DomRoot<WindowProxy>>> {
+ self.browsing_context()
+ .ok_or(Error::InvalidAccess)?
+ .open(url, target, features)
}
// https://html.spec.whatwg.org/multipage/#dom-document-write
@@ -3684,27 +5103,31 @@ impl DocumentMethods for Document {
}
// Step 2.
- // TODO: handle throw-on-dynamic-markup-insertion counter.
- if !self.is_active() {
- // Step 3.
+ if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 3 - what specifies the is_active() part here?
+ 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() => Root::from_ref(&**parser),
+ Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser),
_ => {
// Either there is no parser, which means the parsing ended;
// or script nesting level is 0, which means the method was
// called from outside a parser-executed script.
- if self.ignore_destructive_writes_counter.get() > 0 {
+ if self.is_prompting_or_unloading() ||
+ self.ignore_destructive_writes_counter.get() > 0
+ {
// Step 4.
- // TODO: handle ignore-opens-during-unload counter.
return Ok(());
}
// Step 5.
- self.Open("text/html".into(), "".into())?;
+ self.Open(None, None)?;
self.get_current_parser().unwrap()
- }
+ },
};
// Step 7.
@@ -3731,14 +5154,16 @@ impl DocumentMethods for Document {
}
// Step 2.
- // TODO: handle throw-on-dynamic-markup-insertion counter.
+ 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() => Root::from_ref(&**parser),
+ Some(ref parser) if parser.is_script_created() => DomRoot::from_ref(&**parser),
_ => {
// Step 3.
return Ok(());
- }
+ },
};
// Step 4-6.
@@ -3754,7 +5179,11 @@ impl DocumentMethods for Document {
event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror);
// https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenchange
- event_handler!(fullscreenchange, GetOnfullscreenchange, SetOnfullscreenchange);
+ event_handler!(
+ fullscreenchange,
+ GetOnfullscreenchange,
+ SetOnfullscreenchange
+ );
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled
fn FullscreenEnabled(&self) -> bool {
@@ -3767,30 +5196,47 @@ impl DocumentMethods for Document {
}
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement
- fn GetFullscreenElement(&self) -> Option<Root<Element>> {
+ fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> {
// TODO ShadowRoot
self.fullscreen_element.get()
}
- #[allow(unrooted_must_root)]
// https://fullscreen.spec.whatwg.org/#dom-document-exitfullscreen
fn ExitFullscreen(&self) -> Rc<Promise> {
self.exit_fullscreen()
}
+
+ // check-tidy: no specs after this line
+ // Servo only API to get an instance of the controls of a specific
+ // media element matching the given id.
+ 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),
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-document-getselection
+ fn GetSelection(&self) -> Option<DomRoot<Selection>> {
+ if self.has_browsing_context {
+ Some(self.selection.or_init(|| Selection::new(self)))
+ } else {
+ None
+ }
+ }
}
fn update_with_current_time_ms(marker: &Cell<u64>) {
- if marker.get() == Default::default() {
+ if marker.get() == 0 {
let time = time::get_time();
let current_time_ms = time.sec * 1000 + time.nsec as i64 / 1000000;
marker.set(current_time_ms as u64);
}
}
-/// https://w3c.github.io/webappsec-referrer-policy/#determine-policy-for-token
+/// <https://w3c.github.io/webappsec-referrer-policy/#determine-policy-for-token>
pub fn determine_policy_for_token(token: &str) -> Option<ReferrerPolicy> {
- let lower = token.to_lowercase();
- return match lower.as_ref() {
+ match_ignore_ascii_case! { token,
"never" | "no-referrer" => Some(ReferrerPolicy::NoReferrer),
"default" | "no-referrer-when-downgrade" => Some(ReferrerPolicy::NoReferrerWhenDowngrade),
"origin" => Some(ReferrerPolicy::Origin),
@@ -3804,79 +5250,17 @@ pub fn determine_policy_for_token(token: &str) -> Option<ReferrerPolicy> {
}
}
-pub struct DocumentProgressHandler {
- addr: Trusted<Document>
-}
-
-impl DocumentProgressHandler {
- pub fn new(addr: Trusted<Document>) -> DocumentProgressHandler {
- DocumentProgressHandler {
- addr: addr
- }
- }
-
- fn set_ready_state_complete(&self) {
- let document = self.addr.root();
- document.set_ready_state(DocumentReadyState::Complete);
- }
-
- fn dispatch_load(&self) {
- let document = self.addr.root();
- if document.browsing_context().is_none() {
- return;
- }
- let window = document.window();
- let event = Event::new(window.upcast(),
- atom!("load"),
- EventBubbles::DoesNotBubble,
- EventCancelable::NotCancelable);
- let wintarget = window.upcast::<EventTarget>();
- event.set_trusted(true);
-
- // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart
- update_with_current_time_ms(&document.load_event_start);
-
- debug!("About to dispatch load for {:?}", document.url());
- let _ = wintarget.dispatch_event_with_target(document.upcast(), &event);
-
- // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd
- update_with_current_time_ms(&document.load_event_end);
-
- window.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::DocumentLoaded);
-
- document.notify_constellation_load();
- }
-}
-
-impl Runnable for DocumentProgressHandler {
- fn name(&self) -> &'static str { "DocumentProgressHandler" }
-
- fn handler(self: Box<DocumentProgressHandler>) {
- let document = self.addr.root();
- let window = document.window();
- if window.is_alive() {
- self.set_ready_state_complete();
- self.dispatch_load();
- if let Some(fragment) = document.url().fragment() {
- document.check_and_scroll_fragment(fragment);
- }
- }
- }
-}
-
/// Specifies the type of focus event that is sent to a pipeline
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Clone, Copy, PartialEq)]
pub enum FocusType {
- Element, // The first focus message - focus the element itself
- Parent, // Focusing a parent element (an iframe)
+ Element, // The first focus message - focus the element itself
+ Parent, // Focusing a parent element (an iframe)
}
/// Focus events
pub enum FocusEventType {
- Focus, // Element gained focus. Doesn't bubble.
- Blur, // Element lost focus. Doesn't bubble.
+ Focus, // Element gained focus. Doesn't bubble.
+ Blur, // Element lost focus. Doesn't bubble.
}
/// A fake `requestAnimationFrame()` callback—"fake" because it is not triggered by the video
@@ -3885,10 +5269,10 @@ pub enum FocusEventType {
/// If the page is observed to be using `requestAnimationFrame()` for non-animation purposes (i.e.
/// without mutating the DOM), then we fall back to simple timeouts to save energy over video
/// refresh.
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(JSTraceable, MallocSizeOf)]
pub struct FakeRequestAnimationFrameCallback {
/// The document.
- #[ignore_heap_size_of = "non-owning"]
+ #[ignore_malloc_size_of = "non-owning"]
document: Trusted<Document>,
}
@@ -3899,12 +5283,14 @@ impl FakeRequestAnimationFrameCallback {
}
}
-#[derive(HeapSizeOf, JSTraceable)]
+#[derive(JSTraceable, MallocSizeOf)]
pub enum AnimationFrameCallback {
- DevtoolsFramerateTick { actor_name: String },
+ DevtoolsFramerateTick {
+ actor_name: String,
+ },
FrameRequestCallback {
- #[ignore_heap_size_of = "Rc is hard"]
- callback: Rc<FrameRequestCallback>
+ #[ignore_malloc_size_of = "Rc is hard"]
+ callback: Rc<FrameRequestCallback>,
},
}
@@ -3913,22 +5299,26 @@ impl AnimationFrameCallback {
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();
+ let devtools_sender = document
+ .window()
+ .upcast::<GlobalScope>()
+ .devtools_chan()
+ .unwrap();
devtools_sender.send(msg).unwrap();
- }
+ },
AnimationFrameCallback::FrameRequestCallback { ref callback } => {
// TODO(jdm): The spec says that any exceptions should be suppressed:
// https://github.com/servo/servo/issues/6928
let _ = callback.Call__(Finite::wrap(now), ExceptionHandling::Report);
- }
+ },
}
}
}
-#[derive(Default, HeapSizeOf, JSTraceable)]
-#[must_root]
+#[derive(Default, JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
struct PendingInOrderScriptVec {
- scripts: DOMRefCell<VecDeque<PendingScript>>,
+ scripts: DomRefCell<VecDeque<PendingScript>>,
}
impl PendingInOrderScriptVec {
@@ -3937,23 +5327,25 @@ impl PendingInOrderScriptVec {
}
fn push(&self, element: &HTMLScriptElement) {
- self.scripts.borrow_mut().push_back(PendingScript::new(element));
+ self.scripts
+ .borrow_mut()
+ .push_back(PendingScript::new(element));
}
fn loaded(&self, element: &HTMLScriptElement, result: ScriptResult) {
let mut scripts = self.scripts.borrow_mut();
- let mut entry = scripts.iter_mut().find(|entry| &*entry.element == element).unwrap();
+ let entry = scripts
+ .iter_mut()
+ .find(|entry| &*entry.element == element)
+ .unwrap();
entry.loaded(result);
}
- fn take_next_ready_to_be_executed(&self) -> Option<(Root<HTMLScriptElement>, ScriptResult)> {
+ fn take_next_ready_to_be_executed(&self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> {
let mut scripts = self.scripts.borrow_mut();
- let pair = scripts.front_mut().and_then(PendingScript::take_result);
- if pair.is_none() {
- return None;
- }
+ let pair = scripts.front_mut()?.take_result()?;
scripts.pop_front();
- pair
+ Some(pair)
}
fn clear(&self) {
@@ -3961,20 +5353,26 @@ impl PendingInOrderScriptVec {
}
}
-#[derive(HeapSizeOf, JSTraceable)]
-#[must_root]
+#[derive(JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
struct PendingScript {
- element: JS<HTMLScriptElement>,
+ element: Dom<HTMLScriptElement>,
load: Option<ScriptResult>,
}
impl PendingScript {
fn new(element: &HTMLScriptElement) -> Self {
- Self { element: JS::from_ref(element), load: None }
+ Self {
+ element: Dom::from_ref(element),
+ load: None,
+ }
}
fn new_with_load(element: &HTMLScriptElement, load: Option<ScriptResult>) -> Self {
- Self { element: JS::from_ref(element), load }
+ Self {
+ element: Dom::from_ref(element),
+ load,
+ }
}
fn loaded(&mut self, result: ScriptResult) {
@@ -3982,7 +5380,17 @@ impl PendingScript {
self.load = Some(result);
}
- fn take_result(&mut self) -> Option<(Root<HTMLScriptElement>, ScriptResult)> {
- self.load.take().map(|result| (Root::from_ref(&*self.element), 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,
+}