/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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.
usecrate::applicable_declarations::ApplicableDeclarationBlock; usecrate::bloom::each_relevant_element_hash; usecrate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; usecrate::data::ElementData; usecrate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot}; usecrate::gecko::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl}; usecrate::gecko::snapshot_helpers; usecrate::gecko_bindings::bindings; usecrate::gecko_bindings::bindings::Gecko_ElementHasAnimations; usecrate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; usecrate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; usecrate::gecko_bindings::bindings::Gecko_ElementState; usecrate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; usecrate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; usecrate::gecko_bindings::bindings::Gecko_GetAnimationRule; usecrate::gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations; usecrate::gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; usecrate::gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; usecrate::gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock; usecrate::gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock; usecrate::gecko_bindings::bindings::Gecko_IsSignificantChild; usecrate::gecko_bindings::bindings::Gecko_MatchLang; usecrate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; usecrate::gecko_bindings::bindings::Gecko_UpdateAnimations; usecrate::gecko_bindings::structs; usecrate::gecko_bindings::structs::nsChangeHint; usecrate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; usecrate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; usecrate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; usecrate::gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO; usecrate::gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT; usecrate::gecko_bindings::structs::NODE_DESCENDANTS_NEED_FRAMES; usecrate::gecko_bindings::structs::NODE_NEEDS_FRAME; usecrate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; usecrate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; usecrate::global_style_data::GLOBAL_STYLE_DATA; usecrate::invalidation::element::restyle_hints::RestyleHint; usecrate::media_queries::Device; usecrate::properties::{
animated_properties::{AnimationValue, AnimationValueMap},
ComputedValues, Importance, OwnedPropertyDeclarationId, PropertyDeclaration,
PropertyDeclarationBlock, PropertyDeclarationId, PropertyDeclarationIdSet,
}; usecrate::rule_tree::CascadeLevel as ServoCascadeLevel; usecrate::selector_parser::{AttrValue, Lang}; usecrate::shared_lock::{Locked, SharedRwLock}; usecrate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; usecrate::stylesheets::scope_rule::ImplicitScopeRoot; usecrate::stylist::CascadeData; usecrate::values::computed::Display; usecrate::values::{AtomIdent, AtomString}; usecrate::CaseSensitivityExt; usecrate::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};
// NOTE(emilio): We rely on the in-memory representation of // GeckoElement<'ld> and *mut RawGeckoElement being the same. #[allow(dead_code)] unsafefn static_assert() {
mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
}
mem::transmute(elements)
}
}
/// A simple wrapper over `Document`. #[derive(Clone, Copy)] pubstruct GeckoDocument<'ld>(pub &'ld structs::Document);
impl<'ld> TDocument for GeckoDocument<'ld> { type ConcreteNode = GeckoNode<'ld>;
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)] pubstruct GeckoNode<'ln>(pub &'ln RawGeckoNode);
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
}
// 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<u32>, &AtomicU32>(flags) }
}
// 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
}
#[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 { usecrate::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 { usecrate::gecko_bindings::structs::*; let flags = self.flags();
// NOTE(emilio): If this call is too expensive, we could manually inline more aggressively. unsafe {
bindings::Gecko_GetFlattenedTreeParentNode(self.0)
.as_ref()
.map(GeckoNode)
}
}
/// Returns the previous sibling of this node that is an element. #[inline] pubfn prev_sibling_element(&self) -> Option<GeckoElement> { letmut prev = self.prev_sibling(); whilelet Some(p) = prev { iflet 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] pubfn next_sibling_element(&self) -> Option<GeckoElement> { letmut next = self.next_sibling(); whilelet Some(n) = next { iflet 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] pubfn last_child_element(&self) -> Option<GeckoElement<'ln>> { let last = matchself.last_child() {
Some(n) => n,
None => return None,
}; iflet Some(e) = last.as_element() { return Some(e);
}
None
}
}
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>;
/// 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. pubenum GeckoChildrenIterator<'a> { /// A simple iterator that tracks the current node being iterated and /// replaces it with the next sibling when requested.
Current(Option<GeckoNode<'a>>), /// A Gecko-implemented iterator we need to drop appropriately.
GeckoIterator(std::mem::ManuallyDrop<structs::StyleChildrenIterator>),
}
impl<'a> Iterator for GeckoChildrenIterator<'a> { type Item = GeckoNode<'a>; fn next(&mutself) -> Option<GeckoNode<'a>> { match *self {
GeckoChildrenIterator::Current(curr) => { let next = curr.and_then(|node| node.next_sibling());
*self = GeckoChildrenIterator::Current(next);
curr
},
GeckoChildrenIterator::GeckoIterator(refmut 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)] pubstruct GeckoElement<'le>(pub &'le RawGeckoElement);
impl<'le> fmt::Debug for GeckoElement<'le> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use nsstring::nsCString;
/// Returns true if this element has descendants for lazy frame construction. #[inline] pubfn descendants_need_frames(&self) -> bool { self.flags() & NODE_DESCENDANTS_NEED_FRAMES != 0
}
/// Returns true if this element needs lazy frame construction. #[inline] pubfn 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()
})
}
/// 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. pubunsafefn note_explicit_hints(&self, restyle_hint: RestyleHint, change_hint: nsChangeHint) { usecrate::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"
);
// See comments on borrow_assert_main_thread and co. let data = matchself.get_data() {
Some(d) => d,
None => {
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);
}
#[cfg(debug_assertions)] letmut data = data.borrow_mut(); #[cfg(not(debug_assertions))] let data = &mut *data.as_ptr();
// 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. iflet 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 { returnfalse;
}
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!(
to.is_some() == from.is_some() || // If the declaration contains a custom property and getComputedValue was previously // called before that custom property was defined, `from` will be `None` here.
matches!(from, Some(AnimationValue::Custom(..))) || // Similarly, if the declaration contains a custom property, getComputedValue was // previously called, and the custom property registration is removed, `to` will be // `None`.
matches!(to, Some(AnimationValue::Custom(..)))
);
from != to
}
/// Get slow selector flags required for nth-of invalidation. pubfn 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`. pubfn slow_selector_flags_from_node_selector_flags(flags: u32) -> ElementSelectorFlags { usecrate::gecko_bindings::structs::NodeSelectorFlags; letmut 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 { usecrate::gecko_bindings::structs::NodeSelectorFlags; letmut 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<Arc<Locked<PropertyDeclarationBlock>>> { // 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. letmut animation_values = AnimationValueMap::with_capacity_and_hasher(
effect_count.min(crate::properties::property_counts::ANIMATABLE),
Default::default(),
); ifunsafe { 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)] pubunsafefn 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 implicit_scope_for_sheet_in_shadow_root(
opaque_host: OpaqueElement,
sheet_index: usize,
) -> Option<ImplicitScopeRoot> { // As long as this "unopaqued" element does not escape this function, we're not leaking // potentially-mutable elements from opaque elements. let e = unsafe { Self(
opaque_host
.as_const_ptr::<RawGeckoElement>()
.as_ref()
.unwrap(),
)
}; let shadow_root = match e.shadow_root() {
None => return None,
Some(r) => r,
};
shadow_root.implicit_scope_for_sheet(sheet_index)
}
fn traversal_children(&self) -> LayoutIterator<GeckoChildrenIterator<'le>> { // 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. ifself.is_html_slot_element() || self.shadow_root().is_some() || self.may_have_anonymous_children()
{ unsafe { letmut iter = std::mem::MaybeUninit::<structs::StyleChildrenIterator>::uninit();
bindings::Gecko_ConstructStyleChildrenIterator(self.0, iter.as_mut_ptr()); return LayoutIterator(GeckoChildrenIterator::GeckoIterator(
std::mem::ManuallyDrop::new(iter.assume_init()),
));
}
}
#[inline] fn query_container_size(&self, display: &Display) -> Size2D<Option<Au>> { // 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);
}
/// 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.0as *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<structs::nsINode>] = if !cfg!(target_os = "android") {
debug_assert_eq!( unsafe { bindings::Gecko_GetAssignedNodes(self.0) },
&slot.mAssignedNodes as *const _,
);
fn each_anonymous_content_child<F>(&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 inunsafe { &**array } { let node = GeckoNode::from_content(unsafe { &**content }); let element = match node.as_element() {
Some(e) => e,
None => continue,
};
// 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())
}
/// 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 { usecrate::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
}
let name = unsafe { bindings::Gecko_GetImplementedPseudoIdentifier(self.0) };
PseudoElement::from_pseudo_type( unsafe { bindings::Gecko_GetImplementedPseudoType(self.0) }, if name.is_null() {
None
} else {
Some(AtomIdent::new(unsafe { Atom::from_raw(name) }))
},
)
}
#[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");
}
unsafefn ensure_data(&self) -> AtomicRefMut<ElementData> { 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()
}
unsafefn 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 { iflet 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? returnself.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<Arc<ComputedValues>>,
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(),
);
}
}
// 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();
}
letmut transitions_to_keep = PropertyDeclarationIdSet::default(); for transition_property in after_change_style.transition_properties() { let physical_property = transition_property
.property
.as_borrowed()
.to_physical(after_change_style.writing_mode);
transitions_to_keep.insert(physical_property); ifself.needs_transitions_update_per_property(
physical_property,
after_change_ui_style
.transition_combined_duration_at(transition_property.index)
.seconds(),
before_change_style,
after_change_style,
&existing_transitions,
) { returntrue;
}
}
// 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()
}
fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, 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 synthesize_presentational_hints_for_legacy_attributes<V>(
&self,
visited_handling: VisitedHandlingMode,
hints: &mut V,
) where
V: Push<ApplicableDeclarationBlock>,
{ usecrate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; usecrate::properties::longhands::color::SpecifiedValue as SpecifiedColor; usecrate::stylesheets::layer_rule::LayerOrder; usecrate::values::specified::{color::Color, font::XTextScale};
lazy_static! { staticref 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(),
)
}; staticref 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(),
)
}; staticref 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(); // <th> elements get a default MozCenterOrInherit which may get overridden if ns == structs::kNameSpaceID_XHTML as i32 { ifself.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 { ifself.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() }; iflet 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() }; iflet 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 <body> ifself.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()
},
}; iflet 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() }; iflet Some(decl) = declarations {
hints.push(ApplicableDeclarationBlock::from_declarations( unsafe { Arc::from_raw_addrefed(decl) },
ServoCascadeLevel::PresHints,
LayerOrder::root(),
));
}
}
}
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() { iflet 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 inself.attrs() { if attr.mName.mBits == local_name.as_ptr() as usize { returntrue;
}
} false
}
if !self.as_node().is_in_document() { returnfalse;
}
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 match_pseudo_element(
&self,
pseudo_selector: &PseudoElement,
_context: &mut MatchingContext<Self::Impl>,
) -> 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. let pseudo = matchself.implemented_pseudo_element() {
Some(pseudo) => pseudo,
None => returnfalse,
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.