mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
When something switches to display: none, right now we rely on StopAnimationsForElementsWithoutFrames(), which posts a restyle and the previous ProcessPendingRestyles call was papering over it. For other elements in the display none subtree it doesn't matter, because we don't keep their styles around, but for the display: none element themselves we do need to update transitions on time. We could, possibly more generally, remove StopAnimationsForElementsWithoutFrames() altogether and cancel animations when we clear style data, perhaps... But that's probably worth a follow-up. Differential Revision: https://phabricator.services.mozilla.com/D151600
2199 lines
74 KiB
Rust
2199 lines
74 KiB
Rust
/* 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::author_styles::AuthorStyles;
|
|
use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
|
|
use crate::data::ElementData;
|
|
use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot};
|
|
use dom::{DocumentState, ElementState};
|
|
use crate::gecko::data::GeckoStyleSheet;
|
|
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::gecko_bindings::sugar::ownership::{HasArcFFI, HasSimpleFFI};
|
|
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};
|
|
use crate::properties::{ComputedValues, LonghandId};
|
|
use crate::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
|
|
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::{AtomIdent, AtomString};
|
|
use crate::CaseSensitivityExt;
|
|
use crate::LocalName;
|
|
use app_units::Au;
|
|
use euclid::default::Size2D;
|
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
|
use fxhash::FxHashMap;
|
|
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
|
|
use selectors::attr::{CaseSensitivity, NamespaceConstraint};
|
|
use selectors::matching::VisitedHandlingMode;
|
|
use selectors::matching::{ElementSelectorFlags, MatchingContext};
|
|
use selectors::sink::Push;
|
|
use selectors::{Element, OpaqueElement};
|
|
use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
use std::fmt;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::mem;
|
|
use std::ptr;
|
|
|
|
#[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, "<shadow-root> ({:#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()? };
|
|
let author_styles = AuthorStyles::<GeckoStyleSheet>::from_ffi(author_styles);
|
|
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) -> &[<Self::ConcreteNode as TNode>::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, "<text node> ({:#x})", self.opaque().0);
|
|
}
|
|
|
|
if self.is_document() {
|
|
return write!(f, "<document> ({:#x})", self.opaque().0);
|
|
}
|
|
|
|
if let Some(sr) = self.as_shadow_root() {
|
|
return sr.fmt(f);
|
|
}
|
|
|
|
write!(f, "<non-text node> ({:#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 flags_atomic(&self) -> &AtomicU32 {
|
|
use std::cell::Cell;
|
|
let flags: &Cell<u32> = &(self.0)._base._base_1.mFlags;
|
|
|
|
#[allow(dead_code)]
|
|
fn static_assert() {
|
|
let _: [u8; std::mem::size_of::<Cell<u32>>()] = [0u8; std::mem::size_of::<AtomicU32>()];
|
|
let _: [u8; std::mem::align_of::<Cell<u32>>()] = [0u8; std::mem::align_of::<AtomicU32>()];
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn flags(&self) -> u32 {
|
|
self.flags_atomic().load(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 as u32) != 0
|
|
}
|
|
|
|
#[inline]
|
|
fn is_connected(&self) -> bool {
|
|
self.get_bool_flag(nsINode_BooleanFlag::IsConnected)
|
|
}
|
|
|
|
/// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent.
|
|
/// Make sure to mirror any modifications in both places.
|
|
#[inline]
|
|
fn flattened_tree_parent_is_parent(&self) -> bool {
|
|
use crate::gecko_bindings::structs::*;
|
|
let flags = self.flags();
|
|
|
|
// FIXME(emilio): The shadow tree condition seems it shouldn't be needed
|
|
// anymore, if we check for slots.
|
|
if self.is_in_shadow_tree() {
|
|
return false;
|
|
}
|
|
|
|
let parent = unsafe { self.0.mParent.as_ref() }.map(GeckoNode);
|
|
let parent_el = parent.and_then(|p| p.as_element());
|
|
if flags & (NODE_IS_NATIVE_ANONYMOUS_ROOT as u32) != 0 &&
|
|
parent_el.map_or(false, |el| el.is_root())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if let Some(parent) = parent_el {
|
|
if parent.shadow_root().is_some() {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
#[inline]
|
|
fn flattened_tree_parent(&self) -> Option<Self> {
|
|
// TODO(emilio): Measure and consider not doing this fast-path and take
|
|
// always the common 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`.
|
|
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) }
|
|
}
|
|
}
|
|
|
|
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<Self> {
|
|
unsafe { self.0.mParent.as_ref().map(GeckoNode) }
|
|
}
|
|
|
|
#[inline]
|
|
fn first_child(&self) -> Option<Self> {
|
|
unsafe {
|
|
self.0
|
|
.mFirstChild
|
|
.raw::<nsIContent>()
|
|
.as_ref()
|
|
.map(GeckoNode::from_content)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn last_child(&self) -> Option<Self> {
|
|
unsafe { bindings::Gecko_GetLastChild(self.0).as_ref().map(GeckoNode) }
|
|
}
|
|
|
|
#[inline]
|
|
fn prev_sibling(&self) -> Option<Self> {
|
|
unsafe {
|
|
let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?);
|
|
if prev_or_last.0.mNextSibling.raw::<nsIContent>().is_null() {
|
|
return None;
|
|
}
|
|
Some(prev_or_last)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn next_sibling(&self) -> Option<Self> {
|
|
unsafe {
|
|
self.0
|
|
.mNextSibling
|
|
.raw::<nsIContent>()
|
|
.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<GeckoElement<'ln>> {
|
|
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<GeckoElement<'ln>> {
|
|
if !self.is_element() {
|
|
return None;
|
|
}
|
|
|
|
Some(GeckoElement(unsafe {
|
|
&*(self.0 as *const _ as *const RawGeckoElement)
|
|
}))
|
|
}
|
|
|
|
#[inline]
|
|
fn as_document(&self) -> Option<Self::ConcreteDocument> {
|
|
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<Self::ConcreteShadowRoot> {
|
|
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<GeckoNode<'a>>),
|
|
/// 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<GeckoNode<'a>> {
|
|
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<ElementData>> {
|
|
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 non_mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] {
|
|
unsafe {
|
|
let attrs = match self.0.mAttrs.mImpl.mPtr.as_ref() {
|
|
Some(attrs) => attrs,
|
|
None => return &[],
|
|
};
|
|
|
|
attrs.mBuffer.as_slice(attrs.mAttrCount as usize)
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] {
|
|
unsafe {
|
|
let attrs = match self.0.mAttrs.mImpl.mPtr.as_ref() {
|
|
Some(attrs) => attrs,
|
|
None => return &[],
|
|
};
|
|
|
|
let attrs = match attrs.mMappedAttrs.as_ref() {
|
|
Some(attrs) => attrs,
|
|
None => return &[],
|
|
};
|
|
|
|
attrs.mBuffer.as_slice(attrs.mAttrCount as usize)
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn get_part_attr(&self) -> Option<&structs::nsAttrValue> {
|
|
if !self.has_part_attr() {
|
|
return None;
|
|
}
|
|
snapshot_helpers::find_attr(self.non_mapped_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.non_mapped_attrs(), &atom!("class"))
|
|
}
|
|
|
|
#[inline]
|
|
fn closest_anon_subtree_root_parent(&self) -> Option<Self> {
|
|
debug_assert!(self.is_in_native_anonymous_subtree());
|
|
let mut current = *self;
|
|
|
|
loop {
|
|
if current.is_root_of_native_anonymous_subtree() {
|
|
return current.traversal_parent();
|
|
}
|
|
|
|
current = current.traversal_parent()?;
|
|
}
|
|
}
|
|
|
|
#[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().flags_atomic().fetch_or(flags, Ordering::Relaxed);
|
|
}
|
|
|
|
#[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 as u32) != 0
|
|
}
|
|
|
|
/// Returns true if this element needs lazy frame construction.
|
|
#[inline]
|
|
pub fn needs_frame(&self) -> bool {
|
|
self.flags() & (NODE_NEEDS_FRAME as u32) != 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_truncate(self.as_node().owner_doc().0.mDocumentState.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 as u32) != 0
|
|
}
|
|
|
|
#[inline]
|
|
fn before_or_after_pseudo(&self, is_before: bool) -> Option<Self> {
|
|
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 as u32) != 0;
|
|
}
|
|
|
|
/// Returns true if this node is the shadow root of an use-element shadow tree.
|
|
#[inline]
|
|
fn is_root_of_use_element_shadow_tree(&self) -> bool {
|
|
if !self.as_node().is_in_shadow_tree() {
|
|
return false;
|
|
}
|
|
if !self.parent_node_is_shadow_root() {
|
|
return false;
|
|
}
|
|
let host = self.containing_shadow_host().unwrap();
|
|
host.is_svg_element() && host.local_name() == &**local_name!("use")
|
|
}
|
|
|
|
fn css_transitions_info(&self) -> FxHashMap<LonghandId, Arc<AnimationValue>> {
|
|
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 raw_end_value = unsafe { Gecko_ElementTransitions_EndValueAt(self.0, i).as_ref() };
|
|
|
|
let end_value = AnimationValue::arc_from_borrowed(&raw_end_value)
|
|
.expect("AnimationValue not found in ElementTransitions");
|
|
|
|
let property = end_value.id();
|
|
debug_assert!(!property.is_logical());
|
|
map.insert(property, end_value.clone_arc());
|
|
}
|
|
map
|
|
}
|
|
|
|
fn needs_transitions_update_per_property(
|
|
&self,
|
|
longhand_id: LonghandId,
|
|
combined_duration: f32,
|
|
before_change_style: &ComputedValues,
|
|
after_change_style: &ComputedValues,
|
|
existing_transitions: &FxHashMap<LonghandId, Arc<AnimationValue>>,
|
|
) -> bool {
|
|
use crate::values::animated::{Animate, Procedure};
|
|
debug_assert!(!longhand_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(&longhand_id) {
|
|
let after_value =
|
|
AnimationValue::from_computed_values(longhand_id, after_change_style).unwrap();
|
|
|
|
return ***existing != after_value;
|
|
}
|
|
|
|
let from = AnimationValue::from_computed_values(longhand_id, before_change_style);
|
|
let to = AnimationValue::from_computed_values(longhand_id, after_change_style);
|
|
|
|
debug_assert_eq!(to.is_some(), from.is_some());
|
|
|
|
combined_duration > 0.0f32 &&
|
|
from != to &&
|
|
from.unwrap()
|
|
.animate(
|
|
to.as_ref().unwrap(),
|
|
Procedure::Interpolate { progress: 0.5 },
|
|
)
|
|
.is_ok()
|
|
}
|
|
}
|
|
|
|
/// 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::*;
|
|
let mut gecko_flags = 0u32;
|
|
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) {
|
|
gecko_flags |= NODE_HAS_SLOW_SELECTOR as u32;
|
|
}
|
|
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
|
|
gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS as u32;
|
|
}
|
|
if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) {
|
|
gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR as u32;
|
|
}
|
|
if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) {
|
|
gecko_flags |= NODE_HAS_EMPTY_SELECTOR as u32;
|
|
}
|
|
|
|
gecko_flags
|
|
}
|
|
|
|
fn get_animation_rule(
|
|
element: &GeckoElement,
|
|
cascade_level: CascadeLevel,
|
|
) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
|
use crate::properties::longhands::ANIMATABLE_PROPERTY_COUNT;
|
|
|
|
// 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(ANIMATABLE_PROPERTY_COUNT),
|
|
Default::default(),
|
|
);
|
|
if unsafe {
|
|
Gecko_GetAnimationRule(
|
|
element.0,
|
|
cascade_level,
|
|
AnimationValueMap::as_ffi_mut(&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
|
|
}
|
|
}
|
|
|
|
impl<'le> TElement for GeckoElement<'le> {
|
|
type ConcreteNode = GeckoNode<'le>;
|
|
type TraversalChildrenIterator = GeckoChildrenIterator<'le>;
|
|
|
|
fn inheritance_parent(&self) -> Option<Self> {
|
|
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<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.
|
|
if self.is_in_native_anonymous_subtree() ||
|
|
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> {
|
|
self.before_or_after_pseudo(/* is_before = */ true)
|
|
}
|
|
|
|
fn after_pseudo_element(&self) -> Option<Self> {
|
|
self.before_or_after_pseudo(/* is_before = */ false)
|
|
}
|
|
|
|
fn marker_pseudo_element(&self) -> Option<Self> {
|
|
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 {
|
|
let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr;
|
|
WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn primary_box_size(&self) -> Size2D<Au> {
|
|
if !self.as_node().is_connected() {
|
|
return Size2D::zero();
|
|
}
|
|
|
|
unsafe {
|
|
let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref();
|
|
if frame.is_null() {
|
|
return Size2D::zero();
|
|
}
|
|
Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height))
|
|
}
|
|
}
|
|
|
|
/// 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._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<structs::nsINode>] = 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::<structs::RefPtr<structs::nsINode>>(),
|
|
mem::size_of::<Self::ConcreteNode>(),
|
|
"Bad cast!"
|
|
);
|
|
|
|
unsafe { mem::transmute(assigned_nodes) }
|
|
}
|
|
|
|
#[inline]
|
|
fn shadow_root(&self) -> Option<GeckoShadowRoot<'le>> {
|
|
let slots = self.extended_slots()?;
|
|
unsafe { slots.mShadowRoot.mRawPtr.as_ref().map(GeckoShadowRoot) }
|
|
}
|
|
|
|
#[inline]
|
|
fn containing_shadow(&self) -> Option<GeckoShadowRoot<'le>> {
|
|
let slots = self.extended_slots()?;
|
|
unsafe {
|
|
slots
|
|
._base
|
|
.mContainingShadow
|
|
.mRawPtr
|
|
.as_ref()
|
|
.map(GeckoShadowRoot)
|
|
}
|
|
}
|
|
|
|
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 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<ArcBorrow<Locked<PropertyDeclarationBlock>>> {
|
|
if !self.may_have_style_attribute() {
|
|
return None;
|
|
}
|
|
|
|
let declarations = unsafe { Gecko_GetStyleAttrDeclarationBlock(self.0).as_ref() };
|
|
let declarations: Option<&RawOffsetArc<Locked<PropertyDeclarationBlock>>> =
|
|
declarations.and_then(|s| s.as_arc_opt());
|
|
declarations.map(|s| s.borrow_arc())
|
|
}
|
|
|
|
fn unset_dirty_style_attribute(&self) {
|
|
if !self.may_have_style_attribute() {
|
|
return;
|
|
}
|
|
|
|
unsafe { Gecko_UnsetDirtyStyleAttr(self.0) };
|
|
}
|
|
|
|
fn smil_override(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> {
|
|
unsafe {
|
|
let slots = self.extended_slots()?;
|
|
|
|
let declaration: &structs::DeclarationBlock =
|
|
slots.mSMILOverrideStyleDeclaration.mRawPtr.as_ref()?;
|
|
|
|
let raw: &structs::RawServoDeclarationBlock = declaration.mRaw.mRawPtr.as_ref()?;
|
|
|
|
Some(
|
|
Locked::<PropertyDeclarationBlock>::as_arc(
|
|
&*(&raw as *const &structs::RawServoDeclarationBlock),
|
|
)
|
|
.borrow_arc(),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn animation_rule(
|
|
&self,
|
|
_: &SharedStyleContext,
|
|
) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
|
get_animation_rule(self, CascadeLevel::Animations)
|
|
}
|
|
|
|
fn transition_rule(
|
|
&self,
|
|
_: &SharedStyleContext,
|
|
) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
|
get_animation_rule(self, CascadeLevel::Transitions)
|
|
}
|
|
|
|
#[inline]
|
|
fn state(&self) -> ElementState {
|
|
ElementState::from_bits_truncate(self.state_internal())
|
|
}
|
|
|
|
#[inline]
|
|
fn has_attr(&self, namespace: &Namespace, attr: &AtomIdent) -> bool {
|
|
unsafe { bindings::Gecko_HasAttr(self.0, namespace.0.as_ptr(), attr.as_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.non_mapped_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.non_mapped_attrs())
|
|
}
|
|
|
|
fn each_attr_name<F>(&self, mut callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent),
|
|
{
|
|
for attr in self
|
|
.non_mapped_attrs()
|
|
.iter()
|
|
.chain(self.mapped_attrs().iter())
|
|
{
|
|
let is_nodeinfo = attr.mName.mBits & 1 != 0;
|
|
unsafe {
|
|
let atom = if is_nodeinfo {
|
|
let node_info = &*((attr.mName.mBits & !1) as *const structs::NodeInfo);
|
|
node_info.mInner.mName
|
|
} else {
|
|
attr.mName.mBits as *const nsAtom
|
|
};
|
|
AtomIdent::with(atom, |a| callback(a))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn each_class<F>(&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<F>(&self, name: &AtomIdent, callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent),
|
|
{
|
|
snapshot_helpers::each_exported_part(self.non_mapped_attrs(), name, callback)
|
|
}
|
|
|
|
fn each_part<F>(&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 as u32) != 0
|
|
}
|
|
|
|
#[inline]
|
|
fn handled_snapshot(&self) -> bool {
|
|
self.flags() & (ELEMENT_HANDLED_SNAPSHOT as u32) != 0
|
|
}
|
|
|
|
unsafe fn set_handled_snapshot(&self) {
|
|
debug_assert!(self.has_data());
|
|
self.set_flags(ELEMENT_HANDLED_SNAPSHOT as u32)
|
|
}
|
|
|
|
#[inline]
|
|
fn has_dirty_descendants(&self) -> bool {
|
|
self.flags() & (ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 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 as u32)
|
|
}
|
|
|
|
unsafe fn unset_dirty_descendants(&self) {
|
|
self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
|
}
|
|
|
|
#[inline]
|
|
fn has_animation_only_dirty_descendants(&self) -> bool {
|
|
self.flags() & (ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
|
|
}
|
|
|
|
unsafe fn set_animation_only_dirty_descendants(&self) {
|
|
self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
|
}
|
|
|
|
unsafe fn unset_animation_only_dirty_descendants(&self) {
|
|
self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
|
}
|
|
|
|
unsafe fn clear_descendant_bits(&self) {
|
|
self.unset_flags(
|
|
ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32 |
|
|
ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32 |
|
|
NODE_DESCENDANTS_NEED_FRAMES as u32,
|
|
)
|
|
}
|
|
|
|
fn is_visited_link(&self) -> bool {
|
|
self.state().intersects(ElementState::VISITED)
|
|
}
|
|
|
|
/// This logic is duplicated in Gecko's nsINode::IsInNativeAnonymousSubtree.
|
|
#[inline]
|
|
fn is_in_native_anonymous_subtree(&self) -> bool {
|
|
use crate::gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
|
|
self.flags() & (NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE as u32) != 0
|
|
}
|
|
|
|
#[inline]
|
|
fn matches_user_and_author_rules(&self) -> bool {
|
|
!self.is_in_native_anonymous_subtree()
|
|
}
|
|
|
|
#[inline]
|
|
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
|
|
if !self.is_in_native_anonymous_subtree() {
|
|
return None;
|
|
}
|
|
|
|
if !self.has_properties() {
|
|
return None;
|
|
}
|
|
|
|
let pseudo_type = unsafe { bindings::Gecko_GetImplementedPseudo(self.0) };
|
|
PseudoElement::from_pseudo_type(pseudo_type)
|
|
}
|
|
|
|
#[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<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()
|
|
}
|
|
|
|
unsafe fn clear_data(&self) {
|
|
let ptr = self.0.mServoData.get();
|
|
self.unset_flags(
|
|
ELEMENT_HAS_SNAPSHOT as u32 |
|
|
ELEMENT_HANDLED_SNAPSHOT as u32 |
|
|
structs::Element_kAllServoDescendantBits |
|
|
NODE_NEEDS_FRAME as u32,
|
|
);
|
|
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<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(),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn has_animations(&self, _: &SharedStyleContext) -> bool {
|
|
self.has_any_animation()
|
|
}
|
|
|
|
fn has_css_animations(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool {
|
|
self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
|
|
}
|
|
|
|
fn has_css_transitions(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> 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 {
|
|
use crate::properties::LonghandIdSet;
|
|
|
|
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 = LonghandIdSet::new();
|
|
for transition_property in after_change_style.transition_properties() {
|
|
let physical_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),
|
|
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))
|
|
}
|
|
|
|
/// 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<AtomicRef<ElementData>> {
|
|
self.get_data().map(|x| x.borrow())
|
|
}
|
|
|
|
/// Mutably borrows the ElementData.
|
|
fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>> {
|
|
self.get_data().map(|x| x.borrow_mut())
|
|
}
|
|
|
|
#[inline]
|
|
fn lang_attr(&self) -> Option<AttrValue> {
|
|
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<Option<AttrValue>>, value: &Lang) -> bool {
|
|
// Gecko supports :lang() from CSS Selectors 3, which only accepts a
|
|
// single language tag, and which performs simple dash-prefix matching
|
|
// on it.
|
|
let override_lang_ptr = match override_lang {
|
|
Some(Some(ref atom)) => atom.as_ptr(),
|
|
_ => ptr::null_mut(),
|
|
};
|
|
unsafe {
|
|
Gecko_MatchLang(
|
|
self.0,
|
|
override_lang_ptr,
|
|
override_lang.is_some(),
|
|
value.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<V>(
|
|
&self,
|
|
visited_handling: VisitedHandlingMode,
|
|
hints: &mut V,
|
|
) where
|
|
V: Push<ApplicableDeclarationBlock>,
|
|
{
|
|
use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang;
|
|
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
|
|
use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
|
|
use crate::stylesheets::layer_rule::LayerOrder;
|
|
use crate::values::specified::color::Color;
|
|
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_ZOOM_RULE: ApplicableDeclarationBlock = {
|
|
let global_style_data = &*GLOBAL_STYLE_DATA;
|
|
let pdb = PropertyDeclarationBlock::with_one(
|
|
PropertyDeclaration::XTextZoom(SpecifiedZoom(false)),
|
|
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 {
|
|
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_ZOOM_RULE.clone());
|
|
}
|
|
}
|
|
let declarations =
|
|
unsafe { Gecko_GetHTMLPresentationAttrDeclarationBlock(self.0).as_ref() };
|
|
let declarations: Option<&RawOffsetArc<Locked<PropertyDeclarationBlock>>> =
|
|
declarations.and_then(|s| s.as_arc_opt());
|
|
if let Some(decl) = declarations {
|
|
hints.push(ApplicableDeclarationBlock::from_declarations(
|
|
decl.clone_arc(),
|
|
ServoCascadeLevel::PresHints,
|
|
LayerOrder::root(),
|
|
));
|
|
}
|
|
let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() };
|
|
let declarations: Option<&RawOffsetArc<Locked<PropertyDeclarationBlock>>> =
|
|
declarations.and_then(|s| s.as_arc_opt());
|
|
if let Some(decl) = declarations {
|
|
hints.push(ApplicableDeclarationBlock::from_declarations(
|
|
decl.clone_arc(),
|
|
ServoCascadeLevel::PresHints,
|
|
LayerOrder::root(),
|
|
));
|
|
}
|
|
|
|
// Support for link, vlink, and alink presentation hints on <body>
|
|
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()
|
|
},
|
|
};
|
|
let declarations: Option<&RawOffsetArc<Locked<PropertyDeclarationBlock>>> =
|
|
declarations.and_then(|s| s.as_arc_opt());
|
|
if let Some(decl) = declarations {
|
|
hints.push(ApplicableDeclarationBlock::from_declarations(
|
|
decl.clone_arc(),
|
|
ServoCascadeLevel::PresHints,
|
|
LayerOrder::root(),
|
|
));
|
|
}
|
|
|
|
let active = self
|
|
.state()
|
|
.intersects(NonTSPseudoClass::Active.state_flag());
|
|
if active {
|
|
let declarations =
|
|
unsafe { Gecko_GetActiveLinkAttrDeclarationBlock(self.0).as_ref() };
|
|
let declarations: Option<&RawOffsetArc<Locked<PropertyDeclarationBlock>>> =
|
|
declarations.and_then(|s| s.as_arc_opt());
|
|
if let Some(decl) = declarations {
|
|
hints.push(ApplicableDeclarationBlock::from_declarations(
|
|
decl.clone_arc(),
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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<H: Hasher>(&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<Self> {
|
|
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<Self> {
|
|
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<Self> {
|
|
debug_assert!(self.is_pseudo_element());
|
|
self.closest_anon_subtree_root_parent()
|
|
}
|
|
|
|
#[inline]
|
|
fn assigned_slot(&self) -> Option<Self> {
|
|
let slot = self.extended_slots()?._base.mAssignedSlot.mRawPtr;
|
|
|
|
unsafe { Some(GeckoElement(&slot.as_ref()?._base._base._base._base)) }
|
|
}
|
|
|
|
#[inline]
|
|
fn prev_sibling_element(&self) -> Option<Self> {
|
|
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<Self> {
|
|
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<Self> {
|
|
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 set_selector_flags(&self, flags: ElementSelectorFlags) {
|
|
debug_assert!(!flags.is_empty());
|
|
self.set_flags(selector_flags_to_node_flags(flags));
|
|
}
|
|
|
|
fn attr_matches(
|
|
&self,
|
|
ns: &NamespaceConstraint<&Namespace>,
|
|
local_name: &LocalName,
|
|
operation: &AttrSelectorOperation<&AttrValue>,
|
|
) -> bool {
|
|
unsafe {
|
|
match *operation {
|
|
AttrSelectorOperation::Exists => {
|
|
bindings::Gecko_HasAttr(self.0, ns.atom_or_null(), local_name.as_ptr())
|
|
},
|
|
AttrSelectorOperation::WithValue {
|
|
operator,
|
|
case_sensitivity,
|
|
expected_value,
|
|
} => {
|
|
let ignore_case = match case_sensitivity {
|
|
CaseSensitivity::CaseSensitive => false,
|
|
CaseSensitivity::AsciiCaseInsensitive => true,
|
|
};
|
|
// FIXME: case sensitivity for operators other than Equal
|
|
match operator {
|
|
AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring(
|
|
self.0,
|
|
ns.atom_or_null(),
|
|
local_name.as_ptr(),
|
|
expected_value.as_ptr(),
|
|
ignore_case,
|
|
),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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()));
|
|
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<Self::Impl>,
|
|
) -> 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::PlaceholderShown |
|
|
NonTSPseudoClass::Target |
|
|
NonTSPseudoClass::Valid |
|
|
NonTSPseudoClass::Invalid |
|
|
NonTSPseudoClass::MozBroken |
|
|
NonTSPseudoClass::MozLoading |
|
|
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 |
|
|
NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()),
|
|
NonTSPseudoClass::AnyLink => self.is_link(),
|
|
NonTSPseudoClass::Link => {
|
|
self.is_link() && context.visited_handling().matches_unvisited()
|
|
},
|
|
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.is_in_native_anonymous_subtree(),
|
|
NonTSPseudoClass::MozUseShadowTreeRoot => self.is_root_of_use_element_shadow_tree(),
|
|
NonTSPseudoClass::MozTableBorderNonzero => unsafe {
|
|
bindings::Gecko_IsTableBorderNonzero(self.0)
|
|
},
|
|
NonTSPseudoClass::MozBrowserFrame => unsafe { bindings::Gecko_IsBrowserFrame(self.0) },
|
|
NonTSPseudoClass::MozSelectListBox => unsafe {
|
|
bindings::Gecko_IsSelectListBox(self.0)
|
|
},
|
|
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_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.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<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.
|
|
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.non_mapped_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<AtomIdent> {
|
|
snapshot_helpers::imported_part(self.non_mapped_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()
|
|
}
|
|
}
|
|
|
|
/// A few helpers to help with attribute selectors and snapshotting.
|
|
pub trait NamespaceConstraintHelpers {
|
|
/// Returns the namespace of the selector, or null otherwise.
|
|
fn atom_or_null(&self) -> *mut nsAtom;
|
|
}
|
|
|
|
impl<'a> NamespaceConstraintHelpers for NamespaceConstraint<&'a Namespace> {
|
|
fn atom_or_null(&self) -> *mut nsAtom {
|
|
match *self {
|
|
NamespaceConstraint::Any => ptr::null_mut(),
|
|
NamespaceConstraint::Specific(ref ns) => ns.0.as_ptr(),
|
|
}
|
|
}
|
|
}
|