/* 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 https://mozilla.org/MPL/2.0/. */ #![allow(unsafe_code)] //! Wrapper definitions on top of Gecko types in order to be used in the style //! system. //! //! This really follows the Servo pattern in //! `components/script/layout_wrapper.rs`. //! //! This theoretically should live in its own crate, but now it lives in the //! style system it's kind of pointless in the Stylo case, and only Servo forces //! the separation between the style system implementation and everything else. use crate::applicable_declarations::ApplicableDeclarationBlock; use crate::bloom::each_relevant_element_hash; use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; use crate::data::ElementData; use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot}; use crate::gecko::selector_parser::{CustomState, NonTSPseudoClass, PseudoElement, SelectorImpl}; use crate::gecko::snapshot_helpers; use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; use crate::gecko_bindings::bindings::Gecko_ElementState; use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; use crate::gecko_bindings::bindings::Gecko_GetAnimationRule; use crate::gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations; use crate::gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; use crate::gecko_bindings::bindings::Gecko_MatchLang; use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsChangeHint; use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; use crate::gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO; use crate::gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT; use crate::gecko_bindings::structs::NODE_DESCENDANTS_NEED_FRAMES; use crate::gecko_bindings::structs::NODE_NEEDS_FRAME; use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; use crate::global_style_data::GLOBAL_STYLE_DATA; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::media_queries::Device; use crate::properties::{ animated_properties::{AnimationValue, AnimationValueMap}, ComputedValues, Importance, OwnedPropertyDeclarationId, PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId, PropertyDeclarationIdSet, }; use crate::rule_tree::CascadeLevel as ServoCascadeLevel; use crate::selector_parser::{AttrValue, Lang}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::stylist::CascadeData; use crate::values::computed::Display; use crate::values::{AtomIdent, AtomString}; use crate::CaseSensitivityExt; use crate::LocalName; use app_units::Au; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use dom::{DocumentState, ElementState}; use euclid::default::Size2D; use fxhash::FxHashMap; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; use selectors::bloom::{BloomFilter, BLOOM_HASH_MASK}; use selectors::matching::VisitedHandlingMode; use selectors::matching::{ElementSelectorFlags, MatchingContext}; use selectors::sink::Push; use selectors::{Element, OpaqueElement}; use servo_arc::{Arc, ArcBorrow}; use std::cell::Cell; use std::fmt; use std::hash::{Hash, Hasher}; use std::mem; use std::ptr; use std::sync::atomic::{AtomicU32, Ordering}; #[inline] fn elements_with_id<'a, 'le>( array: *const structs::nsTArray<*mut RawGeckoElement>, ) -> &'a [GeckoElement<'le>] { unsafe { if array.is_null() { return &[]; } let elements: &[*mut RawGeckoElement] = &**array; // NOTE(emilio): We rely on the in-memory representation of // GeckoElement<'ld> and *mut RawGeckoElement being the same. #[allow(dead_code)] unsafe fn static_assert() { mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _); } mem::transmute(elements) } } /// A simple wrapper over `Document`. #[derive(Clone, Copy)] pub struct GeckoDocument<'ld>(pub &'ld structs::Document); impl<'ld> TDocument for GeckoDocument<'ld> { type ConcreteNode = GeckoNode<'ld>; #[inline] fn as_node(&self) -> Self::ConcreteNode { GeckoNode(&self.0._base) } #[inline] fn is_html_document(&self) -> bool { self.0.mType == structs::Document_Type::eHTML } #[inline] fn quirks_mode(&self) -> QuirksMode { self.0.mCompatMode.into() } #[inline] fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'ld>], ()> where Self: 'a, { Ok(elements_with_id(unsafe { bindings::Gecko_Document_GetElementsWithId(self.0, id.as_ptr()) })) } fn shared_lock(&self) -> &SharedRwLock { &GLOBAL_STYLE_DATA.shared_lock } } /// A simple wrapper over `ShadowRoot`. #[derive(Clone, Copy)] pub struct GeckoShadowRoot<'lr>(pub &'lr structs::ShadowRoot); impl<'ln> fmt::Debug for GeckoShadowRoot<'ln> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO(emilio): Maybe print the host or something? write!(f, " ({:#x})", self.as_node().opaque().0) } } impl<'lr> PartialEq for GeckoShadowRoot<'lr> { #[inline] fn eq(&self, other: &Self) -> bool { self.0 as *const _ == other.0 as *const _ } } impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> { type ConcreteNode = GeckoNode<'lr>; #[inline] fn as_node(&self) -> Self::ConcreteNode { GeckoNode(&self.0._base._base._base._base) } #[inline] fn host(&self) -> GeckoElement<'lr> { GeckoElement(unsafe { &*self.0._base.mHost.mRawPtr }) } #[inline] fn style_data<'a>(&self) -> Option<&'a CascadeData> where Self: 'a, { let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? }; Some(&author_styles.data) } #[inline] fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'lr>], ()> where Self: 'a, { Ok(elements_with_id(unsafe { bindings::Gecko_ShadowRoot_GetElementsWithId(self.0, id.as_ptr()) })) } #[inline] fn parts<'a>(&self) -> &[::ConcreteElement] where Self: 'a, { let slice: &[*const RawGeckoElement] = &*self.0.mParts; #[allow(dead_code)] unsafe fn static_assert() { mem::transmute::<*const RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *const _); } unsafe { mem::transmute(slice) } } } /// A simple wrapper over a non-null Gecko node (`nsINode`) pointer. /// /// Important: We don't currently refcount the DOM, because the wrapper lifetime /// magic guarantees that our LayoutFoo references won't outlive the root, and /// we don't mutate any of the references on the Gecko side during restyle. /// /// We could implement refcounting if need be (at a potentially non-trivial /// performance cost) by implementing Drop and making LayoutFoo non-Copy. #[derive(Clone, Copy)] pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode); impl<'ln> PartialEq for GeckoNode<'ln> { #[inline] fn eq(&self, other: &Self) -> bool { self.0 as *const _ == other.0 as *const _ } } impl<'ln> fmt::Debug for GeckoNode<'ln> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(el) = self.as_element() { return el.fmt(f); } if self.is_text_node() { return write!(f, " ({:#x})", self.opaque().0); } if self.is_document() { return write!(f, " ({:#x})", self.opaque().0); } if let Some(sr) = self.as_shadow_root() { return sr.fmt(f); } write!(f, " ({:#x})", self.opaque().0) } } impl<'ln> GeckoNode<'ln> { #[inline] fn is_document(&self) -> bool { // This is a DOM constant that isn't going to change. const DOCUMENT_NODE: u16 = 9; self.node_info().mInner.mNodeType == DOCUMENT_NODE } #[inline] fn is_shadow_root(&self) -> bool { self.is_in_shadow_tree() && self.parent_node().is_none() } #[inline] fn from_content(content: &'ln nsIContent) -> Self { GeckoNode(&content._base) } #[inline] fn set_flags(&self, flags: u32) { self.flags_atomic().fetch_or(flags, Ordering::Relaxed); } fn flags_atomic_for(flags: &Cell) -> &AtomicU32 { const_assert!(std::mem::size_of::>() == std::mem::size_of::()); const_assert!(std::mem::align_of::>() == std::mem::align_of::()); // Rust doesn't provide standalone atomic functions like GCC/clang do // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees // that the memory representation of u32 and AtomicU32 matches: // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html unsafe { std::mem::transmute::<&Cell, &AtomicU32>(flags) } } #[inline] fn flags_atomic(&self) -> &AtomicU32 { Self::flags_atomic_for(&self.0._base._base_1.mFlags) } #[inline] fn flags(&self) -> u32 { self.flags_atomic().load(Ordering::Relaxed) } #[inline] fn selector_flags_atomic(&self) -> &AtomicU32 { Self::flags_atomic_for(&self.0.mSelectorFlags) } #[inline] fn selector_flags(&self) -> u32 { self.selector_flags_atomic().load(Ordering::Relaxed) } #[inline] fn set_selector_flags(&self, flags: u32) { self.selector_flags_atomic() .fetch_or(flags, Ordering::Relaxed); } #[inline] fn node_info(&self) -> &structs::NodeInfo { debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null()); unsafe { &*self.0.mNodeInfo.mRawPtr } } // These live in different locations depending on processor architecture. #[cfg(target_pointer_width = "64")] #[inline] fn bool_flags(&self) -> u32 { (self.0)._base._base_1.mBoolFlags } #[cfg(target_pointer_width = "32")] #[inline] fn bool_flags(&self) -> u32 { (self.0).mBoolFlags } #[inline] fn get_bool_flag(&self, flag: nsINode_BooleanFlag) -> bool { self.bool_flags() & (1u32 << flag as u32) != 0 } /// This logic is duplicate in Gecko's nsINode::IsInShadowTree(). #[inline] fn is_in_shadow_tree(&self) -> bool { use crate::gecko_bindings::structs::NODE_IS_IN_SHADOW_TREE; self.flags() & NODE_IS_IN_SHADOW_TREE != 0 } /// Returns true if we know for sure that `flattened_tree_parent` and `parent_node` return the /// same thing. /// /// TODO(emilio): Measure and consider not doing this fast-path, it's only a function call and /// from profiles it seems that keeping this fast path makes the compiler not inline /// `flattened_tree_parent` as a whole, so we're not gaining much either. #[inline] fn flattened_tree_parent_is_parent(&self) -> bool { use crate::gecko_bindings::structs::*; let flags = self.flags(); let parent = match self.parent_node() { Some(p) => p, None => return true, }; if parent.is_shadow_root() { return false; } if let Some(parent) = parent.as_element() { if flags & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0 && parent.is_root() { return false; } if parent.shadow_root().is_some() || parent.is_html_slot_element() { return false; } } true } #[inline] fn flattened_tree_parent(&self) -> Option { if self.flattened_tree_parent_is_parent() { debug_assert_eq!( unsafe { bindings::Gecko_GetFlattenedTreeParentNode(self.0) .as_ref() .map(GeckoNode) }, self.parent_node(), "Fast path stopped holding!" ); return self.parent_node(); } // NOTE(emilio): If this call is too expensive, we could manually inline more aggressively. unsafe { bindings::Gecko_GetFlattenedTreeParentNode(self.0) .as_ref() .map(GeckoNode) } } #[inline] fn contains_non_whitespace_content(&self) -> bool { unsafe { Gecko_IsSignificantChild(self.0, false) } } /// Returns the previous sibling of this node that is an element. #[inline] pub fn prev_sibling_element(&self) -> Option { let mut prev = self.prev_sibling(); while let Some(p) = prev { if let Some(e) = p.as_element() { return Some(e); } prev = p.prev_sibling(); } None } /// Returns the next sibling of this node that is an element. #[inline] pub fn next_sibling_element(&self) -> Option { let mut next = self.next_sibling(); while let Some(n) = next { if let Some(e) = n.as_element() { return Some(e); } next = n.next_sibling(); } None } /// Returns last child sibling of this node that is an element. #[inline] pub fn last_child_element(&self) -> Option> { let last = match self.last_child() { Some(n) => n, None => return None, }; if let Some(e) = last.as_element() { return Some(e); } None } } impl<'ln> NodeInfo for GeckoNode<'ln> { #[inline] fn is_element(&self) -> bool { self.get_bool_flag(nsINode_BooleanFlag::NodeIsElement) } fn is_text_node(&self) -> bool { // This is a DOM constant that isn't going to change. const TEXT_NODE: u16 = 3; self.node_info().mInner.mNodeType == TEXT_NODE } } impl<'ln> TNode for GeckoNode<'ln> { type ConcreteDocument = GeckoDocument<'ln>; type ConcreteShadowRoot = GeckoShadowRoot<'ln>; type ConcreteElement = GeckoElement<'ln>; #[inline] fn parent_node(&self) -> Option { unsafe { self.0.mParent.as_ref().map(GeckoNode) } } #[inline] fn first_child(&self) -> Option { unsafe { self.0 .mFirstChild .raw() .as_ref() .map(GeckoNode::from_content) } } #[inline] fn last_child(&self) -> Option { unsafe { bindings::Gecko_GetLastChild(self.0).as_ref().map(GeckoNode) } } #[inline] fn prev_sibling(&self) -> Option { unsafe { let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?); if prev_or_last.0.mNextSibling.raw().is_null() { return None; } Some(prev_or_last) } } #[inline] fn next_sibling(&self) -> Option { unsafe { self.0 .mNextSibling .raw() .as_ref() .map(GeckoNode::from_content) } } #[inline] fn owner_doc(&self) -> Self::ConcreteDocument { debug_assert!(!self.node_info().mDocument.is_null()); GeckoDocument(unsafe { &*self.node_info().mDocument }) } #[inline] fn is_in_document(&self) -> bool { self.get_bool_flag(nsINode_BooleanFlag::IsInDocument) } fn traversal_parent(&self) -> Option> { self.flattened_tree_parent().and_then(|n| n.as_element()) } #[inline] fn opaque(&self) -> OpaqueNode { let ptr: usize = self.0 as *const _ as usize; OpaqueNode(ptr) } fn debug_id(self) -> usize { unimplemented!() } #[inline] fn as_element(&self) -> Option> { if !self.is_element() { return None; } Some(GeckoElement(unsafe { &*(self.0 as *const _ as *const RawGeckoElement) })) } #[inline] fn as_document(&self) -> Option { if !self.is_document() { return None; } debug_assert_eq!(self.owner_doc().as_node(), *self, "How?"); Some(self.owner_doc()) } #[inline] fn as_shadow_root(&self) -> Option { if !self.is_shadow_root() { return None; } Some(GeckoShadowRoot(unsafe { &*(self.0 as *const _ as *const structs::ShadowRoot) })) } } /// A wrapper on top of two kind of iterators, depending on the parent being /// iterated. /// /// We generally iterate children by traversing the light-tree siblings of the /// first child like Servo does. /// /// However, for nodes with anonymous children, we use a custom (heavier-weight) /// Gecko-implemented iterator. /// /// FIXME(emilio): If we take into account shadow DOM, we're going to need the /// flat tree pretty much always. We can try to optimize the case where there's /// no shadow root sibling, probably. pub enum GeckoChildrenIterator<'a> { /// A simple iterator that tracks the current node being iterated and /// replaces it with the next sibling when requested. Current(Option>), /// A Gecko-implemented iterator we need to drop appropriately. GeckoIterator(structs::StyleChildrenIterator), } impl<'a> Drop for GeckoChildrenIterator<'a> { fn drop(&mut self) { if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self { unsafe { bindings::Gecko_DestroyStyleChildrenIterator(it); } } } } impl<'a> Iterator for GeckoChildrenIterator<'a> { type Item = GeckoNode<'a>; fn next(&mut self) -> Option> { match *self { GeckoChildrenIterator::Current(curr) => { let next = curr.and_then(|node| node.next_sibling()); *self = GeckoChildrenIterator::Current(next); curr }, GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe { // We do this unsafe lengthening of the lifetime here because // structs::StyleChildrenIterator is actually StyleChildrenIterator<'a>, // however we can't express this easily with bindgen, and it would // introduce functions with two input lifetimes into bindgen, // which would be out of scope for elision. bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _)) .as_ref() .map(GeckoNode) }, } } } /// A simple wrapper over a non-null Gecko `Element` pointer. #[derive(Clone, Copy)] pub struct GeckoElement<'le>(pub &'le RawGeckoElement); impl<'le> fmt::Debug for GeckoElement<'le> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use nsstring::nsCString; write!(f, "<{}", self.local_name())?; let mut attrs = nsCString::new(); unsafe { bindings::Gecko_Element_DebugListAttributes(self.0, &mut attrs); } write!(f, "{}", attrs)?; write!(f, "> ({:#x})", self.as_node().opaque().0) } } impl<'le> GeckoElement<'le> { /// Gets the raw `ElementData` refcell for the element. #[inline(always)] pub fn get_data(&self) -> Option<&AtomicRefCell> { unsafe { self.0.mServoData.get().as_ref() } } /// Returns whether any animation applies to this element. #[inline] pub fn has_any_animation(&self) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) } } #[inline(always)] fn attrs(&self) -> &[structs::AttrArray_InternalAttr] { unsafe { match self.0.mAttrs.mImpl.mPtr.as_ref() { Some(attrs) => attrs.mBuffer.as_slice(attrs.mAttrCount as usize), None => return &[], } } } #[inline(always)] fn get_part_attr(&self) -> Option<&structs::nsAttrValue> { if !self.has_part_attr() { return None; } snapshot_helpers::find_attr(self.attrs(), &atom!("part")) } #[inline(always)] fn get_class_attr(&self) -> Option<&structs::nsAttrValue> { if !self.may_have_class() { return None; } if self.is_svg_element() { let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() }; if let Some(c) = svg_class { return Some(c); } } snapshot_helpers::find_attr(self.attrs(), &atom!("class")) } #[inline] fn may_have_anonymous_children(&self) -> bool { self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveAnonymousChildren) } #[inline] fn flags(&self) -> u32 { self.as_node().flags() } #[inline] fn set_flags(&self, flags: u32) { self.as_node().set_flags(flags); } #[inline] unsafe fn unset_flags(&self, flags: u32) { self.as_node() .flags_atomic() .fetch_and(!flags, Ordering::Relaxed); } /// Returns true if this element has descendants for lazy frame construction. #[inline] pub fn descendants_need_frames(&self) -> bool { self.flags() & NODE_DESCENDANTS_NEED_FRAMES != 0 } /// Returns true if this element needs lazy frame construction. #[inline] pub fn needs_frame(&self) -> bool { self.flags() & NODE_NEEDS_FRAME != 0 } /// Returns a reference to the DOM slots for this Element, if they exist. #[inline] fn dom_slots(&self) -> Option<&structs::FragmentOrElement_nsDOMSlots> { let slots = self.as_node().0.mSlots as *const structs::FragmentOrElement_nsDOMSlots; unsafe { slots.as_ref() } } /// Returns a reference to the extended DOM slots for this Element. #[inline] fn extended_slots(&self) -> Option<&structs::FragmentOrElement_nsExtendedDOMSlots> { self.dom_slots().and_then(|s| unsafe { // For the bit usage, see nsContentSlots::GetExtendedSlots. let e_slots = s._base.mExtendedSlots & !structs::nsIContent_nsContentSlots_sNonOwningExtendedSlotsFlag; (e_slots as *const structs::FragmentOrElement_nsExtendedDOMSlots).as_ref() }) } #[inline] fn namespace_id(&self) -> i32 { self.as_node().node_info().mInner.mNamespaceID } #[inline] fn has_id(&self) -> bool { self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasID) } #[inline] fn state_internal(&self) -> u64 { if !self .as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasLockedStyleStates) { return self.0.mState.bits; } unsafe { Gecko_ElementState(self.0) } } #[inline] fn document_state(&self) -> DocumentState { DocumentState::from_bits_retain(self.as_node().owner_doc().0.mState.bits) } #[inline] fn may_have_class(&self) -> bool { self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveClass) } #[inline] fn has_properties(&self) -> bool { use crate::gecko_bindings::structs::NODE_HAS_PROPERTIES; self.flags() & NODE_HAS_PROPERTIES != 0 } #[inline] fn before_or_after_pseudo(&self, is_before: bool) -> Option { if !self.has_properties() { return None; } unsafe { bindings::Gecko_GetBeforeOrAfterPseudo(self.0, is_before) .as_ref() .map(GeckoElement) } } #[inline] fn may_have_style_attribute(&self) -> bool { self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle) } /// Only safe to call on the main thread, with exclusive access to the /// element and its ancestors. /// /// This function is also called after display property changed for SMIL /// animation. /// /// Also this function schedules style flush. pub unsafe fn note_explicit_hints(&self, restyle_hint: RestyleHint, change_hint: nsChangeHint) { use crate::gecko::restyle_damage::GeckoRestyleDamage; let damage = GeckoRestyleDamage::new(change_hint); debug!( "note_explicit_hints: {:?}, restyle_hint={:?}, change_hint={:?}", self, restyle_hint, change_hint ); debug_assert!( !(restyle_hint.has_animation_hint() && restyle_hint.has_non_animation_hint()), "Animation restyle hints should not appear with non-animation restyle hints" ); let mut data = match self.mutate_data() { Some(d) => d, None => { debug!("(Element not styled, discarding hints)"); return; }, }; debug_assert!(data.has_styles(), "how?"); // Propagate the bit up the chain. if restyle_hint.has_animation_hint() { bindings::Gecko_NoteAnimationOnlyDirtyElement(self.0); } else { bindings::Gecko_NoteDirtyElement(self.0); } data.hint.insert(restyle_hint); data.damage |= damage; } /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree. #[inline] fn is_root_of_native_anonymous_subtree(&self) -> bool { use crate::gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT; return self.flags() & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0; } fn css_transitions_info(&self) -> FxHashMap> { use crate::gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; use crate::gecko_bindings::bindings::Gecko_ElementTransitions_Length; let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0) } as usize; let mut map = FxHashMap::with_capacity_and_hasher(collection_length, Default::default()); for i in 0..collection_length { let end_value = unsafe { Arc::from_raw_addrefed(Gecko_ElementTransitions_EndValueAt(self.0, i)) }; let property = end_value.id(); debug_assert!(!property.is_logical()); map.insert(property.to_owned(), end_value); } map } fn needs_transitions_update_per_property( &self, property_declaration_id: PropertyDeclarationId, combined_duration_seconds: f32, before_change_style: &ComputedValues, after_change_style: &ComputedValues, existing_transitions: &FxHashMap>, ) -> bool { debug_assert!(!property_declaration_id.is_logical()); // If there is an existing transition, update only if the end value // differs. // // If the end value has not changed, we should leave the currently // running transition as-is since we don't want to interrupt its timing // function. if let Some(ref existing) = existing_transitions.get(&property_declaration_id.to_owned()) { let after_value = AnimationValue::from_computed_values(property_declaration_id, after_change_style) .unwrap(); return ***existing != after_value; } if combined_duration_seconds <= 0.0f32 { return false; } let from = AnimationValue::from_computed_values(property_declaration_id, before_change_style); let to = AnimationValue::from_computed_values(property_declaration_id, after_change_style); debug_assert_eq!(to.is_some(), from.is_some()); from != to } /// Get slow selector flags required for nth-of invalidation. pub fn slow_selector_flags(&self) -> ElementSelectorFlags { slow_selector_flags_from_node_selector_flags(self.as_node().selector_flags()) } } /// Convert slow selector flags from the raw `NodeSelectorFlags`. pub fn slow_selector_flags_from_node_selector_flags(flags: u32) -> ElementSelectorFlags { use crate::gecko_bindings::structs::NodeSelectorFlags; let mut result = ElementSelectorFlags::empty(); if flags & NodeSelectorFlags::HasSlowSelector.0 != 0 { result.insert(ElementSelectorFlags::HAS_SLOW_SELECTOR); } if flags & NodeSelectorFlags::HasSlowSelectorLaterSiblings.0 != 0 { result.insert(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS); } result } /// Converts flags from the layout used by rust-selectors to the layout used /// by Gecko. We could align these and then do this without conditionals, but /// it's probably not worth the trouble. fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 { use crate::gecko_bindings::structs::NodeSelectorFlags; let mut gecko_flags = 0u32; if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) { gecko_flags |= NodeSelectorFlags::HasSlowSelector.0; } if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) { gecko_flags |= NodeSelectorFlags::HasSlowSelectorLaterSiblings.0; } if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH) { gecko_flags |= NodeSelectorFlags::HasSlowSelectorNth.0; } if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) { gecko_flags |= NodeSelectorFlags::HasSlowSelectorNthOf.0; } if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) { gecko_flags |= NodeSelectorFlags::HasEdgeChildSelector.0; } if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) { gecko_flags |= NodeSelectorFlags::HasEmptySelector.0; } if flags.contains(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR) { gecko_flags |= NodeSelectorFlags::RelativeSelectorAnchor.0; } if flags.contains(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR_NON_SUBJECT) { gecko_flags |= NodeSelectorFlags::RelativeSelectorAnchorNonSubject.0; } if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR) { gecko_flags |= NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0; } if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) { gecko_flags |= NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0; } gecko_flags } fn get_animation_rule( element: &GeckoElement, cascade_level: CascadeLevel, ) -> Option>> { // There's a very rough correlation between the number of effects // (animations) on an element and the number of properties it is likely to // animate, so we use that as an initial guess for the size of the // AnimationValueMap in order to reduce the number of re-allocations needed. let effect_count = unsafe { Gecko_GetAnimationEffectCount(element.0) }; // Also, we should try to reuse the PDB, to avoid creating extra rule nodes. let mut animation_values = AnimationValueMap::with_capacity_and_hasher( effect_count.min(crate::properties::property_counts::ANIMATABLE), Default::default(), ); if unsafe { Gecko_GetAnimationRule(element.0, cascade_level, &mut animation_values) } { let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; Some(Arc::new(shared_lock.wrap( PropertyDeclarationBlock::from_animation_value_map(&animation_values), ))) } else { None } } /// Turns a gecko namespace id into an atom. Might panic if you pass any random thing that isn't a /// namespace id. #[inline(always)] pub unsafe fn namespace_id_to_atom(id: i32) -> *mut nsAtom { unsafe { let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr; (*namespace_manager).mURIArray[id as usize].mRawPtr } } impl<'le> TElement for GeckoElement<'le> { type ConcreteNode = GeckoNode<'le>; type TraversalChildrenIterator = GeckoChildrenIterator<'le>; fn inheritance_parent(&self) -> Option { if self.is_pseudo_element() { return self.pseudo_element_originating_element(); } self.as_node() .flattened_tree_parent() .and_then(|n| n.as_element()) } fn traversal_children(&self) -> LayoutIterator> { // This condition is similar to the check that // StyleChildrenIterator::IsNeeded does, except that it might return // true if we used to (but no longer) have anonymous content from // ::before/::after, or nsIAnonymousContentCreators. if self.is_html_slot_element() || self.shadow_root().is_some() || self.may_have_anonymous_children() { unsafe { let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed(); bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter); return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)); } } LayoutIterator(GeckoChildrenIterator::Current(self.as_node().first_child())) } fn before_pseudo_element(&self) -> Option { self.before_or_after_pseudo(/* is_before = */ true) } fn after_pseudo_element(&self) -> Option { self.before_or_after_pseudo(/* is_before = */ false) } fn marker_pseudo_element(&self) -> Option { if !self.has_properties() { return None; } unsafe { bindings::Gecko_GetMarkerPseudo(self.0) .as_ref() .map(GeckoElement) } } #[inline] fn is_html_element(&self) -> bool { self.namespace_id() == structs::kNameSpaceID_XHTML as i32 } #[inline] fn is_mathml_element(&self) -> bool { self.namespace_id() == structs::kNameSpaceID_MathML as i32 } #[inline] fn is_svg_element(&self) -> bool { self.namespace_id() == structs::kNameSpaceID_SVG as i32 } #[inline] fn is_xul_element(&self) -> bool { self.namespace_id() == structs::root::kNameSpaceID_XUL as i32 } #[inline] fn local_name(&self) -> &WeakAtom { unsafe { WeakAtom::new(self.as_node().node_info().mInner.mName) } } #[inline] fn namespace(&self) -> &WeakNamespace { unsafe { WeakNamespace::new(namespace_id_to_atom(self.namespace_id())) } } #[inline] fn query_container_size(&self, display: &Display) -> Size2D> { // If an element gets 'display: contents' and its nsIFrame has not been removed yet, // Gecko_GetQueryContainerSize will not notice that it can't have size containment. // Other cases like 'display: inline' will be handled once the new nsIFrame is created. if display.is_contents() { return Size2D::new(None, None); } let mut width = -1; let mut height = -1; unsafe { bindings::Gecko_GetQueryContainerSize(self.0, &mut width, &mut height); } Size2D::new( if width >= 0 { Some(Au(width)) } else { None }, if height >= 0 { Some(Au(height)) } else { None }, ) } /// Return the list of slotted nodes of this node. #[inline] fn slotted_nodes(&self) -> &[Self::ConcreteNode] { if !self.is_html_slot_element() || !self.as_node().is_in_shadow_tree() { return &[]; } let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) }; if cfg!(debug_assertions) { let base: &RawGeckoElement = &slot._base._base._base; assert_eq!(base as *const _, self.0 as *const _, "Bad cast"); } // FIXME(emilio): Workaround a bindgen bug on Android that causes // mAssignedNodes to be at the wrong offset. See bug 1466406. // // Bug 1466580 tracks running the Android layout tests on automation. // // The actual bindgen bug still needs reduction. let assigned_nodes: &[structs::RefPtr] = if !cfg!(target_os = "android") { debug_assert_eq!( unsafe { bindings::Gecko_GetAssignedNodes(self.0) }, &slot.mAssignedNodes as *const _, ); &*slot.mAssignedNodes } else { unsafe { &**bindings::Gecko_GetAssignedNodes(self.0) } }; debug_assert_eq!( mem::size_of::>(), mem::size_of::(), "Bad cast!" ); unsafe { mem::transmute(assigned_nodes) } } #[inline] fn shadow_root(&self) -> Option> { let slots = self.extended_slots()?; unsafe { slots.mShadowRoot.mRawPtr.as_ref().map(GeckoShadowRoot) } } #[inline] fn containing_shadow(&self) -> Option> { let slots = self.extended_slots()?; unsafe { slots ._base .mContainingShadow .mRawPtr .as_ref() .map(GeckoShadowRoot) } } fn each_anonymous_content_child(&self, mut f: F) where F: FnMut(Self), { if !self.may_have_anonymous_children() { return; } let array: *mut structs::nsTArray<*mut nsIContent> = unsafe { bindings::Gecko_GetAnonymousContentForElement(self.0) }; if array.is_null() { return; } for content in unsafe { &**array } { let node = GeckoNode::from_content(unsafe { &**content }); let element = match node.as_element() { Some(e) => e, None => continue, }; f(element); } unsafe { bindings::Gecko_DestroyAnonymousContentList(array) }; } #[inline] fn as_node(&self) -> Self::ConcreteNode { unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) } } fn owner_doc_matches_for_testing(&self, device: &Device) -> bool { self.as_node().owner_doc().0 as *const structs::Document == device.document() as *const _ } fn style_attribute(&self) -> Option>> { if !self.may_have_style_attribute() { return None; } unsafe { let declarations = Gecko_GetStyleAttrDeclarationBlock(self.0).as_ref()?; Some(ArcBorrow::from_ref(declarations)) } } fn unset_dirty_style_attribute(&self) { if !self.may_have_style_attribute() { return; } unsafe { Gecko_UnsetDirtyStyleAttr(self.0) }; } fn smil_override(&self) -> Option>> { unsafe { let slots = self.extended_slots()?; let declaration: &structs::DeclarationBlock = slots.mSMILOverrideStyleDeclaration.mRawPtr.as_ref()?; let raw: &structs::StyleLockedDeclarationBlock = declaration.mRaw.mRawPtr.as_ref()?; Some(ArcBorrow::from_ref(raw)) } } fn animation_rule( &self, _: &SharedStyleContext, ) -> Option>> { get_animation_rule(self, CascadeLevel::Animations) } fn transition_rule( &self, _: &SharedStyleContext, ) -> Option>> { get_animation_rule(self, CascadeLevel::Transitions) } #[inline] fn state(&self) -> ElementState { ElementState::from_bits_retain(self.state_internal()) } #[inline] fn has_custom_state(&self, state: &CustomState) -> bool { if !self.is_html_element() { return false; } let check_state_ptr: *const nsAtom = state.0.as_ptr(); self.extended_slots().map_or(false, |slot| { (&slot.mCustomStates).iter().any(|setstate| { let setstate_ptr: *const nsAtom = setstate.mRawPtr; setstate_ptr == check_state_ptr }) }) } #[inline] fn has_part_attr(&self) -> bool { self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasPart) } #[inline] fn exports_any_part(&self) -> bool { snapshot_helpers::find_attr(self.attrs(), &atom!("exportparts")).is_some() } // FIXME(emilio): we should probably just return a reference to the Atom. #[inline] fn id(&self) -> Option<&WeakAtom> { if !self.has_id() { return None; } snapshot_helpers::get_id(self.attrs()) } fn each_attr_name(&self, mut callback: F) where F: FnMut(&AtomIdent), { for attr in self.attrs() { unsafe { AtomIdent::with(attr.mName.name(), |a| callback(a)) } } } fn each_class(&self, callback: F) where F: FnMut(&AtomIdent), { let attr = match self.get_class_attr() { Some(c) => c, None => return, }; snapshot_helpers::each_class_or_part(attr, callback) } #[inline] fn each_exported_part(&self, name: &AtomIdent, callback: F) where F: FnMut(&AtomIdent), { snapshot_helpers::each_exported_part(self.attrs(), name, callback) } fn each_part(&self, callback: F) where F: FnMut(&AtomIdent), { let attr = match self.get_part_attr() { Some(c) => c, None => return, }; snapshot_helpers::each_class_or_part(attr, callback) } #[inline] fn has_snapshot(&self) -> bool { self.flags() & ELEMENT_HAS_SNAPSHOT != 0 } #[inline] fn handled_snapshot(&self) -> bool { self.flags() & ELEMENT_HANDLED_SNAPSHOT != 0 } unsafe fn set_handled_snapshot(&self) { debug_assert!(self.has_data()); self.set_flags(ELEMENT_HANDLED_SNAPSHOT) } #[inline] fn has_dirty_descendants(&self) -> bool { self.flags() & ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO != 0 } unsafe fn set_dirty_descendants(&self) { debug_assert!(self.has_data()); debug!("Setting dirty descendants: {:?}", self); self.set_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) } unsafe fn unset_dirty_descendants(&self) { self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) } #[inline] fn has_animation_only_dirty_descendants(&self) -> bool { self.flags() & ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO != 0 } unsafe fn set_animation_only_dirty_descendants(&self) { self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) } unsafe fn unset_animation_only_dirty_descendants(&self) { self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) } unsafe fn clear_descendant_bits(&self) { self.unset_flags( ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO | ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO | NODE_DESCENDANTS_NEED_FRAMES, ) } fn is_visited_link(&self) -> bool { self.state().intersects(ElementState::VISITED) } /// We want to match rules from the same tree in all cases, except for native anonymous content /// that _isn't_ part directly of a UA widget (e.g., such generated by form controls, or /// pseudo-elements). #[inline] fn matches_user_and_content_rules(&self) -> bool { use crate::gecko_bindings::structs::{ NODE_HAS_BEEN_IN_UA_WIDGET, NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE, }; let flags = self.flags(); (flags & NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) == 0 || (flags & NODE_HAS_BEEN_IN_UA_WIDGET) != 0 } #[inline] fn implemented_pseudo_element(&self) -> Option { if self.matches_user_and_content_rules() { return None; } if !self.has_properties() { return None; } PseudoElement::from_pseudo_type( unsafe { bindings::Gecko_GetImplementedPseudo(self.0) }, None, ) } #[inline] fn store_children_to_process(&self, _: isize) { // This is only used for bottom-up traversal, and is thus a no-op for Gecko. } fn did_process_child(&self) -> isize { panic!("Atomic child count not implemented in Gecko"); } unsafe fn ensure_data(&self) -> AtomicRefMut { if !self.has_data() { debug!("Creating ElementData for {:?}", self); let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::default()))); self.0.mServoData.set(ptr); } self.mutate_data().unwrap() } unsafe fn clear_data(&self) { let ptr = self.0.mServoData.get(); self.unset_flags( ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT | structs::Element_kAllServoDescendantBits | NODE_NEEDS_FRAME, ); if !ptr.is_null() { debug!("Dropping ElementData for {:?}", self); let data = Box::from_raw(self.0.mServoData.get()); self.0.mServoData.set(ptr::null_mut()); // Perform a mutable borrow of the data in debug builds. This // serves as an assertion that there are no outstanding borrows // when we destroy the data. debug_assert!({ let _ = data.borrow_mut(); true }); } } #[inline] fn skip_item_display_fixup(&self) -> bool { debug_assert!( !self.is_pseudo_element(), "Just don't call me if I'm a pseudo, you should know the answer already" ); self.is_root_of_native_anonymous_subtree() } #[inline] fn may_have_animations(&self) -> bool { if let Some(pseudo) = self.implemented_pseudo_element() { if pseudo.animations_stored_in_parent() { // FIXME(emilio): When would the parent of a ::before / ::after // pseudo-element be null? return self.parent_element().map_or(false, |p| { p.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) }); } } self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) } /// Process various tasks that are a result of animation-only restyle. fn process_post_animation(&self, tasks: PostAnimationTasks) { debug_assert!(!tasks.is_empty(), "Should be involved a task"); // If display style was changed from none to other, we need to resolve // the descendants in the display:none subtree. Instead of resolving // those styles in animation-only restyle, we defer it to a subsequent // normal restyle. if tasks.intersects(PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL) { debug_assert!( self.implemented_pseudo_element() .map_or(true, |p| !p.is_before_or_after()), "display property animation shouldn't run on pseudo elements \ since it's only for SMIL" ); unsafe { self.note_explicit_hints( RestyleHint::restyle_subtree(), nsChangeHint::nsChangeHint_Empty, ); } } } /// Update various animation-related state on a given (pseudo-)element as /// results of normal restyle. fn update_animations( &self, before_change_style: Option>, tasks: UpdateAnimationsTasks, ) { // We have to update animations even if the element has no computed // style since it means the element is in a display:none subtree, we // should destroy all CSS animations in display:none subtree. let computed_data = self.borrow_data(); let computed_values = computed_data.as_ref().map(|d| d.styles.primary()); let before_change_values = before_change_style .as_ref() .map_or(ptr::null(), |x| x.as_gecko_computed_style()); let computed_values_opt = computed_values .as_ref() .map_or(ptr::null(), |x| x.as_gecko_computed_style()); unsafe { Gecko_UpdateAnimations( self.0, before_change_values, computed_values_opt, tasks.bits(), ); } } #[inline] fn has_animations(&self, _: &SharedStyleContext) -> bool { self.has_any_animation() } fn has_css_animations(&self, _: &SharedStyleContext, _: Option) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) } } fn has_css_transitions(&self, _: &SharedStyleContext, _: Option) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) } } // Detect if there are any changes that require us to update transitions. // // This is used as a more thoroughgoing check than the cheaper // might_need_transitions_update check. // // The following logic shadows the logic used on the Gecko side // (nsTransitionManager::DoUpdateTransitions) where we actually perform the // update. // // https://drafts.csswg.org/css-transitions/#starting fn needs_transitions_update( &self, before_change_style: &ComputedValues, after_change_style: &ComputedValues, ) -> bool { let after_change_ui_style = after_change_style.get_ui(); let existing_transitions = self.css_transitions_info(); if after_change_style.get_box().clone_display().is_none() { // We need to cancel existing transitions. return !existing_transitions.is_empty(); } let mut transitions_to_keep = PropertyDeclarationIdSet::default(); for transition_property in after_change_style.transition_properties() { let physical_longhand = PropertyDeclarationId::Longhand( transition_property .longhand_id .to_physical(after_change_style.writing_mode), ); transitions_to_keep.insert(physical_longhand); if self.needs_transitions_update_per_property( physical_longhand, after_change_ui_style .transition_combined_duration_at(transition_property.index) .seconds(), before_change_style, after_change_style, &existing_transitions, ) { return true; } } // Check if we have to cancel the running transition because this is not // a matching transition-property value. existing_transitions .keys() .any(|property| !transitions_to_keep.contains(property.as_borrowed())) } /// Whether there is an ElementData container. #[inline] fn has_data(&self) -> bool { self.get_data().is_some() } /// Immutably borrows the ElementData. fn borrow_data(&self) -> Option> { self.get_data().map(|x| x.borrow()) } /// Mutably borrows the ElementData. fn mutate_data(&self) -> Option> { self.get_data().map(|x| x.borrow_mut()) } #[inline] fn lang_attr(&self) -> Option { let ptr = unsafe { bindings::Gecko_LangValue(self.0) }; if ptr.is_null() { None } else { Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) } } fn match_element_lang(&self, override_lang: Option>, value: &Lang) -> bool { // Gecko supports :lang() from CSS Selectors 4, which accepts a list // of language tags, and does BCP47-style range matching. let override_lang_ptr = match override_lang { Some(Some(ref atom)) => atom.as_ptr(), _ => ptr::null_mut(), }; value.0.iter().any(|lang| unsafe { Gecko_MatchLang( self.0, override_lang_ptr, override_lang.is_some(), lang.as_slice().as_ptr(), ) }) } fn is_html_document_body_element(&self) -> bool { if self.local_name() != &**local_name!("body") { return false; } if !self.is_html_element() { return false; } unsafe { bindings::Gecko_IsDocumentBody(self.0) } } fn synthesize_presentational_hints_for_legacy_attributes( &self, visited_handling: VisitedHandlingMode, hints: &mut V, ) where V: Push, { use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; use crate::stylesheets::layer_rule::LayerOrder; use crate::values::specified::{color::Color, font::XTextScale}; lazy_static! { static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( PropertyDeclaration::Color(SpecifiedColor(Color::InheritFromBodyQuirk.into())), Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, LayerOrder::root(), ) }; static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( PropertyDeclaration::XLang(SpecifiedLang(atom!("x-math"))), Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, LayerOrder::root(), ) }; static ref SVG_TEXT_DISABLE_SCALE_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( PropertyDeclaration::XTextScale(XTextScale::None), Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, LayerOrder::root(), ) }; }; let ns = self.namespace_id(); // elements get a default MozCenterOrInherit which may get overridden if ns == structs::kNameSpaceID_XHTML as i32 { if self.local_name().as_ptr() == atom!("table").as_ptr() && self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks { hints.push(TABLE_COLOR_RULE.clone()); } } if ns == structs::kNameSpaceID_SVG as i32 { if self.local_name().as_ptr() == atom!("text").as_ptr() { hints.push(SVG_TEXT_DISABLE_SCALE_RULE.clone()); } } let declarations = unsafe { Gecko_GetHTMLPresentationAttrDeclarationBlock(self.0).as_ref() }; if let Some(decl) = declarations { hints.push(ApplicableDeclarationBlock::from_declarations( unsafe { Arc::from_raw_addrefed(decl) }, ServoCascadeLevel::PresHints, LayerOrder::root(), )); } let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() }; if let Some(decl) = declarations { hints.push(ApplicableDeclarationBlock::from_declarations( unsafe { Arc::from_raw_addrefed(decl) }, ServoCascadeLevel::PresHints, LayerOrder::root(), )); } // Support for link, vlink, and alink presentation hints on if self.is_link() { // Unvisited vs. visited styles are computed up-front based on the // visited mode (not the element's actual state). let declarations = match visited_handling { VisitedHandlingMode::AllLinksVisitedAndUnvisited => { unreachable!( "We should never try to selector match with \ AllLinksVisitedAndUnvisited" ); }, VisitedHandlingMode::AllLinksUnvisited => unsafe { Gecko_GetUnvisitedLinkAttrDeclarationBlock(self.0).as_ref() }, VisitedHandlingMode::RelevantLinkVisited => unsafe { Gecko_GetVisitedLinkAttrDeclarationBlock(self.0).as_ref() }, }; if let Some(decl) = declarations { hints.push(ApplicableDeclarationBlock::from_declarations( unsafe { Arc::from_raw_addrefed(decl) }, ServoCascadeLevel::PresHints, LayerOrder::root(), )); } let active = self .state() .intersects(NonTSPseudoClass::Active.state_flag()); if active { let declarations = unsafe { Gecko_GetActiveLinkAttrDeclarationBlock(self.0).as_ref() }; if let Some(decl) = declarations { hints.push(ApplicableDeclarationBlock::from_declarations( unsafe { Arc::from_raw_addrefed(decl) }, ServoCascadeLevel::PresHints, LayerOrder::root(), )); } } } // xml:lang has precedence over lang, which can be // set by Gecko_GetHTMLPresentationAttrDeclarationBlock // // http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#language let ptr = unsafe { bindings::Gecko_GetXMLLangValue(self.0) }; if !ptr.is_null() { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( PropertyDeclaration::XLang(SpecifiedLang(unsafe { Atom::from_addrefed(ptr) })), Importance::Normal, ); let arc = Arc::new(global_style_data.shared_lock.wrap(pdb)); hints.push(ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, LayerOrder::root(), )) } // MathML's default lang has precedence over both `lang` and `xml:lang` if ns == structs::kNameSpaceID_MathML as i32 { if self.local_name().as_ptr() == atom!("math").as_ptr() { hints.push(MATHML_LANG_RULE.clone()); } } } fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { let node_flags = selector_flags_to_node_flags(flags); self.as_node().selector_flags() & node_flags == node_flags } fn relative_selector_search_direction(&self) -> Option { use crate::gecko_bindings::structs::NodeSelectorFlags; let flags = self.as_node().selector_flags(); if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling.0) != 0 { Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING) } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0) != 0 { Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR) } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0) != 0 { Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) } else { None } } } impl<'le> PartialEq for GeckoElement<'le> { #[inline] fn eq(&self, other: &Self) -> bool { self.0 as *const _ == other.0 as *const _ } } impl<'le> Eq for GeckoElement<'le> {} impl<'le> Hash for GeckoElement<'le> { #[inline] fn hash(&self, state: &mut H) { (self.0 as *const RawGeckoElement).hash(state); } } impl<'le> ::selectors::Element for GeckoElement<'le> { type Impl = SelectorImpl; #[inline] fn opaque(&self) -> OpaqueElement { OpaqueElement::new(self.0) } #[inline] fn parent_element(&self) -> Option { let parent_node = self.as_node().parent_node(); parent_node.and_then(|n| n.as_element()) } #[inline] fn parent_node_is_shadow_root(&self) -> bool { self.as_node() .parent_node() .map_or(false, |p| p.is_shadow_root()) } #[inline] fn containing_shadow_host(&self) -> Option { let shadow = self.containing_shadow()?; Some(shadow.host()) } #[inline] fn is_pseudo_element(&self) -> bool { self.implemented_pseudo_element().is_some() } #[inline] fn pseudo_element_originating_element(&self) -> Option { debug_assert!(self.is_pseudo_element()); debug_assert!(!self.matches_user_and_content_rules()); let mut current = *self; loop { if current.is_root_of_native_anonymous_subtree() { return current.traversal_parent(); } current = current.traversal_parent()?; } } #[inline] fn assigned_slot(&self) -> Option { let slot = self.extended_slots()?._base.mAssignedSlot.mRawPtr; unsafe { Some(GeckoElement(&slot.as_ref()?._base._base._base)) } } #[inline] fn prev_sibling_element(&self) -> Option { let mut sibling = self.as_node().prev_sibling(); while let Some(sibling_node) = sibling { if let Some(el) = sibling_node.as_element() { return Some(el); } sibling = sibling_node.prev_sibling(); } None } #[inline] fn next_sibling_element(&self) -> Option { let mut sibling = self.as_node().next_sibling(); while let Some(sibling_node) = sibling { if let Some(el) = sibling_node.as_element() { return Some(el); } sibling = sibling_node.next_sibling(); } None } #[inline] fn first_element_child(&self) -> Option { let mut child = self.as_node().first_child(); while let Some(child_node) = child { if let Some(el) = child_node.as_element() { return Some(el); } child = child_node.next_sibling(); } None } fn apply_selector_flags(&self, flags: ElementSelectorFlags) { // Handle flags that apply to the element. let self_flags = flags.for_self(); if !self_flags.is_empty() { self.as_node() .set_selector_flags(selector_flags_to_node_flags(flags)) } // Handle flags that apply to the parent. let parent_flags = flags.for_parent(); if !parent_flags.is_empty() { if let Some(p) = self.as_node().parent_node() { if p.is_element() || p.is_shadow_root() { p.set_selector_flags(selector_flags_to_node_flags(parent_flags)); } } } } fn has_attr_in_no_namespace(&self, local_name: &LocalName) -> bool { for attr in self.attrs() { if attr.mName.mBits == local_name.as_ptr() as usize { return true; } } false } fn attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, local_name: &LocalName, operation: &AttrSelectorOperation<&AttrValue>, ) -> bool { snapshot_helpers::attr_matches(self.attrs(), ns, local_name, operation) } #[inline] fn is_root(&self) -> bool { if self .as_node() .get_bool_flag(nsINode_BooleanFlag::ParentIsContent) { return false; } if !self.as_node().is_in_document() { return false; } debug_assert!(self .as_node() .parent_node() .map_or(false, |p| p.is_document())); // XXX this should always return true at this point, shouldn't it? unsafe { bindings::Gecko_IsRootElement(self.0) } } fn is_empty(&self) -> bool { !self .as_node() .dom_children() .any(|child| unsafe { Gecko_IsSignificantChild(child.0, true) }) } #[inline] fn has_local_name(&self, name: &WeakAtom) -> bool { self.local_name() == name } #[inline] fn has_namespace(&self, ns: &WeakNamespace) -> bool { self.namespace() == ns } #[inline] fn is_same_type(&self, other: &Self) -> bool { self.local_name() == other.local_name() && self.namespace() == other.namespace() } fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, ) -> bool { use selectors::matching::*; match *pseudo_class { NonTSPseudoClass::Autofill | NonTSPseudoClass::Defined | NonTSPseudoClass::Focus | NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | NonTSPseudoClass::Fullscreen | NonTSPseudoClass::Indeterminate | NonTSPseudoClass::MozInert | NonTSPseudoClass::PopoverOpen | NonTSPseudoClass::PlaceholderShown | NonTSPseudoClass::Target | NonTSPseudoClass::Valid | NonTSPseudoClass::Invalid | NonTSPseudoClass::MozBroken | NonTSPseudoClass::Required | NonTSPseudoClass::Optional | NonTSPseudoClass::ReadOnly | NonTSPseudoClass::ReadWrite | NonTSPseudoClass::FocusWithin | NonTSPseudoClass::FocusVisible | NonTSPseudoClass::MozDragOver | NonTSPseudoClass::MozDevtoolsHighlighted | NonTSPseudoClass::MozStyleeditorTransitioning | NonTSPseudoClass::MozMathIncrementScriptLevel | NonTSPseudoClass::InRange | NonTSPseudoClass::OutOfRange | NonTSPseudoClass::Default | NonTSPseudoClass::UserValid | NonTSPseudoClass::UserInvalid | NonTSPseudoClass::MozMeterOptimum | NonTSPseudoClass::MozMeterSubOptimum | NonTSPseudoClass::MozMeterSubSubOptimum | NonTSPseudoClass::MozHasDirAttr | NonTSPseudoClass::MozDirAttrLTR | NonTSPseudoClass::MozDirAttrRTL | NonTSPseudoClass::MozDirAttrLikeAuto | NonTSPseudoClass::Modal | NonTSPseudoClass::MozTopmostModal | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | NonTSPseudoClass::MozRevealed | NonTSPseudoClass::MozValueEmpty => self.state().intersects(pseudo_class.state_flag()), // TODO: This applying only to HTML elements is weird. NonTSPseudoClass::Dir(ref dir) => { self.is_html_element() && self.state().intersects(dir.element_state()) }, NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Link => { self.is_link() && context.visited_handling().matches_unvisited() }, NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(state), NonTSPseudoClass::Visited => { self.is_link() && context.visited_handling().matches_visited() }, NonTSPseudoClass::MozFirstNode => { if context.needs_selector_flags() { self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); } let mut elem = self.as_node(); while let Some(prev) = elem.prev_sibling() { if prev.contains_non_whitespace_content() { return false; } elem = prev; } true }, NonTSPseudoClass::MozLastNode => { if context.needs_selector_flags() { self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); } let mut elem = self.as_node(); while let Some(next) = elem.next_sibling() { if next.contains_non_whitespace_content() { return false; } elem = next; } true }, NonTSPseudoClass::MozOnlyWhitespace => { if context.needs_selector_flags() { self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); } if self .as_node() .dom_children() .any(|c| c.contains_non_whitespace_content()) { return false; } true }, NonTSPseudoClass::MozNativeAnonymous => !self.matches_user_and_content_rules(), NonTSPseudoClass::MozTableBorderNonzero => unsafe { bindings::Gecko_IsTableBorderNonzero(self.0) }, NonTSPseudoClass::MozSelectListBox => unsafe { bindings::Gecko_IsSelectListBox(self.0) }, NonTSPseudoClass::MozIsHTML => self.as_node().owner_doc().is_html_document(), NonTSPseudoClass::MozLWTheme | NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { let state_bit = pseudo_class.document_state_flag(); if state_bit.is_empty() { debug_assert!( matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), "Only moz-locale-dir should ever return an empty state" ); return false; } if context .extra_data .invalidation_data .document_state .intersects(state_bit) { return !context.in_negation(); } self.document_state().contains(state_bit) }, NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg), } } fn match_pseudo_element( &self, pseudo_element: &PseudoElement, _context: &mut MatchingContext, ) -> bool { // TODO(emilio): I believe we could assert we are a pseudo-element and // match the proper pseudo-element, given how we rulehash the stuff // based on the pseudo. match self.implemented_pseudo_element() { Some(ref pseudo) => *pseudo == *pseudo_element, None => false, } } #[inline] fn is_link(&self) -> bool { self.state().intersects(ElementState::VISITED_OR_UNVISITED) } #[inline] fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { if !self.has_id() { return false; } let element_id = match snapshot_helpers::get_id(self.attrs()) { Some(id) => id, None => return false, }; case_sensitivity.eq_atom(element_id, id) } #[inline] fn is_part(&self, name: &AtomIdent) -> bool { let attr = match self.get_part_attr() { Some(c) => c, None => return false, }; snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) } #[inline] fn imported_part(&self, name: &AtomIdent) -> Option { snapshot_helpers::imported_part(self.attrs(), name) } #[inline(always)] fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { let attr = match self.get_class_attr() { Some(c) => c, None => return false, }; snapshot_helpers::has_class_or_part(name, case_sensitivity, attr) } #[inline] fn is_html_element_in_html_document(&self) -> bool { self.is_html_element() && self.as_node().owner_doc().is_html_document() } #[inline] fn is_html_slot_element(&self) -> bool { self.is_html_element() && self.local_name().as_ptr() == local_name!("slot").as_ptr() } #[inline] fn ignores_nth_child_selectors(&self) -> bool { self.is_root_of_native_anonymous_subtree() } fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool { each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK)); true } }