From 7614fdb51b177e6975fce5bf9a7facef170e61aa Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 17 Apr 2020 05:10:25 -0400 Subject: Bug 1355351 - Make pseudo-elements return the correct style via getComputedStyle * Add a node property to access the ::before and ::after pseudo-elements * Look for the frame for ::before and ::after pseudos * Clean up pseudo-element props * Simplify nsLayoutUtils callers, and make child iterators notice display: contents pseudos Tag #1375 --- layout/base/RestyleManager.cpp | 4 +- layout/base/ServoRestyleManager.cpp | 13 ++--- layout/base/nsCSSFrameConstructor.cpp | 61 +++++++++++----------- layout/base/nsLayoutUtils.cpp | 96 ++++++++--------------------------- layout/base/nsLayoutUtils.h | 44 ++++------------ layout/generic/nsFrame.cpp | 15 +++--- layout/generic/nsIFrame.h | 4 -- layout/style/nsComputedDOMStyle.cpp | 47 +++++++++++++++-- 8 files changed, 117 insertions(+), 167 deletions(-) (limited to 'layout') diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 4d08dee06..8db3d9302 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -3626,14 +3626,14 @@ ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType, // Check for a ::before pseudo style and the absence of a ::before content, // but only if aFrame is null or is the first continuation/ib-split. if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) || - nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) { + nsLayoutUtils::GetBeforeFrame(aContent)) { return false; } } else { // Similarly for ::after, but check for being the last continuation/ // ib-split. if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) || - nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) { + nsLayoutUtils::GetAfterFrame(aContent)) { return false; } } diff --git a/layout/base/ServoRestyleManager.cpp b/layout/base/ServoRestyleManager.cpp index 9624c678b..0659ab857 100644 --- a/layout/base/ServoRestyleManager.cpp +++ b/layout/base/ServoRestyleManager.cpp @@ -277,24 +277,17 @@ ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent, nsIAtom* aPseudoTagOrNull) { MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement()); - nsIFrame* primaryFrame = aContent->GetPrimaryFrame(); if (!aPseudoTagOrNull) { - return primaryFrame; - } - - if (!primaryFrame) { - return nullptr; + return aContent->GetPrimaryFrame(); } - // NOTE: we probably need to special-case display: contents here. Gecko's - // RestyleManager passes the primary frame of the parent instead. if (aPseudoTagOrNull == nsCSSPseudoElements::before) { - return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent); + return nsLayoutUtils::GetBeforeFrame(aContent); } if (aPseudoTagOrNull == nsCSSPseudoElements::after) { - return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent); + return nsLayoutUtils::GetAfterFrame(aContent); } MOZ_CRASH("Unkown pseudo-element given to " diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index b9778d36d..866fa148b 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -1832,11 +1832,7 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat aPseudoElement == CSSPseudoElementType::after, "unexpected aPseudoElement"); - // XXXbz is this ever true? - if (!aParentContent->IsElement()) { - NS_ERROR("Bogus generated content parent"); - return; - } + MOZ_ASSERT(aParentContent->IsElement()); StyleSetHandle styleSet = mPresShell->StyleSet(); @@ -1864,6 +1860,13 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget()); if (NS_FAILED(rv)) return; + + // Cleared when the pseudo is unbound from the tree, so no need to store a + // strong reference, nor a destructor. + nsIAtom* property = isBefore + ? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty; + aParentContent->SetProperty(property, container.get()); + container->SetIsNativeAnonymousRoot(); container->SetPseudoElementType(aPseudoElement); @@ -6079,6 +6082,9 @@ AddGenConPseudoToFrame(nsIFrame* aOwnerFrame, nsIContent* aContent) NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame), "property should only be set on first continuation/ib-sibling"); + // FIXME(emilio): Remove this property, and use the frame of the generated + // content itself to tear the content down? It should be quite simpler. + nsIFrame::ContentArray* value = aOwnerFrame->GetProperty(nsIFrame::GenConProperty()); if (!value) { @@ -6340,7 +6346,7 @@ AdjustAppendParentForAfterContent(nsFrameManager* aFrameManager, // frames to find the first one that is either a ::after frame for an // ancestor of aChild or a frame that is for a node later in the // document than aChild and return that in aAfterFrame. - if (aParentFrame->GetGenConPseudos() || + if (aParentFrame->GetProperty(nsIFrame::GenConProperty()) || nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(), CSSPseudoElementType::after, aParentFrame->PresContext()) || @@ -6669,9 +6675,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent, nsIFrame* sibling = aContent->GetPrimaryFrame(); if (!sibling && GetDisplayContentsStyleFor(aContent)) { // A display:contents node - check if it has a ::before / ::after frame... - sibling = aPrevSibling ? - nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent) : - nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent); + sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent) + : nsLayoutUtils::GetBeforeFrame(aContent); if (!sibling) { // ... then recurse into children ... const bool forward = !aPrevSibling; @@ -6682,9 +6687,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent, } if (!sibling) { // ... then ::after / ::before on the opposite end. - sibling = aPrevSibling ? - nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent) : - nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent); + sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent) + : nsLayoutUtils::GetBeforeFrame(aContent); } if (!sibling) { return nullptr; @@ -8291,26 +8295,23 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer, MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild), "display:contents nodes shouldn't have a frame"); if (!childFrame && GetDisplayContentsStyleFor(aChild)) { - nsIFrame* ancestorFrame = nullptr; nsIContent* ancestor = aContainer; - for (; ancestor; ancestor = ancestor->GetParent()) { - ancestorFrame = ancestor->GetPrimaryFrame(); - if (ancestorFrame) { - break; - } + while (!ancestor->GetPrimaryFrame()) { + // FIXME(emilio): Should this use the flattened tree parent instead? + ancestor = ancestor->GetParent(); + MOZ_ASSERT(ancestor, "we can't have a display: contents subtree root!"); } - if (ancestorFrame) { - nsIFrame* contentInsertion = ancestorFrame->GetContentInsertionFrame(); - if (ancestorFrame->GetGenConPseudos() || - (contentInsertion && contentInsertion->GetGenConPseudos())) { - *aDidReconstruct = true; - LAYOUT_PHASE_TEMP_EXIT(); - // XXXmats Can we recreate frames only for the ::after/::before content? - // XXX Perhaps even only those that belong to the aChild sub-tree? - RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor); - LAYOUT_PHASE_TEMP_REENTER(); - return; - } + + nsIFrame* ancestorFrame = ancestor->GetPrimaryFrame(); + if (ancestorFrame->GetProperty(nsIFrame::GenConProperty())) { + *aDidReconstruct = true; + LAYOUT_PHASE_TEMP_EXIT(); + + // XXXmats Can we recreate frames only for the ::after/::before content? + // XXX Perhaps even only those that belong to the aChild sub-tree? + RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor); + LAYOUT_PHASE_TEMP_REENTER(); + return; } FlattenedChildIterator iter(aChild); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 21d20c69f..de3482246 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1486,90 +1486,38 @@ nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) return id; } -/*static*/ nsIFrame* -nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame, - const nsIContent* aContent) -{ - // We need to call GetGenConPseudos() on the first continuation/ib-split. - // Find it, for symmetry with GetAfterFrameForContent. - nsContainerFrame* genConParentFrame = - FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame(); - if (!genConParentFrame) { - return nullptr; - } - nsTArray* prop = genConParentFrame->GetGenConPseudos(); - if (prop) { - const nsTArray& pseudos(*prop); - for (uint32_t i = 0; i < pseudos.Length(); ++i) { - if (pseudos[i]->GetParent() == aContent && - pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) { - return pseudos[i]->GetPrimaryFrame(); - } - } - } - // If the first child frame is a pseudo-frame, then try that. - // Note that the frame we create for the generated content is also a - // pseudo-frame and so don't drill down in that case. - nsIFrame* childFrame = genConParentFrame->PrincipalChildList().FirstChild(); - if (childFrame && - childFrame->IsPseudoFrame(aContent) && - !childFrame->IsGeneratedContentFrame()) { - return GetBeforeFrameForContent(childFrame, aContent); - } - return nullptr; +static Element* +GetPseudo(const nsIContent* aContent, nsIAtom* aPseudoProperty) +{ + MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty || + aPseudoProperty == nsGkAtoms::afterPseudoProperty); + return static_cast(aContent->GetProperty(aPseudoProperty)); } -/*static*/ nsIFrame* -nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame) +/*static*/ Element* +nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) { - return GetBeforeFrameForContent(aFrame, aFrame->GetContent()); + return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty); } /*static*/ nsIFrame* -nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame, - const nsIContent* aContent) -{ - // We need to call GetGenConPseudos() on the first continuation, - // but callers are likely to pass the last. - nsContainerFrame* genConParentFrame = - FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame(); - if (!genConParentFrame) { - return nullptr; - } - nsTArray* prop = genConParentFrame->GetGenConPseudos(); - if (prop) { - const nsTArray& pseudos(*prop); - for (uint32_t i = 0; i < pseudos.Length(); ++i) { - if (pseudos[i]->GetParent() == aContent && - pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) { - return pseudos[i]->GetPrimaryFrame(); - } - } - } - // If the last child frame is a pseudo-frame, then try that. - // Note that the frame we create for the generated content is also a - // pseudo-frame and so don't drill down in that case. - genConParentFrame = aFrame->GetContentInsertionFrame(); - if (!genConParentFrame) { - return nullptr; - } - nsIFrame* lastParentContinuation = - LastContinuationWithChild(static_cast( - LastContinuationOrIBSplitSibling(genConParentFrame))); - nsIFrame* childFrame = - lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild(); - if (childFrame && - childFrame->IsPseudoFrame(aContent) && - !childFrame->IsGeneratedContentFrame()) { - return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent); - } - return nullptr; +nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) +{ + Element* pseudo = GetBeforePseudo(aContent); + return pseudo ? pseudo->GetPrimaryFrame() : nullptr; +} + +/*static*/ Element* +nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) +{ + return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty); } /*static*/ nsIFrame* -nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) +nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) { - return GetAfterFrameForContent(aFrame, aFrame->GetContent()); + Element* pseudo = GetAfterPseudo(aContent); + return pseudo ? pseudo->GetPrimaryFrame() : nullptr; } // static diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index bba1f3265..9757cb799 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -276,50 +276,26 @@ public: static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame); /** - * GetBeforeFrameForContent returns the ::before frame for aContent, if - * one exists. This is typically O(1). The frame passed in must be - * the first-in-flow. - * - * @param aGenConParentFrame an ancestor of the ::before frame - * @param aContent the content whose ::before is wanted - * @return the ::before frame or nullptr if there isn't one + * Returns the ::before pseudo-element for aContent, if any. */ - static nsIFrame* GetBeforeFrameForContent(nsIFrame* aGenConParentFrame, - const nsIContent* aContent); + static mozilla::dom::Element* GetBeforePseudo(const nsIContent* aContent); /** - * GetBeforeFrame returns the outermost ::before frame of the given frame, if - * one exists. This is typically O(1). The frame passed in must be - * the first-in-flow. - * - * @param aFrame the frame whose ::before is wanted - * @return the :before frame or nullptr if there isn't one + * Returns the frame corresponding to the ::before pseudo-element for + * aContent, if any. */ - static nsIFrame* GetBeforeFrame(nsIFrame* aFrame); + static nsIFrame* GetBeforeFrame(const nsIContent* aContent); /** - * GetAfterFrameForContent returns the ::after frame for aContent, if one - * exists. This will walk the in-flow chain of aGenConParentFrame to the - * last-in-flow if needed. This function is typically O(N) in the number - * of child frames, following in-flows, etc. - * - * @param aGenConParentFrame an ancestor of the ::after frame - * @param aContent the content whose ::after is wanted - * @return the ::after frame or nullptr if there isn't one + * Returns the ::after pseudo-element for aContent, if any. */ - static nsIFrame* GetAfterFrameForContent(nsIFrame* aGenConParentFrame, - const nsIContent* aContent); + static mozilla::dom::Element* GetAfterPseudo(const nsIContent* aContent); /** - * GetAfterFrame returns the outermost ::after frame of the given frame, if one - * exists. This will walk the in-flow chain to the last-in-flow if - * needed. This function is typically O(N) in the number of child - * frames, following in-flows, etc. - * - * @param aFrame the frame whose ::after is wanted - * @return the :after frame or nullptr if there isn't one + * Returns the frame corresponding to the ::after pseudo-element for aContent, + * if any. */ - static nsIFrame* GetAfterFrame(nsIFrame* aFrame); + static nsIFrame* GetAfterFrame(const nsIContent* aContent); /** * Given a frame, search up the frame tree until we find an diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 71f6172bd..25b685fb2 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -10008,19 +10008,16 @@ nsIFrame::IsPseudoStackingContextFromStyle() { Element* nsIFrame::GetPseudoElement(CSSPseudoElementType aType) { - nsIFrame* frame = nullptr; + if (!mContent) { + return nullptr; + } if (aType == CSSPseudoElementType::before) { - frame = nsLayoutUtils::GetBeforeFrame(this); - } else if (aType == CSSPseudoElementType::after) { - frame = nsLayoutUtils::GetAfterFrame(this); + return nsLayoutUtils::GetBeforePseudo(mContent); } - if (frame) { - nsIContent* content = frame->GetContent(); - if (content->IsElement()) { - return content->AsElement(); - } + if (aType == CSSPseudoElementType::after) { + return nsLayoutUtils::GetAfterPseudo(mContent); } return nullptr; diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 3137aaee2..9372b15a2 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -1062,10 +1062,6 @@ public: return GetBidiData().embeddingLevel; } - nsTArray* GetGenConPseudos() { - return GetProperty(GenConProperty()); - } - /** * Return the distance between the border edge of the frame and the * margin edge of the frame. Like GetRect(), returns the dimensions diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 102e0df18..20e5651bd 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -638,6 +638,34 @@ nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext) mStyleContext = aContext; } +/** + * The following function checks whether we need to explicitly resolve the style + * again, even though we have a style context coming from the frame. + * + * This basically checks whether the style is or may be under a ::first-line or + * ::first-letter frame, in which case we can't return the frame style, and we + * need to resolve it. See bug 505515. + */ +static bool +MustReresolveStyle(const nsStyleContext* aContext) +{ + MOZ_ASSERT(aContext); + + if (aContext->HasPseudoElementData()) { + if (!aContext->GetPseudo() || + aContext->StyleSource().IsServoComputedValues()) { + // TODO(emilio): When ::first-line is supported in Servo, we may want to + // fix this to avoid re-resolving pseudo-element styles. + return true; + } + + return aContext->GetParent() && + aContext->GetParent()->HasPseudoElementData(); + } + + return false; +} + void nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) { @@ -690,9 +718,20 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) // XXX the !mElement->IsHTMLElement(nsGkAtoms::area) // check is needed due to bug 135040 (to avoid using // mPrimaryFrame). Remove it once that's fixed. - if (!mPseudo && mStyleType == eAll && - !mElement->IsHTMLElement(nsGkAtoms::area)) { - mOuterFrame = mElement->GetPrimaryFrame(); + if (mStyleType == eAll && !mElement->IsHTMLElement(nsGkAtoms::area)) { + mOuterFrame = nullptr; + + if (!mPseudo) { + mOuterFrame = mElement->GetPrimaryFrame(); + } else if (mPseudo == nsCSSPseudoElements::before || + mPseudo == nsCSSPseudoElements::after) { + nsIAtom* property = mPseudo == nsCSSPseudoElements::before + ? nsGkAtoms::beforePseudoProperty + : nsGkAtoms::afterPseudoProperty; + + auto* pseudo = static_cast(mElement->GetProperty(property)); + mOuterFrame = pseudo ? pseudo->GetPrimaryFrame() : nullptr; + } mInnerFrame = mOuterFrame; if (mOuterFrame) { nsIAtom* type = mOuterFrame->GetType(); @@ -711,7 +750,7 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) } } - if (!mStyleContext || mStyleContext->HasPseudoElementData()) { + if (!mStyleContext || MustReresolveStyle(mStyleContext)) { #ifdef DEBUG if (mStyleContext) { // We want to check that going through this path because of -- cgit v1.2.3