From ee4857f2098163c1355716944753ab1da1b09611 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 17 Apr 2020 07:20:06 -0400 Subject: Bug 1413619 - Fix insertion point computation when display: contents pseudos are involved. Tag #1375 --- layout/base/nsCSSFrameConstructor.cpp | 240 ++++++++++++++++------------------ layout/base/nsCSSFrameConstructor.h | 93 ++++++------- layout/base/nsFrameManager.cpp | 3 +- layout/base/nsFrameManager.h | 6 +- layout/base/nsLayoutUtils.cpp | 27 ---- layout/base/nsLayoutUtils.h | 17 --- 6 files changed, 160 insertions(+), 226 deletions(-) (limited to 'layout/base') diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 617e9a940..8b3bb2e9d 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -6662,115 +6662,147 @@ nsCSSFrameConstructor::IsValidSibling(nsIFrame* aSibling, return true; } +// FIXME(emilio): If we ever kill IsValidSibling() we can simplify this quite a +// bit (no need to pass aTargetContent or aTargetContentDisplay, and the +// adjust() calls can be responsibility of the caller). +template nsIFrame* -nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent, - nsIContent* aTargetContent, - StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame, - bool aPrevSibling) -{ - nsIFrame* sibling = aContent->GetPrimaryFrame(); - if (!sibling && GetDisplayContentsStyleFor(aContent)) { - // A display:contents node - check if it has a ::before / ::after frame... - sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent) - : nsLayoutUtils::GetBeforeFrame(aContent); - if (!sibling) { - // ... then recurse into children ... - const bool forward = !aPrevSibling; - FlattenedChildIterator iter(aContent, forward); - sibling = aPrevSibling ? - FindPreviousSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame) : - FindNextSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame); - - // The recursion above has already done all the placeholder and - // continuation fixups. +nsCSSFrameConstructor::FindSiblingInternal( + FlattenedChildIterator aIter, + nsIContent* aTargetContent, + StyleDisplay& aTargetContentDisplay) +{ + auto adjust = [&](nsIFrame* aPotentialSiblingFrame) -> nsIFrame* { + return AdjustSiblingFrame( + aPotentialSiblingFrame, aTargetContent, aTargetContentDisplay, + aDirection); + }; + + auto nextDomSibling = [](FlattenedChildIterator& aIter) -> nsIContent* { + return aDirection == SiblingDirection::Forward + ? aIter.GetNextChild() : aIter.GetPreviousChild(); + }; + + auto getNearPseudo = [](const nsIContent* aContent) -> nsIFrame* { + return aDirection == SiblingDirection::Forward + ? nsLayoutUtils::GetBeforeFrame(aContent) + : nsLayoutUtils::GetAfterFrame(aContent); + }; + + auto getFarPseudo = [](const nsIContent* aContent) -> nsIFrame* { + return aDirection == SiblingDirection::Forward + ? nsLayoutUtils::GetAfterFrame(aContent) + : nsLayoutUtils::GetBeforeFrame(aContent); + }; + + while (nsIContent* sibling = nextDomSibling(aIter)) { + if (nsIFrame* primaryFrame = sibling->GetPrimaryFrame()) { + // XXX the GetContent() == sibling check is needed due to bug 135040. + // Remove it once that's fixed. + if (primaryFrame->GetContent() == sibling) { + if (nsIFrame* frame = adjust(primaryFrame)) { + return frame; + } + } + } + + if (GetDisplayContentsStyleFor(sibling)) { + if (nsIFrame* frame = adjust(getNearPseudo(sibling))) { + return frame; + } + + const bool startFromBeginning = aDirection == SiblingDirection::Forward; + FlattenedChildIterator iter(sibling, startFromBeginning); + nsIFrame* sibling = FindSiblingInternal( + iter, aTargetContent, aTargetContentDisplay); if (sibling) { return sibling; } } - if (!sibling) { - // ... then ::after / ::before on the opposite end. - sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent) - : nsLayoutUtils::GetBeforeFrame(aContent); - } - if (!sibling) { - return nullptr; - } - } else if (!sibling || sibling->GetContent() != aContent) { - // XXX the GetContent() != aContent check is needed due to bug 135040. - // Remove it once that's fixed. - return nullptr; } - // If the frame is out-of-flow, GetPrimaryFrame() will have returned the - // out-of-flow frame; we want the placeholder. - if (sibling->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { - nsIFrame* placeholderFrame = sibling->GetPlaceholderFrame(); - NS_ASSERTION(placeholderFrame, "no placeholder for out-of-flow frame"); - sibling = placeholderFrame; + return adjust(getFarPseudo(aIter.Parent())); +} + +nsIFrame* +nsCSSFrameConstructor::AdjustSiblingFrame( + nsIFrame* aSibling, + nsIContent* aTargetContent, + mozilla::StyleDisplay& aTargetContentDisplay, + SiblingDirection aDirection) +{ + if (!aSibling) { + return nullptr; } - // The frame we have now should never be a continuation. - NS_ASSERTION(!sibling->GetPrevContinuation(), "How did that happen?"); + if (aSibling->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { + aSibling = aSibling->GetPlaceholderFrame(); + MOZ_ASSERT(aSibling); + } - if (aPrevSibling) { - // The frame may be a ib-split frame (a split inline frame that - // contains a block). Get the last part of that split. - if (IsFramePartOfIBSplit(sibling)) { - sibling = GetLastIBSplitSibling(sibling, true); + MOZ_ASSERT(!aSibling->GetPrevContinuation(), "How?"); + if (aDirection == SiblingDirection::Backward) { + // The frame may be a ib-split frame (a split inline frame that contains a + // block). Get the last part of that split. + if (IsFramePartOfIBSplit(aSibling)) { + aSibling = GetLastIBSplitSibling(aSibling, true); } // The frame may have a continuation. If so, we want the last // non-overflow-container continuation as our previous sibling. - sibling = sibling->GetTailContinuation(); + aSibling = aSibling->GetTailContinuation(); } - if (aTargetContent && - !IsValidSibling(sibling, aTargetContent, aTargetContentDisplay)) { - sibling = nullptr; + if (!IsValidSibling(aSibling, aTargetContent, aTargetContentDisplay)) { + return nullptr; } - return sibling; + return aSibling; } nsIFrame* -nsCSSFrameConstructor::FindPreviousSibling(FlattenedChildIterator aIter, - nsIContent* aTargetContent, - StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame) +nsCSSFrameConstructor::FindPreviousSibling(const FlattenedChildIterator& aIter, + StyleDisplay& aTargetContentDisplay) { - // Note: not all content objects are associated with a frame (e.g., if it's - // `display: none') so keep looking until we find a previous frame. - while (nsIContent* sibling = aIter.GetPreviousChild()) { - MOZ_ASSERT(sibling != aTargetContent); - nsIFrame* prevSibling = - FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay, - aParentFrame, true); - if (prevSibling) { - // Found a previous sibling, we're done! - return prevSibling; - } - } + return FindSibling(aIter, aTargetContentDisplay); +} - return nullptr; +nsIFrame* +nsCSSFrameConstructor::FindNextSibling(const FlattenedChildIterator& aIter, + StyleDisplay& aTargetContentDisplay) +{ + return FindSibling(aIter, aTargetContentDisplay); } +template nsIFrame* -nsCSSFrameConstructor::FindNextSibling(FlattenedChildIterator aIter, - nsIContent* aTargetContent, - StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame) +nsCSSFrameConstructor::FindSibling(const FlattenedChildIterator& aIter, + StyleDisplay& aTargetContentDisplay) { - while (nsIContent* sibling = aIter.GetNextChild()) { - MOZ_ASSERT(sibling != aTargetContent); - nsIFrame* nextSibling = - FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay, - aParentFrame, false); + nsIContent* targetContent = aIter.Get(); + nsIFrame* sibling = + FindSiblingInternal(aIter, targetContent, aTargetContentDisplay); + if (sibling) { + return sibling; + } - if (nextSibling) { - // We found a next sibling, we're done! - return nextSibling; + // Our siblings (if any) do not have a frame to guide us. The frame for the + // target content should be inserted whereever a frame for the container would + // be inserted. This is needed when inserting into display: contents nodes. + const nsIContent* current = aIter.Parent(); + while (GetDisplayContentsStyleFor(current)) { + const nsIContent* parent = current->GetFlattenedTreeParent(); + MOZ_ASSERT(parent, "No display: contents on the root"); + + FlattenedChildIterator iter(parent); + iter.Seek(current); + sibling = FindSiblingInternal( + iter, targetContent, aTargetContentDisplay); + if (sibling) { + return sibling; } + + current = parent; } return nullptr; @@ -6836,8 +6868,7 @@ nsCSSFrameConstructor::GetInsertionPrevSibling(InsertionPoint* aInsertion, // Note that FindPreviousSibling is passed the iterator by value, so that // the later usage of the iterator starts from the same place. StyleDisplay childDisplay = UNSET_DISPLAY; - nsIFrame* prevSibling = - FindPreviousSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame); + nsIFrame* prevSibling = FindPreviousSibling(iter, childDisplay); // Now, find the geometric parent so that we can handle // continuations properly. Use the prev sibling if we have it; @@ -6850,34 +6881,7 @@ nsCSSFrameConstructor::GetInsertionPrevSibling(InsertionPoint* aInsertion, iter.Seek(aEndSkipChild); iter.GetPreviousChild(); } - nsIFrame* nextSibling = - FindNextSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame); - if (GetDisplayContentsStyleFor(aInsertion->mContainer)) { - if (!nextSibling) { - // Our siblings (if any) does not have a frame to guide us. - // The frame for aChild should be inserted whereever a frame for - // the container would be inserted. This is needed when inserting - // into nested display:contents nodes. - nsIContent* child = aInsertion->mContainer; - nsIContent* parent = child->GetParent(); - aInsertion->mParentFrame = - ::GetAdjustedParentFrame(aInsertion->mParentFrame, - aInsertion->mParentFrame->GetType(), - parent); - InsertionPoint fakeInsertion(aInsertion->mParentFrame, parent); - nsIFrame* result = GetInsertionPrevSibling(&fakeInsertion, child, aIsAppend, - aIsRangeInsertSafe, nullptr, nullptr); - MOZ_ASSERT(aInsertion->mParentFrame->GetContent() == - fakeInsertion.mParentFrame->GetContent()); - // fakeInsertion.mParentFrame may now be a continuation of the frame - // we started with in the ctor above. - aInsertion->mParentFrame = fakeInsertion.mParentFrame; - return result; - } - - prevSibling = nextSibling->GetPrevSibling(); - } - + nsIFrame* nextSibling = FindNextSibling(iter, childDisplay); if (nextSibling) { aInsertion->mParentFrame = nextSibling->GetParent()->GetContentInsertionFrame(); } else { @@ -8022,22 +8026,6 @@ nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer, } } - if (!prevSibling) { - // We're inserting the new frames as the first child. See if the - // parent has a :before pseudo-element - nsIFrame* firstChild = insertion.mParentFrame->PrincipalChildList().FirstChild(); - - if (firstChild && - nsLayoutUtils::IsGeneratedContentFor(container, firstChild, - nsCSSPseudoElements::before)) { - // Insert the new frames after the last continuation of the :before - prevSibling = firstChild->GetTailContinuation(); - insertion.mParentFrame = prevSibling->GetParent()->GetContentInsertionFrame(); - // Don't change isAppend here; we'll can call AppendFrames as needed, and - // the change to our prevSibling doesn't affect that. - } - } - FrameConstructionItemList items; ParentType parentType = GetParentType(frameType); FlattenedChildIterator iter(aContainer); diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 49107ee52..8c565cb02 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -1962,64 +1962,53 @@ private: nsIFrame* aPrevSibling, nsFrameItems& aFrameItems); - /** - * Find the right frame to use for aContent when looking for sibling - * frames for aTargetContent. If aPrevSibling is true, this - * will look for last continuations, etc, as necessary. This calls - * IsValidSibling as needed; if that returns false it returns null. - * - * @param aContent the content to search for frames - * @param aTargetContent the content we're finding a sibling frame for - * @param aTargetContentDisplay the CSS display enum for aTargetContent if - * already known, UNSET_DISPLAY otherwise. It will be filled in - * if needed. - * @param aParentFrame the nearest ancestor frame, used internally for - * finding ::after / ::before frames - * @param aPrevSibling true if we're searching in reverse DOM order - */ - nsIFrame* FindFrameForContentSibling(nsIContent* aContent, - nsIContent* aTargetContent, - mozilla::StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame, - bool aPrevSibling); + // The direction in which we should look for siblings. + enum class SiblingDirection + { + Forward, + Backward, + }; /** - * Find the frame for the content immediately preceding the one aIter - * points to, following continuations if necessary. aIter is passed by - * value on purpose, so as not to modify the caller's iterator. + * Find the frame for the content immediately next to the one aIter points to, + * in the direction SiblingDirection indicates, following continuations if + * necessary. * - * @param aIter should be positioned such that aIter.GetPreviousChild() - * is the first content to search for frames - * @param aTargetContent the content we're finding a sibling frame for - * @param aTargetContentDisplay the CSS display enum for aTargetContent if - * already known, UNSET_DISPLAY otherwise. It will be filled in - * if needed. - * @param aParentFrame the nearest ancestor frame, used inernally for - * finding ::after / ::before frames - */ - nsIFrame* FindPreviousSibling(mozilla::dom::FlattenedChildIterator aIter, - nsIContent* aTargetContent, - mozilla::StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame); - - /** - * Find the frame for the content node immediately following the one aIter - * points to, following continuations if necessary. aIter is passed by value - * on purpose, so as not to modify the caller's iterator. + * aIter is passed by const reference on purpose, so as not to modify the + * caller's iterator. * - * @param aIter should be positioned such that aIter.GetNextChild() + * @param aIter should be positioned such that aIter.GetPreviousChild() * is the first content to search for frames - * @param aTargetContent the content we're finding a sibling frame for - * @param aTargetContentDisplay the CSS display enum for aTargetContent if - * already known, UNSET_DISPLAY otherwise. It will be filled in - * if needed. - * @param aParentFrame the nearest ancestor frame, used inernally for - * finding ::after / ::before frames + * @param aTargetContentDisplay the CSS display enum for the content aIter + * points to if already known, UNSET_DISPLAY otherwise. It will be + * filled in if needed. */ - nsIFrame* FindNextSibling(mozilla::dom::FlattenedChildIterator aIter, - nsIContent* aTargetContent, - mozilla::StyleDisplay& aTargetContentDisplay, - nsContainerFrame* aParentFrame); + template + nsIFrame* FindSibling(const mozilla::dom::FlattenedChildIterator& aIter, + mozilla::StyleDisplay& aTargetContentDisplay); + + // Helper for the implementation of FindSibling. + template + nsIFrame* FindSiblingInternal( + mozilla::dom::FlattenedChildIterator, + nsIContent* aTargetContent, + mozilla::StyleDisplay& aTargetContentDisplay); + + // An alias of FindSibling. + nsIFrame* FindNextSibling(const mozilla::dom::FlattenedChildIterator& aIter, + mozilla::StyleDisplay& aTargetContentDisplay); + // An alias of FindSibling. + nsIFrame* FindPreviousSibling(const mozilla::dom::FlattenedChildIterator& aIter, + mozilla::StyleDisplay& aTargetContentDisplay); + + // Given a potential first-continuation sibling frame for aTargetContent, + // verify that it is an actual valid sibling for it, and return the + // appropriate continuation the new frame for aTargetContent should be + // inserted next to. + nsIFrame* AdjustSiblingFrame(nsIFrame* aSibling, + nsIContent* aTargetContent, + mozilla::StyleDisplay& aTargetContentDisplay, + SiblingDirection aDirection); // Find the right previous sibling for an insertion. This also updates the // parent frame to point to the correct continuation of the parent frame to diff --git a/layout/base/nsFrameManager.cpp b/layout/base/nsFrameManager.cpp index d703537fa..ce5c60bbc 100644 --- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -132,7 +132,8 @@ nsFrameManager::Destroy() //---------------------------------------------------------------------- /* static */ nsStyleContext* -nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent) +nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, + const nsIContent* aContent) { if (!aContent) { return nullptr; diff --git a/layout/base/nsFrameManager.h b/layout/base/nsFrameManager.h index 84d2c93e7..ae7477d3d 100644 --- a/layout/base/nsFrameManager.h +++ b/layout/base/nsFrameManager.h @@ -80,7 +80,7 @@ public: void Destroy(); // Mapping undisplayed content - nsStyleContext* GetUndisplayedContent(nsIContent* aContent) + nsStyleContext* GetUndisplayedContent(const nsIContent* aContent) { if (!mUndisplayedMap) { return nullptr; @@ -105,7 +105,7 @@ public: /** * Return the registered display:contents style context for aContent, if any. */ - nsStyleContext* GetDisplayContentsStyleFor(nsIContent* aContent) + nsStyleContext* GetDisplayContentsStyleFor(const nsIContent* aContent) { if (!mDisplayContentsMap) { return nullptr; @@ -185,7 +185,7 @@ public: nsILayoutHistoryState* aState); protected: static nsStyleContext* GetStyleContextInMap(UndisplayedMap* aMap, - nsIContent* aContent); + const nsIContent* aContent); static mozilla::UndisplayedNode* GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap, nsIContent* aParentContent); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 2c4449994..7496a4946 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1587,33 +1587,6 @@ nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) { return nullptr; } -// static -bool -nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent, - nsIFrame* aFrame, - nsIAtom* aPseudoElement) -{ - NS_PRECONDITION(aFrame, "Must have a frame"); - NS_PRECONDITION(aPseudoElement, "Must have a pseudo name"); - - if (!aFrame->IsGeneratedContentFrame()) { - return false; - } - nsIFrame* parent = aFrame->GetParent(); - NS_ASSERTION(parent, "Generated content can't be root frame"); - if (parent->IsGeneratedContentFrame()) { - // Not the root of the generated content - return false; - } - - if (aContent && parent->GetContent() != aContent) { - return false; - } - - return (aFrame->GetContent()->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) == - (aPseudoElement == nsCSSPseudoElements::before); -} - // static nsIFrame* nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame, diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 9757cb799..36a43e46b 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -353,23 +353,6 @@ public: */ static nsIFrame* GetRealPrimaryFrameFor(const nsIContent* aContent); - /** - * IsGeneratedContentFor returns true if aFrame is the outermost - * frame for generated content of type aPseudoElement for aContent. - * aFrame *might not* have the aPseudoElement pseudo-style! For example - * it might be a table wrapper frame and the inner table frame might - * have the pseudo-style. - * - * @param aContent the content node we're looking at. If this is - * null, then we just assume that aFrame has the right content - * pointer. - * @param aFrame the frame we're looking at - * @param aPseudoElement the pseudo type we're interested in - * @return whether aFrame is the generated aPseudoElement frame for aContent - */ - static bool IsGeneratedContentFor(nsIContent* aContent, nsIFrame* aFrame, - nsIAtom* aPseudoElement); - #ifdef DEBUG // TODO: remove, see bug 598468. static bool gPreventAssertInCompareTreePosition; -- cgit v1.2.3