style: Use RestyleDamage to determine whether we must continue cascading style changes to children.

This commit is contained in:
Cameron McCormack 2017-05-19 17:39:15 +08:00 committed by Emilio Cobos Álvarez
parent 715d18d377
commit 8b7a414b1c
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
5 changed files with 221 additions and 109 deletions

View file

@ -160,7 +160,9 @@ pub fn recalc_style_for_animations(context: &LayoutContext,
animation, animation,
&mut fragment.style, &mut fragment.style,
&ServoMetricsProvider); &ServoMetricsProvider);
damage |= RestyleDamage::compute(&old_style, &fragment.style); let difference =
RestyleDamage::compute_style_difference(&old_style, &fragment.style);
damage |= difference.damage;
} }
} }
}); });

View file

@ -8,6 +8,7 @@ use gecko_bindings::bindings;
use gecko_bindings::structs; use gecko_bindings::structs;
use gecko_bindings::structs::{nsChangeHint, nsStyleContext}; use gecko_bindings::structs::{nsChangeHint, nsStyleContext};
use gecko_bindings::sugar::ownership::FFIArcHelpers; use gecko_bindings::sugar::ownership::FFIArcHelpers;
use matching::{StyleChange, StyleDifference};
use properties::ComputedValues; use properties::ComputedValues;
use std::ops::{BitAnd, BitOr, BitOrAssign, Not}; use std::ops::{BitAnd, BitOr, BitOrAssign, Not};
use stylearc::Arc; use stylearc::Arc;
@ -38,15 +39,17 @@ impl GeckoRestyleDamage {
self.0 == nsChangeHint(0) self.0 == nsChangeHint(0)
} }
/// Computes a change hint given an old style (in the form of a /// Computes the `StyleDifference` (including the appropriate change hint)
/// `nsStyleContext`, and a new style (in the form of `ComputedValues`). /// given an old style (in the form of a `nsStyleContext`, and a new style
/// (in the form of `ComputedValues`).
/// ///
/// Note that we could in theory just get two `ComputedValues` here and diff /// Note that we could in theory just get two `ComputedValues` here and diff
/// them, but Gecko has an interesting optimization when they mark accessed /// them, but Gecko has an interesting optimization when they mark accessed
/// structs, so they effectively only diff structs that have ever been /// structs, so they effectively only diff structs that have ever been
/// accessed from layout. /// accessed from layout.
pub fn compute(source: &nsStyleContext, pub fn compute_style_difference(source: &nsStyleContext,
new_style: &Arc<ComputedValues>) -> Self { new_style: &Arc<ComputedValues>)
-> StyleDifference {
// TODO(emilio): Const-ify this? // TODO(emilio): Const-ify this?
let context = source as *const nsStyleContext as *mut nsStyleContext; let context = source as *const nsStyleContext as *mut nsStyleContext;
let mut any_style_changed: bool = false; let mut any_style_changed: bool = false;
@ -55,7 +58,8 @@ impl GeckoRestyleDamage {
new_style.as_borrowed_opt().unwrap(), new_style.as_borrowed_opt().unwrap(),
&mut any_style_changed) &mut any_style_changed)
}; };
GeckoRestyleDamage(hint) let change = if any_style_changed { StyleChange::Changed } else { StyleChange::Unchanged };
StyleDifference::new(GeckoRestyleDamage(hint), change)
} }
/// Returns true if this restyle damage contains all the damage of |other|. /// Returns true if this restyle damage contains all the damage of |other|.

View file

