servo: Merge #16630 - Make stylo traverse Native Anonymous Content, fixing a bunch of restyle bugs (from emilio:nac); r=bholley,hiro

Source-Repo: https://github.com/servo/servo
Source-Revision: c633e291c93c942d34c731fe6ceb4db3b7347a16

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 2e9c223acb6e494c3945c47fc5ab7817c54de504
This commit is contained in:
Emilio Cobos Álvarez 2017-04-27 07:39:42 -05:00
parent c5eba64d1c
commit 9d915de239
12 changed files with 568 additions and 384 deletions

View File

@ -461,16 +461,16 @@ impl<'le> TElement for ServoLayoutElement<'le> {
self.element.has_selector_flags(flags)
}
fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool {
panic!("this should be only called on gecko");
fn has_animations(&self) -> bool {
unreachable!("this should be only called on gecko");
}
fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool {
panic!("this should be only called on gecko");
fn has_css_animations(&self) -> bool {
unreachable!("this should be only called on gecko");
}
fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool {
panic!("this should be only called on gecko");
fn has_css_transitions(&self) -> bool {
unreachable!("this should be only called on gecko");
}
}

View File

@ -20,7 +20,6 @@ use font_metrics::FontMetricsProvider;
use matching::StyleSharingCandidateCache;
use parking_lot::RwLock;
#[cfg(feature = "gecko")] use properties::ComputedValues;
#[cfg(feature = "gecko")] use selector_parser::PseudoElement;
use selectors::matching::ElementSelectorFlags;
#[cfg(feature = "servo")] use servo_config::opts;
use shared_lock::StylesheetGuards;
@ -270,7 +269,8 @@ impl TraversalStatistics {
#[cfg(feature = "gecko")]
bitflags! {
/// Represents which tasks are performed in a SequentialTask of UpdateAnimations.
/// Represents which tasks are performed in a SequentialTask of
/// UpdateAnimations.
pub flags UpdateAnimationsTasks: u8 {
/// Update CSS Animations.
const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations,
@ -296,10 +296,8 @@ pub enum SequentialTask<E: TElement> {
/// of the non-animation style traversal, and updating the computed effect properties.
#[cfg(feature = "gecko")]
UpdateAnimations {
/// The target element.
/// The target element or pseudo-element.
el: SendElement<E>,
/// The target pseudo element.
pseudo: Option<PseudoElement>,
/// The before-change style for transitions. We use before-change style as the initial
/// value of its Keyframe. Required if |tasks| includes CSSTransitions.
before_change_style: Option<Arc<ComputedValues>>,
@ -316,8 +314,8 @@ impl<E: TElement> SequentialTask<E> {
match self {
Unused(_) => unreachable!(),
#[cfg(feature = "gecko")]
UpdateAnimations { el, pseudo, before_change_style, tasks } => {
unsafe { el.update_animations(pseudo.as_ref(), before_change_style, tasks) };
UpdateAnimations { el, before_change_style, tasks } => {
unsafe { el.update_animations(before_change_style, tasks) };
}
}
}
@ -326,14 +324,14 @@ impl<E: TElement> SequentialTask<E> {
/// a given (pseudo-)element.
#[cfg(feature = "gecko")]
pub fn update_animations(el: E,
pseudo: Option<PseudoElement>,
before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks) -> Self {
use self::SequentialTask::*;
UpdateAnimations { el: unsafe { SendElement::new(el) },
pseudo: pseudo,
before_change_style: before_change_style,
tasks: tasks }
UpdateAnimations {
el: unsafe { SendElement::new(el) },
before_change_style: before_change_style,
tasks: tasks,
}
}
}

View File

@ -532,6 +532,21 @@ impl ElementData {
self.styles = Some(styles);
}
/// Sets the computed element rules, and returns whether the rules changed.
pub fn set_primary_rules(&mut self, rules: StrongRuleNode) -> bool {
if !self.has_styles() {
self.set_styles(ElementStyles::new(ComputedStyle::new_partial(rules)));
return true;
}
if self.styles().primary.rules == rules {
return false;
}
self.styles_mut().primary.rules = rules;
true
}
/// Returns true if the Element has a RestyleData.
pub fn has_restyle(&self) -> bool {
self.restyle.is_some()

View File

@ -274,11 +274,20 @@ pub trait PresentationalHintsSynthetizer {
where V: Push<ApplicableDeclarationBlock>;
}
/// The animation rules. The first one is for Animation cascade level, and the second one is for
/// The animation rules.
///
/// The first one is for Animation cascade level, and the second one is for
/// Transition cascade level.
pub struct AnimationRules(pub Option<Arc<Locked<PropertyDeclarationBlock>>>,
pub Option<Arc<Locked<PropertyDeclarationBlock>>>);
impl AnimationRules {
/// Returns whether these animation rules represents an actual rule or not.
pub fn is_empty(&self) -> bool {
self.0.is_none() && self.1.is_none()
}
}
/// The element trait, the main abstraction the style crate acts over.
pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
ElementExt + PresentationalHintsSynthetizer {
@ -325,26 +334,25 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
}
/// Get this element's animation rules.
fn get_animation_rules(&self, _pseudo: Option<&PseudoElement>) -> AnimationRules {
fn get_animation_rules(&self) -> AnimationRules {
AnimationRules(None, None)
}
/// Get this element's animation rule by the cascade level.
fn get_animation_rule_by_cascade(&self,
_pseudo: Option<&PseudoElement>,
_cascade_level: CascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None
}
/// Get this element's animation rule.
fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>)
fn get_animation_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None
}
/// Get this element's transition rule.
fn get_transition_rule(&self, _pseudo: Option<&PseudoElement>)
fn get_transition_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None
}
@ -428,6 +436,18 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// anonymous content).
fn is_native_anonymous(&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);
@ -469,21 +489,21 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// Creates a task to update various animation state on a given (pseudo-)element.
#[cfg(feature = "gecko")]
fn update_animations(&self, _pseudo: Option<&PseudoElement>,
fn update_animations(&self,
before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks);
/// 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, _pseudo: Option<&PseudoElement>) -> bool;
fn has_animations(&self) -> bool;
/// Returns true if the element has a CSS animation.
fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool;
fn has_css_animations(&self) -> bool;
/// Returns true if the element has a CSS transition (including running transitions and
/// completed transitions).
fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool;
fn has_css_transitions(&self) -> bool;
/// Returns true if the element has animation restyle hints.
fn has_animation_restyle_hints(&self) -> bool {
@ -497,8 +517,7 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// Gets the current existing CSS transitions, by |property, end value| pairs in a HashMap.
#[cfg(feature = "gecko")]
fn get_css_transitions_info(&self,
pseudo: Option<&PseudoElement>)
fn get_css_transitions_info(&self)
-> HashMap<TransitionProperty, Arc<AnimationValue>>;
/// Does a rough (and cheap) check for whether or not transitions might need to be updated that
@ -508,9 +527,9 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// reduce the possibility of false positives.
#[cfg(feature = "gecko")]
fn might_need_transitions_update(&self,
old_values: &Option<&Arc<ComputedValues>>,
new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) -> bool;
old_values: Option<&ComputedValues>,
new_values: &ComputedValues)
-> bool;
/// 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.
@ -518,17 +537,17 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// passed the same parameters.
#[cfg(feature = "gecko")]
fn needs_transitions_update(&self,
before_change_style: &Arc<ComputedValues>,
after_change_style: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) -> bool;
before_change_style: &ComputedValues,
after_change_style: &ComputedValues)
-> bool;
/// Returns true if we need to update transitions for the specified property on this element.
#[cfg(feature = "gecko")]
fn needs_transitions_update_per_property(&self,
property: &TransitionProperty,
combined_duration: f32,
before_change_style: &Arc<ComputedValues>,
after_change_style: &Arc<ComputedValues>,
before_change_style: &ComputedValues,
after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>)
-> bool;

View File

@ -54,6 +54,26 @@ pub const EAGER_PSEUDO_COUNT: usize = 2;
impl PseudoElement {
/// Returns the kind of cascade type that a given pseudo is going to use.
///
/// In Gecko we only compute ::before and ::after eagerly. We save the rules
/// for anonymous boxes separately, so we resolve them as precomputed
/// pseudos.
///
/// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
pub fn cascade_type(&self) -> PseudoElementCascadeType {
if self.is_eager() {
debug_assert!(!self.is_anon_box());
return PseudoElementCascadeType::Eager
}
if self.is_anon_box() {
return PseudoElementCascadeType::Precomputed
}
PseudoElementCascadeType::Lazy
}
/// Gets the canonical index of this eagerly-cascaded pseudo-element.
#[inline]
pub fn eager_index(&self) -> usize {
@ -437,24 +457,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
impl SelectorImpl {
#[inline]
/// Returns the kind of cascade type that a given pseudo is going to use.
///
/// In Gecko we only compute ::before and ::after eagerly. We save the rules
/// for anonymous boxes separately, so we resolve them as precomputed
/// pseudos.
///
/// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
/// Legacy alias for PseudoElement::cascade_type.
pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
if pseudo.is_eager() {
debug_assert!(!pseudo.is_anon_box());
return PseudoElementCascadeType::Eager
}
if pseudo.is_anon_box() {
return PseudoElementCascadeType::Precomputed
}
PseudoElementCascadeType::Lazy
pseudo.cascade_type()
}
/// A helper to traverse each eagerly cascaded pseudo-element, executing

View File

@ -421,12 +421,15 @@ fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
}
fn get_animation_rule(element: &GeckoElement,
pseudo: Option<&PseudoElement>,
cascade_level: CascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
// FIXME(emilio): This is quite dumb, why an RwLock, it's local to this
// function?
//
// Also, we should try to reuse the PDB, to avoid creating extra rule nodes.
let animation_values = Arc::new(RwLock::new(AnimationValueMap::new()));
if unsafe { Gecko_GetAnimationRule(element.0, atom_ptr, cascade_level,
if unsafe { Gecko_GetAnimationRule(element.0,
cascade_level,
HasArcFFI::arc_as_borrowed(&animation_values)) } {
let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
Some(Arc::new(shared_lock.wrap(
@ -531,30 +534,28 @@ impl<'le> TElement for GeckoElement<'le> {
declarations.map(|s| s.as_arc_opt()).unwrap_or(None)
}
fn get_animation_rules(&self, pseudo: Option<&PseudoElement>) -> AnimationRules {
AnimationRules(self.get_animation_rule(pseudo),
self.get_transition_rule(pseudo))
fn get_animation_rules(&self) -> AnimationRules {
AnimationRules(self.get_animation_rule(),
self.get_transition_rule())
}
fn get_animation_rule_by_cascade(&self,
pseudo: Option<&PseudoElement>,
cascade_level: ServoCascadeLevel)
fn get_animation_rule_by_cascade(&self, cascade_level: ServoCascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
match cascade_level {
ServoCascadeLevel::Animations => self.get_animation_rule(pseudo),
ServoCascadeLevel::Transitions => self.get_transition_rule(pseudo),
ServoCascadeLevel::Animations => self.get_animation_rule(),
ServoCascadeLevel::Transitions => self.get_transition_rule(),
_ => panic!("Unsupported cascade level for getting the animation rule")
}
}
fn get_animation_rule(&self, pseudo: Option<&PseudoElement>)
fn get_animation_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
get_animation_rule(self, pseudo, CascadeLevel::Animations)
get_animation_rule(self, CascadeLevel::Animations)
}
fn get_transition_rule(&self, pseudo: Option<&PseudoElement>)
fn get_transition_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
get_animation_rule(self, pseudo, CascadeLevel::Transitions)
get_animation_rule(self, CascadeLevel::Transitions)
}
fn get_state(&self) -> ElementState {
@ -589,7 +590,7 @@ impl<'le> TElement for GeckoElement<'le> {
-> Option<&'a nsStyleContext> {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
unsafe {
let context_ptr = Gecko_GetStyleContext(self.as_node().0, atom_ptr);
let context_ptr = Gecko_GetStyleContext(self.0, atom_ptr);
context_ptr.as_ref()
}
}
@ -641,6 +642,22 @@ impl<'le> TElement for GeckoElement<'le> {
self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0
}
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
if !self.is_native_anonymous() {
return None;
}
let maybe_atom =
unsafe { bindings::Gecko_GetImplementedPseudo(self.0) };
if maybe_atom.is_null() {
return None;
}
let atom = Atom::from(maybe_atom);
Some(PseudoElement::from_atom_unchecked(atom, /* anon_box = */ false))
}
fn store_children_to_process(&self, _: isize) {
// This is only used for bottom-up traversal, and is thus a no-op for Gecko.
}
@ -673,37 +690,27 @@ impl<'le> TElement for GeckoElement<'le> {
}
fn update_animations(&self,
pseudo: Option<&PseudoElement>,
before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks) {
// We have to update animations even if the element has no computed style
// since it means the element is in a display:none subtree, we should destroy
// all CSS animations in display:none subtree.
// We have to update animations even if the element has no computed
// style since it means the element is in a display:none subtree, we
// should destroy all CSS animations in display:none subtree.
let computed_data = self.borrow_data();
let computed_values =
computed_data.as_ref().map(|d|
pseudo.map_or_else(|| d.styles().primary.values(),
|p| d.styles().pseudos.get(p).unwrap().values())
);
let computed_values_opt = computed_values.map(|v|
*HasArcFFI::arc_as_borrowed(v)
);
let parent_element = if pseudo.is_none() {
self.parent_element()
} else {
Some(*self)
};
let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data());
let parent_values = parent_data.as_ref().map(|d| d.styles().primary.values());
let parent_values_opt = parent_values.map(|v|
*HasArcFFI::arc_as_borrowed(v)
);
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
let before_change_values = before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
computed_data.as_ref().map(|d| d.styles().primary.values());
let computed_values_opt =
computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
let parent_element = self.parent_element();
let parent_data =
parent_element.as_ref().and_then(|e| e.borrow_data());
let parent_values =
parent_data.as_ref().map(|d| d.styles().primary.values());
let parent_values_opt =
parent_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
let before_change_values =
before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
unsafe {
Gecko_UpdateAnimations(self.0, atom_ptr,
Gecko_UpdateAnimations(self.0,
before_change_values,
computed_values_opt,
parent_values_opt,
@ -711,35 +718,31 @@ impl<'le> TElement for GeckoElement<'le> {
}
}
fn has_animations(&self, pseudo: Option<&PseudoElement>) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
unsafe { Gecko_ElementHasAnimations(self.0, atom_ptr) }
fn has_animations(&self) -> bool {
unsafe { Gecko_ElementHasAnimations(self.0) }
}
fn has_css_animations(&self, pseudo: Option<&PseudoElement>) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
unsafe { Gecko_ElementHasCSSAnimations(self.0, atom_ptr) }
fn has_css_animations(&self) -> bool {
unsafe { Gecko_ElementHasCSSAnimations(self.0) }
}
fn has_css_transitions(&self, pseudo: Option<&PseudoElement>) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
unsafe { Gecko_ElementHasCSSTransitions(self.0, atom_ptr) }
fn has_css_transitions(&self) -> bool {
unsafe { Gecko_ElementHasCSSTransitions(self.0) }
}
fn get_css_transitions_info(&self,
pseudo: Option<&PseudoElement>)
fn get_css_transitions_info(&self)
-> HashMap<TransitionProperty, Arc<AnimationValue>> {
use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
use gecko_bindings::bindings::Gecko_ElementTransitions_Length;
use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt;
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0, atom_ptr) };
let collection_length =
unsafe { Gecko_ElementTransitions_Length(self.0) };
let mut map = HashMap::with_capacity(collection_length);
for i in 0..collection_length {
let (property, raw_end_value) = unsafe {
(Gecko_ElementTransitions_PropertyAt(self.0, atom_ptr, i as usize).into(),
Gecko_ElementTransitions_EndValueAt(self.0, atom_ptr, i as usize))
(Gecko_ElementTransitions_PropertyAt(self.0, i as usize).into(),
Gecko_ElementTransitions_EndValueAt(self.0, i as usize))
};
let end_value = AnimationValue::arc_from_borrowed(&raw_end_value);
debug_assert!(end_value.is_some());
@ -749,21 +752,21 @@ impl<'le> TElement for GeckoElement<'le> {
}
fn might_need_transitions_update(&self,
old_values: &Option<&Arc<ComputedValues>>,
new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) -> bool {
old_values: Option<&ComputedValues>,
new_values: &ComputedValues) -> bool {
use properties::longhands::display::computed_value as display;
if old_values.is_none() {
return false;
}
let old_values = match old_values {
Some(v) => v,
None => return false,
};
let ref new_box_style = new_values.get_box();
let transition_not_running = !self.has_css_transitions(pseudo) &&
let new_box_style = new_values.get_box();
let transition_not_running = !self.has_css_transitions() &&
new_box_style.transition_property_count() == 1 &&
new_box_style.transition_combined_duration_at(0) <= 0.0f32;
let new_display_style = new_box_style.clone_display();
let old_display_style = old_values.map(|ref old| old.get_box().clone_display()).unwrap();
let old_display_style = old_values.get_box().clone_display();
new_box_style.transition_property_count() > 0 &&
!transition_not_running &&
@ -771,28 +774,31 @@ impl<'le> TElement for GeckoElement<'le> {
old_display_style != display::T::none)
}
// Detect if there are any changes that require us to update transitions. This is used as a
// more thoroughgoing check than the, cheaper might_need_transitions_update check.
// Detect if there are any changes that require us to update transitions.
// This is used as a more thoroughgoing check than the, cheaper
// might_need_transitions_update check.
//
// The following logic shadows the logic used on the Gecko side
// (nsTransitionManager::DoUpdateTransitions) where we actually perform the update.
// (nsTransitionManager::DoUpdateTransitions) where we actually perform the
// update.
//
// https://drafts.csswg.org/css-transitions/#starting
fn needs_transitions_update(&self,
before_change_style: &Arc<ComputedValues>,
after_change_style: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) -> bool {
before_change_style: &ComputedValues,
after_change_style: &ComputedValues)
-> bool {
use gecko_bindings::structs::nsCSSPropertyID;
use properties::{PropertyId, animated_properties};
use std::collections::HashSet;
debug_assert!(self.might_need_transitions_update(&Some(before_change_style),
after_change_style,
pseudo),
debug_assert!(self.might_need_transitions_update(Some(before_change_style),
after_change_style),
"We should only call needs_transitions_update if \
might_need_transitions_update returns true");
let ref after_change_box_style = after_change_style.get_box();
let after_change_box_style = after_change_style.get_box();
let transitions_count = after_change_box_style.transition_property_count();
let existing_transitions = self.get_css_transitions_info(pseudo);
let existing_transitions = self.get_css_transitions_info();
let mut transitions_to_keep = if !existing_transitions.is_empty() &&
(after_change_box_style.transition_nscsspropertyid_at(0) !=
nsCSSPropertyID::eCSSPropertyExtra_all_properties) {
@ -865,8 +871,8 @@ impl<'le> TElement for GeckoElement<'le> {
fn needs_transitions_update_per_property(&self,
property: &TransitionProperty,
combined_duration: f32,
before_change_style: &Arc<ComputedValues>,
after_change_style: &Arc<ComputedValues>,
before_change_style: &ComputedValues,
after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>)
-> bool {

View File

@ -615,8 +615,7 @@ extern "C" {
-> RawServoDeclarationBlockStrongBorrowedOrNull;
}
extern "C" {
pub fn Gecko_GetAnimationRule(aElement: RawGeckoElementBorrowed,
aPseudoTag: *mut nsIAtom,
pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed,
aCascadeLevel:
EffectCompositor_CascadeLevel,
aAnimationValues:
@ -636,46 +635,42 @@ extern "C" {
-> bool;
}
extern "C" {
pub fn Gecko_UpdateAnimations(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
pub fn Gecko_UpdateAnimations(aElementOrPseudo: RawGeckoElementBorrowed,
aOldComputedValues:
ServoComputedValuesBorrowedOrNull,
aComputedValues:
ServoComputedValuesBorrowedOrNull,
aParentComputedValues:
ServoComputedValuesBorrowedOrNull,
aTaskBits: UpdateAnimationsTasks);
aTasks: UpdateAnimationsTasks);
}
extern "C" {
pub fn Gecko_ElementHasAnimations(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom) -> bool;
pub fn Gecko_ElementHasAnimations(aElementOrPseudo:
RawGeckoElementBorrowed) -> bool;
}
extern "C" {
pub fn Gecko_ElementHasCSSAnimations(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom)
pub fn Gecko_ElementHasCSSAnimations(aElementOrPseudo:
RawGeckoElementBorrowed) -> bool;
}
extern "C" {
pub fn Gecko_ElementHasCSSTransitions(aElementOrPseudo:
RawGeckoElementBorrowed)
-> bool;
}
extern "C" {
pub fn Gecko_ElementHasCSSTransitions(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom)
-> bool;
}
extern "C" {
pub fn Gecko_ElementTransitions_Length(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom)
pub fn Gecko_ElementTransitions_Length(aElementOrPseudo:
RawGeckoElementBorrowed)
-> usize;
}
extern "C" {
pub fn Gecko_ElementTransitions_PropertyAt(aElement:
pub fn Gecko_ElementTransitions_PropertyAt(aElementOrPseudo:
RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
aIndex: usize)
-> nsCSSPropertyID;
}
extern "C" {
pub fn Gecko_ElementTransitions_EndValueAt(aElement:
pub fn Gecko_ElementTransitions_EndValueAt(aElementOrPseudo:
RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
aIndex: usize)
-> RawServoAnimationValueBorrowedOrNull;
}
@ -860,10 +855,14 @@ extern "C" {
RawGeckoElementBorrowed);
}
extern "C" {
pub fn Gecko_GetStyleContext(node: RawGeckoNodeBorrowed,
pub fn Gecko_GetStyleContext(element: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom)
-> *mut nsStyleContext;
}
extern "C" {
pub fn Gecko_GetImplementedPseudo(element: RawGeckoElementBorrowed)
-> *mut nsIAtom;
}
extern "C" {
pub fn Gecko_CalcStyleDifference(oldstyle: *mut nsStyleContext,
newstyle: ServoComputedValuesBorrowed)

View File

@ -30,6 +30,15 @@ use sink::ForgetfulSink;
use std::sync::Arc;
use stylist::ApplicableDeclarationBlock;
/// The way a style should be inherited.
enum InheritMode {
/// Inherit from the parent element, as normal CSS dictates.
FromParentElement,
/// Inherit from the primary style, this is used while computing eager
/// pseudos, like ::before and ::after when we're traversing the parent.
FromPrimaryStyle,
}
/// Determines the amount of relations where we're going to share style.
#[inline]
fn relations_are_shareable(relations: &StyleRelations) -> bool {
@ -78,6 +87,8 @@ pub struct StyleSharingCandidateCache<E: TElement> {
pub enum CacheMiss {
/// The parents don't match.
Parent,
/// One element was NAC, while the other wasn't.
NativeAnonymousContent,
/// The local name of the element and the candidate don't match.
LocalName,
/// The namespace of the element and the candidate don't match.
@ -137,6 +148,12 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(Parent)
}
if element.is_native_anonymous() {
debug_assert!(!candidate_element.is_native_anonymous(),
"Why inserting NAC into the cache?");
miss!(NativeAnonymousContent)
}
if *element.get_local_name() != *candidate_element.get_local_name() {
miss!(LocalName)
}
@ -298,6 +315,11 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
}
};
if element.is_native_anonymous() {
debug!("Failing to insert into the cache: NAC");
return;
}
// These are things we don't check in the candidate match because they
// are either uncommon or expensive.
if !relations_are_shareable(&relations) {
@ -312,7 +334,6 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
debug_assert!(hints.is_empty(), "Style relations should not be shareable!");
}
let box_style = style.get_box();
if box_style.specifies_transitions() {
debug!("Failing to insert to the cache: transitions");
@ -387,7 +408,7 @@ trait PrivateMatchMethods: TElement {
font_metrics_provider: &FontMetricsProvider,
rule_node: &StrongRuleNode,
primary_style: &ComputedStyle,
is_pseudo: bool)
inherit_mode: InheritMode)
-> Arc<ComputedValues> {
let mut cascade_info = CascadeInfo::new();
let mut cascade_flags = CascadeFlags::empty();
@ -398,24 +419,27 @@ trait PrivateMatchMethods: TElement {
// Grab the inherited values.
let parent_el;
let parent_data;
let style_to_inherit_from = if !is_pseudo {
parent_el = self.parent_element();
parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
let parent_values = parent_data.as_ref().map(|d| {
// Sometimes Gecko eagerly styles things without processing
// pending restyles first. In general we'd like to avoid this,
// but there can be good reasons (for example, needing to
// construct a frame for some small piece of newly-added
// content in order to do something specific with that frame,
// but not wanting to flush all of layout).
debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
d.styles().primary.values()
});
let style_to_inherit_from = match inherit_mode {
InheritMode::FromParentElement => {
parent_el = self.parent_element();
parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
let parent_values = parent_data.as_ref().map(|d| {
// Sometimes Gecko eagerly styles things without processing
// pending restyles first. In general we'd like to avoid this,
// but there can be good reasons (for example, needing to
// construct a frame for some small piece of newly-added
// content in order to do something specific with that frame,
// but not wanting to flush all of layout).
debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
d.styles().primary.values()
});
parent_values
} else {
parent_el = Some(self.clone());
Some(primary_style.values())
parent_values
}
InheritMode::FromPrimaryStyle => {
parent_el = Some(self.clone());
Some(primary_style.values())
}
};
let mut layout_parent_el = parent_el.clone();
@ -433,15 +457,16 @@ trait PrivateMatchMethods: TElement {
// Propagate the "can be fragmented" bit. It would be nice to
// encapsulate this better.
//
// Note that this is not needed for pseudos since we already do that
// when we resolve the non-pseudo style.
if !is_pseudo {
if let Some(ref p) = layout_parent_style {
let can_be_fragmented =
p.is_multicol() ||
layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
}
// 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.
//
// TODO(emilio): This is servo-only, move somewhere else?
if let Some(ref p) = layout_parent_style {
let can_be_fragmented =
p.is_multicol() ||
layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
}
// Invoke the cascade algorithm.
@ -463,16 +488,21 @@ trait PrivateMatchMethods: TElement {
fn cascade_internal(&self,
context: &StyleContext<Self>,
primary_style: &ComputedStyle,
pseudo_style: Option<&ComputedStyle>)
eager_pseudo_style: Option<&ComputedStyle>)
-> Arc<ComputedValues> {
// Grab the rule node.
let rule_node = &pseudo_style.unwrap_or(primary_style).rules;
let rule_node = &eager_pseudo_style.unwrap_or(primary_style).rules;
let inherit_mode = if eager_pseudo_style.is_some() {
InheritMode::FromPrimaryStyle
} else {
InheritMode::FromParentElement
};
self.cascade_with_rules(context.shared,
&context.thread_local.font_metrics_provider,
rule_node,
primary_style,
pseudo_style.is_some())
inherit_mode)
}
/// Computes values and damage for the primary or pseudo style of an element,
@ -480,8 +510,9 @@ trait PrivateMatchMethods: TElement {
fn cascade_primary_or_pseudo(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
pseudo: Option<&PseudoElement>,
animate: bool) {
pseudo: Option<&PseudoElement>) {
debug_assert!(pseudo.is_none() || self.implemented_pseudo_element().is_none(),
"Pseudo-element-implementing elements can't have pseudos!");
// Collect some values.
let (mut styles, restyle) = data.styles_and_restyle_mut();
let mut primary_style = &mut styles.primary;
@ -501,27 +532,65 @@ trait PrivateMatchMethods: TElement {
};
// Compute the new values.
let mut new_values = self.cascade_internal(context,
primary_style,
pseudo_style.as_ref().map(|s| &**s));
let mut new_values = match self.implemented_pseudo_element() {
Some(ref pseudo) => {
// This is an element-backed pseudo, just grab the styles from
// the parent if it's eager, and recascade otherwise.
//
// We also recascade if the eager pseudo-style has any animation
// rules, because we don't cascade those during the eager
// traversal. We could make that a bit better if the complexity
// cost is not too big, but given further restyles are posted
// directly to pseudo-elements, it doesn't seem worth the effort
// at a glance.
if pseudo.is_eager() &&
self.get_animation_rules().is_empty() {
let parent = self.parent_element().unwrap();
// Handle animations.
if animate && !context.shared.traversal_flags.for_animation_only() {
let parent_data = parent.borrow_data().unwrap();
let pseudo_style =
parent_data.styles().pseudos.get(pseudo).unwrap();
pseudo_style.values().clone()
} else {
self.cascade_internal(context,
primary_style,
None)
}
}
None => {
// Else it's an eager pseudo or a normal element, do the cascade
// work.
self.cascade_internal(context,
primary_style,
pseudo_style.as_ref().map(|s| &**s))
}
};
// NB: Animations for pseudo-elements in Gecko are handled while
// traversing the pseudo-elements themselves.
if pseudo.is_none() &&
!context.shared.traversal_flags.for_animation_only() {
self.process_animations(context,
&mut old_values,
&mut new_values,
primary_style,
pseudo,
pseudo_style.as_ref().map(|s| &**s));
primary_style);
}
// Accumulate restyle damage.
if let Some(old) = old_values {
self.accumulate_damage(&context.shared,
restyle.unwrap(),
&old,
&new_values,
pseudo);
// ::before and ::after are element-backed in Gecko, so they do
// the damage calculation for themselves.
//
// FIXME(emilio): We have more element-backed stuff, and this is
// redundant for them right now.
if cfg!(feature = "servo") ||
pseudo.map_or(true, |p| !p.is_before_or_after()) {
self.accumulate_damage(&context.shared,
restyle.unwrap(),
&old,
&new_values,
pseudo);
}
}
// Set the new computed values.
@ -534,11 +603,9 @@ trait PrivateMatchMethods: TElement {
#[cfg(feature = "gecko")]
fn get_after_change_style(&self,
context: &mut StyleContext<Self>,
primary_style: &ComputedStyle,
pseudo_style: Option<&ComputedStyle>)
primary_style: &ComputedStyle)
-> Option<Arc<ComputedValues>> {
let relevant_style = pseudo_style.unwrap_or(primary_style);
let rule_node = &relevant_style.rules;
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 {
@ -551,21 +618,21 @@ trait PrivateMatchMethods: TElement {
&context.thread_local.font_metrics_provider,
&without_transition_rules,
primary_style,
pseudo_style.is_some()))
InheritMode::FromParentElement))
}
#[cfg(feature = "gecko")]
fn needs_animations_update(&self,
old_values: &Option<Arc<ComputedValues>>,
new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) -> bool {
let ref new_box_style = new_values.get_box();
old_values: Option<&Arc<ComputedValues>>,
new_values: &ComputedValues)
-> bool {
let new_box_style = new_values.get_box();
let has_new_animation_style = new_box_style.animation_name_count() >= 1 &&
new_box_style.animation_name_at(0).0.is_some();
let has_animations = self.has_css_animations(pseudo);
let has_animations = self.has_css_animations();
old_values.as_ref().map_or(has_new_animation_style, |ref old| {
let ref old_box_style = old.get_box();
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();
// FIXME: Bug 1344581: We still need to compare keyframe rules.
@ -584,40 +651,34 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
primary_style: &ComputedStyle,
pseudo: Option<&PseudoElement>,
pseudo_style: Option<&ComputedStyle>) {
primary_style: &ComputedStyle) {
use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
use context::UpdateAnimationsTasks;
debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
let mut tasks = UpdateAnimationsTasks::empty();
if self.needs_animations_update(old_values, new_values, pseudo) {
if self.needs_animations_update(old_values.as_ref(), new_values) {
tasks.insert(CSS_ANIMATIONS);
}
let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(),
new_values,
pseudo) {
let after_change_style = if self.has_css_transitions(pseudo) {
self.get_after_change_style(context, primary_style, pseudo_style)
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, primary_style)
} 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.
// 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 = match after_change_style {
Some(ref value) => value,
None => &new_values
};
// 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,
pseudo)
after_change_style_ref)
};
if needs_transitions_update {
@ -635,13 +696,12 @@ trait PrivateMatchMethods: TElement {
None
};
if self.has_animations(pseudo) {
if self.has_animations() {
tasks.insert(EFFECT_PROPERTIES);
}
if !tasks.is_empty() {
let task = ::context::SequentialTask::update_animations(*self,
pseudo.cloned(),
before_change_style,
tasks);
context.thread_local.tasks.push(task);
@ -653,11 +713,7 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
_primary_style: &ComputedStyle,
pseudo: Option<&PseudoElement>,
pseudo_style: Option<&ComputedStyle>) {
debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
_primary_style: &ComputedStyle) {
let possibly_expired_animations =
&mut context.thread_local.current_element_info.as_mut().unwrap()
.possibly_expired_animations;
@ -690,6 +746,10 @@ trait PrivateMatchMethods: TElement {
}
/// Computes and applies non-redundant damage.
///
/// FIXME(emilio): Damage for non-::before and non-::after element-backed
/// pseudo-elements should be refactored to go on themselves (right now they
/// do, but we apply this twice).
#[cfg(feature = "gecko")]
fn accumulate_damage(&self,
shared_context: &SharedStyleContext,
@ -716,7 +776,9 @@ trait PrivateMatchMethods: TElement {
// 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);
let new_damage = self.compute_restyle_damage(&old_values,
&new_values,
pseudo);
if !restyle.damage_handled.contains(new_damage) {
restyle.damage |= new_damage;
}
@ -813,7 +875,8 @@ pub enum StyleSharingBehavior {
/// The public API that elements expose for selector matching.
pub trait MatchMethods : TElement {
/// Performs selector matching and property cascading on an element and its eager pseudos.
/// Performs selector matching and property cascading on an element and its
/// eager pseudos.
fn match_and_cascade(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
@ -878,14 +941,54 @@ pub trait MatchMethods : TElement {
relations: &mut StyleRelations)
-> bool
{
let implemented_pseudo = self.implemented_pseudo_element();
if let Some(ref pseudo) = implemented_pseudo {
if pseudo.is_eager() {
// If it's an eager element-backed pseudo, just grab the matched
// rules from the parent, and update animations.
let parent = self.parent_element().unwrap();
let parent_data = parent.borrow_data().unwrap();
let pseudo_style =
parent_data.styles().pseudos.get(&pseudo).unwrap();
let mut rules = pseudo_style.rules.clone();
let animation_rules = self.get_animation_rules();
// Handle animations here.
if let Some(animation_rule) = animation_rules.0 {
let animation_rule_node =
context.shared.stylist.rule_tree
.update_rule_at_level(CascadeLevel::Animations,
Some(&animation_rule),
&mut rules,
&context.shared.guards);
if let Some(node) = animation_rule_node {
rules = node;
}
}
if let Some(animation_rule) = animation_rules.1 {
let animation_rule_node =
context.shared.stylist.rule_tree
.update_rule_at_level(CascadeLevel::Transitions,
Some(&animation_rule),
&mut rules,
&context.shared.guards);
if let Some(node) = animation_rule_node {
rules = node;
}
}
return data.set_primary_rules(rules);
}
}
let mut applicable_declarations =
Vec::<ApplicableDeclarationBlock>::with_capacity(16);
let stylist = &context.shared.stylist;
let style_attribute = self.style_attribute();
let smil_override = self.get_smil_override();
let animation_rules = self.get_animation_rules(None);
let mut rule_nodes_changed = false;
let animation_rules = self.get_animation_rules();
let bloom = context.thread_local.bloom_filter.filter();
let map = &mut context.thread_local.selector_flags;
@ -899,22 +1002,15 @@ pub trait MatchMethods : TElement {
style_attribute,
smil_override,
animation_rules,
None,
implemented_pseudo.as_ref(),
&context.shared.guards,
&mut applicable_declarations,
&mut set_selector_flags);
let primary_rule_node =
compute_rule_node::<Self>(&stylist.rule_tree, &mut applicable_declarations);
if !data.has_styles() {
data.set_styles(ElementStyles::new(ComputedStyle::new_partial(primary_rule_node)));
rule_nodes_changed = true;
} else if data.styles().primary.rules != primary_rule_node {
data.styles_mut().primary.rules = primary_rule_node;
rule_nodes_changed = true;
}
rule_nodes_changed
return data.set_primary_rules(primary_rule_node);
}
/// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
@ -927,9 +1023,13 @@ pub trait MatchMethods : TElement {
data: &mut ElementData)
-> bool
{
if self.implemented_pseudo_element().is_some() {
// Element pseudos can't have any other pseudo.
return false;
}
let mut applicable_declarations =
Vec::<ApplicableDeclarationBlock>::with_capacity(16);
let mut rule_nodes_changed = false;
let map = &mut context.thread_local.selector_flags;
let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
@ -945,19 +1045,17 @@ pub trait MatchMethods : TElement {
// Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = false;
let mut rule_nodes_changed = false;
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let mut pseudos = &mut data.styles_mut().pseudos;
debug_assert!(applicable_declarations.is_empty());
let pseudo_animation_rules = if pseudo.is_before_or_after() {
self.get_animation_rules(Some(&pseudo))
} else {
AnimationRules(None, None)
};
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(self,
Some(bloom_filter),
None,
None,
pseudo_animation_rules,
AnimationRules(None, None),
Some(&pseudo),
&guards,
&mut applicable_declarations,
@ -1073,9 +1171,11 @@ pub trait MatchMethods : TElement {
}
};
// 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.
// 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 hint.intersects(RestyleHint::for_animations()) {
debug_assert!(context.shared.traversal_flags.for_animation_only());
@ -1085,37 +1185,24 @@ pub trait MatchMethods : TElement {
primary_rules);
}
use data::EagerPseudoStyles;
let mut replace_rule_node_for_animation = |level: CascadeLevel,
primary_rules: &mut StrongRuleNode,
pseudos: &mut EagerPseudoStyles| {
let animation_rule = self.get_animation_rule_by_cascade(None, level);
primary_rules: &mut StrongRuleNode| {
let animation_rule = self.get_animation_rule_by_cascade(level);
replace_rule_node(level,
animation_rule.as_ref(),
primary_rules);
for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) {
let animation_rule = self.get_animation_rule_by_cascade(Some(&pseudo), level);
let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules;
replace_rule_node(level,
animation_rule.as_ref(),
pseudo_rules);
}
};
// Apply Transition rules and Animation rules if the corresponding restyle hint
// is contained.
let pseudos = &mut element_styles.pseudos;
if hint.contains(RESTYLE_CSS_TRANSITIONS) {
replace_rule_node_for_animation(CascadeLevel::Transitions,
primary_rules,
pseudos);
primary_rules);
}
if hint.contains(RESTYLE_CSS_ANIMATIONS) {
replace_rule_node_for_animation(CascadeLevel::Animations,
primary_rules,
pseudos);
primary_rules);
}
} else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
let style_attribute = self.style_attribute();
@ -1125,11 +1212,9 @@ pub trait MatchMethods : TElement {
replace_rule_node(CascadeLevel::StyleAttributeImportant,
style_attribute,
primary_rules);
// The per-pseudo rule nodes never change in this path.
}
}
// The per-pseudo rule nodes never change in this path.
rule_node_changed
}
@ -1151,6 +1236,11 @@ pub trait MatchMethods : TElement {
return StyleSharingResult::CannotShare
}
if self.is_native_anonymous() {
debug!("{:?} Cannot share style: NAC", self);
return StyleSharingResult::CannotShare;
}
if self.style_attribute().is_some() {
debug!("{:?} Cannot share style: element has style attribute", self);
return StyleSharingResult::CannotShare
@ -1310,7 +1400,7 @@ pub trait MatchMethods : TElement {
context: &mut StyleContext<Self>,
mut data: &mut ElementData)
{
self.cascade_primary_or_pseudo(context, &mut data, None, /* animate = */ true);
self.cascade_primary_or_pseudo(context, &mut data, None);
}
/// Performs the cascade for the element's eager pseudos.
@ -1325,9 +1415,7 @@ pub trait MatchMethods : TElement {
// let us pass the mutable |data| to the cascade function.
let matched_pseudos = data.styles().pseudos.keys();
for pseudo in matched_pseudos {
// Only ::before and ::after are animatable.
let animate = pseudo.is_before_or_after();
self.cascade_primary_or_pseudo(context, data, Some(&pseudo), animate);
self.cascade_primary_or_pseudo(context, data, Some(&pseudo));
}
}
@ -1352,7 +1440,7 @@ pub trait MatchMethods : TElement {
font_metrics_provider,
&without_animation_rules,
primary_style,
pseudo_style.is_some())
InheritMode::FromParentElement)
}
}

View File

@ -17,7 +17,7 @@ use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::matches_selector;
use selectors::parser::{AttrSelector, Combinator, ComplexSelector, Component};
use selectors::parser::{AttrSelector, Combinator, Component, Selector};
use selectors::parser::{SelectorInner, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use std::clone::Clone;
@ -122,8 +122,7 @@ impl RestyleHint {
#[cfg(feature = "gecko")]
impl From<nsRestyleHint> for RestyleHint {
fn from(raw: nsRestyleHint) -> Self {
use std::mem;
let raw_bits: u32 = unsafe { mem::transmute(raw) };
let raw_bits: u32 = raw.0;
// FIXME(bholley): Finish aligning the binary representations here and
// then .expect() the result of the checked version.
if Self::from_bits(raw_bits).is_none() {
@ -574,11 +573,10 @@ impl DependencySet {
/// Adds a selector to this `DependencySet`, and returns whether it may need
/// cache revalidation, that is, whether two siblings of the same "shape"
/// may have different style due to this selector.
pub fn note_selector(&mut self,
base: &ComplexSelector<SelectorImpl>)
-> bool
{
let mut next = Some(base.clone());
pub fn note_selector(&mut self, selector: &Selector<SelectorImpl>) -> bool {
let mut is_pseudo_element = selector.pseudo_element.is_some();
let mut next = Some(selector.inner.complex.clone());
let mut combinator = None;
let mut needs_revalidation = false;
@ -590,6 +588,19 @@ impl DependencySet {
needs_revalidation: false,
};
if is_pseudo_element {
// TODO(emilio): use more fancy restyle hints to avoid restyling
// the whole subtree when pseudos change.
//
// We currently need is_pseudo_element to handle eager pseudos
// (so the style the parent stores doesn't become stale), and
// restyle_descendants to handle all of them (::before and
// ::after, because we find them in the subtree, and other lazy
// pseudos for the same reason).
visitor.hint |= RESTYLE_SELF | RESTYLE_DESCENDANTS;
is_pseudo_element = false;
}
{
// Visit all the simple selectors.
let mut iter = current.iter();

View File

@ -333,7 +333,7 @@ impl Stylist {
for selector in &style_rule.selectors.0 {
let needs_cache_revalidation =
self.dependencies.note_selector(&selector.inner.complex);
self.dependencies.note_selector(selector);
if needs_cache_revalidation {
self.selectors_for_cache_revalidation.push(selector.clone());
}
@ -646,7 +646,10 @@ impl Stylist {
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(!self.is_device_dirty);
debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
// Gecko definitely has pseudo-elements with style attributes, like
// ::-moz-color-swatch.
debug_assert!(cfg!(feature = "gecko") ||
style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.is_precomputed()));

View File

@ -264,88 +264,118 @@ pub trait DomTraversal<E: TElement> : Sync {
return true;
}
match node.as_element() {
None => Self::text_node_needs_traversal(node),
Some(el) => {
// If the element is native-anonymous and an ancestor frame will
// be reconstructed, the child and all its descendants will be
// destroyed. In that case, we don't need to traverse the subtree.
if el.is_native_anonymous() {
if let Some(parent) = el.parent_element() {
let parent_data = parent.borrow_data().unwrap();
if let Some(r) = parent_data.get_restyle() {
if (r.damage | r.damage_handled()).contains(RestyleDamage::reconstruct()) {
debug!("Element {:?} is in doomed NAC subtree - culling traversal", el);
return false;
}
let el = match node.as_element() {
None => return Self::text_node_needs_traversal(node),
Some(el) => el,
};
// If the element is native-anonymous and an ancestor frame will
// be reconstructed, the child and all its descendants will be
// destroyed. In that case, we wouldn't need to traverse the
// subtree...
//
// Except if there could be transitions of pseudo-elements, in
// which
// case we still need to process them, unfortunately.
//
// We need to conservatively continue the traversal to style the
// pseudo-element in order to properly process potentially-new
// transitions that we won't see otherwise.
//
// But it may be that we no longer match, so detect that case
// and act appropriately here.
if el.is_native_anonymous() {
if let Some(parent) = el.parent_element() {
let parent_data = parent.borrow_data().unwrap();
let going_to_reframe = parent_data.get_restyle().map_or(false, |r| {
(r.damage | r.damage_handled())
.contains(RestyleDamage::reconstruct())
});
let mut is_before_or_after_pseudo = false;
if let Some(pseudo) = el.implemented_pseudo_element() {
if pseudo.is_before_or_after() {
is_before_or_after_pseudo = true;
let still_match =
parent_data.styles().pseudos.get(&pseudo).is_some();
if !still_match {
debug_assert!(going_to_reframe,
"We're removing a pseudo, so we \
should reframe!");
return false;
}
}
}
// In case of animation-only traversal we need to traverse
// the element if the element has animation only dirty
// descendants bit, animation-only restyle hint or recascade.
if traversal_flags.for_animation_only() {
if el.has_animation_only_dirty_descendants() {
return true;
}
let data = match el.borrow_data() {
Some(d) => d,
None => return false,
};
return data.get_restyle()
.map_or(false, |r| r.hint.has_animation_hint() || r.recascade);
if going_to_reframe && !is_before_or_after_pseudo {
debug!("Element {:?} is in doomed NAC subtree, \
culling traversal", el);
return false;
}
// If the dirty descendants bit is set, we need to traverse no
// matter what. Skip examining the ElementData.
if el.has_dirty_descendants() {
return true;
}
// Check the element data. If it doesn't exist, we need to visit
// the element.
let data = match el.borrow_data() {
Some(d) => d,
None => return true,
};
// If we don't have any style data, we need to visit the element.
if !data.has_styles() {
return true;
}
// Check the restyle data.
if let Some(r) = data.get_restyle() {
// If we have a restyle hint or need to recascade, we need to
// visit the element.
//
// Note that this is different than checking has_current_styles(),
// since that can return true even if we have a restyle hint
// indicating that the element's descendants (but not necessarily
// the element) need restyling.
if !r.hint.is_empty() || r.recascade {
return true;
}
}
// Servo uses the post-order traversal for flow construction, so
// we need to traverse any element with damage so that we can perform
// fixup / reconstruction on our way back up the tree.
//
// We also need to traverse nodes with explicit damage and no other
// restyle data, so that this damage can be cleared.
if (cfg!(feature = "servo") ||
traversal_flags.for_reconstruct()) &&
data.get_restyle().map_or(false, |r| r.damage != RestyleDamage::empty())
{
return true;
}
false
},
}
}
// In case of animation-only traversal we need to traverse
// the element if the element has animation only dirty
// descendants bit, animation-only restyle hint or recascade.
if traversal_flags.for_animation_only() {
if el.has_animation_only_dirty_descendants() {
return true;
}
let data = match el.borrow_data() {
Some(d) => d,
None => return false,
};
return data.get_restyle()
.map_or(false, |r| r.hint.has_animation_hint() || r.recascade);
}
// If the dirty descendants bit is set, we need to traverse no
// matter what. Skip examining the ElementData.
if el.has_dirty_descendants() {
return true;
}
// Check the element data. If it doesn't exist, we need to visit
// the element.
let data = match el.borrow_data() {
Some(d) => d,
None => return true,
};
// If we don't have any style data, we need to visit the element.
if !data.has_styles() {
return true;
}
// Check the restyle data.
if let Some(r) = data.get_restyle() {
// If we have a restyle hint or need to recascade, we need to
// visit the element.
//
// Note that this is different than checking has_current_styles(),
// since that can return true even if we have a restyle hint
// indicating that the element's descendants (but not necessarily
// the element) need restyling.
if !r.hint.is_empty() || r.recascade {
return true;
}
}
// Servo uses the post-order traversal for flow construction, so
// we need to traverse any element with damage so that we can perform
// fixup / reconstruction on our way back up the tree.
//
// We also need to traverse nodes with explicit damage and no other
// restyle data, so that this damage can be cleared.
if (cfg!(feature = "servo") || traversal_flags.for_reconstruct()) &&
data.get_restyle().map_or(false, |r| !r.damage.is_empty()) {
return true;
}
false
}
/// Returns true if traversal of this element's children is allowed. We use
@ -396,7 +426,6 @@ pub trait DomTraversal<E: TElement> : Sync {
}
return true;
}
/// Helper for the traversal implementations to select the children that
@ -489,7 +518,9 @@ fn resolve_style_internal<E, F>(context: &mut StyleContext<E>,
// Compute our style.
context.thread_local.begin_element(element, &data);
element.match_and_cascade(context, &mut data, StyleSharingBehavior::Disallow);
element.match_and_cascade(context,
&mut data,
StyleSharingBehavior::Disallow);
context.thread_local.end_element(element);
// Conservatively mark us as having dirty descendants, since there might
@ -607,9 +638,13 @@ pub fn recalc_style_at<E, D>(traversal: &D,
};
debug_assert!(data.has_current_styles() ||
context.shared.traversal_flags.for_animation_only(),
"Should have computed style or haven't yet valid computed style in case of animation-only restyle");
trace!("propagated_hint={:?}, inherited_style_changed={:?}",
propagated_hint, inherited_style_changed);
"Should have computed style or haven't yet valid computed \
style in case of animation-only restyle");
trace!("propagated_hint={:?}, inherited_style_changed={:?}, \
is_display_none={:?}, implementing_pseudo={:?}",
propagated_hint, inherited_style_changed,
data.styles().is_display_none(),
element.implemented_pseudo_element());
let has_dirty_descendants_for_this_restyle =
if context.shared.traversal_flags.for_animation_only() {
@ -664,7 +699,8 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// The second case is when we are in a restyle for reconstruction,
// where we won't need to perform a post-traversal to pick up any
// change hints.
if data.styles().is_display_none() || context.shared.traversal_flags.for_reconstruct() {
if data.styles().is_display_none() ||
context.shared.traversal_flags.for_reconstruct() {
unsafe { element.unset_dirty_descendants(); }
}
}

View File

@ -23,7 +23,7 @@ use style::font_metrics::get_metrics_provider_for_product;
use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData};
use style::gecko::restyle_damage::GeckoRestyleDamage;
use style::gecko::selector_parser::{SelectorImpl, PseudoElement};
use style::gecko::selector_parser::PseudoElement;
use style::gecko::traversal::RecalcStyleOnly;
use style::gecko::wrapper::GeckoElement;
use style::gecko_bindings::bindings;
@ -166,7 +166,8 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
}
}
fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed,
fn traverse_subtree(element: GeckoElement,
raw_data: RawServoStyleSetBorrowed,
traversal_flags: TraversalFlags) {
// When new content is inserted in a display:none subtree, we will call into
// servo to try to style it. Detect that here and bail out.
@ -936,12 +937,15 @@ pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
}
}
fn get_pseudo_style(guard: &SharedRwLockReadGuard, element: GeckoElement, pseudo_tag: *mut nsIAtom,
styles: &ElementStyles, doc_data: &PerDocumentStyleData)
fn get_pseudo_style(guard: &SharedRwLockReadGuard,
element: GeckoElement,
pseudo_tag: *mut nsIAtom,
styles: &ElementStyles,
doc_data: &PerDocumentStyleData)
-> Option<Arc<ComputedValues>>
{
let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false);
match SelectorImpl::pseudo_element_cascade_type(&pseudo) {
match pseudo.cascade_type() {
PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
PseudoElementCascadeType::Lazy => {