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
This commit is contained in:
Matt A. Tobin 2020-04-17 05:10:25 -04:00 • committed by Roy Tam
commit d3ffda520f
16 changed files with 188 additions and 241 deletions

View file

@ -33,7 +33,7 @@ function* testTopLeft(inspector, view) {
let afterElement = children.nodes[children.nodes.length - 1]; let afterElement = children.nodes[children.nodes.length - 1];
yield selectNode(afterElement, inspector); yield selectNode(afterElement, inspector);
top = getComputedViewPropertyValue(view, "top"); top = getComputedViewPropertyValue(view, "top");
is(top, "50%", "The computed view shows the correct top"); is(top, "96px", "The computed view shows the correct top");
left = getComputedViewPropertyValue(view, "left"); left = getComputedViewPropertyValue(view, "left");
is(left, "50%", "The computed view shows the correct left"); is(left, "96px", "The computed view shows the correct left");
} }

View file

@ -356,24 +356,16 @@ EffectCompositor::GetElementToRestyle(dom::Element* aElement,
return aElement; return aElement;
} }
nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
if (!primaryFrame) {
return nullptr;
}
nsIFrame* pseudoFrame;
if (aPseudoType == CSSPseudoElementType::before) { if (aPseudoType == CSSPseudoElementType::before) {
pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame); return nsLayoutUtils::GetBeforePseudo(aElement);
} else if (aPseudoType == CSSPseudoElementType::after) { }
pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame); if (aPseudoType == CSSPseudoElementType::after) {
} else { return nsLayoutUtils::GetAfterPseudo(aElement);
}
NS_NOTREACHED("Should not try to get the element to restyle for a pseudo " NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
"other that :before or :after"); "other that :before or :after");
return nullptr; return nullptr;
}
if (!pseudoFrame) {
return nullptr;
}
return pseudoFrame->GetContent()->AsElement();
} }
bool bool

View file

@ -1105,19 +1105,17 @@ KeyframeEffectReadOnly::GetAnimationFrame() const
return nullptr; return nullptr;
} }
nsIFrame* frame = mTarget->mElement->GetPrimaryFrame(); nsIFrame* frame;
if (!frame) {
return nullptr;
}
if (mTarget->mPseudoType == CSSPseudoElementType::before) { if (mTarget->mPseudoType == CSSPseudoElementType::before) {
frame = nsLayoutUtils::GetBeforeFrame(frame); frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement);
} else if (mTarget->mPseudoType == CSSPseudoElementType::after) { } else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
frame = nsLayoutUtils::GetAfterFrame(frame); frame = nsLayoutUtils::GetAfterFrame(mTarget->mElement);
} else { } else {
frame = mTarget->mElement->GetPrimaryFrame();
MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo, MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
"unknown mTarget->mPseudoType"); "unknown mTarget->mPseudoType");
} }
if (!frame) { if (!frame) {
return nullptr; return nullptr;
} }

View file

