mirror of
https://github.com/servo/servo.git
synced 2025-06-21 23:59:00 +01:00
836 lines
34 KiB
Rust
836 lines
34 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
//! High-level interface to CSS selector matching.
|
|
|
|
#![allow(unsafe_code)]
|
|
#![deny(missing_docs)]
|
|
|
|
use context::{ElementCascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext};
|
|
use data::{ElementData, ElementStyles, RestyleData};
|
|
use dom::TElement;
|
|
use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS};
|
|
use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE};
|
|
use invalidation::element::restyle_hints::RestyleHint;
|
|
use properties::ComputedValues;
|
|
use rule_tree::{CascadeLevel, StrongRuleNode};
|
|
use selector_parser::{PseudoElement, RestyleDamage};
|
|
use selectors::matching::ElementSelectorFlags;
|
|
use servo_arc::{Arc, ArcBorrow};
|
|
use traversal_flags;
|
|
|
|
/// Represents the result of comparing an element's old and new style.
|
|
pub struct StyleDifference {
|
|
/// The resulting damage.
|
|
pub damage: RestyleDamage,
|
|
|
|
/// Whether any styles changed.
|
|
pub change: StyleChange,
|
|
}
|
|
|
|
impl StyleDifference {
|
|
/// Creates a new `StyleDifference`.
|
|
pub fn new(damage: RestyleDamage, change: StyleChange) -> Self {
|
|
StyleDifference {
|
|
change: change,
|
|
damage: damage,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents whether or not the style of an element has changed.
|
|
#[derive(Clone, Copy)]
|
|
pub enum StyleChange {
|
|
/// The style hasn't changed.
|
|
Unchanged,
|
|
/// The style has changed.
|
|
Changed,
|
|
}
|
|
|
|
/// Whether or not newly computed values for an element need to be cascade
|
|
/// to children.
|
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
|
pub enum ChildCascadeRequirement {
|
|
/// Old and new computed values were the same, or we otherwise know that
|
|
/// we won't bother recomputing style for children, so we can skip cascading
|
|
/// the new values into child elements.
|
|
CanSkipCascade = 0,
|
|
/// Old and new computed values were different, so we must cascade the
|
|
/// new values to children.
|
|
///
|
|
/// FIXME(heycam) Although this is "must" cascade, in the future we should
|
|
/// track whether child elements rely specifically on inheriting particular
|
|
/// property values. When we do that, we can treat `MustCascadeChildren` as
|
|
/// "must cascade unless we know that changes to these properties can be
|
|
/// ignored".
|
|
MustCascadeChildren = 1,
|
|
/// The same as `MustCascadeChildren`, but for the entire subtree. This is
|
|
/// used to handle root font-size updates needing to recascade the whole
|
|
/// document.
|
|
MustCascadeDescendants = 2,
|
|
}
|
|
|
|
bitflags! {
|
|
/// Flags that represent the result of replace_rules.
|
|
pub flags RulesChanged: u8 {
|
|
/// Normal rules are changed.
|
|
const NORMAL_RULES_CHANGED = 0x01,
|
|
/// Important rules are changed.
|
|
const IMPORTANT_RULES_CHANGED = 0x02,
|
|
}
|
|
}
|
|
|
|
impl RulesChanged {
|
|
/// Return true if there are any normal rules changed.
|
|
#[inline]
|
|
pub fn normal_rules_changed(&self) -> bool {
|
|
self.contains(NORMAL_RULES_CHANGED)
|
|
}
|
|
|
|
/// Return true if there are any important rules changed.
|
|
#[inline]
|
|
pub fn important_rules_changed(&self) -> bool {
|
|
self.contains(IMPORTANT_RULES_CHANGED)
|
|
}
|
|
}
|
|
|
|
/// Determines which styles are being cascaded currently.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum CascadeVisitedMode {
|
|
/// Cascade the regular, unvisited styles.
|
|
Unvisited,
|
|
/// Cascade the styles used when an element's relevant link is visited. A
|
|
/// "relevant link" is the element being matched if it is a link or the
|
|
/// nearest ancestor link.
|
|
Visited,
|
|
}
|
|
|
|
impl CascadeVisitedMode {
|
|
/// Returns whether the cascade should filter to only visited dependent
|
|
/// properties based on the cascade mode.
|
|
pub fn visited_dependent_only(&self) -> bool {
|
|
*self == CascadeVisitedMode::Visited
|
|
}
|
|
}
|
|
|
|
trait PrivateMatchMethods: TElement {
|
|
/// If there is no transition rule in the ComputedValues, it returns None.
|
|
#[cfg(feature = "gecko")]
|
|
fn get_after_change_style(
|
|
&self,
|
|
context: &mut StyleContext<Self>,
|
|
primary_style: &Arc<ComputedValues>
|
|
) -> Option<Arc<ComputedValues>> {
|
|
use context::CascadeInputs;
|
|
use style_resolver::{PseudoElementResolution, StyleResolverForElement};
|
|
use stylist::RuleInclusion;
|
|
|
|
let rule_node = primary_style.rules();
|
|
let without_transition_rules =
|
|
context.shared.stylist.rule_tree().remove_transition_rule_if_applicable(rule_node);
|
|
if without_transition_rules == *rule_node {
|
|
// We don't have transition rule in this case, so return None to let
|
|
// the caller use the original ComputedValues.
|
|
return None;
|
|
}
|
|
|
|
// FIXME(bug 868975): We probably need to transition visited style as
|
|
// well.
|
|
let inputs =
|
|
CascadeInputs {
|
|
rules: Some(without_transition_rules),
|
|
visited_rules: primary_style.get_visited_style().and_then(|s| s.rules.clone()),
|
|
};
|
|
|
|
// Actually `PseudoElementResolution` doesn't really matter.
|
|
let style =
|
|
StyleResolverForElement::new(*self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable)
|
|
.cascade_style_and_visited_with_default_parents(inputs);
|
|
|
|
Some(style)
|
|
}
|
|
|
|
#[cfg(feature = "gecko")]
|
|
fn needs_animations_update(
|
|
&self,
|
|
context: &mut StyleContext<Self>,
|
|
old_values: Option<&Arc<ComputedValues>>,
|
|
new_values: &ComputedValues,
|
|
) -> bool {
|
|
use properties::longhands::display::computed_value as display;
|
|
|
|
let new_box_style = new_values.get_box();
|
|
let has_new_animation_style = new_box_style.specifies_animations();
|
|
let has_animations = self.has_css_animations();
|
|
|
|
old_values.map_or(has_new_animation_style, |old| {
|
|
let old_box_style = old.get_box();
|
|
let old_display_style = old_box_style.clone_display();
|
|
let new_display_style = new_box_style.clone_display();
|
|
|
|
// If the traverse is triggered by CSS rule changes, we need to
|
|
// try to update all CSS animations on the element if the element
|
|
// has or will have CSS animation style regardless of whether the
|
|
// animation is running or not.
|
|
// TODO: We should check which @keyframes changed/added/deleted
|
|
// and update only animations corresponding to those @keyframes.
|
|
(context.shared.traversal_flags.contains(traversal_flags::ForCSSRuleChanges) &&
|
|
(has_new_animation_style || has_animations)) ||
|
|
!old_box_style.animations_equals(new_box_style) ||
|
|
(old_display_style == display::T::none &&
|
|
new_display_style != display::T::none &&
|
|
has_new_animation_style) ||
|
|
(old_display_style != display::T::none &&
|
|
new_display_style == display::T::none &&
|
|
has_animations)
|
|
})
|
|
}
|
|
|
|
/// Create a SequentialTask for resolving descendants in a SMIL display property
|
|
/// animation if the display property changed from none.
|
|
#[cfg(feature = "gecko")]
|
|
fn handle_display_change_for_smil_if_needed(
|
|
&self,
|
|
context: &mut StyleContext<Self>,
|
|
old_values: Option<&ComputedValues>,
|
|
new_values: &ComputedValues,
|
|
restyle_hints: RestyleHint
|
|
) {
|
|
use context::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL;
|
|
use properties::longhands::display::computed_value as display;
|
|
|
|
let display_changed_from_none = old_values.map_or(false, |old| {
|
|
let old_display_style = old.get_box().clone_display();
|
|
let new_display_style = new_values.get_box().clone_display();
|
|
old_display_style == display::T::none &&
|
|
new_display_style != display::T::none
|
|
});
|
|
|
|
if display_changed_from_none {
|
|
// When display value is changed from none to other, we need
|
|
// to traverse descendant elements in a subsequent normal
|
|
// traversal (we can't traverse them in this animation-only
|
|
// restyle since we have no way to know whether the decendants
|
|
// need to be traversed at the beginning of the animation-only
|
|
// restyle)
|
|
debug_assert!(restyle_hints.intersects(RESTYLE_SMIL),
|
|
"Display animation should only happen for SMIL");
|
|
let task = ::context::SequentialTask::process_post_animation(*self,
|
|
DISPLAY_CHANGED_FROM_NONE_FOR_SMIL);
|
|
context.thread_local.tasks.push(task);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "gecko")]
|
|
fn process_animations(&self,
|
|
context: &mut StyleContext<Self>,
|
|
old_values: &mut Option<Arc<ComputedValues>>,
|
|
new_values: &mut Arc<ComputedValues>,
|
|
restyle_hint: RestyleHint,
|
|
important_rules_changed: bool) {
|
|
use context::{CASCADE_RESULTS, CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
|
|
use context::UpdateAnimationsTasks;
|
|
|
|
if context.shared.traversal_flags.for_animation_only() {
|
|
self.handle_display_change_for_smil_if_needed(context,
|
|
old_values.as_ref().map(|v| &**v),
|
|
new_values,
|
|
restyle_hint);
|
|
return;
|
|
}
|
|
|
|
// Bug 868975: These steps should examine and update the visited styles
|
|
// in addition to the unvisited styles.
|
|
|
|
let mut tasks = UpdateAnimationsTasks::empty();
|
|
if self.needs_animations_update(context, old_values.as_ref(), new_values) {
|
|
tasks.insert(CSS_ANIMATIONS);
|
|
}
|
|
|
|
let before_change_style = if self.might_need_transitions_update(old_values.as_ref().map(|s| &**s),
|
|
new_values) {
|
|
let after_change_style = if self.has_css_transitions() {
|
|
self.get_after_change_style(context, new_values)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// In order to avoid creating a SequentialTask for transitions which
|
|
// may not be updated, we check it per property to make sure Gecko
|
|
// side will really update transition.
|
|
let needs_transitions_update = {
|
|
// We borrow new_values here, so need to add a scope to make
|
|
// sure we release it before assigning a new value to it.
|
|
let after_change_style_ref =
|
|
after_change_style.as_ref().unwrap_or(&new_values);
|
|
|
|
self.needs_transitions_update(old_values.as_ref().unwrap(),
|
|
after_change_style_ref)
|
|
};
|
|
|
|
if needs_transitions_update {
|
|
if let Some(values_without_transitions) = after_change_style {
|
|
*new_values = values_without_transitions;
|
|
}
|
|
tasks.insert(CSS_TRANSITIONS);
|
|
|
|
// We need to clone old_values into SequentialTask, so we can use it later.
|
|
old_values.clone()
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if self.has_animations() {
|
|
tasks.insert(EFFECT_PROPERTIES);
|
|
if important_rules_changed {
|
|
tasks.insert(CASCADE_RESULTS);
|
|
}
|
|
}
|
|
|
|
if !tasks.is_empty() {
|
|
let task = ::context::SequentialTask::update_animations(*self,
|
|
before_change_style,
|
|
tasks);
|
|
context.thread_local.tasks.push(task);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "servo")]
|
|
fn process_animations(&self,
|
|
context: &mut StyleContext<Self>,
|
|
old_values: &mut Option<Arc<ComputedValues>>,
|
|
new_values: &mut Arc<ComputedValues>,
|
|
_restyle_hint: RestyleHint,
|
|
_important_rules_changed: bool) {
|
|
use animation;
|
|
use dom::TNode;
|
|
|
|
let possibly_expired_animations =
|
|
&mut context.thread_local.current_element_info.as_mut().unwrap()
|
|
.possibly_expired_animations;
|
|
let shared_context = context.shared;
|
|
if let Some(ref mut old) = *old_values {
|
|
self.update_animations_for_cascade(shared_context, old,
|
|
possibly_expired_animations,
|
|
&context.thread_local.font_metrics_provider);
|
|
}
|
|
|
|
let new_animations_sender = &context.thread_local.new_animations_sender;
|
|
let this_opaque = self.as_node().opaque();
|
|
// Trigger any present animations if necessary.
|
|
animation::maybe_start_animations(&shared_context,
|
|
new_animations_sender,
|
|
this_opaque, &new_values);
|
|
|
|
// Trigger transitions if necessary. This will reset `new_values` back
|
|
// to its old value if it did trigger a transition.
|
|
if let Some(ref values) = *old_values {
|
|
animation::start_transitions_if_applicable(
|
|
new_animations_sender,
|
|
this_opaque,
|
|
&**values,
|
|
new_values,
|
|
&shared_context.timer,
|
|
&possibly_expired_animations);
|
|
}
|
|
}
|
|
|
|
|
|
/// Computes and applies non-redundant damage.
|
|
#[cfg(feature = "gecko")]
|
|
fn accumulate_damage_for(&self,
|
|
shared_context: &SharedStyleContext,
|
|
restyle: &mut RestyleData,
|
|
old_values: &ComputedValues,
|
|
new_values: &ComputedValues,
|
|
pseudo: Option<&PseudoElement>)
|
|
-> ChildCascadeRequirement {
|
|
// Don't accumulate damage if we're in a forgetful traversal.
|
|
if shared_context.traversal_flags.contains(traversal_flags::Forgetful) {
|
|
return ChildCascadeRequirement::MustCascadeChildren;
|
|
}
|
|
|
|
// If an ancestor is already getting reconstructed by Gecko's top-down
|
|
// frame constructor, no need to apply damage. Similarly if we already
|
|
// have an explicitly stored ReconstructFrame hint.
|
|
//
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1301258#c12
|
|
// for followup work to make the optimization here more optimal by considering
|
|
// each bit individually.
|
|
let skip_applying_damage =
|
|
restyle.reconstructed_self_or_ancestor();
|
|
|
|
let difference =
|
|
self.compute_style_difference(old_values, new_values, pseudo);
|
|
|
|
if !skip_applying_damage {
|
|
restyle.damage |= difference.damage;
|
|
}
|
|
|
|
// We need to cascade the children in order to ensure the correct
|
|
// propagation of computed value flags.
|
|
//
|
|
// FIXME(emilio): If we start optimizing changes to reset-only
|
|
// properties that aren't explicitly inherited, we'd need to add a flag
|
|
// to handle justify-items: auto correctly when there's a legacy
|
|
// justify-items.
|
|
if old_values.flags != new_values.flags {
|
|
return ChildCascadeRequirement::MustCascadeChildren;
|
|
}
|
|
|
|
match difference.change {
|
|
StyleChange::Unchanged => ChildCascadeRequirement::CanSkipCascade,
|
|
StyleChange::Changed => ChildCascadeRequirement::MustCascadeChildren,
|
|
}
|
|
}
|
|
|
|
/// Computes and applies restyle damage unless we've already maxed it out.
|
|
#[cfg(feature = "servo")]
|
|
fn accumulate_damage_for(&self,
|
|
_shared_context: &SharedStyleContext,
|
|
restyle: &mut RestyleData,
|
|
old_values: &ComputedValues,
|
|
new_values: &ComputedValues,
|
|
pseudo: Option<&PseudoElement>)
|
|
-> ChildCascadeRequirement {
|
|
let difference = self.compute_style_difference(old_values, new_values, pseudo);
|
|
restyle.damage |= difference.damage;
|
|
match difference.change {
|
|
StyleChange::Changed => ChildCascadeRequirement::MustCascadeChildren,
|
|
StyleChange::Unchanged => ChildCascadeRequirement::CanSkipCascade,
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "servo")]
|
|
fn update_animations_for_cascade(&self,
|
|
context: &SharedStyleContext,
|
|
style: &mut Arc<ComputedValues>,
|
|
possibly_expired_animations: &mut Vec<::animation::PropertyAnimation>,
|
|
font_metrics: &::font_metrics::FontMetricsProvider) {
|
|
use animation::{self, Animation};
|
|
use dom::TNode;
|
|
|
|
// Finish any expired transitions.
|
|
let this_opaque = self.as_node().opaque();
|
|
animation::complete_expired_transitions(this_opaque, style, context);
|
|
|
|
// Merge any running transitions into the current style, and cancel them.
|
|
let had_running_animations = context.running_animations
|
|
.read()
|
|
.get(&this_opaque)
|
|
.is_some();
|
|
if had_running_animations {
|
|
let mut all_running_animations = context.running_animations.write();
|
|
for running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
|
|
// This shouldn't happen frequently, but under some
|
|
// circumstances mainly huge load or debug builds, the
|
|
// constellation might be delayed in sending the
|
|
// `TickAllAnimations` message to layout.
|
|
//
|
|
// Thus, we can't assume all the animations have been already
|
|
// updated by layout, because other restyle due to script might
|
|
// be triggered by layout before the animation tick.
|
|
//
|
|
// See #12171 and the associated PR for an example where this
|
|
// happened while debugging other release panic.
|
|
if !running_animation.is_expired() {
|
|
animation::update_style_for_animation(context,
|
|
running_animation,
|
|
style,
|
|
font_metrics);
|
|
if let Animation::Transition(_, _, ref frame, _) = *running_animation {
|
|
possibly_expired_animations.push(frame.property_animation.clone())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: TElement> PrivateMatchMethods for E {}
|
|
|
|
/// The public API that elements expose for selector matching.
|
|
pub trait MatchMethods : TElement {
|
|
/// Returns the closest parent element that doesn't have a display: contents
|
|
/// style (and thus generates a box).
|
|
///
|
|
/// This is needed to correctly handle blockification of flex and grid
|
|
/// items.
|
|
///
|
|
/// Returns itself if the element has no parent. In practice this doesn't
|
|
/// happen because the root element is blockified per spec, but it could
|
|
/// happen if we decide to not blockify for roots of disconnected subtrees,
|
|
/// which is a kind of dubious beahavior.
|
|
fn layout_parent(&self) -> Self {
|
|
let mut current = self.clone();
|
|
loop {
|
|
current = match current.traversal_parent() {
|
|
Some(el) => el,
|
|
None => return current,
|
|
};
|
|
|
|
let is_display_contents =
|
|
current.borrow_data().unwrap().styles.primary().is_display_contents();
|
|
|
|
if !is_display_contents {
|
|
return current;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Updates the styles with the new ones, diffs them, and stores the restyle
|
|
/// damage.
|
|
fn finish_restyle(
|
|
&self,
|
|
context: &mut StyleContext<Self>,
|
|
data: &mut ElementData,
|
|
mut new_styles: ElementStyles,
|
|
important_rules_changed: bool,
|
|
) -> ChildCascadeRequirement {
|
|
use dom::TNode;
|
|
use std::cmp;
|
|
use std::mem;
|
|
|
|
debug_assert!(new_styles.primary.is_some(), "How did that happen?");
|
|
|
|
self.process_animations(
|
|
context,
|
|
&mut data.styles.primary,
|
|
&mut new_styles.primary.as_mut().unwrap(),
|
|
data.restyle.hint,
|
|
important_rules_changed,
|
|
);
|
|
|
|
// First of all, update the styles.
|
|
let old_styles = mem::replace(&mut data.styles, new_styles);
|
|
|
|
// Propagate the "can be fragmented" bit. It would be nice to
|
|
// encapsulate this better.
|
|
//
|
|
// Note that this is technically not needed for pseudos since we already
|
|
// do that when we resolve the non-pseudo style, but it doesn't hurt
|
|
// anyway.
|
|
if cfg!(feature = "servo") {
|
|
let layout_parent =
|
|
self.inheritance_parent().map(|e| e.layout_parent());
|
|
let layout_parent_data =
|
|
layout_parent.as_ref().and_then(|e| e.borrow_data());
|
|
let layout_parent_style =
|
|
layout_parent_data.as_ref().map(|d| d.styles.primary());
|
|
|
|
if let Some(ref p) = layout_parent_style {
|
|
let can_be_fragmented =
|
|
p.is_multicol() ||
|
|
layout_parent.as_ref().unwrap().as_node().can_be_fragmented();
|
|
unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
|
|
}
|
|
}
|
|
|
|
let new_primary_style = data.styles.primary.as_ref().unwrap();
|
|
|
|
let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
|
|
if self.is_root() && !self.is_native_anonymous() {
|
|
let device = context.shared.stylist.device();
|
|
let new_font_size = new_primary_style.get_font().clone_font_size();
|
|
|
|
if old_styles.primary.as_ref().map_or(true, |s| s.get_font().clone_font_size() != new_font_size) {
|
|
debug_assert!(self.owner_doc_matches_for_testing(device));
|
|
device.set_root_font_size(new_font_size.0);
|
|
// If the root font-size changed since last time, and something
|
|
// in the document did use rem units, ensure we recascade the
|
|
// entire tree.
|
|
if device.used_root_font_size() {
|
|
cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't accumulate damage if we're in a forgetful traversal.
|
|
if context.shared.traversal_flags.contains(traversal_flags::Forgetful) {
|
|
return ChildCascadeRequirement::MustCascadeChildren;
|
|
}
|
|
|
|
// Also, don't do anything if there was no style.
|
|
let old_primary_style = match old_styles.primary {
|
|
Some(s) => s,
|
|
None => return ChildCascadeRequirement::MustCascadeChildren,
|
|
};
|
|
|
|
cascade_requirement = cmp::max(
|
|
cascade_requirement,
|
|
self.accumulate_damage_for(
|
|
context.shared,
|
|
&mut data.restyle,
|
|
&old_primary_style,
|
|
new_primary_style,
|
|
None,
|
|
)
|
|
);
|
|
|
|
if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() {
|
|
// This is the common case; no need to examine pseudos here.
|
|
return cascade_requirement;
|
|
}
|
|
|
|
let pseudo_styles =
|
|
old_styles.pseudos.as_array().iter().zip(
|
|
data.styles.pseudos.as_array().iter());
|
|
|
|
for (i, (old, new)) in pseudo_styles.enumerate() {
|
|
match (old, new) {
|
|
(&Some(ref old), &Some(ref new)) => {
|
|
self.accumulate_damage_for(
|
|
context.shared,
|
|
&mut data.restyle,
|
|
old,
|
|
new,
|
|
Some(&PseudoElement::from_eager_index(i)),
|
|
);
|
|
}
|
|
(&None, &None) => {},
|
|
_ => {
|
|
// It's possible that we're switching from not having
|
|
// ::before/::after at all to having styles for them but not
|
|
// actually having a useful pseudo-element. Check for that
|
|
// case.
|
|
let pseudo = PseudoElement::from_eager_index(i);
|
|
let new_pseudo_should_exist =
|
|
new.as_ref().map_or(false,
|
|
|s| pseudo.should_exist(s));
|
|
let old_pseudo_should_exist =
|
|
old.as_ref().map_or(false,
|
|
|s| pseudo.should_exist(s));
|
|
if new_pseudo_should_exist != old_pseudo_should_exist {
|
|
data.restyle.damage |= RestyleDamage::reconstruct();
|
|
return cascade_requirement;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cascade_requirement
|
|
}
|
|
|
|
|
|
/// Applies selector flags to an element, deferring mutations of the parent
|
|
/// until after the traversal.
|
|
///
|
|
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
|
|
/// advantage of us knowing that the traversal is sequential.
|
|
fn apply_selector_flags(&self,
|
|
map: &mut SelectorFlagsMap<Self>,
|
|
element: &Self,
|
|
flags: ElementSelectorFlags) {
|
|
// Handle flags that apply to the element.
|
|
let self_flags = flags.for_self();
|
|
if !self_flags.is_empty() {
|
|
if element == self {
|
|
// If this is the element we're styling, we have exclusive
|
|
// access to the element, and thus it's fine inserting them,
|
|
// even from the worker.
|
|
unsafe { element.set_selector_flags(self_flags); }
|
|
} else {
|
|
// Otherwise, this element is an ancestor of the current element
|
|
// we're styling, and thus multiple children could write to it
|
|
// if we did from here.
|
|
//
|
|
// Instead, we can read them, and post them if necessary as a
|
|
// sequential task in order for them to be processed later.
|
|
if !element.has_selector_flags(self_flags) {
|
|
map.insert_flags(*element, self_flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle flags that apply to the parent.
|
|
let parent_flags = flags.for_parent();
|
|
if !parent_flags.is_empty() {
|
|
if let Some(p) = element.parent_element() {
|
|
if !p.has_selector_flags(parent_flags) {
|
|
map.insert_flags(p, parent_flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Computes and applies restyle damage.
|
|
fn accumulate_damage(&self,
|
|
shared_context: &SharedStyleContext,
|
|
restyle: &mut RestyleData,
|
|
old_values: Option<&ComputedValues>,
|
|
new_values: &ComputedValues,
|
|
pseudo: Option<&PseudoElement>)
|
|
-> ChildCascadeRequirement {
|
|
let old_values = match old_values {
|
|
Some(v) => v,
|
|
None => return ChildCascadeRequirement::MustCascadeChildren,
|
|
};
|
|
|
|
// ::before and ::after are element-backed in Gecko, so they do the
|
|
// damage calculation for themselves, when there's an actual pseudo.
|
|
let is_existing_before_or_after =
|
|
cfg!(feature = "gecko") &&
|
|
pseudo.map_or(false, |p| {
|
|
(p.is_before() && self.before_pseudo_element().is_some()) ||
|
|
(p.is_after() && self.after_pseudo_element().is_some())
|
|
});
|
|
|
|
if is_existing_before_or_after {
|
|
return ChildCascadeRequirement::CanSkipCascade;
|
|
}
|
|
|
|
self.accumulate_damage_for(
|
|
shared_context,
|
|
restyle,
|
|
old_values,
|
|
new_values,
|
|
pseudo
|
|
)
|
|
}
|
|
|
|
/// Updates the rule nodes without re-running selector matching, using just
|
|
/// the rule tree.
|
|
///
|
|
/// Returns true if an !important rule was replaced.
|
|
fn replace_rules(
|
|
&self,
|
|
replacements: RestyleHint,
|
|
context: &mut StyleContext<Self>,
|
|
cascade_inputs: &mut ElementCascadeInputs,
|
|
) -> bool {
|
|
let mut result = false;
|
|
result |= self.replace_rules_internal(
|
|
replacements,
|
|
context,
|
|
CascadeVisitedMode::Unvisited,
|
|
cascade_inputs,
|
|
);
|
|
result |= self.replace_rules_internal(
|
|
replacements,
|
|
context,
|
|
CascadeVisitedMode::Visited,
|
|
cascade_inputs
|
|
);
|
|
result
|
|
}
|
|
|
|
/// Updates the rule nodes without re-running selector matching, using just
|
|
/// the rule tree, for a specific visited mode.
|
|
///
|
|
/// Returns true if an !important rule was replaced.
|
|
fn replace_rules_internal(
|
|
&self,
|
|
replacements: RestyleHint,
|
|
context: &mut StyleContext<Self>,
|
|
cascade_visited: CascadeVisitedMode,
|
|
cascade_inputs: &mut ElementCascadeInputs,
|
|
) -> bool {
|
|
use properties::PropertyDeclarationBlock;
|
|
use shared_lock::Locked;
|
|
|
|
debug_assert!(replacements.intersects(RestyleHint::replacements()) &&
|
|
(replacements & !RestyleHint::replacements()).is_empty());
|
|
|
|
let stylist = &context.shared.stylist;
|
|
let guards = &context.shared.guards;
|
|
|
|
let primary_rules =
|
|
match cascade_visited {
|
|
CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(),
|
|
CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(),
|
|
};
|
|
|
|
let primary_rules = match primary_rules {
|
|
Some(r) => r,
|
|
None => return false,
|
|
};
|
|
|
|
let replace_rule_node = |level: CascadeLevel,
|
|
pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
|
|
path: &mut StrongRuleNode| -> bool {
|
|
let mut important_rules_changed = false;
|
|
let new_node = stylist.rule_tree()
|
|
.update_rule_at_level(level,
|
|
pdb,
|
|
path,
|
|
guards,
|
|
&mut important_rules_changed);
|
|
if let Some(n) = new_node {
|
|
*path = n;
|
|
}
|
|
important_rules_changed
|
|
};
|
|
|
|
if !context.shared.traversal_flags.for_animation_only() {
|
|
let mut result = false;
|
|
if replacements.contains(RESTYLE_STYLE_ATTRIBUTE) {
|
|
let style_attribute = self.style_attribute();
|
|
result |= replace_rule_node(CascadeLevel::StyleAttributeNormal,
|
|
style_attribute,
|
|
primary_rules);
|
|
result |= replace_rule_node(CascadeLevel::StyleAttributeImportant,
|
|
style_attribute,
|
|
primary_rules);
|
|
// FIXME(emilio): Still a hack!
|
|
self.unset_dirty_style_attribute();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Animation restyle hints are processed prior to other restyle
|
|
// hints in the animation-only traversal.
|
|
//
|
|
// Non-animation restyle hints will be processed in a subsequent
|
|
// normal traversal.
|
|
if replacements.intersects(RestyleHint::for_animations()) {
|
|
debug_assert!(context.shared.traversal_flags.for_animation_only());
|
|
|
|
if replacements.contains(RESTYLE_SMIL) {
|
|
replace_rule_node(CascadeLevel::SMILOverride,
|
|
self.get_smil_override(),
|
|
primary_rules);
|
|
}
|
|
|
|
let replace_rule_node_for_animation = |level: CascadeLevel,
|
|
primary_rules: &mut StrongRuleNode| {
|
|
let animation_rule = self.get_animation_rule_by_cascade(level);
|
|
replace_rule_node(level,
|
|
animation_rule.as_ref().map(|a| a.borrow_arc()),
|
|
primary_rules);
|
|
};
|
|
|
|
// Apply Transition rules and Animation rules if the corresponding restyle hint
|
|
// is contained.
|
|
if replacements.contains(RESTYLE_CSS_TRANSITIONS) {
|
|
replace_rule_node_for_animation(CascadeLevel::Transitions,
|
|
primary_rules);
|
|
}
|
|
|
|
if replacements.contains(RESTYLE_CSS_ANIMATIONS) {
|
|
replace_rule_node_for_animation(CascadeLevel::Animations,
|
|
primary_rules);
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Given the old and new style of this element, and whether it's a
|
|
/// pseudo-element, compute the restyle damage used to determine which
|
|
/// kind of layout or painting operations we'll need.
|
|
fn compute_style_difference(
|
|
&self,
|
|
old_values: &ComputedValues,
|
|
new_values: &ComputedValues,
|
|
pseudo: Option<&PseudoElement>
|
|
) -> StyleDifference {
|
|
debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
|
|
RestyleDamage::compute_style_difference(old_values, new_values)
|
|
}
|
|
}
|
|
|
|
impl<E: TElement> MatchMethods for E {}
|