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

@ -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;
}
}

View file

@ -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;
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) {
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 "

View file

@ -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);

View file

@ -1486,90 +1486,38 @@ nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame)
return id;
}
/*static*/ nsIFrame*
nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame,
const nsIContent* aContent)
static Element*
GetPseudo(const nsIContent* aContent, nsIAtom* aPseudoProperty)
{
// 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<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::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;
MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
aPseudoProperty == nsGkAtoms::afterPseudoProperty);
return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
}
/*static*/ Element*
nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent)
{
return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
}
/*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*
nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame,
const nsIContent* aContent)
nsLayoutUtils::GetAfterFrame(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<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());
Element* pseudo = GetAfterPseudo(aContent);
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
// static

View file

@ -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