@ -317,11 +317,9 @@ AllChildrenIterator::Get() const
{ {
switch (mPhase) { switch (mPhase) {
case eAtBeforeKid: { case eAtBeforeKid: {
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* before = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
MOZ_ASSERT(frame, "No frame at eAtBeforeKid phase"); MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame); return before;
MOZ_ASSERT(beforeFrame, "No content before frame at eAtBeforeKid phase");
return beforeFrame->GetContent();
} }
case eAtExplicitKids: case eAtExplicitKids:
@ -331,11 +329,9 @@ AllChildrenIterator::Get() const
return mAnonKids[mAnonKidsIdx]; return mAnonKids[mAnonKidsIdx];
case eAtAfterKid: { case eAtAfterKid: {
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* after = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
MOZ_ASSERT(frame, "No frame at eAtAfterKid phase"); MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame); return after;
MOZ_ASSERT(afterFrame, "No content before frame at eAtBeforeKid phase");
return afterFrame->GetContent();
} }
default: default:
@ -349,17 +345,12 @@ AllChildrenIterator::Seek(nsIContent* aChildToFind)
{ {
if (mPhase == eAtBegin || mPhase == eAtBeforeKid) { if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
mPhase = eAtExplicitKids; mPhase = eAtExplicitKids;
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
if (frame) { if (beforePseudo && beforePseudo == aChildToFind) {
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
if (beforeFrame) {
if (beforeFrame->GetContent() == aChildToFind) {
mPhase = eAtBeforeKid; mPhase = eAtBeforeKid;
return true; return true;
} }
} }
}
}
if (mPhase == eAtExplicitKids) { if (mPhase == eAtExplicitKids) {
if (ExplicitChildIterator::Seek(aChildToFind)) { if (ExplicitChildIterator::Seek(aChildToFind)) {
@ -404,13 +395,10 @@ AllChildrenIterator::GetNextChild()
{ {
if (mPhase == eAtBegin) { if (mPhase == eAtBegin) {
mPhase = eAtExplicitKids; mPhase = eAtExplicitKids;
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
if (frame) { if (beforeContent) {
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
if (beforeFrame) {
mPhase = eAtBeforeKid; mPhase = eAtBeforeKid;
return beforeFrame->GetContent(); return beforeContent;
}
} }
} }
@ -446,13 +434,10 @@ AllChildrenIterator::GetNextChild()
return mAnonKids[mAnonKidsIdx]; return mAnonKids[mAnonKidsIdx];
} }
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
if (frame) { if (afterContent) {
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
if (afterFrame) {
mPhase = eAtAfterKid; mPhase = eAtAfterKid;
return afterFrame->GetContent(); return afterContent;
}
} }
} }
@ -466,13 +451,10 @@ AllChildrenIterator::GetPreviousChild()
if (mPhase == eAtEnd) { if (mPhase == eAtEnd) {
MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length()); MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
mPhase = eAtAnonKids; mPhase = eAtAnonKids;
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
if (frame) { if (afterContent) {
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
if (afterFrame) {
mPhase = eAtAfterKid; mPhase = eAtAfterKid;
return afterFrame->GetContent(); return afterContent;
}
} }
} }
@ -501,13 +483,10 @@ AllChildrenIterator::GetPreviousChild()
return kid; return kid;
} }
nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
if (frame) { if (beforeContent) {
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
if (beforeFrame) {
mPhase = eAtBeforeKid; mPhase = eAtBeforeKid;
return beforeFrame->GetContent(); return beforeContent;
}
} }
} }

View file

@ -3657,11 +3657,11 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement,
RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr; RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
nsIFrame* frame = element->GetPrimaryFrame(); nsIFrame* frame = element->GetPrimaryFrame();
if (frame && !aPseudoElement.IsEmpty()) { if (!aPseudoElement.IsEmpty()) {
if (aPseudoElement.EqualsLiteral("::before")) { if (aPseudoElement.EqualsLiteral("::before")) {
frame = nsLayoutUtils::GetBeforeFrame(frame); frame = nsLayoutUtils::GetBeforeFrame(element);
} else if (aPseudoElement.EqualsLiteral("::after")) { } else if (aPseudoElement.EqualsLiteral("::after")) {
frame = nsLayoutUtils::GetAfterFrame(frame); frame = nsLayoutUtils::GetAfterFrame(element);
} else { } else {
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;
} }

View file

@ -2144,12 +2144,14 @@ GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
#endif #endif
// Content property names // Content property names
GK_ATOM(afterPseudoProperty, "afterPseudoProperty") // nsXMLElement*
GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations* GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*
GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations* GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations* GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet* GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet* GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet* GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
GK_ATOM(beforePseudoProperty, "beforePseudoProperty") // nsXMLElement*
GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement* GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement*
GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement* GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement*
GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions* GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*

View file

@ -9,6 +9,7 @@
#include "mozilla/dom/ElementInlines.h" #include "mozilla/dom/ElementInlines.h"
#include "nsContentUtils.h" // nsAutoScriptBlocker #include "nsContentUtils.h" // nsAutoScriptBlocker
using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
nsresult nsresult
@ -29,4 +30,26 @@ nsXMLElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
return ElementBinding::Wrap(aCx, this, aGivenProto); return ElementBinding::Wrap(aCx, this, aGivenProto);
} }
void
nsXMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
CSSPseudoElementType pseudoType = GetPseudoElementType();
bool isBefore = pseudoType == CSSPseudoElementType::before;
nsIAtom* property = isBefore
? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty;
switch (pseudoType) {
case CSSPseudoElementType::before:
case CSSPseudoElementType::after: {
MOZ_ASSERT(GetParent());
MOZ_ASSERT(GetParent()->IsElement());
GetParent()->DeleteProperty(property);
break;
}
default:
break;
}
Element::UnbindFromTree(aDeep, aNullParent);
}
NS_IMPL_ELEMENT_CLONE(nsXMLElement) NS_IMPL_ELEMENT_CLONE(nsXMLElement)

View file

@ -35,6 +35,9 @@ public:
virtual nsIDOMNode* AsDOMNode() override { return this; } virtual nsIDOMNode* AsDOMNode() override { return this; }
virtual void UnbindFromTree(bool aDeep = true,
bool aNullParent = true) override;
protected: protected:
virtual ~nsXMLElement() {} virtual ~nsXMLElement() {}

View file

@ -3626,14 +3626,14 @@ ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType,
// Check for a ::before pseudo style and the absence of a ::before content, // 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. // but only if aFrame is null or is the first continuation/ib-split.
if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) || if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) { nsLayoutUtils::GetBeforeFrame(aContent)) {
return false; return false;
} }
} else { } else {
// Similarly for ::after, but check for being the last continuation/ // Similarly for ::after, but check for being the last continuation/
// ib-split. // ib-split.
if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) || if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) { nsLayoutUtils::GetAfterFrame(aContent)) {
return false; return false;
} }
} }

View file

@ -277,24 +277,17 @@ ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
nsIAtom* aPseudoTagOrNull) nsIAtom* aPseudoTagOrNull)
{ {
MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement()); MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
if (!aPseudoTagOrNull) { if (!aPseudoTagOrNull) {
return primaryFrame; return aContent->GetPrimaryFrame();
} }
if (!primaryFrame) {
return nullptr;
}
// 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) { if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent); return nsLayoutUtils::GetBeforeFrame(aContent);
} }
if (aPseudoTagOrNull == nsCSSPseudoElements::after) { if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent); return nsLayoutUtils::GetAfterFrame(aContent);
} }
MOZ_CRASH("Unkown pseudo-element given to " MOZ_CRASH("Unkown pseudo-element given to "

View file

@ -1832,11 +1832,7 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat
aPseudoElement == CSSPseudoElementType::after, aPseudoElement == CSSPseudoElementType::after,
"unexpected aPseudoElement"); "unexpected aPseudoElement");
// XXXbz is this ever true? MOZ_ASSERT(aParentContent->IsElement());
if (!aParentContent->IsElement()) {
NS_ERROR("Bogus generated content parent");
return;
}
StyleSetHandle styleSet = mPresShell->StyleSet(); StyleSetHandle styleSet = mPresShell->StyleSet();
@ -1864,6 +1860,13 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat
nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget()); nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
if (NS_FAILED(rv)) if (NS_FAILED(rv))
return; 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->SetIsNativeAnonymousRoot();
container->SetPseudoElementType(aPseudoElement); container->SetPseudoElementType(aPseudoElement);
@ -6079,6 +6082,9 @@ AddGenConPseudoToFrame(nsIFrame* aOwnerFrame, nsIContent* aContent)
NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame), NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame),
"property should only be set on first continuation/ib-sibling"); "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 = nsIFrame::ContentArray* value =
aOwnerFrame->GetProperty(nsIFrame::GenConProperty()); aOwnerFrame->GetProperty(nsIFrame::GenConProperty());
if (!value) { if (!value) {
@ -6340,7 +6346,7 @@ AdjustAppendParentForAfterContent(nsFrameManager* aFrameManager,
// frames to find the first one that is either a ::after frame for an // 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 // ancestor of aChild or a frame that is for a node later in the
// document than aChild and return that in aAfterFrame. // document than aChild and return that in aAfterFrame.
if (aParentFrame->GetGenConPseudos() || if (aParentFrame->GetProperty(nsIFrame::GenConProperty()) ||
nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(), nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(),
CSSPseudoElementType::after, CSSPseudoElementType::after,
aParentFrame->PresContext()) || aParentFrame->PresContext()) ||
@ -6669,9 +6675,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
nsIFrame* sibling = aContent->GetPrimaryFrame(); nsIFrame* sibling = aContent->GetPrimaryFrame();
if (!sibling && GetDisplayContentsStyleFor(aContent)) { if (!sibling && GetDisplayContentsStyleFor(aContent)) {
// A display:contents node - check if it has a ::before / ::after frame... // A display:contents node - check if it has a ::before / ::after frame...
sibling = aPrevSibling ? sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent) : : nsLayoutUtils::GetBeforeFrame(aContent);
nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent);
if (!sibling) { if (!sibling) {
// ... then recurse into children ... // ... then recurse into children ...
const bool forward = !aPrevSibling; const bool forward = !aPrevSibling;
@ -6682,9 +6687,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
} }
if (!sibling) { if (!sibling) {
// ... then ::after / ::before on the opposite end. // ... then ::after / ::before on the opposite end.
sibling = aPrevSibling ? sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent) : : nsLayoutUtils::GetBeforeFrame(aContent);
nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent);
} }
if (!sibling) { if (!sibling) {
return nullptr; return nullptr;
@ -8291,27 +8295,24 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer,
MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild), MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
"display:contents nodes shouldn't have a frame"); "display:contents nodes shouldn't have a frame");
if (!childFrame && GetDisplayContentsStyleFor(aChild)) { if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
nsIFrame* ancestorFrame = nullptr;
nsIContent* ancestor = aContainer; nsIContent* ancestor = aContainer;
for (; ancestor; ancestor = ancestor->GetParent()) { while (!ancestor->GetPrimaryFrame()) {
ancestorFrame = ancestor->GetPrimaryFrame(); // FIXME(emilio): Should this use the flattened tree parent instead?
if (ancestorFrame) { ancestor = ancestor->GetParent();
break; MOZ_ASSERT(ancestor, "we can't have a display: contents subtree root!");
} }
}
if (ancestorFrame) { nsIFrame* ancestorFrame = ancestor->GetPrimaryFrame();
nsIFrame* contentInsertion = ancestorFrame->GetContentInsertionFrame(); if (ancestorFrame->GetProperty(nsIFrame::GenConProperty())) {
if (ancestorFrame->GetGenConPseudos() ||
(contentInsertion && contentInsertion->GetGenConPseudos())) {
*aDidReconstruct = true; *aDidReconstruct = true;
LAYOUT_PHASE_TEMP_EXIT(); LAYOUT_PHASE_TEMP_EXIT();
// XXXmats Can we recreate frames only for the ::after/::before content? // XXXmats Can we recreate frames only for the ::after/::before content?
// XXX Perhaps even only those that belong to the aChild sub-tree? // XXX Perhaps even only those that belong to the aChild sub-tree?
RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor); RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
LAYOUT_PHASE_TEMP_REENTER(); LAYOUT_PHASE_TEMP_REENTER();
return; return;
} }
}
FlattenedChildIterator iter(aChild); FlattenedChildIterator iter(aChild);
for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {

View file

@ -1486,90 +1486,38 @@ nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame)
return id; return id;
} }
/*static*/ nsIFrame* static Element*
nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame, GetPseudo(const nsIContent* aContent, nsIAtom* aPseudoProperty)
const nsIContent* aContent)
{ {
// We need to call GetGenConPseudos() on the first continuation/ib-split. MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
// Find it, for symmetry with GetAfterFrameForContent. aPseudoProperty == nsGkAtoms::afterPseudoProperty);
nsContainerFrame* genConParentFrame = return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame(); }
if (!genConParentFrame) {
return nullptr; /*static*/ Element*
} nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent)
nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos(); {
if (prop) { return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
const nsTArray<nsIContent*>& 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*/ nsIFrame* /*static*/ nsIFrame*
nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame) nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent)
{ {
return GetBeforeFrameForContent(aFrame, aFrame->GetContent()); Element* pseudo = GetBeforePseudo(aContent);
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
/*static*/ Element*
nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent)
{
return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
} }
/*static*/ nsIFrame* /*static*/ nsIFrame*
nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame, nsLayoutUtils::GetAfterFrame(const nsIContent* aContent)
const nsIContent* aContent)
{ {
// We need to call GetGenConPseudos() on the first continuation, Element* pseudo = GetAfterPseudo(aContent);
// but callers are likely to pass the last. return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
nsContainerFrame* genConParentFrame =
FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
if (!genConParentFrame) {
return nullptr;
}
nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
if (prop) {
const nsTArray<nsIContent*>& 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<nsContainerFrame*>(
LastContinuationOrIBSplitSibling(genConParentFrame)));
nsIFrame* childFrame =
lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild();
if (childFrame &&
childFrame->IsPseudoFrame(aContent) &&
!childFrame->IsGeneratedContentFrame()) {
return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent);
}
return nullptr;
}
/*static*/ nsIFrame*
nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
{
return GetAfterFrameForContent(aFrame, aFrame->GetContent());
} }
// static // static

