diff options
Diffstat (limited to 'components/script/dom/document.rs')
-rw-r--r-- | components/script/dom/document.rs | 5054 |
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, +} |