@ -51,6 +51,34 @@ fn relations_are_shareable(relations: &StyleRelations) -> bool {
AFFECTED_BY_PRESENTATIONAL_HINTS) 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. /// Information regarding a style sharing candidate.
/// ///
/// Note that this information is stored in TLS and cleared after the traversal, /// Note that this information is stored in TLS and cleared after the traversal,
@ -375,8 +403,37 @@ pub enum StyleSharingResult {
/// We didn't find anybody to share the style with. /// We didn't find anybody to share the style with.
CannotShare, CannotShare,
/// The node's style can be shared. The integer specifies the index in the /// 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. /// LRU cache that was hit and the damage that was done. The
StyleWasShared(usize), /// `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. /// The result status for match primary rules.
@ -570,7 +627,8 @@ trait PrivateMatchMethods: TElement {
fn cascade_primary(&self, fn cascade_primary(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
data: &mut ElementData, data: &mut ElementData,
important_rules_changed: bool) { important_rules_changed: bool)
-> ChildCascadeRequirement {
// Collect some values. // Collect some values.
let (mut styles, restyle) = data.styles_and_restyle_mut(); let (mut styles, restyle) = data.styles_and_restyle_mut();
let mut primary_style = &mut styles.primary; let mut primary_style = &mut styles.primary;
@ -589,16 +647,19 @@ trait PrivateMatchMethods: TElement {
important_rules_changed); important_rules_changed);
} }
if let Some(old) = old_values { let child_cascade_requirement =
self.accumulate_damage(&context.shared, self.accumulate_damage(&context.shared,
restyle.unwrap(), restyle,
&old, old_values.as_ref().map(|v| v.as_ref()),
&new_values, &new_values,
None); None);
}
// Set the new computed values. // Set the new computed values.
primary_style.values = Some(new_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, fn cascade_eager_pseudo(&self,
@ -613,29 +674,11 @@ trait PrivateMatchMethods: TElement {
let new_values = let new_values =
self.cascade_internal(context, &styles.primary, Some(pseudo_style)); self.cascade_internal(context, &styles.primary, Some(pseudo_style));
if let Some(old) = old_values { self.accumulate_damage(&context.shared,
// ::before and ::after are element-backed in Gecko, so they do the restyle,
// damage calculation for themselves, when there's an actual pseudo. old_values.as_ref().map(|v| &**v),
// &new_values,
// NOTE(emilio): An alternative to this check is to just remove the Some(pseudo));
// pseudo-style entry here if new_values is display: none or has
// an ineffective content property...
//
// I think it's nice to handle it here instead for symmetry with how
// we handle display: none elements, but the other approach may be
// ok too?
let is_unexisting_before_or_after =
pseudo.is_before_or_after() &&
self.existing_style_for_restyle_damage(&old, Some(pseudo)).is_none();
if cfg!(feature = "servo") || is_unexisting_before_or_after {
self.accumulate_damage(&context.shared,
restyle.unwrap(),
&old,
&new_values,
Some(pseudo));
}
}
pseudo_style.values = Some(new_values) pseudo_style.values = Some(new_values)
} }
@ -794,54 +837,89 @@ trait PrivateMatchMethods: TElement {
} }
} }
/// Computes and applies non-redundant damage. /// Computes and applies restyle damage.
#[cfg(feature = "gecko")]
fn accumulate_damage(&self, fn accumulate_damage(&self,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
restyle: &mut RestyleData, restyle: Option<&mut RestyleData>,
old_values: &ComputedValues, old_values: Option<&ComputedValues>,
new_values: &Arc<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. // Don't accumulate damage if we're in a restyle for reconstruction.
if shared_context.traversal_flags.for_reconstruct() { if shared_context.traversal_flags.for_reconstruct() {
return; return ChildCascadeRequirement::MustCascade;
} }
// If an ancestor is already getting reconstructed by Gecko's top-down // If an ancestor is already getting reconstructed by Gecko's top-down
// frame constructor, no need to apply damage. // frame constructor, no need to apply damage. Similarly if we already
if restyle.damage_handled.contains(RestyleDamage::reconstruct()) { // have an explicitly stored ReconstructFrame hint.
restyle.damage = RestyleDamage::empty();
return;
}
// Add restyle damage, but only the bits that aren't redundant with
// respect to damage applied on our ancestors.
// //
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1301258#c12 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1301258#c12
// for followup work to make the optimization here more optimal by // for followup work to make the optimization here more optimal by considering
// considering each bit individually. // each bit individually.
if !restyle.damage.contains(RestyleDamage::reconstruct()) { let skip_applying_damage =
let new_damage = self.compute_restyle_damage(&old_values, restyle.damage_handled.contains(RestyleDamage::reconstruct()) ||
&new_values, restyle.damage.contains(RestyleDamage::reconstruct());
pseudo);
if !restyle.damage_handled.contains(new_damage) { let difference = self.compute_style_difference(&old_values,
restyle.damage |= new_damage; &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. /// Computes and applies restyle damage unless we've already maxed it out.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
fn accumulate_damage(&self, fn accumulate_damage_for(&self,
_shared_context: &SharedStyleContext, _shared_context: &SharedStyleContext,
restyle: &mut RestyleData, restyle: &mut RestyleData,
old_values: &ComputedValues, old_values: &ComputedValues,
new_values: &Arc<ComputedValues>, new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) { pseudo: Option<&PseudoElement>)
if restyle.damage != RestyleDamage::rebuild_and_reflow() { -> ChildCascadeRequirement {
restyle.damage |= let difference = self.compute_style_difference(&old_values, &new_values, pseudo);
self.compute_restyle_damage(&old_values, &new_values, pseudo); restyle.damage |= difference.damage;
} difference.change.into()
} }
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
@ -929,14 +1007,19 @@ pub trait MatchMethods : TElement {
fn match_and_cascade(&self, fn match_and_cascade(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
data: &mut ElementData, data: &mut ElementData,
sharing: StyleSharingBehavior) sharing: StyleSharingBehavior) -> ChildCascadeRequirement
{ {
// Perform selector matching for the primary style. // Perform selector matching for the primary style.
let mut relations = StyleRelations::empty(); let mut relations = StyleRelations::empty();
let result = self.match_primary(context, data, &mut relations); let result = self.match_primary(context, data, &mut relations);
// Cascade properties and compute primary values. // 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. // Match and cascade eager pseudo-elements.
if !data.styles().is_display_none() { if !data.styles().is_display_none() {
@ -969,6 +1052,8 @@ pub trait MatchMethods : TElement {
relations, relations,
revalidation_match_results); revalidation_match_results);
} }
child_cascade_requirement
} }
/// Performs the cascade, without matching. /// Performs the cascade, without matching.
@ -976,9 +1061,12 @@ pub trait MatchMethods : TElement {
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
mut data: &mut ElementData, mut data: &mut ElementData,
important_rules_changed: bool) 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); self.cascade_pseudos(context, &mut data);
child_cascade_requirement
} }
/// Runs selector matching to (re)compute the primary rule node for this element. /// Runs selector matching to (re)compute the primary rule node for this element.
@ -1354,11 +1442,12 @@ pub trait MatchMethods : TElement {
debug_assert_eq!(data.has_styles(), data.has_restyle()); debug_assert_eq!(data.has_styles(), data.has_restyle());
let old_values = data.get_styles_mut() let old_values = data.get_styles_mut()
.and_then(|s| s.primary.values.take()); .and_then(|s| s.primary.values.take());
if let Some(old) = old_values { let child_cascade_requirement =
self.accumulate_damage(&context.shared, self.accumulate_damage(&context.shared,
data.restyle_mut(), &old, data.get_restyle_mut(),
shared_style.values(), None); old_values.as_ref().map(|v| v.as_ref()),
} shared_style.values(),
None);
// We never put elements with pseudo style into the style // We never put elements with pseudo style into the style
// sharing cache, so we can just mint an ElementStyles // sharing cache, so we can just mint an ElementStyles
@ -1368,7 +1457,7 @@ pub trait MatchMethods : TElement {
let styles = ElementStyles::new(shared_style); let styles = ElementStyles::new(shared_style);
data.set_styles(styles); data.set_styles(styles);
return StyleSharingResult::StyleWasShared(i) return StyleSharingResult::StyleWasShared(i, child_cascade_requirement)
} }
Err(miss) => { Err(miss) => {
debug!("Cache miss: {:?}", miss); debug!("Cache miss: {:?}", miss);
@ -1452,14 +1541,14 @@ pub trait MatchMethods : TElement {
/// Given the old and new style of this element, and whether it's a /// Given the old and new style of this element, and whether it's a
/// pseudo-element, compute the restyle damage used to determine which /// pseudo-element, compute the restyle damage used to determine which
/// kind of layout or painting operations we'll need. /// kind of layout or painting operations we'll need.
fn compute_restyle_damage(&self, fn compute_style_difference(&self,
old_values: &ComputedValues, old_values: &ComputedValues,
new_values: &Arc<ComputedValues>, new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) pseudo: Option<&PseudoElement>)
-> RestyleDamage -> StyleDifference
{ {
if let Some(source) = self.existing_style_for_restyle_damage(old_values, pseudo) { if let Some(source) = self.existing_style_for_restyle_damage(old_values, pseudo) {
return RestyleDamage::compute(source, new_values); return RestyleDamage::compute_style_difference(source, new_values)
} }
let new_style_is_display_none = let new_style_is_display_none =
@ -1474,7 +1563,7 @@ pub trait MatchMethods : TElement {
// pseudo-elements. // pseudo-elements.
if new_style_is_display_none && old_style_is_display_none { if new_style_is_display_none && old_style_is_display_none {
// The style remains display:none. No need for damage. // The style remains display:none. No need for damage.
return RestyleDamage::empty() return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged)
} }
if pseudo.map_or(false, |p| p.is_before_or_after()) { if pseudo.map_or(false, |p| p.is_before_or_after()) {
@ -1484,18 +1573,19 @@ pub trait MatchMethods : TElement {
new_values.ineffective_content_property()) { new_values.ineffective_content_property()) {
// The pseudo-element will remain undisplayed, so just avoid // The pseudo-element will remain undisplayed, so just avoid
// triggering any change. // triggering any change.
return RestyleDamage::empty() return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged)
} }
return RestyleDamage::reconstruct() return StyleDifference::new(RestyleDamage::reconstruct(), StyleChange::Changed)
} }
// Something else. Be conservative for now. // Something else. Be conservative for now.
warn!("Reframing due to lack of old style source: {:?}, pseudo: {:?}", warn!("Reframing due to lack of old style source: {:?}, pseudo: {:?}",
self, pseudo); self, pseudo);
RestyleDamage::reconstruct() // 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, fn cascade_pseudos(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
mut data: &mut ElementData) mut data: &mut ElementData)

View file

@ -9,6 +9,7 @@
use computed_values::display; use computed_values::display;
use heapsize::HeapSizeOf; use heapsize::HeapSizeOf;
use matching::{StyleChange, StyleDifference};
use properties::ServoComputedValues; use properties::ServoComputedValues;
use std::fmt; use std::fmt;
@ -57,12 +58,14 @@ impl HeapSizeOf for ServoRestyleDamage {
} }
impl ServoRestyleDamage { impl ServoRestyleDamage {
/// Compute the appropriate restyle damage for a given style change between /// Compute the `StyleDifference` (including the appropriate restyle damage)
/// `old` and `new`. /// for a given style change between `old` and `new`.
pub fn compute(old: &ServoComputedValues, pub fn compute_style_difference(old: &ServoComputedValues,
new: &ServoComputedValues) new: &ServoComputedValues)
-> ServoRestyleDamage { -> StyleDifference {
compute_damage(old, new) let damage = compute_damage(old, new);
let change = if damage.is_empty() { StyleChange::Unchanged } else { StyleChange::Changed };
StyleDifference::new(damage, change)
} }
/// Returns a bitmask that represents a flow that needs to be rebuilt and /// Returns a bitmask that represents a flow that needs to be rebuilt and

View file

@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext}; use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use data::{ElementData, ElementStyles, StoredRestyleHint}; use data::{ElementData, ElementStyles, StoredRestyleHint};
use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode}; use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
use matching::{MatchMethods, StyleSharingBehavior}; use matching::{ChildCascadeRequirement, MatchMethods, StyleSharingBehavior};
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
use selector_parser::RestyleDamage; use selector_parser::RestyleDamage;
#[cfg(feature = "servo")] use servo_config::opts; #[cfg(feature = "servo")] use servo_config::opts;
@ -611,7 +611,12 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// Compute style for this element if necessary. // Compute style for this element if necessary.
if compute_self { if compute_self {
compute_style(traversal, traversal_data, context, element, &mut data); match compute_style(traversal, traversal_data, context, element, &mut data) {
ChildCascadeRequirement::MustCascade => {
inherited_style_changed = true;
}
ChildCascadeRequirement::CanSkipCascade => {}
};
// If we're restyling this element to display:none, throw away all style // If we're restyling this element to display:none, throw away all style
// data in the subtree, notify the caller to early-return. // data in the subtree, notify the caller to early-return.
@ -620,9 +625,6 @@ pub fn recalc_style_at<E, D>(traversal: &D,
element); element);
clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) }); clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) });
} }
// FIXME(bholley): Compute this accurately from the call to CalcStyleDifference.
inherited_style_changed = true;
} }
// Now that matching and cascading is done, clear the bits corresponding to // Now that matching and cascading is done, clear the bits corresponding to
@ -711,7 +713,7 @@ fn compute_style<E, D>(_traversal: &D,
traversal_data: &PerLevelTraversalData, traversal_data: &PerLevelTraversalData,
context: &mut StyleContext<E>, context: &mut StyleContext<E>,
element: E, element: E,
mut data: &mut AtomicRefMut<ElementData>) mut data: &mut AtomicRefMut<ElementData>) -> ChildCascadeRequirement
where E: TElement, where E: TElement,
D: DomTraversal<E>, D: DomTraversal<E>,
{ {
@ -727,10 +729,10 @@ fn compute_style<E, D>(_traversal: &D,
let sharing_result = unsafe { let sharing_result = unsafe {
element.share_style_if_possible(context, &mut data) element.share_style_if_possible(context, &mut data)
}; };
if let StyleWasShared(index) = sharing_result { if let StyleWasShared(index, had_damage) = sharing_result {
context.thread_local.statistics.styles_shared += 1; context.thread_local.statistics.styles_shared += 1;
context.thread_local.style_sharing_candidate_cache.touch(index); context.thread_local.style_sharing_candidate_cache.touch(index);
return; return had_damage;
} }
} }
@ -744,17 +746,28 @@ fn compute_style<E, D>(_traversal: &D,
context.thread_local.statistics.elements_matched += 1; context.thread_local.statistics.elements_matched += 1;
// Perform the matching and cascading. // Perform the matching and cascading.
element.match_and_cascade(context, &mut data, StyleSharingBehavior::Allow); element.match_and_cascade(
context,
&mut data,
StyleSharingBehavior::Allow
)
} }
CascadeWithReplacements(hint) => { CascadeWithReplacements(flags) => {
let rules_changed = element.replace_rules(hint, context, &mut data); let rules_changed = element.replace_rules(flags, context, &mut data);
element.cascade_primary_and_pseudos(context, &mut data, element.cascade_primary_and_pseudos(
rules_changed.important_rules_changed()); context,
&mut data,
rules_changed.important_rules_changed()
)
} }
CascadeOnly => { CascadeOnly => {
element.cascade_primary_and_pseudos(context, &mut data, false); element.cascade_primary_and_pseudos(
context,
&mut data,
/* important_rules_changed = */ false
)
} }
}; }
} }
fn preprocess_children<E, D>(traversal: &D, fn preprocess_children<E, D>(traversal: &D,