View file

@ -276,50 +276,26 @@ public:
static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame); static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
/** /**
* GetBeforeFrameForContent returns the ::before frame for aContent, if * Returns the ::before pseudo-element for aContent, if any.
* 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
*/ */
static nsIFrame* GetBeforeFrameForContent(nsIFrame* aGenConParentFrame, static mozilla::dom::Element* GetBeforePseudo(const nsIContent* aContent);
const nsIContent* aContent);
/** /**
* GetBeforeFrame returns the outermost ::before frame of the given frame, if * Returns the frame corresponding to the ::before pseudo-element for
* one exists. This is typically O(1). The frame passed in must be * aContent, if any.
* the first-in-flow.
*
* @param aFrame the frame whose ::before is wanted
* @return the :before frame or nullptr if there isn't one
*/ */
static nsIFrame* GetBeforeFrame(nsIFrame* aFrame); static nsIFrame* GetBeforeFrame(const nsIContent* aContent);
/** /**
* GetAfterFrameForContent returns the ::after frame for aContent, if one * Returns the ::after pseudo-element for aContent, if any.
* 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
*/ */
static nsIFrame* GetAfterFrameForContent(nsIFrame* aGenConParentFrame, static mozilla::dom::Element* GetAfterPseudo(const nsIContent* aContent);
const nsIContent* aContent);
/** /**
* GetAfterFrame returns the outermost ::after frame of the given frame, if one * Returns the frame corresponding to the ::after pseudo-element for aContent,
* exists. This will walk the in-flow chain to the last-in-flow if * if any.
* 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
*/ */
static nsIFrame* GetAfterFrame(nsIFrame* aFrame); static nsIFrame* GetAfterFrame(const nsIContent* aContent);
/** /**
* Given a frame, search up the frame tree until we find an * Given a frame, search up the frame tree until we find an

View file

@ -9995,19 +9995,16 @@ nsIFrame::IsPseudoStackingContextFromStyle() {
Element* Element*
nsIFrame::GetPseudoElement(CSSPseudoElementType aType) nsIFrame::GetPseudoElement(CSSPseudoElementType aType)
{ {
nsIFrame* frame = nullptr; if (!mContent) {
return nullptr;
}
if (aType == CSSPseudoElementType::before) { if (aType == CSSPseudoElementType::before) {
frame = nsLayoutUtils::GetBeforeFrame(this); return nsLayoutUtils::GetBeforePseudo(mContent);
} else if (aType == CSSPseudoElementType::after) {
frame = nsLayoutUtils::GetAfterFrame(this);
} }
if (frame) { if (aType == CSSPseudoElementType::after) {
nsIContent* content = frame->GetContent(); return nsLayoutUtils::GetAfterPseudo(mContent);
if (content->IsElement()) {
return content->AsElement();
}
} }
return nullptr; return nullptr;

View file

@ -1062,10 +1062,6 @@ public:
return GetBidiData().embeddingLevel; return GetBidiData().embeddingLevel;
} }
nsTArray<nsIContent*>* GetGenConPseudos() {
return GetProperty(GenConProperty());
}
/** /**
* Return the distance between the border edge of the frame and the * Return the distance between the border edge of the frame and the
* margin edge of the frame. Like GetRect(), returns the dimensions * margin edge of the frame. Like GetRect(), returns the dimensions

View file

@ -638,6 +638,34 @@ nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext)
mStyleContext = 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 void
nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
{ {
@ -690,9 +718,20 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
// XXX the !mElement->IsHTMLElement(nsGkAtoms::area) // XXX the !mElement->IsHTMLElement(nsGkAtoms::area)
// check is needed due to bug 135040 (to avoid using // check is needed due to bug 135040 (to avoid using
// mPrimaryFrame). Remove it once that's fixed. // mPrimaryFrame). Remove it once that's fixed.
if (!mPseudo && mStyleType == eAll && if (mStyleType == eAll && !mElement->IsHTMLElement(nsGkAtoms::area)) {
!mElement->IsHTMLElement(nsGkAtoms::area)) { mOuterFrame = nullptr;
if (!mPseudo) {
mOuterFrame = mElement->GetPrimaryFrame(); mOuterFrame = mElement->GetPrimaryFrame();
} else if (mPseudo == nsCSSPseudoElements::before ||
mPseudo == nsCSSPseudoElements::after) {
nsIAtom* property = mPseudo == nsCSSPseudoElements::before
? nsGkAtoms::beforePseudoProperty
: nsGkAtoms::afterPseudoProperty;
auto* pseudo = static_cast<Element*>(mElement->GetProperty(property));
mOuterFrame = pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
mInnerFrame = mOuterFrame; mInnerFrame = mOuterFrame;
if (mOuterFrame) { if (mOuterFrame) {
nsIAtom* type = mOuterFrame->GetType(); nsIAtom* type = mOuterFrame->GetType();
@ -711,7 +750,7 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
} }
} }
if (!mStyleContext || mStyleContext->HasPseudoElementData()) { if (!mStyleContext || MustReresolveStyle(mStyleContext)) {
#ifdef DEBUG #ifdef DEBUG
if (mStyleContext) { if (mStyleContext) {
// We want to check that going through this path because of // We want to check that going through this path because of