mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Auto merge of #16967 - emilio:after, r=heycam,emilio
Bug 1366144: Correctly diff ::before and ::after pseudo-element styles if there's no generated content. r=heycam <!-- Reviewable:start --> This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16967) <!-- Reviewable:end -->
This commit is contained in:
commit
05a26a2996
15 changed files with 692 additions and 227 deletions
|
@ -16,9 +16,10 @@ use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext, StyleCon
|
|||
use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
|
||||
use dom::{AnimationRules, SendElement, TElement, TNode};
|
||||
use font_metrics::FontMetricsProvider;
|
||||
use log::LogLevel::Trace;
|
||||
use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
|
||||
use properties::longhands::display::computed_value as display;
|
||||
use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleHint};
|
||||
use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleReplacements};
|
||||
use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
|
||||
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
|
||||
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
||||
|
@ -51,6 +52,34 @@ fn relations_are_shareable(relations: &StyleRelations) -> bool {
|
|||
AFFECTED_BY_PRESENTATIONAL_HINTS)
|
||||
}
|
||||
|
||||
/// 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(Copy, Clone)]
|
||||
pub enum StyleChange {
|
||||
/// The style hasn't changed.
|
||||
Unchanged,
|
||||
/// The style has changed.
|
||||
Changed,
|
||||
}
|
||||
|
||||
/// Information regarding a style sharing candidate.
|
||||
///
|
||||
/// Note that this information is stored in TLS and cleared after the traversal,
|
||||
|
@ -375,8 +404,37 @@ pub enum StyleSharingResult {
|
|||
/// We didn't find anybody to share the style with.
|
||||
CannotShare,
|
||||
/// The node's style can be shared. The integer specifies the index in the
|
||||
/// LRU cache that was hit and the damage that was done.
|
||||
StyleWasShared(usize),
|
||||
/// LRU cache that was hit and the damage that was done. The
|
||||
/// `ChildCascadeRequirement` indicates whether style changes due to using
|
||||
/// the shared style mean we need to recascade to children.
|
||||
StyleWasShared(usize, ChildCascadeRequirement),
|
||||
}
|
||||
|
||||
/// Whether or not newly computed values for an element need to be cascade
|
||||
/// to children.
|
||||
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,
|
||||
/// 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 `MustCascade` as "must
|
||||
/// cascade unless we know that changes to these properties can be
|
||||
/// ignored".
|
||||
MustCascade,
|
||||
}
|
||||
|
||||
impl From<StyleChange> for ChildCascadeRequirement {
|
||||
fn from(change: StyleChange) -> ChildCascadeRequirement {
|
||||
match change {
|
||||
StyleChange::Unchanged => ChildCascadeRequirement::CanSkipCascade,
|
||||
StyleChange::Changed => ChildCascadeRequirement::MustCascade,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result status for match primary rules.
|
||||
|
@ -570,7 +628,8 @@ trait PrivateMatchMethods: TElement {
|
|||
fn cascade_primary(&self,
|
||||
context: &mut StyleContext<Self>,
|
||||
data: &mut ElementData,
|
||||
important_rules_changed: bool) {
|
||||
important_rules_changed: bool)
|
||||
-> ChildCascadeRequirement {
|
||||
// Collect some values.
|
||||
let (mut styles, restyle) = data.styles_and_restyle_mut();
|
||||
let mut primary_style = &mut styles.primary;
|
||||
|
@ -589,16 +648,19 @@ trait PrivateMatchMethods: TElement {
|
|||
important_rules_changed);
|
||||
}
|
||||
|
||||
if let Some(old) = old_values {
|
||||
let child_cascade_requirement =
|
||||
self.accumulate_damage(&context.shared,
|
||||
restyle.unwrap(),
|
||||
&old,
|
||||
restyle,
|
||||
old_values.as_ref().map(|v| v.as_ref()),
|
||||
&new_values,
|
||||
None);
|
||||
}
|
||||
|
||||
// Set the new computed values.
|
||||
primary_style.values = Some(new_values);
|
||||
|
||||
// Return whether the damage indicates we must cascade new inherited
|
||||
// values into children.
|
||||
child_cascade_requirement
|
||||
}
|
||||
|
||||
fn cascade_eager_pseudo(&self,
|
||||
|
@ -613,17 +675,11 @@ trait PrivateMatchMethods: TElement {
|
|||
let new_values =
|
||||
self.cascade_internal(context, &styles.primary, Some(pseudo_style));
|
||||
|
||||
if let Some(old) = old_values {
|
||||
// ::before and ::after are element-backed in Gecko, so they do
|
||||
// the damage calculation for themselves.
|
||||
if cfg!(feature = "servo") || !pseudo.is_before_or_after() {
|
||||
self.accumulate_damage(&context.shared,
|
||||
restyle.unwrap(),
|
||||
&old,
|
||||
&new_values,
|
||||
Some(pseudo));
|
||||
}
|
||||
}
|
||||
self.accumulate_damage(&context.shared,
|
||||
restyle,
|
||||
old_values.as_ref().map(|v| &**v),
|
||||
&new_values,
|
||||
Some(pseudo));
|
||||
|
||||
pseudo_style.values = Some(new_values)
|
||||
}
|
||||
|
@ -782,54 +838,89 @@ trait PrivateMatchMethods: TElement {
|
|||
}
|
||||
}
|
||||
|
||||
/// Computes and applies non-redundant damage.
|
||||
#[cfg(feature = "gecko")]
|
||||
/// Computes and applies restyle damage.
|
||||
fn accumulate_damage(&self,
|
||||
shared_context: &SharedStyleContext,
|
||||
restyle: &mut RestyleData,
|
||||
old_values: &ComputedValues,
|
||||
restyle: Option<&mut RestyleData>,
|
||||
old_values: Option<&ComputedValues>,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) {
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> ChildCascadeRequirement {
|
||||
let restyle = match restyle {
|
||||
Some(r) => r,
|
||||
None => return ChildCascadeRequirement::MustCascade,
|
||||
};
|
||||
|
||||
let old_values = match old_values {
|
||||
Some(v) => v,
|
||||
None => return ChildCascadeRequirement::MustCascade,
|
||||
};
|
||||
|
||||
// ::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_or_after()) &&
|
||||
self.existing_style_for_restyle_damage(old_values, pseudo)
|
||||
.is_some();
|
||||
|
||||
if is_existing_before_or_after {
|
||||
return ChildCascadeRequirement::CanSkipCascade;
|
||||
}
|
||||
|
||||
self.accumulate_damage_for(shared_context,
|
||||
restyle,
|
||||
old_values,
|
||||
new_values,
|
||||
pseudo)
|
||||
}
|
||||
|
||||
/// 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: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> ChildCascadeRequirement {
|
||||
// Don't accumulate damage if we're in a restyle for reconstruction.
|
||||
if shared_context.traversal_flags.for_reconstruct() {
|
||||
return;
|
||||
return ChildCascadeRequirement::MustCascade;
|
||||
}
|
||||
|
||||
// If an ancestor is already getting reconstructed by Gecko's top-down
|
||||
// frame constructor, no need to apply damage.
|
||||
if restyle.damage_handled.contains(RestyleDamage::reconstruct()) {
|
||||
restyle.damage = RestyleDamage::empty();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add restyle damage, but only the bits that aren't redundant with respect
|
||||
// to damage applied on our ancestors.
|
||||
// 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.
|
||||
if !restyle.damage.contains(RestyleDamage::reconstruct()) {
|
||||
let new_damage = self.compute_restyle_damage(&old_values,
|
||||
&new_values,
|
||||
pseudo);
|
||||
if !restyle.damage_handled.contains(new_damage) {
|
||||
restyle.damage |= new_damage;
|
||||
}
|
||||
let skip_applying_damage =
|
||||
restyle.damage_handled.contains(RestyleDamage::reconstruct()) ||
|
||||
restyle.damage.contains(RestyleDamage::reconstruct());
|
||||
|
||||
let difference = self.compute_style_difference(&old_values,
|
||||
&new_values,
|
||||
pseudo);
|
||||
if !skip_applying_damage {
|
||||
restyle.damage |= difference.damage;
|
||||
}
|
||||
difference.change.into()
|
||||
}
|
||||
|
||||
/// Computes and applies restyle damage unless we've already maxed it out.
|
||||
#[cfg(feature = "servo")]
|
||||
fn accumulate_damage(&self,
|
||||
_shared_context: &SharedStyleContext,
|
||||
restyle: &mut RestyleData,
|
||||
old_values: &ComputedValues,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) {
|
||||
if restyle.damage != RestyleDamage::rebuild_and_reflow() {
|
||||
restyle.damage |=
|
||||
self.compute_restyle_damage(&old_values, &new_values, pseudo);
|
||||
}
|
||||
fn accumulate_damage_for(&self,
|
||||
_shared_context: &SharedStyleContext,
|
||||
restyle: &mut RestyleData,
|
||||
old_values: &ComputedValues,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> ChildCascadeRequirement {
|
||||
let difference = self.compute_style_difference(&old_values, &new_values, pseudo);
|
||||
restyle.damage |= difference.damage;
|
||||
difference.change.into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "servo")]
|
||||
|
@ -917,14 +1008,19 @@ pub trait MatchMethods : TElement {
|
|||
fn match_and_cascade(&self,
|
||||
context: &mut StyleContext<Self>,
|
||||
data: &mut ElementData,
|
||||
sharing: StyleSharingBehavior)
|
||||
sharing: StyleSharingBehavior) -> ChildCascadeRequirement
|
||||
{
|
||||
// Perform selector matching for the primary style.
|
||||
let mut relations = StyleRelations::empty();
|
||||
let result = self.match_primary(context, data, &mut relations);
|
||||
|
||||
// Cascade properties and compute primary values.
|
||||
self.cascade_primary(context, data, result.important_rules_overriding_animation_changed);
|
||||
let child_cascade_requirement =
|
||||
self.cascade_primary(
|
||||
context,
|
||||
data,
|
||||
result.important_rules_overriding_animation_changed
|
||||
);
|
||||
|
||||
// Match and cascade eager pseudo-elements.
|
||||
if !data.styles().is_display_none() {
|
||||
|
@ -957,6 +1053,8 @@ pub trait MatchMethods : TElement {
|
|||
relations,
|
||||
revalidation_match_results);
|
||||
}
|
||||
|
||||
child_cascade_requirement
|
||||
}
|
||||
|
||||
/// Performs the cascade, without matching.
|
||||
|
@ -964,9 +1062,12 @@ pub trait MatchMethods : TElement {
|
|||
context: &mut StyleContext<Self>,
|
||||
mut data: &mut ElementData,
|
||||
important_rules_changed: bool)
|
||||
-> ChildCascadeRequirement
|
||||
{
|
||||
self.cascade_primary(context, &mut data, important_rules_changed);
|
||||
let child_cascade_requirement =
|
||||
self.cascade_primary(context, &mut data, important_rules_changed);
|
||||
self.cascade_pseudos(context, &mut data);
|
||||
child_cascade_requirement
|
||||
}
|
||||
|
||||
/// Runs selector matching to (re)compute the primary rule node for this element.
|
||||
|
@ -1063,10 +1164,22 @@ pub trait MatchMethods : TElement {
|
|||
&mut applicable_declarations,
|
||||
&context.shared.guards);
|
||||
|
||||
let important_rules_changed = self.has_animations() &&
|
||||
data.has_styles() &&
|
||||
data.important_rules_are_different(&primary_rule_node,
|
||||
&context.shared.guards);
|
||||
if log_enabled!(Trace) {
|
||||
trace!("Matched rules:");
|
||||
for rn in primary_rule_node.self_and_ancestors() {
|
||||
if let Some(source) = rn.style_source() {
|
||||
trace!(" > {:?}", source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let important_rules_changed =
|
||||
self.has_animations() &&
|
||||
data.has_styles() &&
|
||||
data.important_rules_are_different(
|
||||
&primary_rule_node,
|
||||
&context.shared.guards
|
||||
);
|
||||
|
||||
RulesMatchedResult {
|
||||
rule_nodes_changed: data.set_primary_rules(primary_rule_node),
|
||||
|
@ -1214,7 +1327,7 @@ pub trait MatchMethods : TElement {
|
|||
/// the rule tree. Returns RulesChanged which indicates whether the rule nodes changed
|
||||
/// and whether the important rules changed.
|
||||
fn replace_rules(&self,
|
||||
hint: RestyleHint,
|
||||
replacements: RestyleReplacements,
|
||||
context: &StyleContext<Self>,
|
||||
data: &mut AtomicRefMut<ElementData>)
|
||||
-> RulesChanged {
|
||||
|
@ -1246,10 +1359,10 @@ pub trait MatchMethods : TElement {
|
|||
//
|
||||
// Non-animation restyle hints will be processed in a subsequent
|
||||
// normal traversal.
|
||||
if hint.intersects(RestyleHint::for_animations()) {
|
||||
if replacements.intersects(RestyleReplacements::for_animations()) {
|
||||
debug_assert!(context.shared.traversal_flags.for_animation_only());
|
||||
|
||||
if hint.contains(RESTYLE_SMIL) {
|
||||
if replacements.contains(RESTYLE_SMIL) {
|
||||
replace_rule_node(CascadeLevel::SMILOverride,
|
||||
self.get_smil_override(),
|
||||
primary_rules);
|
||||
|
@ -1265,16 +1378,16 @@ pub trait MatchMethods : TElement {
|
|||
|
||||
// Apply Transition rules and Animation rules if the corresponding restyle hint
|
||||
// is contained.
|
||||
if hint.contains(RESTYLE_CSS_TRANSITIONS) {
|
||||
if replacements.contains(RESTYLE_CSS_TRANSITIONS) {
|
||||
replace_rule_node_for_animation(CascadeLevel::Transitions,
|
||||
primary_rules);
|
||||
}
|
||||
|
||||
if hint.contains(RESTYLE_CSS_ANIMATIONS) {
|
||||
if replacements.contains(RESTYLE_CSS_ANIMATIONS) {
|
||||
replace_rule_node_for_animation(CascadeLevel::Animations,
|
||||
primary_rules);
|
||||
}
|
||||
} else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
|
||||
} else if replacements.contains(RESTYLE_STYLE_ATTRIBUTE) {
|
||||
let style_attribute = self.style_attribute();
|
||||
replace_rule_node(CascadeLevel::StyleAttributeNormal,
|
||||
style_attribute,
|
||||
|
@ -1342,11 +1455,12 @@ pub trait MatchMethods : TElement {
|
|||
debug_assert_eq!(data.has_styles(), data.has_restyle());
|
||||
let old_values = data.get_styles_mut()
|
||||
.and_then(|s| s.primary.values.take());
|
||||
if let Some(old) = old_values {
|
||||
let child_cascade_requirement =
|
||||
self.accumulate_damage(&context.shared,
|
||||
data.restyle_mut(), &old,
|
||||
shared_style.values(), None);
|
||||
}
|
||||
data.get_restyle_mut(),
|
||||
old_values.as_ref().map(|v| v.as_ref()),
|
||||
shared_style.values(),
|
||||
None);
|
||||
|
||||
// We never put elements with pseudo style into the style
|
||||
// sharing cache, so we can just mint an ElementStyles
|
||||
|
@ -1356,7 +1470,7 @@ pub trait MatchMethods : TElement {
|
|||
let styles = ElementStyles::new(shared_style);
|
||||
data.set_styles(styles);
|
||||
|
||||
return StyleSharingResult::StyleWasShared(i)
|
||||
return StyleSharingResult::StyleWasShared(i, child_cascade_requirement)
|
||||
}
|
||||
Err(miss) => {
|
||||
debug!("Cache miss: {:?}", miss);
|
||||
|
@ -1391,32 +1505,51 @@ pub trait MatchMethods : TElement {
|
|||
/// 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_restyle_damage(&self,
|
||||
old_values: &ComputedValues,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> RestyleDamage
|
||||
fn compute_style_difference(&self,
|
||||
old_values: &ComputedValues,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> StyleDifference
|
||||
{
|
||||
match self.existing_style_for_restyle_damage(old_values, pseudo) {
|
||||
Some(ref source) => RestyleDamage::compute(source, new_values),
|
||||
None => {
|
||||
// If there's no style source, that likely means that Gecko
|
||||
// couldn't find a style context. This happens with display:none
|
||||
// elements, and probably a number of other edge cases that
|
||||
// we don't handle well yet (like display:contents).
|
||||
if new_values.get_box().clone_display() == display::T::none &&
|
||||
old_values.get_box().clone_display() == display::T::none {
|
||||
// The style remains display:none. No need for damage.
|
||||
RestyleDamage::empty()
|
||||
} else {
|
||||
// Something else. Be conservative for now.
|
||||
RestyleDamage::reconstruct()
|
||||
}
|
||||
}
|
||||
if let Some(source) = self.existing_style_for_restyle_damage(old_values, pseudo) {
|
||||
return RestyleDamage::compute_style_difference(source, new_values)
|
||||
}
|
||||
|
||||
let new_style_is_display_none =
|
||||
new_values.get_box().clone_display() == display::T::none;
|
||||
let old_style_is_display_none =
|
||||
old_values.get_box().clone_display() == display::T::none;
|
||||
|
||||
// If there's no style source, that likely means that Gecko couldn't
|
||||
// find a style context.
|
||||
//
|
||||
// This happens with display:none elements, and not-yet-existing
|
||||
// pseudo-elements.
|
||||
if new_style_is_display_none && old_style_is_display_none {
|
||||
// The style remains display:none. No need for damage.
|
||||
return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged)
|
||||
}
|
||||
|
||||
if pseudo.map_or(false, |p| p.is_before_or_after()) {
|
||||
if (old_style_is_display_none ||
|
||||
old_values.ineffective_content_property()) &&
|
||||
(new_style_is_display_none ||
|
||||
new_values.ineffective_content_property()) {
|
||||
// The pseudo-element will remain undisplayed, so just avoid
|
||||
// triggering any change.
|
||||
return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged)
|
||||
}
|
||||
return StyleDifference::new(RestyleDamage::reconstruct(), StyleChange::Changed)
|
||||
}
|
||||
|
||||
// Something else. Be conservative for now.
|
||||
warn!("Reframing due to lack of old style source: {:?}, pseudo: {:?}",
|
||||
self, pseudo);
|
||||
// Something else. Be conservative for now.
|
||||
StyleDifference::new(RestyleDamage::reconstruct(), StyleChange::Changed)
|
||||
}
|
||||
|
||||
/// Cascade the eager pseudo-elements of this element.
|
||||
/// Performs the cascade for the element's eager pseudos.
|
||||
fn cascade_pseudos(&self,
|
||||
context: &mut StyleContext<Self>,
|
||||
mut data: &mut ElementData)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue