/* 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::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; use crate::data::{ElementDataMut, ElementDataRef, ElementDataWrapper}; use crate::device::Device; use crate::dom::{ AttributeProvider, LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot, }; use crate::gecko::selector_parser::{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::properties::{ animated_properties::{AnimationValue, AnimationValueMap}, ComputedValues, Importance, OwnedPropertyDeclarationId, PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId, PropertyDeclarationIdSet, }; use crate::rule_tree::CascadeLevel as ServoCascadeLevel; use crate::rule_tree::CascadeOrigin as ServoCascadeOrigin; use crate::selector_parser::{AttrValue, Lang}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::stylesheets::scope_rule::ImplicitScopeRoot; 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 dom::{DocumentState, ElementState}; use euclid::default::Size2D; use nsstring::nsString; use rustc_hash::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::hash::{Hash, Hasher}; use std::sync::atomic::{AtomicU32, Ordering}; use std::{fmt, mem, ptr}; #[inline] fn elements_with_id<'a, 'le>( array: structs::RustSpan<*const RawGeckoElement>, ) -> &'a [GeckoElement<'le>] { unsafe { let elements: &[*const RawGeckoElement] = std::slice::from_raw_parts(array.begin, array.length); // NOTE(emilio): We rely on the in-memory representation of // GeckoElement<'ld> and *const 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) } } #[inline] fn implicit_scope_for_sheet(&self, sheet_index: usize) -> Option { use crate::stylesheets::StylesheetInDocument; let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? }; let sheet = author_styles.stylesheets.get(sheet_index)?; sheet.implicit_scope_root() } } /// 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!(mem::size_of::>() == mem::size_of::()); const_assert!(mem::align_of::>() == 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 { 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 may_have_element_children(&self) -> bool { self.flags() & structs::NODE_MAY_HAVE_ELEMENT_CHILDREN != 0 } #[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> { if !self.parent_node()?.may_have_element_children() { return None; } 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> { if !self.parent_node()?.may_have_element_children() { return None; } 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(mem::ManuallyDrop), } impl<'a> Drop for GeckoChildrenIterator<'a> { fn drop(&mut self) { if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self { unsafe { bindings::Gecko_DestroyStyleChildrenIterator(&mut **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_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 `ElementDataWrapper` for the element. #[inline(always)] pub fn get_data(&self) -> Option<&ElementDataWrapper> { 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) } } /// Check if mImpl contains a real pointer (not a bloom filter). #[inline(always)] fn has_attr_impl(&self) -> bool { let ptr = self.0.mAttrs.mImpl.mPtr as usize; ptr != 0 && (ptr & 1) == 0 } #[inline(always)] fn attrs(&self) -> &[structs::AttrArray_InternalAttr] { unsafe { if !self.has_attr_impl() { return &[]; } match self.0.mAttrs.mImpl.mPtr.as_ref() { Some(attrs) => attrs.mBuffer.as_slice(attrs.mAttrCount as usize), None => &[], } } } #[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 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!(bindings::Gecko_IsMainThread()); 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 Some(mut data) = self.mutate_data() else { debug!("(Element not styled, discarding hints)"); return; }; // 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 { return self.flags() & structs::NODE_IS_NATIVE_ANONYMOUS_ROOT != 0; } /// Whether the element is in an anonymous subtree. Note that this includes UA widgets! #[inline] fn in_native_anonymous_subtree(&self) -> bool { (self.flags() & structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) != 0 } /// Whether the element has been in a UA widget #[inline] fn in_ua_widget(&self) -> bool { (self.flags() & structs::NODE_HAS_BEEN_IN_UA_WIDGET) != 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); debug_assert!( after_value.is_some() || matches!(property_declaration_id, PropertyDeclarationId::Custom(..)) ); return after_value.is_none() || ***existing != after_value.unwrap(); } if combined_duration_seconds <= 0.0f32 { return false; } AnimationValue::is_different_for( property_declaration_id, before_change_style, after_change_style, ) } /// 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()) } /// Returns whether this element is an HTML