--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -208,19 +208,27 @@ ServoRestyleManager::ProcessPostTraversa
// elements, though that is buggy right now even in non-stylo mode, see
// bug 1251799.
const bool recreateContext = oldStyleContext &&
oldStyleContext->StyleSource().AsServoComputedValues() != computedValues;
RefPtr<nsStyleContext> newContext = nullptr;
if (recreateContext) {
MOZ_ASSERT(styleFrame || displayContentsNode);
+
+ auto pseudo = aElement->GetPseudoElementType();
+ nsIAtom* pseudoTag = pseudo == CSSPseudoElementType::NotPseudo
+ ? nullptr : nsCSSPseudoElements::GetPseudoAtom(pseudo);
+
newContext =
- aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
- CSSPseudoElementType::NotPseudo, aElement);
+ aStyleSet->GetContext(computedValues.forget(),
+ aParentContext,
+ pseudoTag,
+ pseudo,
+ aElement);
newContext->EnsureSameStructsCached(oldStyleContext);
// XXX This could not always work as expected: there are kinds of content
// with the first split and the last sharing style, but others not. We
// should handle those properly.
// XXXbz I think the UpdateStyleOfOwnedAnonBoxes call below handles _that_
// right, but not other cases where we happen to have different styles on
@@ -233,57 +241,16 @@ ServoRestyleManager::ProcessPostTraversa
if (MOZ_UNLIKELY(displayContentsNode)) {
MOZ_ASSERT(!styleFrame);
displayContentsNode->mStyle = newContext;
}
if (styleFrame) {
styleFrame->UpdateStyleOfOwnedAnonBoxes(*aStyleSet, aChangeList, changeHint);
}
-
- // Update pseudo-elements state if appropriate.
- const static CSSPseudoElementType pseudosToRestyle[] = {
- CSSPseudoElementType::before,
- CSSPseudoElementType::after,
- };
-
- for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
- nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
-
- if (nsIFrame* pseudoFrame = FrameForPseudoElement(aElement, pseudoTag)) {
- // TODO: we could maybe make this more performant via calling into
- // Servo just once to know which pseudo-elements we've got to restyle?
- RefPtr<nsStyleContext> pseudoContext =
- aStyleSet->ProbePseudoElementStyle(aElement, pseudoType, newContext);
- MOZ_ASSERT(pseudoContext, "should have taken the ReconstructFrame path above");
- pseudoFrame->SetStyleContext(pseudoContext);
-
- if (pseudoFrame->GetStateBits() & NS_FRAME_OWNS_ANON_BOXES) {
- // XXX It really would be good to pass the actual changehint for our
- // ::before/::after here, but we never computed it!
- pseudoFrame->UpdateStyleOfOwnedAnonBoxes(*aStyleSet, aChangeList,
- nsChangeHint_Hints_NotHandledForDescendants);
- }
-
- // We only care restyling text nodes, since other type of nodes
- // (images), are still not supported. If that eventually changes, we
- // may have to write more code here... Or not, I don't think too
- // many inherited properties can affect those other frames.
- StyleChildrenIterator it(pseudoFrame->GetContent());
- for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
- if (n->IsNodeOfType(nsINode::eTEXT)) {
- RefPtr<nsStyleContext> childContext =
- aStyleSet->ResolveStyleForText(n, pseudoContext);
- MOZ_ASSERT(n->GetPrimaryFrame(),
- "How? This node is created at FC time!");
- n->GetPrimaryFrame()->SetStyleContext(childContext);
- }
- }
- }
- }
}
bool descendantsNeedFrames = aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
bool traverseElementChildren =
aElement->HasDirtyDescendantsForServo() || descendantsNeedFrames;
bool traverseTextChildren = recreateContext || descendantsNeedFrames;
if (traverseElementChildren || traverseTextChildren) {
nsStyleContext* upToDateContext =
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -282,37 +282,45 @@ Gecko_SetOwnerDocumentNeedsStyleFlush(Ra
if (nsIPresShell* shell = aElement->OwnerDoc()->GetShell()) {
shell->SetNeedStyleFlush();
shell->ObserveStyleFlushes();
}
}
nsStyleContext*
-Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull)
+Gecko_GetStyleContext(RawGeckoElementBorrowed aElement,
+ nsIAtom* aPseudoTagOrNull)
{
- MOZ_ASSERT(aNode->IsContent());
nsIFrame* relevantFrame =
- ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
- aPseudoTagOrNull);
+ ServoRestyleManager::FrameForPseudoElement(aElement, aPseudoTagOrNull);
if (relevantFrame) {
return relevantFrame->StyleContext();
}
if (aPseudoTagOrNull) {
return nullptr;
}
// FIXME(emilio): Is there a shorter path?
nsCSSFrameConstructor* fc =
- aNode->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor();
+ aElement->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor();
// NB: This is only called for CalcStyleDifference, and we handle correctly
// the display: none case since Servo still has the older style.
- return fc->GetDisplayContentsStyleFor(aNode->AsContent());
+ return fc->GetDisplayContentsStyleFor(aElement);
+}
+
+nsIAtom*
+Gecko_GetImplementedPseudo(RawGeckoElementBorrowed aElement)
+{
+ CSSPseudoElementType pseudo = aElement->GetPseudoElementType();
+ if (pseudo == CSSPseudoElementType::NotPseudo)
+ return nullptr;
+ return nsCSSPseudoElements::GetPseudoAtom(pseudo);
}
nsChangeHint
Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
ServoComputedValuesBorrowed aComputedValues)
{
MOZ_ASSERT(aOldStyleContext);
MOZ_ASSERT(aComputedValues);
@@ -407,79 +415,90 @@ Gecko_GetExtraContentStyleDeclarations(R
const HTMLTableCellElement* cell = static_cast<const HTMLTableCellElement*>(aElement);
if (nsMappedAttributes* attrs = cell->GetMappedAttributesInheritedFromTable()) {
const RefPtr<RawServoDeclarationBlock>& servo = attrs->GetServoStyle();
return reinterpret_cast<const RawServoDeclarationBlockStrong*>(&servo);
}
return nullptr;
}
+static nsIAtom*
+PseudoTagAndCorrectElementFor(const Element*& aElementOrPseudo) {
+ if (aElementOrPseudo->IsGeneratedContentContainerForBefore()) {
+ aElementOrPseudo = aElementOrPseudo->GetParent()->AsElement();
+ return nsCSSPseudoElements::before;
+ }
+
+ if (aElementOrPseudo->IsGeneratedContentContainerForAfter()) {
+ aElementOrPseudo = aElementOrPseudo->GetParent()->AsElement();
+ return nsCSSPseudoElements::after;
+ }
+
+ return nullptr;
+}
+
bool
Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTag,
EffectCompositor::CascadeLevel aCascadeLevel,
RawServoAnimationValueMapBorrowed aAnimationValues)
{
- MOZ_ASSERT(aElement, "Invalid GeckoElement");
- MOZ_ASSERT(!aPseudoTag ||
- aPseudoTag == nsCSSPseudoElements::before ||
- aPseudoTag == nsCSSPseudoElements::after);
+ MOZ_ASSERT(aElement);
nsIDocument* doc = aElement->GetComposedDoc();
if (!doc || !doc->GetShell()) {
return false;
}
nsPresContext* presContext = doc->GetShell()->GetPresContext();
if (!presContext || !presContext->IsDynamic()) {
// For print or print preview, ignore animations.
return false;
}
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
+
CSSPseudoElementType pseudoType =
nsCSSPseudoElements::GetPseudoType(
- aPseudoTag,
+ pseudoTag,
nsCSSProps::EnabledState::eIgnoreEnabledState);
return presContext->EffectCompositor()
- ->GetServoAnimationRule(aElement, pseudoType,
+ ->GetServoAnimationRule(aElement,
+ pseudoType,
aCascadeLevel,
aAnimationValues);
}
bool
Gecko_StyleAnimationsEquals(RawGeckoStyleAnimationListBorrowed aA,
RawGeckoStyleAnimationListBorrowed aB)
{
return *aA == *aB;
}
void
Gecko_UpdateAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
ServoComputedValuesBorrowedOrNull aOldComputedValues,
ServoComputedValuesBorrowedOrNull aComputedValues,
ServoComputedValuesBorrowedOrNull aParentComputedValues,
UpdateAnimationsTasks aTasks)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aElement);
- MOZ_ASSERT(!aPseudoTagOrNull ||
- aPseudoTagOrNull == nsCSSPseudoElements::before ||
- aPseudoTagOrNull == nsCSSPseudoElements::after);
nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
if (!presContext) {
return;
}
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
if (presContext->IsDynamic() && aElement->IsInComposedDoc()) {
const ServoComputedValuesWithParent servoValues =
{ aComputedValues, aParentComputedValues };
CSSPseudoElementType pseudoType =
- nsCSSPseudoElements::GetPseudoType(aPseudoTagOrNull,
+ nsCSSPseudoElements::GetPseudoType(pseudoTag,
CSSEnabledState::eForAllContent);
if (aTasks & UpdateAnimationsTasks::CSSAnimations) {
presContext->AnimationManager()->
UpdateAnimations(const_cast<dom::Element*>(aElement), pseudoType,
servoValues);
}
@@ -504,99 +523,89 @@ Gecko_UpdateAnimations(RawGeckoElementBo
if (aTasks & UpdateAnimationsTasks::EffectProperties) {
presContext->EffectCompositor()->UpdateEffectProperties(
servoValues, const_cast<dom::Element*>(aElement), pseudoType);
}
}
}
bool
-Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement)
{
- MOZ_ASSERT(!aPseudoTagOrNull ||
- aPseudoTagOrNull == nsCSSPseudoElements::before ||
- aPseudoTagOrNull == nsCSSPseudoElements::after);
-
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
CSSPseudoElementType pseudoType =
- nsCSSPseudoElements::GetPseudoType(aPseudoTagOrNull,
+ nsCSSPseudoElements::GetPseudoType(pseudoTag,
CSSEnabledState::eForAllContent);
return !!EffectSet::GetEffectSet(aElement, pseudoType);
}
bool
-Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement)
{
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
nsAnimationManager::CSSAnimationCollection* collection =
nsAnimationManager::CSSAnimationCollection
- ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+ ::GetAnimationCollection(aElement, pseudoTag);
return collection && !collection->mAnimations.IsEmpty();
}
bool
-Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement)
{
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
nsTransitionManager::CSSTransitionCollection* collection =
nsTransitionManager::CSSTransitionCollection
- ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+ ::GetAnimationCollection(aElement, pseudoTag);
return collection && !collection->mAnimations.IsEmpty();
}
size_t
-Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull)
+Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement)
{
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
nsTransitionManager::CSSTransitionCollection* collection =
nsTransitionManager::CSSTransitionCollection
- ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+ ::GetAnimationCollection(aElement, pseudoTag);
return collection ? collection->mAnimations.Length() : 0;
}
static CSSTransition*
-GetCurrentTransitionAt(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
- size_t aIndex)
+GetCurrentTransitionAt(RawGeckoElementBorrowed aElement, size_t aIndex)
{
+ nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
nsTransitionManager::CSSTransitionCollection* collection =
nsTransitionManager::CSSTransitionCollection
- ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+ ::GetAnimationCollection(aElement, pseudoTag);
if (!collection) {
return nullptr;
}
nsTArray<RefPtr<CSSTransition>>& transitions = collection->mAnimations;
return aIndex < transitions.Length()
? transitions[aIndex].get()
: nullptr;
}
nsCSSPropertyID
Gecko_ElementTransitions_PropertyAt(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
size_t aIndex)
{
- CSSTransition* transition = GetCurrentTransitionAt(aElement,
- aPseudoTagOrNull,
- aIndex);
+ CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
return transition ? transition->TransitionProperty()
: nsCSSPropertyID::eCSSProperty_UNKNOWN;
}
RawServoAnimationValueBorrowedOrNull
Gecko_ElementTransitions_EndValueAt(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
size_t aIndex)
{
CSSTransition* transition = GetCurrentTransitionAt(aElement,
- aPseudoTagOrNull,
aIndex);
return transition ? transition->ToValue().mServo.get() : nullptr;
}
double
Gecko_GetProgressFromComputedTiming(RawGeckoComputedTimingBorrowed aComputedTiming)
{
return aComputedTiming->mProgress.Value();
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -185,43 +185,35 @@ RawServoDeclarationBlockStrongBorrowedOr
Gecko_GetStyleAttrDeclarationBlock(RawGeckoElementBorrowed element);
RawServoDeclarationBlockStrongBorrowedOrNull
Gecko_GetHTMLPresentationAttrDeclarationBlock(RawGeckoElementBorrowed element);
RawServoDeclarationBlockStrongBorrowedOrNull
Gecko_GetExtraContentStyleDeclarations(RawGeckoElementBorrowed element);
// Animations
bool
-Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTag,
+Gecko_GetAnimationRule(RawGeckoElementBorrowed aElementOrPseudo,
mozilla::EffectCompositor::CascadeLevel aCascadeLevel,
RawServoAnimationValueMapBorrowed aAnimationValues);
bool Gecko_StyleAnimationsEquals(RawGeckoStyleAnimationListBorrowed,
RawGeckoStyleAnimationListBorrowed);
-void Gecko_UpdateAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
+void Gecko_UpdateAnimations(RawGeckoElementBorrowed aElementOrPseudo,
ServoComputedValuesBorrowedOrNull aOldComputedValues,
ServoComputedValuesBorrowedOrNull aComputedValues,
ServoComputedValuesBorrowedOrNull aParentComputedValues,
mozilla::UpdateAnimationsTasks aTasks);
-bool Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull);
-bool Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull);
-bool Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull);
-size_t Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull);
+bool Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElementOrPseudo);
+bool Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElementOrPseudo);
+bool Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElementOrPseudo);
+size_t Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElementOrPseudo);
nsCSSPropertyID Gecko_ElementTransitions_PropertyAt(
- RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
+ RawGeckoElementBorrowed aElementOrPseudo,
size_t aIndex);
RawServoAnimationValueBorrowedOrNull Gecko_ElementTransitions_EndValueAt(
- RawGeckoElementBorrowed aElement,
- nsIAtom* aPseudoTagOrNull,
+ RawGeckoElementBorrowed aElementOrPseudo,
size_t aIndex);
double Gecko_GetProgressFromComputedTiming(RawGeckoComputedTimingBorrowed aComputedTiming);
double Gecko_GetPositionInSegment(
RawGeckoAnimationPropertySegmentBorrowed aSegment,
double aProgress,
mozilla::ComputedTimingFunction::BeforeFlag aBeforeFlag);
// Get servo's AnimationValue for |aProperty| from the cached base style
// |aBaseStyles|.
@@ -306,18 +298,19 @@ void Gecko_SetContentDataArray(nsStyleCo
uint32_t Gecko_GetNodeFlags(RawGeckoNodeBorrowed node);
void Gecko_SetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
void Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
void Gecko_SetOwnerDocumentNeedsStyleFlush(RawGeckoElementBorrowed element);
// Incremental restyle.
// Also, we might want a ComputedValues to ComputedValues API for animations?
// Not if we do them in Gecko...
-nsStyleContext* Gecko_GetStyleContext(RawGeckoNodeBorrowed node,
+nsStyleContext* Gecko_GetStyleContext(RawGeckoElementBorrowed element,
nsIAtom* aPseudoTagOrNull);
+nsIAtom* Gecko_GetImplementedPseudo(RawGeckoElementBorrowed element);
nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
ServoComputedValuesBorrowed newstyle);
nsChangeHint Gecko_HintsHandledForDescendants(nsChangeHint aHint);
// Element snapshot.
ServoElementSnapshotOwned Gecko_CreateElementSnapshot(RawGeckoElementBorrowed element);
void Gecko_DropElementSnapshot(ServoElementSnapshotOwned snapshot);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -436,17 +436,16 @@ ServoStyleSet::ResolveTransientStyle(Ele
aPseudoType, nullptr);
}
already_AddRefed<ServoComputedValues>
ServoStyleSet::ResolveTransientServoStyle(Element* aElement,
nsIAtom* aPseudoTag)
{
PreTraverseSync();
-
return ResolveStyleLazily(aElement, aPseudoTag);
}
// aFlags is an nsStyleSet flags bitfield
already_AddRefed<nsStyleContext>
ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
nsStyleContext* aParentContext,
uint32_t aFlags)
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -456,26 +456,26 @@ impl<'le> TElement for ServoLayoutElemen
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
self.element.insert_selector_flags(flags);
}
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
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");
}
}
impl<'le> PartialEq for ServoLayoutElement<'le> {
fn eq(&self, other: &Self) -> bool {
self.as_node() == other.as_node()
}
}
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -15,17 +15,16 @@ use dom::{OpaqueNode, TNode, TElement, S
use error_reporting::ParseErrorReporter;
use euclid::Size2D;
use fnv::FnvHashMap;
use font_metrics::FontMetricsProvider;
#[cfg(feature = "gecko")] use gecko_bindings::structs;
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;
use std::collections::HashMap;
#[cfg(not(feature = "servo"))] use std::env;
use std::fmt;
use std::ops::Add;
use std::sync::{Arc, Mutex};
@@ -265,17 +264,18 @@ impl TraversalStatistics {
/// from lots of tiny traversals.
pub fn is_large_traversal(&self) -> bool {
self.elements_traversed >= 50
}
}
#[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,
/// Update CSS Transitions.
const CSS_TRANSITIONS = structs::UpdateAnimationsTasks_CSSTransitions,
/// Update effect properties.
const EFFECT_PROPERTIES = structs::UpdateAnimationsTasks_EffectProperties,
/// Update animation cacade results for animations running on the compositor.
@@ -291,54 +291,52 @@ pub enum SequentialTask<E: TElement> {
/// Entry to avoid an unused type parameter error on servo.
Unused(SendElement<E>),
/// Performs one of a number of possible tasks related to updating animations based on the
/// |tasks| field. These include updating CSS animations/transitions that changed as part
/// 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>>,
/// The tasks which are performed in this SequentialTask.
tasks: UpdateAnimationsTasks
},
}
impl<E: TElement> SequentialTask<E> {
/// Executes this task.
pub fn execute(self) {
use self::SequentialTask::*;
debug_assert!(thread_state::get() == thread_state::LAYOUT);
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) };
}
}
}
/// Creates a task to update various animation-related state on
/// 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,
+ }
}
}
/// Map from Elements to ElementSelectorFlags. Used to defer applying selector
/// flags until after the traversal.
pub struct SelectorFlagsMap<E: TElement> {
/// The hashmap storing the flags to apply.
map: FnvHashMap<SendElement<E>, ElementSelectorFlags>,
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -527,16 +527,31 @@ impl ElementData {
/// Sets the computed element styles.
pub fn set_styles(&mut self, styles: ElementStyles) {
debug_assert!(self.get_restyle().map_or(true, |r| r.snapshot.is_none()),
"Traversal should have expanded snapshots");
self.styles = Some(styles);
}
+ /// Set the computed element rules, and returns whether the rules changed.
+ pub fn set_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()
}
/// Drops any RestyleData.
pub fn clear_restyle(&mut self) {
self.restyle = None;
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -269,21 +269,30 @@ pub unsafe fn raw_note_descendants<E, B>
/// A trait used to synthesize presentational hints for HTML element attributes.
pub trait PresentationalHintsSynthetizer {
/// Generate the proper applicable declarations due to presentational hints,
/// and insert them into `hints`.
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, hints: &mut V)
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 {
/// The concrete node type.
type ConcreteNode: TNode<ConcreteElement = Self>;
/// Type of the font metrics provider
///
@@ -315,36 +324,35 @@ pub trait TElement : Eq + PartialEq + De
self.parent_element()
}
}
/// Get this element's style attribute.
fn style_attribute(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>>;
/// 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
}
/// Get this element's state, for non-tree-structural pseudos.
fn get_state(&self) -> ElementState;
/// Whether this element has an attribute with a given namespace.
@@ -418,16 +426,28 @@ pub trait TElement : Eq + PartialEq + De
/// Only safe to call with exclusive access to the element.
unsafe fn unset_animation_only_dirty_descendants(&self) {
}
/// Returns true if this element is native anonymous (only Gecko has native
/// 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);
/// Atomically notes that a child has been processed during bottom-up
/// traversal. Returns the number of children left to process.
fn did_process_child(&self) -> isize;
@@ -459,76 +479,75 @@ pub trait TElement : Eq + PartialEq + De
/// set to run after the threads join.
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
/// Returns true if the element has all the specified selector flags.
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
/// 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 {
let data = match self.borrow_data() {
Some(d) => d,
None => return false,
};
return data.get_restyle()
.map_or(false, |r| r.hint.has_animation_hint());
}
/// 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
/// will quickly return false for the common case of no transitions specified or running. If
/// this returns false, we definitely don't need to update transitions but if it returns true
/// we can perform the more thoroughgoing check, needs_transitions_update, to further
/// 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.
/// This method should only be called if might_needs_transitions_update returns true when
/// passed the same parameters.
#[cfg(feature = "gecko")]
fn needs_transitions_update(&self,
- before_change_style: &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;
}
/// Trait abstracting over different kinds of dirty-descendants bits.
pub trait DescendantsBit<E: TElement> {
/// Returns true if the Element has the bit.
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -49,16 +49,36 @@ macro_rules! each_eager_pseudo {
}
}
/// The number of eager pseudo-elements (just ::before and ::after).
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 {
macro_rules! case {
($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return $idx; } }
}
each_eager_pseudo!(case, atom);
panic!("Not eager")
@@ -432,34 +452,19 @@ impl<'a> ::selectors::Parser for Selecto
fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
self.namespaces.prefixes.get(prefix).cloned()
}
}
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
/// `fun` on it.
#[inline]
pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
where F: FnMut(PseudoElement),
{
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -415,22 +415,25 @@ fn selector_flags_to_node_flags(flags: E
if flags.contains(HAS_EMPTY_SELECTOR) {
gecko_flags |= NODE_HAS_EMPTY_SELECTOR as u32;
}
gecko_flags
}
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(
PropertyDeclarationBlock::from_animation_value_map(&animation_values.read()))))
} else {
None
}
}
@@ -520,40 +523,38 @@ impl<'le> TElement for GeckoElement<'le>
unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) }
}
fn style_attribute(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>> {
let declarations = unsafe { Gecko_GetStyleAttrDeclarationBlock(self.0) };
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 {
unsafe {
ElementState::from_bits_truncate(Gecko_ElementState(self.0))
}
}
@@ -578,17 +579,17 @@ impl<'le> TElement for GeckoElement<'le>
}
fn existing_style_for_restyle_damage<'a>(&'a self,
_existing_values: &'a Arc<ComputedValues>,
pseudo: Option<&PseudoElement>)
-> 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()
}
}
fn has_dirty_descendants(&self) -> bool {
self.flags() & (NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
}
@@ -630,16 +631,32 @@ impl<'le> TElement for GeckoElement<'le>
unsafe fn unset_animation_only_dirty_descendants(&self) {
self.unset_flags(NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
}
fn is_native_anonymous(&self) -> bool {
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.
}
fn did_process_child(&self) -> isize {
panic!("Atomic child count not implemented in Gecko");
}
@@ -662,136 +679,125 @@ impl<'le> TElement for GeckoElement<'le>
}
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
let node_flags = selector_flags_to_node_flags(flags);
(self.flags() & node_flags) == node_flags
}
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,
tasks.bits());
}
}
- 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());
map.insert(property, end_value.unwrap().clone());
}
map
}
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 &&
(new_display_style != display::T::none &&
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) {
Some(HashSet::<TransitionProperty>::with_capacity(transitions_count))
} else {
None
};
@@ -852,18 +858,18 @@ impl<'le> TElement for GeckoElement<'le>
transitions_to_keep.map_or(false, |set| {
existing_transitions.keys().any(|property| !set.contains(property))
})
}
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 {
use properties::animated_properties::AnimatedProperty;
// We don't allow transitions on properties that are not interpolable.
if property.is_discrete() {
return false;
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -24,16 +24,25 @@ use rule_tree::{CascadeLevel, RuleTree,
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
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 {
use selectors::matching::*;
!relations.intersects(AFFECTED_BY_ID_SELECTOR |
AFFECTED_BY_PSEUDO_ELEMENTS |
AFFECTED_BY_STYLE_ATTRIBUTE |
AFFECTED_BY_PRESENTATIONAL_HINTS)
@@ -72,16 +81,18 @@ pub struct StyleSharingCandidateCache<E:
cache: LRUCache<StyleSharingCandidate<E>>,
}
/// A cache miss result.
#[derive(Clone, Debug)]
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.
Namespace,
/// One of the element or the candidate was a link, but the other one
/// wasn't.
Link,
/// The element and the candidate match different kind of rules. This can
@@ -131,16 +142,22 @@ fn element_matches_candidate<E: TElement
// for parent computed style. The latter check allows us to share style
// between cousins if the parents shared style.
let parent = element.parent_element();
let candidate_parent = candidate_element.parent_element();
if parent != candidate_parent && !same_computed_values(parent, candidate_parent) {
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)
}
if *element.get_namespace() != *candidate_element.get_namespace() {
miss!(Namespace)
}
@@ -292,31 +309,35 @@ impl<E: TElement> StyleSharingCandidateC
let parent = match element.parent_element() {
Some(element) => element,
None => {
debug!("Failing to insert to the cache: no parent element");
return;
}
};
+ 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) {
debug!("Failing to insert to the cache: {:?}", relations);
return;
}
// Make sure we noted any presentational hints in the StyleRelations.
if cfg!(debug_assertions) {
let mut hints = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut hints);
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");
return;
}
if box_style.specifies_animations() {
debug!("Failing to insert to the cache: animations");
@@ -381,45 +402,48 @@ trait PrivateMatchMethods: TElement {
}
}
fn cascade_with_rules(&self,
shared_context: &SharedStyleContext,
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();
if self.skip_root_and_item_based_display_fixup() {
cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP)
}
// 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();
let layout_parent_data;
let mut layout_parent_style = style_to_inherit_from;
if style_to_inherit_from.map_or(false, |s| s.is_display_contents()) {
layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
@@ -427,25 +451,26 @@ trait PrivateMatchMethods: TElement {
}
let style_to_inherit_from = style_to_inherit_from.map(|x| &**x);
let layout_parent_style = layout_parent_style.map(|x| &**x);
// 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.
let values =
Arc::new(cascade(&shared_context.stylist.device,
rule_node,
&shared_context.guards,
style_to_inherit_from,
@@ -457,35 +482,41 @@ trait PrivateMatchMethods: TElement {
cascade_info.finish(&self.as_node());
values
}
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,
/// setting them on the ElementData.
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;
let pseudos = &mut styles.pseudos;
let mut pseudo_style = match pseudo {
Some(p) => {
let style = pseudos.get_mut(p);
debug_assert!(style.is_some());
@@ -495,81 +526,117 @@ trait PrivateMatchMethods: TElement {
};
let mut old_values = match pseudo_style {
Some(ref mut s) => s.values.take(),
None => primary_style.values.take(),
};
// 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.
let mut relevant_style = pseudo_style.unwrap_or(primary_style);
relevant_style.values = Some(new_values);
}
/// get_after_change_style removes the transition rules from the ComputedValues.
/// If there is no transition rule in the ComputedValues, it returns None.
#[cfg(feature = "gecko")]
fn get_after_change_style(&self,
context: &mut StyleContext<Self>,
- primary_style: &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 {
// We don't have transition rule in this case, so return None to let the caller
// use the original ComputedValues.
return None;
}
Some(self.cascade_with_rules(context.shared,
&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.len() != 0;
- 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.
!old_box_style.animations_equals(&new_box_style) ||
(old_display_style == display::T::none &&
new_display_style != display::T::none &&
has_new_animation_style) ||
(old_display_style != display::T::none &&
@@ -578,50 +645,44 @@ trait PrivateMatchMethods: TElement {
})
}
#[cfg(feature = "gecko")]
fn process_animations(&self,
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 {
if let Some(values_without_transitions) = after_change_style {
*new_values = values_without_transitions;
}
tasks.insert(CSS_TRANSITIONS);
@@ -629,39 +690,34 @@ trait PrivateMatchMethods: TElement {
old_values.clone()
} else {
None
}
} else {
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);
}
}
#[cfg(feature = "servo")]
fn process_animations(&self,
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;
let shared_context = context.shared;
if let Some(ref mut old) = *old_values {
self.update_animations_for_cascade(shared_context, old,
possibly_expired_animations,
&context.thread_local.font_metrics_provider);
@@ -684,16 +740,20 @@ trait PrivateMatchMethods: TElement {
&**values,
new_values,
&shared_context.timer,
&possibly_expired_animations);
}
}
/// 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,
restyle: &mut RestyleData,
old_values: &Arc<ComputedValues>,
new_values: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>) {
// Don't accumulate damage if we're in a restyle for reconstruction.
@@ -710,17 +770,19 @@ trait PrivateMatchMethods: TElement {
// 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
// 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;
}
}
}
/// Computes and applies restyle damage unless we've already maxed it out.
#[cfg(feature = "servo")]
@@ -807,17 +869,21 @@ pub enum StyleSharingBehavior {
/// Style sharing allowed.
Allow,
/// Style sharing disallowed.
Disallow,
}
/// 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.
+ ///
+ /// Returns whether the traversal should stop immediately, because a
+ /// previous ::before or ::after pseudo-element has stopped matching.
fn match_and_cascade(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
sharing: StyleSharingBehavior)
{
// Perform selector matching for the primary style.
let mut primary_relations = StyleRelations::empty();
let _rule_node_changed = self.match_primary(context, data, &mut primary_relations);
@@ -872,63 +938,101 @@ pub trait MatchMethods : TElement {
///
/// Returns whether the primary rule node changed.
fn match_primary(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
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_rules(rules);
+ }
+ }
+
let mut applicable_declarations =
Vec::<ApplicableDeclarationBlock>::with_capacity(16);
let stylist = &context.shared.stylist;
let style_attribute = self.style_attribute();
- 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;
let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
self.apply_selector_flags(map, element, flags);
};
// Compute the primary rule node.
*relations = stylist.push_applicable_declarations(self,
Some(bloom),
style_attribute,
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_rules(primary_rule_node);
}
/// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
/// element.
///
/// Returns whether any of the pseudo rule nodes changed (including, but not
/// limited to, cases where we match different pseudos altogether).
fn match_pseudos(&self,
context: &mut StyleContext<Self>,
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| {
self.apply_selector_flags(map, element, flags);
};
@@ -940,24 +1044,22 @@ pub trait MatchMethods : TElement {
let rule_tree = &stylist.rule_tree;
let bloom_filter = context.thread_local.bloom_filter.filter();
// Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = 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, pseudo_animation_rules,
+ None,
+ AnimationRules(None, None),
Some(&pseudo),
&guards,
&mut applicable_declarations,
&mut set_selector_flags);
if !applicable_declarations.is_empty() {
let new_rules =
compute_rule_node::<Self>(rule_tree, &mut applicable_declarations);
@@ -1069,61 +1171,46 @@ pub trait MatchMethods : TElement {
};
// RESTYLE_CSS_ANIMATIONS or RESTYLE_CSS_TRANSITIONS is processed prior to other
// restyle hints in the name of animation-only traversal. Rest of 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());
- 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();
replace_rule_node(CascadeLevel::StyleAttributeNormal,
style_attribute,
primary_rules);
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
}
/// Attempts to share a style with another node. This method is unsafe
/// because it depends on the `style_sharing_candidate_cache` having only
/// live nodes in it, and we have no way to guarantee that at the type
/// system level yet.
unsafe fn share_style_if_possible(&self,
@@ -1135,16 +1222,21 @@ pub trait MatchMethods : TElement {
return StyleSharingResult::CannotShare
}
if self.parent_element().is_none() {
debug!("{:?} Cannot share style: element has style attribute", self);
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
}
if self.has_attr(&ns!(), &local_name!("id")) {
debug!("{:?} Cannot share style: element has id", self);
return StyleSharingResult::CannotShare
@@ -1294,34 +1386,32 @@ pub trait MatchMethods : TElement {
}
}
/// Performs the cascade for the element's primary style.
fn cascade_primary(&self,
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.
fn cascade_pseudos(&self,
context: &mut StyleContext<Self>,
mut data: &mut ElementData)
{
// Note that we've already set up the map of matching pseudo-elements
// in match_pseudos (and handled the damage implications of changing
// which pseudos match), so now we can just iterate what we have. This
// does mean collecting owned pseudos, so that the borrow checker will
// 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));
}
}
/// Returns computed values without animation and transition rules.
fn get_base_style(&self,
shared_context: &SharedStyleContext,
font_metrics_provider: &FontMetricsProvider,
primary_style: &ComputedStyle,
@@ -1336,14 +1426,14 @@ pub trait MatchMethods : TElement {
// only incomplete during the styling process.
return relevant_style.values.as_ref().unwrap().clone();
}
self.cascade_with_rules(shared_context,
font_metrics_provider,
&without_animation_rules,
primary_style,
- pseudo_style.is_some())
+ InheritMode::FromParentElement)
}
}
impl<E: TElement> MatchMethods for E {}
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -12,17 +12,17 @@ use element_state::*;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::nsRestyleHint;
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
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;
bitflags! {
/// When the ElementState of an element (like IN_HOVER_STATE) changes,
/// certain pseudo-classes (like :hover) may require us to restyle that
/// element, its siblings, and/or its descendants. Similarly, when various
@@ -109,18 +109,17 @@ impl RestyleHint {
pub fn for_animations() -> Self {
RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS
}
}
#[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() {
warn!("stylo: dropping unsupported restyle hint bits");
}
let mut bits = Self::from_bits_truncate(raw_bits);
@@ -561,32 +560,44 @@ impl DependencySet {
} else {
self.state_deps.push(dep)
}
}
/// 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;
while let Some(current) = next.take() {
// Set up our visitor.
let mut visitor = SensitivitiesVisitor {
sensitivities: Sensitivities::new(),
hint: combinator_to_restyle_hint(combinator),
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();
let mut index = 0usize;
for ss in &mut iter {
ss.visit(&mut visitor);
index += 1;
}
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -328,17 +328,17 @@ impl Stylist {
locked.clone(),
self.rules_source_order,
selector.specificity));
}
self.rules_source_order += 1;
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());
}
}
}
CssRule::Import(ref import) => {
let import = import.read_with(guard);
self.add_stylesheet(&import.stylesheet, guard, extra_data)
@@ -639,17 +639,19 @@ impl Stylist {
-> StyleRelations
where E: TElement +
fmt::Debug +
PresentationalHintsSynthetizer,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(!self.is_device_dirty);
- debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
+ // Have you heard about ::-moz-color-swatch? Now you have.
+ 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()));
let map = match pseudo_element {
Some(ref pseudo) => self.pseudos_map.get(pseudo).unwrap(),
None => &self.element_map,
};
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -264,24 +264,30 @@ pub trait DomTraversal<E: TElement> : Sy
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.
+ // 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.
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;
+ if el.implemented_pseudo_element().map_or(true, |p| !p.is_before_or_after()) {
+ 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;
+ }
}
}
}
}
// 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.
@@ -391,24 +397,28 @@ pub trait DomTraversal<E: TElement> : Sy
// an Element is changed.
if cfg!(feature = "gecko") && thread_local.is_initial_style() &&
parent_data.styles().primary.values().has_moz_binding() {
if log.allow() { debug!("Parent {:?} has XBL binding, deferring traversal", parent); }
return false;
}
return true;
-
}
/// Helper for the traversal implementations to select the children that
/// should be enqueued for processing.
fn traverse_children<F>(&self, thread_local: &mut Self::ThreadLocalContext, parent: E, mut f: F)
where F: FnMut(&mut Self::ThreadLocalContext, E::ConcreteNode)
{
+ // We bailed out early, so nothing else to do in this subtree.
+ if thread_local.borrow_mut().current_element_info.is_none() {
+ return;
+ }
+
// Check if we're allowed to traverse past this element.
let should_traverse =
self.should_traverse_children(thread_local.borrow_mut(), parent,
&parent.borrow_data().unwrap(), MayLog);
thread_local.borrow_mut().end_element(parent);
if !should_traverse {
return;
}
@@ -484,17 +494,19 @@ fn resolve_style_internal<E, F>(context:
context.thread_local.bloom_filter.rebuild(element);
} else {
context.thread_local.bloom_filter.push(parent.unwrap());
context.thread_local.bloom_filter.assert_complete(element);
}
// 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
// be other unstyled siblings we miss when walking straight up the parent
// chain.
unsafe { element.note_descendants::<DirtyDescendants>() };
}
@@ -556,16 +568,42 @@ pub fn resolve_style<E, F, G, H>(context
pub fn recalc_style_at<E, D>(traversal: &D,
traversal_data: &PerLevelTraversalData,
context: &mut StyleContext<E>,
element: E,
mut data: &mut AtomicRefMut<ElementData>)
where E: TElement,
D: DomTraversal<E>
{
+ if let Some(pseudo) = element.implemented_pseudo_element() {
+ // We have this special-case for the case a parent is reframed and
+ // we're a ::before or ::after pseudo.
+ //
+ // 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, by effectively doing no work.
+ //
+ // This means that we effectively do nothing in this case, but
+ // that's fine, because we're going away anyway.
+ //
+ // Note that this is inherent to the traversal, not to the one-off
+ // restyles for getComputedStyle, so we should be good there.
+ if pseudo.is_before_or_after() {
+ let parent = element.parent_element().unwrap();
+ let parent_data = parent.borrow_data().unwrap();
+ if parent_data.styles().pseudos.get(&pseudo).is_none() {
+ debug_assert!(data.restyle().damage_handled.contains(RestyleDamage::reconstruct()));
+ return;
+ }
+ }
+ }
+
context.thread_local.begin_element(element, &data);
context.thread_local.statistics.elements_traversed += 1;
debug_assert!(data.get_restyle().map_or(true, |r| {
r.snapshot.is_none() && !r.has_sibling_invalidations()
}), "Should've computed the final hint and handled later_siblings already");
let compute_self = !data.has_current_styles();
let mut inherited_style_changed = false;
@@ -599,19 +637,23 @@ pub fn recalc_style_at<E, D>(traversal:
"animation restyle hint should be handled during \
animation-only restyles");
r.recascade = false;
r.hint.propagate(&context.shared.traversal_flags)
},
};
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() {
element.has_animation_only_dirty_descendants()
} else {
element.has_dirty_descendants()
};
@@ -656,17 +698,18 @@ pub fn recalc_style_at<E, D>(traversal:
// check display:none on the parent when inserting new children (which
// can be moderately expensive). Instead, DOM implementations can
// unconditionally set the dirty descendants bit on any styled parent,
// and let the traversal sort it out.
//
// 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(); }
}
}
fn compute_style<E, D>(_traversal: &D,
traversal_data: &PerLevelTraversalData,
context: &mut StyleContext<E>,
element: E,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -18,17 +18,17 @@ use style::context::{ThreadLocalStyleCon
use style::data::{ElementData, ElementStyles, RestyleData};
use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants};
use style::dom::{ShowSubtreeData, TElement, TNode};
use style::error_reporting::RustLogReporter;
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;
use style::gecko_bindings::bindings::{RawGeckoKeyframeListBorrowed, RawGeckoKeyframeListBorrowedMut};
use style::gecko_bindings::bindings::{RawServoDeclarationBlockBorrowed, RawServoDeclarationBlockStrong};
use style::gecko_bindings::bindings::{RawServoMediaList, RawServoMediaListBorrowed, RawServoMediaListStrong};
use style::gecko_bindings::bindings::{RawServoMediaRule, RawServoMediaRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoNamespaceRule, RawServoNamespaceRuleBorrowed};
@@ -161,17 +161,18 @@ fn create_shared_context<'a>(global_styl
local_context_creation_data: Mutex::new(local_context_data),
timer: Timer::new(),
// FIXME Find the real QuirksMode information for this document
quirks_mode: QuirksMode::NoQuirks,
traversal_flags: traversal_flags,
}
}
-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.
if let Some(parent) = element.parent_element() {
if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) {
debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent);
return;
}
@@ -930,22 +931,25 @@ pub extern "C" fn Servo_ResolvePseudoSty
let guard = global_style_data.shared_lock.read();
match get_pseudo_style(&guard, element, pseudo_tag, data.styles(), doc_data) {
Some(values) => values.into_strong(),
None if !is_probe => data.styles().primary.values().clone().into_strong(),
None => Strong::null(),
}
}
-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 => {
let d = doc_data.borrow_mut();
let base = styles.primary.values();
let guards = StylesheetGuards::same(guard);
let metrics = get_metrics_provider_for_product();
d.stylist.lazily_compute_pseudo_element_style(&guards,