mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Provide container information in computed::Context and use it to resolve the container queries. This still fails a lot of tests because we are not ensuring that layout is up-to-date when we style the container descendants, but that's expected. Differential Revision: https://phabricator.services.mozilla.com/D146478
974 lines
33 KiB
Rust
974 lines
33 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/. */
|
|
|
|
//! Types and traits used to access the DOM from style calculation.
|
|
|
|
#![allow(unsafe_code)]
|
|
#![deny(missing_docs)]
|
|
|
|
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
|
use crate::context::SharedStyleContext;
|
|
#[cfg(feature = "gecko")]
|
|
use crate::context::{PostAnimationTasks, UpdateAnimationsTasks};
|
|
use crate::data::ElementData;
|
|
use crate::element_state::ElementState;
|
|
use crate::media_queries::Device;
|
|
use crate::properties::{AnimationDeclarations, ComputedValues, PropertyDeclarationBlock};
|
|
use crate::selector_parser::{AttrValue, Lang, PseudoElement, SelectorImpl};
|
|
use crate::shared_lock::{Locked, SharedRwLock};
|
|
use crate::stylist::CascadeData;
|
|
use crate::traversal_flags::TraversalFlags;
|
|
use crate::values::AtomIdent;
|
|
use crate::{LocalName, Namespace, WeakAtom};
|
|
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
|
use selectors::matching::{QuirksMode, VisitedHandlingMode};
|
|
use selectors::sink::Push;
|
|
use selectors::Element as SelectorsElement;
|
|
use servo_arc::{Arc, ArcBorrow};
|
|
use std::fmt;
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
use std::ops::Deref;
|
|
|
|
pub use style_traits::dom::OpaqueNode;
|
|
|
|
/// Simple trait to provide basic information about the type of an element.
|
|
///
|
|
/// We avoid exposing the full type id, since computing it in the general case
|
|
/// would be difficult for Gecko nodes.
|
|
pub trait NodeInfo {
|
|
/// Whether this node is an element.
|
|
fn is_element(&self) -> bool;
|
|
/// Whether this node is a text node.
|
|
fn is_text_node(&self) -> bool;
|
|
}
|
|
|
|
/// A node iterator that only returns node that don't need layout.
|
|
pub struct LayoutIterator<T>(pub T);
|
|
|
|
impl<T, N> Iterator for LayoutIterator<T>
|
|
where
|
|
T: Iterator<Item = N>,
|
|
N: NodeInfo,
|
|
{
|
|
type Item = N;
|
|
|
|
fn next(&mut self) -> Option<N> {
|
|
loop {
|
|
let n = self.0.next()?;
|
|
// Filter out nodes that layout should ignore.
|
|
if n.is_text_node() || n.is_element() {
|
|
return Some(n);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An iterator over the DOM children of a node.
|
|
pub struct DomChildren<N>(Option<N>);
|
|
impl<N> Iterator for DomChildren<N>
|
|
where
|
|
N: TNode,
|
|
{
|
|
type Item = N;
|
|
|
|
fn next(&mut self) -> Option<N> {
|
|
let n = self.0.take()?;
|
|
self.0 = n.next_sibling();
|
|
Some(n)
|
|
}
|
|
}
|
|
|
|
/// An iterator over the DOM descendants of a node in pre-order.
|
|
pub struct DomDescendants<N> {
|
|
previous: Option<N>,
|
|
scope: N,
|
|
}
|
|
|
|
impl<N> Iterator for DomDescendants<N>
|
|
where
|
|
N: TNode,
|
|
{
|
|
type Item = N;
|
|
|
|
#[inline]
|
|
fn next(&mut self) -> Option<N> {
|
|
let prev = self.previous.take()?;
|
|
self.previous = prev.next_in_preorder(Some(self.scope));
|
|
self.previous
|
|
}
|
|
}
|
|
|
|
/// The `TDocument` trait, to represent a document node.
|
|
pub trait TDocument: Sized + Copy + Clone {
|
|
/// The concrete `TNode` type.
|
|
type ConcreteNode: TNode<ConcreteDocument = Self>;
|
|
|
|
/// Get this document as a `TNode`.
|
|
fn as_node(&self) -> Self::ConcreteNode;
|
|
|
|
/// Returns whether this document is an HTML document.
|
|
fn is_html_document(&self) -> bool;
|
|
|
|
/// Returns the quirks mode of this document.
|
|
fn quirks_mode(&self) -> QuirksMode;
|
|
|
|
/// Get a list of elements with a given ID in this document, sorted by
|
|
/// tree position.
|
|
///
|
|
/// Can return an error to signal that this list is not available, or also
|
|
/// return an empty slice.
|
|
fn elements_with_id<'a>(
|
|
&self,
|
|
_id: &AtomIdent,
|
|
) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()>
|
|
where
|
|
Self: 'a,
|
|
{
|
|
Err(())
|
|
}
|
|
|
|
/// This document's shared lock.
|
|
fn shared_lock(&self) -> &SharedRwLock;
|
|
}
|
|
|
|
/// The `TNode` trait. This is the main generic trait over which the style
|
|
/// system can be implemented.
|
|
pub trait TNode: Sized + Copy + Clone + Debug + NodeInfo + PartialEq {
|
|
/// The concrete `TElement` type.
|
|
type ConcreteElement: TElement<ConcreteNode = Self>;
|
|
|
|
/// The concrete `TDocument` type.
|
|
type ConcreteDocument: TDocument<ConcreteNode = Self>;
|
|
|
|
/// The concrete `TShadowRoot` type.
|
|
type ConcreteShadowRoot: TShadowRoot<ConcreteNode = Self>;
|
|
|
|
/// Get this node's parent node.
|
|
fn parent_node(&self) -> Option<Self>;
|
|
|
|
/// Get this node's first child.
|
|
fn first_child(&self) -> Option<Self>;
|
|
|
|
/// Get this node's last child.
|
|
fn last_child(&self) -> Option<Self>;
|
|
|
|
/// Get this node's previous sibling.
|
|
fn prev_sibling(&self) -> Option<Self>;
|
|
|
|
/// Get this node's next sibling.
|
|
fn next_sibling(&self) -> Option<Self>;
|
|
|
|
/// Get the owner document of this node.
|
|
fn owner_doc(&self) -> Self::ConcreteDocument;
|
|
|
|
/// Iterate over the DOM children of a node.
|
|
fn dom_children(&self) -> DomChildren<Self> {
|
|
DomChildren(self.first_child())
|
|
}
|
|
|
|
/// Returns whether the node is attached to a document.
|
|
fn is_in_document(&self) -> bool;
|
|
|
|
/// Iterate over the DOM children of a node, in preorder.
|
|
fn dom_descendants(&self) -> DomDescendants<Self> {
|
|
DomDescendants {
|
|
previous: Some(*self),
|
|
scope: *self,
|
|
}
|
|
}
|
|
|
|
/// Returns the next children in pre-order, optionally scoped to a subtree
|
|
/// root.
|
|
#[inline]
|
|
fn next_in_preorder(&self, scoped_to: Option<Self>) -> Option<Self> {
|
|
if let Some(c) = self.first_child() {
|
|
return Some(c);
|
|
}
|
|
|
|
let mut current = Some(*self);
|
|
loop {
|
|
if current == scoped_to {
|
|
return None;
|
|
}
|
|
|
|
debug_assert!(current.is_some(), "not a descendant of the scope?");
|
|
if let Some(s) = current?.next_sibling() {
|
|
return Some(s);
|
|
}
|
|
|
|
current = current?.parent_node();
|
|
}
|
|
}
|
|
|
|
/// Get this node's parent element from the perspective of a restyle
|
|
/// traversal.
|
|
fn traversal_parent(&self) -> Option<Self::ConcreteElement>;
|
|
|
|
/// Get this node's parent element if present.
|
|
fn parent_element(&self) -> Option<Self::ConcreteElement> {
|
|
self.parent_node().and_then(|n| n.as_element())
|
|
}
|
|
|
|
/// Converts self into an `OpaqueNode`.
|
|
fn opaque(&self) -> OpaqueNode;
|
|
|
|
/// A debug id, only useful, mm... for debugging.
|
|
fn debug_id(self) -> usize;
|
|
|
|
/// Get this node as an element, if it's one.
|
|
fn as_element(&self) -> Option<Self::ConcreteElement>;
|
|
|
|
/// Get this node as a document, if it's one.
|
|
fn as_document(&self) -> Option<Self::ConcreteDocument>;
|
|
|
|
/// Get this node as a ShadowRoot, if it's one.
|
|
fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot>;
|
|
}
|
|
|
|
/// Wrapper to output the subtree rather than the single node when formatting
|
|
/// for Debug.
|
|
pub struct ShowSubtree<N: TNode>(pub N);
|
|
impl<N: TNode> Debug for ShowSubtree<N> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
writeln!(f, "DOM Subtree:")?;
|
|
fmt_subtree(f, &|f, n| write!(f, "{:?}", n), self.0, 1)
|
|
}
|
|
}
|
|
|
|
/// Wrapper to output the subtree along with the ElementData when formatting
|
|
/// for Debug.
|
|
pub struct ShowSubtreeData<N: TNode>(pub N);
|
|
impl<N: TNode> Debug for ShowSubtreeData<N> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
writeln!(f, "DOM Subtree:")?;
|
|
fmt_subtree(f, &|f, n| fmt_with_data(f, n), self.0, 1)
|
|
}
|
|
}
|
|
|
|
/// Wrapper to output the subtree along with the ElementData and primary
|
|
/// ComputedValues when formatting for Debug. This is extremely verbose.
|
|
#[cfg(feature = "servo")]
|
|
pub struct ShowSubtreeDataAndPrimaryValues<N: TNode>(pub N);
|
|
#[cfg(feature = "servo")]
|
|
impl<N: TNode> Debug for ShowSubtreeDataAndPrimaryValues<N> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
writeln!(f, "DOM Subtree:")?;
|
|
fmt_subtree(f, &|f, n| fmt_with_data_and_primary_values(f, n), self.0, 1)
|
|
}
|
|
}
|
|
|
|
fn fmt_with_data<N: TNode>(f: &mut fmt::Formatter, n: N) -> fmt::Result {
|
|
if let Some(el) = n.as_element() {
|
|
write!(
|
|
f,
|
|
"{:?} dd={} aodd={} data={:?}",
|
|
el,
|
|
el.has_dirty_descendants(),
|
|
el.has_animation_only_dirty_descendants(),
|
|
el.borrow_data(),
|
|
)
|
|
} else {
|
|
write!(f, "{:?}", n)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "servo")]
|
|
fn fmt_with_data_and_primary_values<N: TNode>(f: &mut fmt::Formatter, n: N) -> fmt::Result {
|
|
if let Some(el) = n.as_element() {
|
|
let dd = el.has_dirty_descendants();
|
|
let aodd = el.has_animation_only_dirty_descendants();
|
|
let data = el.borrow_data();
|
|
let values = data.as_ref().and_then(|d| d.styles.get_primary());
|
|
write!(
|
|
f,
|
|
"{:?} dd={} aodd={} data={:?} values={:?}",
|
|
el, dd, aodd, &data, values
|
|
)
|
|
} else {
|
|
write!(f, "{:?}", n)
|
|
}
|
|
}
|
|
|
|
fn fmt_subtree<F, N: TNode>(f: &mut fmt::Formatter, stringify: &F, n: N, indent: u32) -> fmt::Result
|
|
where
|
|
F: Fn(&mut fmt::Formatter, N) -> fmt::Result,
|
|
{
|
|
for _ in 0..indent {
|
|
write!(f, " ")?;
|
|
}
|
|
stringify(f, n)?;
|
|
if let Some(e) = n.as_element() {
|
|
for kid in e.traversal_children() {
|
|
writeln!(f, "")?;
|
|
fmt_subtree(f, stringify, kid, indent + 1)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// The ShadowRoot trait.
|
|
pub trait TShadowRoot: Sized + Copy + Clone + Debug + PartialEq {
|
|
/// The concrete node type.
|
|
type ConcreteNode: TNode<ConcreteShadowRoot = Self>;
|
|
|
|
/// Get this ShadowRoot as a node.
|
|
fn as_node(&self) -> Self::ConcreteNode;
|
|
|
|
/// Get the shadow host that hosts this ShadowRoot.
|
|
fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement;
|
|
|
|
/// Get the style data for this ShadowRoot.
|
|
fn style_data<'a>(&self) -> Option<&'a CascadeData>
|
|
where
|
|
Self: 'a;
|
|
|
|
/// Get the list of shadow parts for this shadow root.
|
|
fn parts<'a>(&self) -> &[<Self::ConcreteNode as TNode>::ConcreteElement]
|
|
where
|
|
Self: 'a,
|
|
{
|
|
&[]
|
|
}
|
|
|
|
/// Get a list of elements with a given ID in this shadow root, sorted by
|
|
/// tree position.
|
|
///
|
|
/// Can return an error to signal that this list is not available, or also
|
|
/// return an empty slice.
|
|
fn elements_with_id<'a>(
|
|
&self,
|
|
_id: &AtomIdent,
|
|
) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()>
|
|
where
|
|
Self: 'a,
|
|
{
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
/// The element trait, the main abstraction the style crate acts over.
|
|
pub trait TElement:
|
|
Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + SelectorsElement<Impl = SelectorImpl>
|
|
{
|
|
/// The concrete node type.
|
|
type ConcreteNode: TNode<ConcreteElement = Self>;
|
|
|
|
/// A concrete children iterator type in order to iterate over the `Node`s.
|
|
///
|
|
/// TODO(emilio): We should eventually replace this with the `impl Trait`
|
|
/// syntax.
|
|
type TraversalChildrenIterator: Iterator<Item = Self::ConcreteNode>;
|
|
|
|
/// Get this element as a node.
|
|
fn as_node(&self) -> Self::ConcreteNode;
|
|
|
|
/// A debug-only check that the device's owner doc matches the actual doc
|
|
/// we're the root of.
|
|
///
|
|
/// Otherwise we may set document-level state incorrectly, like the root
|
|
/// font-size used for rem units.
|
|
fn owner_doc_matches_for_testing(&self, _: &Device) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Whether this element should match user and author rules.
|
|
///
|
|
/// We use this for Native Anonymous Content in Gecko.
|
|
fn matches_user_and_author_rules(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Returns the depth of this element in the DOM.
|
|
fn depth(&self) -> usize {
|
|
let mut depth = 0;
|
|
let mut curr = *self;
|
|
while let Some(parent) = curr.traversal_parent() {
|
|
depth += 1;
|
|
curr = parent;
|
|
}
|
|
|
|
depth
|
|
}
|
|
|
|
/// Get this node's parent element from the perspective of a restyle
|
|
/// traversal.
|
|
fn traversal_parent(&self) -> Option<Self> {
|
|
self.as_node().traversal_parent()
|
|
}
|
|
|
|
/// Get this node's children from the perspective of a restyle traversal.
|
|
fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator>;
|
|
|
|
/// Returns the parent element we should inherit from.
|
|
///
|
|
/// This is pretty much always the parent element itself, except in the case
|
|
/// of Gecko's Native Anonymous Content, which uses the traversal parent
|
|
/// (i.e. the flattened tree parent) and which also may need to find the
|
|
/// closest non-NAC ancestor.
|
|
fn inheritance_parent(&self) -> Option<Self> {
|
|
self.parent_element()
|
|
}
|
|
|
|
/// The ::before pseudo-element of this element, if it exists.
|
|
fn before_pseudo_element(&self) -> Option<Self> {
|
|
None
|
|
}
|
|
|
|
/// The ::after pseudo-element of this element, if it exists.
|
|
fn after_pseudo_element(&self) -> Option<Self> {
|
|
None
|
|
}
|
|
|
|
/// The ::marker pseudo-element of this element, if it exists.
|
|
fn marker_pseudo_element(&self) -> Option<Self> {
|
|
None
|
|
}
|
|
|
|
/// Execute `f` for each anonymous content child (apart from ::before and
|
|
/// ::after) whose originating element is `self`.
|
|
fn each_anonymous_content_child<F>(&self, _f: F)
|
|
where
|
|
F: FnMut(Self),
|
|
{
|
|
}
|
|
|
|
/// Return whether this element is an element in the HTML namespace.
|
|
fn is_html_element(&self) -> bool;
|
|
|
|
/// Return whether this element is an element in the MathML namespace.
|
|
fn is_mathml_element(&self) -> bool;
|
|
|
|
/// Return whether this element is an element in the SVG namespace.
|
|
fn is_svg_element(&self) -> bool;
|
|
|
|
/// Return whether this element is an element in the XUL namespace.
|
|
fn is_xul_element(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Return the list of slotted nodes of this node.
|
|
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
|
|
&[]
|
|
}
|
|
|
|
/// Get this element's style attribute.
|
|
fn style_attribute(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>;
|
|
|
|
/// Unset the style attribute's dirty bit.
|
|
/// Servo doesn't need to manage ditry bit for style attribute.
|
|
fn unset_dirty_style_attribute(&self) {}
|
|
|
|
/// Get this element's SMIL override declarations.
|
|
fn smil_override(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> {
|
|
None
|
|
}
|
|
|
|
/// Get the combined animation and transition rules.
|
|
///
|
|
/// FIXME(emilio): Is this really useful?
|
|
fn animation_declarations(&self, context: &SharedStyleContext) -> AnimationDeclarations {
|
|
if !self.may_have_animations() {
|
|
return Default::default();
|
|
}
|
|
|
|
AnimationDeclarations {
|
|
animations: self.animation_rule(context),
|
|
transitions: self.transition_rule(context),
|
|
}
|
|
}
|
|
|
|
/// Get this element's animation rule.
|
|
fn animation_rule(
|
|
&self,
|
|
_: &SharedStyleContext,
|
|
) -> Option<Arc<Locked<PropertyDeclarationBlock>>>;
|
|
|
|
/// Get this element's transition rule.
|
|
fn transition_rule(
|
|
&self,
|
|
context: &SharedStyleContext,
|
|
) -> Option<Arc<Locked<PropertyDeclarationBlock>>>;
|
|
|
|
/// Get this element's state, for non-tree-structural pseudos.
|
|
fn state(&self) -> ElementState;
|
|
|
|
/// Whether this element has an attribute with a given namespace.
|
|
fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool;
|
|
|
|
/// Returns whether this element has a `part` attribute.
|
|
fn has_part_attr(&self) -> bool;
|
|
|
|
/// Returns whether this element exports any part from its shadow tree.
|
|
fn exports_any_part(&self) -> bool;
|
|
|
|
/// The ID for this element.
|
|
fn id(&self) -> Option<&WeakAtom>;
|
|
|
|
/// Internal iterator for the classes of this element.
|
|
fn each_class<F>(&self, callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent);
|
|
|
|
/// Internal iterator for the part names of this element.
|
|
fn each_part<F>(&self, _callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent),
|
|
{
|
|
}
|
|
|
|
/// Internal iterator for the attribute names of this element.
|
|
fn each_attr_name<F>(&self, callback: F)
|
|
where
|
|
F: FnMut(&LocalName);
|
|
|
|
/// Internal iterator for the part names that this element exports for a
|
|
/// given part name.
|
|
fn each_exported_part<F>(&self, _name: &AtomIdent, _callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent),
|
|
{
|
|
}
|
|
|
|
/// Whether a given element may generate a pseudo-element.
|
|
///
|
|
/// This is useful to avoid computing, for example, pseudo styles for
|
|
/// `::-first-line` or `::-first-letter`, when we know it won't affect us.
|
|
///
|
|
/// TODO(emilio, bz): actually implement the logic for it.
|
|
fn may_generate_pseudo(&self, pseudo: &PseudoElement, _primary_style: &ComputedValues) -> bool {
|
|
// ::before/::after are always supported for now, though we could try to
|
|
// optimize out leaf elements.
|
|
|
|
// ::first-letter and ::first-line are only supported for block-inside
|
|
// things, and only in Gecko, not Servo. Unfortunately, Gecko has
|
|
// block-inside things that might have any computed display value due to
|
|
// things like fieldsets, legends, etc. Need to figure out how this
|
|
// should work.
|
|
debug_assert!(
|
|
pseudo.is_eager(),
|
|
"Someone called may_generate_pseudo with a non-eager pseudo."
|
|
);
|
|
true
|
|
}
|
|
|
|
/// Returns true if this element may have a descendant needing style processing.
|
|
///
|
|
/// Note that we cannot guarantee the existence of such an element, because
|
|
/// it may have been removed from the DOM between marking it for restyle and
|
|
/// the actual restyle traversal.
|
|
fn has_dirty_descendants(&self) -> bool;
|
|
|
|
/// Returns whether state or attributes that may change style have changed
|
|
/// on the element, and thus whether the element has been snapshotted to do
|
|
/// restyle hint computation.
|
|
fn has_snapshot(&self) -> bool;
|
|
|
|
/// Returns whether the current snapshot if present has been handled.
|
|
fn handled_snapshot(&self) -> bool;
|
|
|
|
/// Flags this element as having handled already its snapshot.
|
|
unsafe fn set_handled_snapshot(&self);
|
|
|
|
/// Returns whether the element's styles are up-to-date for |traversal_flags|.
|
|
fn has_current_styles_for_traversal(
|
|
&self,
|
|
data: &ElementData,
|
|
traversal_flags: TraversalFlags,
|
|
) -> bool {
|
|
if traversal_flags.for_animation_only() {
|
|
// In animation-only restyle we never touch snapshots and don't care
|
|
// about them. But we can't assert '!self.handled_snapshot()'
|
|
// here since there are some cases that a second animation-only
|
|
// restyle which is a result of normal restyle (e.g. setting
|
|
// animation-name in normal restyle and creating a new CSS
|
|
// animation in a SequentialTask) is processed after the normal
|
|
// traversal in that we had elements that handled snapshot.
|
|
if !data.has_styles() {
|
|
return false;
|
|
}
|
|
|
|
if !data.hint.has_animation_hint_or_recascade() {
|
|
return true;
|
|
}
|
|
|
|
// FIXME: This should ideally always return false, but it is a hack
|
|
// to work around our weird animation-only traversal
|
|
// stuff: If we're display: none and the rules we could match could
|
|
// change, we consider our style up-to-date. This is because
|
|
// re-cascading with and old style doesn't guarantee returning the
|
|
// correct animation style (that's bug 1393323). So if our display
|
|
// changed, and it changed from display: none, we would incorrectly
|
|
// forget about it and wouldn't be able to correctly style our
|
|
// descendants later.
|
|
if data.styles.is_display_none() && data.hint.match_self() {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if self.has_snapshot() && !self.handled_snapshot() {
|
|
return false;
|
|
}
|
|
|
|
data.has_styles() && !data.hint.has_non_animation_invalidations()
|
|
}
|
|
|
|
/// Returns whether the element's styles are up-to-date after traversal
|
|
/// (i.e. in post traversal).
|
|
fn has_current_styles(&self, data: &ElementData) -> bool {
|
|
if self.has_snapshot() && !self.handled_snapshot() {
|
|
return false;
|
|
}
|
|
|
|
data.has_styles() &&
|
|
// TODO(hiro): When an animating element moved into subtree of
|
|
// contenteditable element, there remains animation restyle hints in
|
|
// post traversal. It's generally harmless since the hints will be
|
|
// processed in a next styling but ideally it should be processed soon.
|
|
//
|
|
// Without this, we get failures in:
|
|
// layout/style/crashtests/1383319.html
|
|
// layout/style/crashtests/1383001.html
|
|
//
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1389675 tracks fixing
|
|
// this.
|
|
!data.hint.has_non_animation_invalidations()
|
|
}
|
|
|
|
/// Flag that this element has a descendant for style processing.
|
|
///
|
|
/// Only safe to call with exclusive access to the element.
|
|
unsafe fn set_dirty_descendants(&self);
|
|
|
|
/// Flag that this element has no descendant for style processing.
|
|
///
|
|
/// Only safe to call with exclusive access to the element.
|
|
unsafe fn unset_dirty_descendants(&self);
|
|
|
|
/// Similar to the dirty_descendants but for representing a descendant of
|
|
/// the element needs to be updated in animation-only traversal.
|
|
fn has_animation_only_dirty_descendants(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Flag that this element has a descendant for animation-only restyle
|
|
/// processing.
|
|
///
|
|
/// Only safe to call with exclusive access to the element.
|
|
unsafe fn set_animation_only_dirty_descendants(&self) {}
|
|
|
|
/// Flag that this element has no descendant for animation-only restyle processing.
|
|
///
|
|
/// Only safe to call with exclusive access to the element.
|
|
unsafe fn unset_animation_only_dirty_descendants(&self) {}
|
|
|
|
/// Clear all bits related describing the dirtiness of descendants.
|
|
///
|
|
/// In Gecko, this corresponds to the regular dirty descendants bit, the
|
|
/// animation-only dirty descendants bit, and the lazy frame construction
|
|
/// descendants bit.
|
|
unsafe fn clear_descendant_bits(&self) {
|
|
self.unset_dirty_descendants();
|
|
}
|
|
|
|
/// Returns true if this element is a visited link.
|
|
///
|
|
/// Servo doesn't support visited styles yet.
|
|
fn is_visited_link(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Returns true if this element is in a native anonymous subtree.
|
|
fn is_in_native_anonymous_subtree(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Returns the pseudo-element implemented by this element, if any.
|
|
///
|
|
/// Gecko traverses pseudo-elements during the style traversal, and we need
|
|
/// to know this so we can properly grab the pseudo-element style from the
|
|
/// parent element.
|
|
///
|
|
/// Note that we still need to compute the pseudo-elements before-hand,
|
|
/// given otherwise we don't know if we need to create an element or not.
|
|
///
|
|
/// Servo doesn't have to deal with this.
|
|
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
|
|
None
|
|
}
|
|
|
|
/// Atomically stores the number of children of this node that we will
|
|
/// need to process during bottom-up traversal.
|
|
fn store_children_to_process(&self, n: isize);
|
|
|
|
/// Atomically notes that a child has been processed during bottom-up
|
|
/// traversal. Returns the number of children left to process.
|
|
fn did_process_child(&self) -> isize;
|
|
|
|
/// Gets a reference to the ElementData container, or creates one.
|
|
///
|
|
/// Unsafe because it can race to allocate and leak if not used with
|
|
/// exclusive access to the element.
|
|
unsafe fn ensure_data(&self) -> AtomicRefMut<ElementData>;
|
|
|
|
/// Clears the element data reference, if any.
|
|
///
|
|
/// Unsafe following the same reasoning as ensure_data.
|
|
unsafe fn clear_data(&self);
|
|
|
|
/// Whether there is an ElementData container.
|
|
fn has_data(&self) -> bool;
|
|
|
|
/// Immutably borrows the ElementData.
|
|
fn borrow_data(&self) -> Option<AtomicRef<ElementData>>;
|
|
|
|
/// Mutably borrows the ElementData.
|
|
fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>>;
|
|
|
|
/// Whether we should skip any root- or item-based display property
|
|
/// blockification on this element. (This function exists so that Gecko
|
|
/// native anonymous content can opt out of this style fixup.)
|
|
fn skip_item_display_fixup(&self) -> bool;
|
|
|
|
/// In Gecko, element has a flag that represents the element may have
|
|
/// any type of animations or not to bail out animation stuff early.
|
|
/// Whereas Servo doesn't have such flag.
|
|
fn may_have_animations(&self) -> bool;
|
|
|
|
/// Creates a task to update various animation state on a given (pseudo-)element.
|
|
#[cfg(feature = "gecko")]
|
|
fn update_animations(
|
|
&self,
|
|
before_change_style: Option<Arc<ComputedValues>>,
|
|
tasks: UpdateAnimationsTasks,
|
|
);
|
|
|
|
/// Creates a task to process post animation on a given element.
|
|
#[cfg(feature = "gecko")]
|
|
fn process_post_animation(&self, tasks: PostAnimationTasks);
|
|
|
|
/// Returns true if the element has relevant animations. Relevant
|
|
/// animations are those animations that are affecting the element's style
|
|
/// or are scheduled to do so in the future.
|
|
fn has_animations(&self, context: &SharedStyleContext) -> bool;
|
|
|
|
/// Returns true if the element has a CSS animation. The `context` and `pseudo_element`
|
|
/// arguments are only used by Servo, since it stores animations globally and pseudo-elements
|
|
/// are not in the DOM.
|
|
fn has_css_animations(
|
|
&self,
|
|
context: &SharedStyleContext,
|
|
pseudo_element: Option<PseudoElement>,
|
|
) -> bool;
|
|
|
|
/// Returns true if the element has a CSS transition (including running transitions and
|
|
/// completed transitions). The `context` and `pseudo_element` arguments are only used
|
|
/// by Servo, since it stores animations globally and pseudo-elements are not in the DOM.
|
|
fn has_css_transitions(
|
|
&self,
|
|
context: &SharedStyleContext,
|
|
pseudo_element: Option<PseudoElement>,
|
|
) -> bool;
|
|
|
|
/// Returns true if the element has animation restyle hints.
|
|
fn has_animation_restyle_hints(&self) -> bool {
|
|
let data = match self.borrow_data() {
|
|
Some(d) => d,
|
|
None => return false,
|
|
};
|
|
return data.hint.has_animation_hint();
|
|
}
|
|
|
|
/// The shadow root this element is a host of.
|
|
fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>;
|
|
|
|
/// The shadow root which roots the subtree this element is contained in.
|
|
fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>;
|
|
|
|
/// Return the element which we can use to look up rules in the selector
|
|
/// maps.
|
|
///
|
|
/// This is always the element itself, except in the case where we are an
|
|
/// element-backed pseudo-element, in which case we return the originating
|
|
/// element.
|
|
fn rule_hash_target(&self) -> Self {
|
|
if self.is_pseudo_element() {
|
|
self.pseudo_element_originating_element()
|
|
.expect("Trying to collect rules for a detached pseudo-element")
|
|
} else {
|
|
*self
|
|
}
|
|
}
|
|
|
|
/// Executes the callback for each applicable style rule data which isn't
|
|
/// the main document's data (which stores UA / author rules).
|
|
///
|
|
/// The element passed to the callback is the containing shadow host for the
|
|
/// data if it comes from Shadow DOM.
|
|
///
|
|
/// Returns whether normal document author rules should apply.
|
|
///
|
|
/// TODO(emilio): We could separate the invalidation data for elements
|
|
/// matching in other scopes to avoid over-invalidation.
|
|
fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
|
|
where
|
|
Self: 'a,
|
|
F: FnMut(&'a CascadeData, Self),
|
|
{
|
|
use crate::rule_collector::containing_shadow_ignoring_svg_use;
|
|
|
|
let target = self.rule_hash_target();
|
|
if !target.matches_user_and_author_rules() {
|
|
return false;
|
|
}
|
|
|
|
let mut doc_rules_apply = true;
|
|
|
|
// Use the same rules to look for the containing host as we do for rule
|
|
// collection.
|
|
if let Some(shadow) = containing_shadow_ignoring_svg_use(target) {
|
|
doc_rules_apply = false;
|
|
if let Some(data) = shadow.style_data() {
|
|
f(data, shadow.host());
|
|
}
|
|
}
|
|
|
|
if let Some(shadow) = target.shadow_root() {
|
|
if let Some(data) = shadow.style_data() {
|
|
f(data, shadow.host());
|
|
}
|
|
}
|
|
|
|
let mut current = target.assigned_slot();
|
|
while let Some(slot) = current {
|
|
// Slots can only have assigned nodes when in a shadow tree.
|
|
let shadow = slot.containing_shadow().unwrap();
|
|
if let Some(data) = shadow.style_data() {
|
|
if data.any_slotted_rule() {
|
|
f(data, shadow.host());
|
|
}
|
|
}
|
|
current = slot.assigned_slot();
|
|
}
|
|
|
|
if target.has_part_attr() {
|
|
if let Some(mut inner_shadow) = target.containing_shadow() {
|
|
loop {
|
|
let inner_shadow_host = inner_shadow.host();
|
|
match inner_shadow_host.containing_shadow() {
|
|
Some(shadow) => {
|
|
if let Some(data) = shadow.style_data() {
|
|
if data.any_part_rule() {
|
|
f(data, shadow.host())
|
|
}
|
|
}
|
|
// TODO: Could be more granular.
|
|
if !inner_shadow_host.exports_any_part() {
|
|
break;
|
|
}
|
|
inner_shadow = shadow;
|
|
},
|
|
None => {
|
|
// TODO(emilio): Should probably distinguish with
|
|
// MatchesDocumentRules::{No,Yes,IfPart} or
|
|
// something so that we could skip some work.
|
|
doc_rules_apply = true;
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doc_rules_apply
|
|
}
|
|
|
|
/// Returns true if one of the transitions needs to be updated on this element. We check all
|
|
/// the transition properties to make sure that updating transitions is necessary.
|
|
/// This method should only be called if might_needs_transitions_update returns true when
|
|
/// passed the same parameters.
|
|
#[cfg(feature = "gecko")]
|
|
fn needs_transitions_update(
|
|
&self,
|
|
before_change_style: &ComputedValues,
|
|
after_change_style: &ComputedValues,
|
|
) -> bool;
|
|
|
|
/// Returns the value of the `xml:lang=""` attribute (or, if appropriate,
|
|
/// the `lang=""` attribute) on this element.
|
|
fn lang_attr(&self) -> Option<AttrValue>;
|
|
|
|
/// Returns whether this element's language matches the language tag
|
|
/// `value`. If `override_lang` is not `None`, it specifies the value
|
|
/// of the `xml:lang=""` or `lang=""` attribute to use in place of
|
|
/// looking at the element and its ancestors. (This argument is used
|
|
/// to implement matching of `:lang()` against snapshots.)
|
|
fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool;
|
|
|
|
/// Returns whether this element is the main body element of the HTML
|
|
/// document it is on.
|
|
fn is_html_document_body_element(&self) -> bool;
|
|
|
|
/// Generate the proper applicable declarations due to presentational hints,
|
|
/// and insert them into `hints`.
|
|
fn synthesize_presentational_hints_for_legacy_attributes<V>(
|
|
&self,
|
|
visited_handling: VisitedHandlingMode,
|
|
hints: &mut V,
|
|
) where
|
|
V: Push<ApplicableDeclarationBlock>;
|
|
|
|
/// Returns element's local name.
|
|
fn local_name(&self) -> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedLocalName;
|
|
|
|
/// Returns element's namespace.
|
|
fn namespace(&self)
|
|
-> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl;
|
|
|
|
/// Returns the size of the primary box of the element.
|
|
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au>;
|
|
}
|
|
|
|
/// TNode and TElement aren't Send because we want to be careful and explicit
|
|
/// about our parallel traversal. However, there are certain situations
|
|
/// (including but not limited to the traversal) where we need to send DOM
|
|
/// objects to other threads.
|
|
///
|
|
/// That's the reason why `SendNode` exists.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SendNode<N: TNode>(N);
|
|
unsafe impl<N: TNode> Send for SendNode<N> {}
|
|
impl<N: TNode> SendNode<N> {
|
|
/// Unsafely construct a SendNode.
|
|
pub unsafe fn new(node: N) -> Self {
|
|
SendNode(node)
|
|
}
|
|
}
|
|
impl<N: TNode> Deref for SendNode<N> {
|
|
type Target = N;
|
|
fn deref(&self) -> &N {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
/// Same reason as for the existence of SendNode, SendElement does the proper
|
|
/// things for a given `TElement`.
|
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
|
pub struct SendElement<E: TElement>(E);
|
|
unsafe impl<E: TElement> Send for SendElement<E> {}
|
|
impl<E: TElement> SendElement<E> {
|
|
/// Unsafely construct a SendElement.
|
|
pub unsafe fn new(el: E) -> Self {
|
|
SendElement(el)
|
|
}
|
|
}
|
|
impl<E: TElement> Deref for SendElement<E> {
|
|
type Target = E;
|
|
fn deref(&self) -> &E {
|
|
&self.0
|
|
}
|
|
}
|