Skip to content

Commit be35373

Browse files
committed
[leading-trim][IFC] Add support for nested block containers with leading-trim: end
https://bugs.webkit.org/show_bug.cgi?id=248854 Reviewed by Antti Koivisto. This patch adds multi-pass inline layout to support last formatted line trimming. "leading-trim: end" trims the last line of the last inline formatting context. While this property is not inherited, it is propagated through nested block containers. With nested block containers, we can end up with multiple inline formatting contexts e.g. <div style="leading-trim: end"> <div> <div>first line</div> </div> <div> last line</div> </div> The last formatted line can not be identified until after finishing the layout process on the entire subtree where the property is set. In this patch, we run an additional inline layout on the last formatting context with the leading trim (end) set. 1. set "trim start" (when applicable) on the layout state. 2. reset the "trim start" state after trimming the first line (so that subsequent IFCs don't apply "trim start") 3. when the entire subtree is laid out, check if we need to apply "trim end'. 4. find the last formatting context 5. set "trim end" on the layout state and run layout on the last IFC. (Fast path for the simple case when the leading trim block container is also an IFC root.) * Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp: (WebCore::LayoutIntegration::leadingTrim): * Source/WebCore/rendering/RenderBlockFlow.cpp: (WebCore::RenderBlockFlow::layoutBlock): (WebCore::firstInlineFormattingContextRoot): (WebCore::lastInlineFormattingContextRoot): (WebCore::RenderBlockFlow::setLeadingTrimForSubtree): (WebCore::RenderBlockFlow::adjustLeadingTrimAfterLayout): (WebCore::RenderBlockFlow::layoutInFlowChildren): (WebCore::RenderBlockFlow::resetLeadingTrim): Deleted. * Source/WebCore/rendering/RenderBlockFlow.h: * Source/WebCore/rendering/RenderElement.h: * Source/WebCore/rendering/RenderLayoutState.cpp: (WebCore::RenderLayoutState::RenderLayoutState): * Source/WebCore/rendering/RenderLayoutState.h: (WebCore::RenderLayoutState::leadingTrim): (WebCore::RenderLayoutState::hasLeadingTrimStart const): (WebCore::RenderLayoutState::hasLeadingTrimEnd const): (WebCore::RenderLayoutState::addLeadingTrimStart): (WebCore::RenderLayoutState::removeLeadingTrimStart): (WebCore::RenderLayoutState::addLeadingTrimEnd): (WebCore::RenderLayoutState::hasLeadingTrim const): Deleted. (WebCore::RenderLayoutState::leadingTrim const): Deleted. (WebCore::RenderLayoutState::addLeadingTrim): Deleted. (WebCore::RenderLayoutState::removeLeadingTrim): Deleted. Canonical link: https://commits.webkit.org/257526@main
1 parent be0b82e commit be35373

File tree

6 files changed

+175
-59
lines changed

6 files changed

+175
-59
lines changed

Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,11 +475,13 @@ static inline std::optional<Layout::BlockLayoutState::LineClamp> lineClamp(const
475475

476476
static inline Layout::BlockLayoutState::LeadingTrim leadingTrim(const RenderBlockFlow& rootRenderer)
477477
{
478-
auto leadingTrimSides = rootRenderer.view().frameView().layoutContext().layoutState()->leadingTrim();
478+
auto* layoutState = rootRenderer.view().frameView().layoutContext().layoutState();
479+
if (!layoutState)
480+
return { };
479481
auto leadingTrimForIFC = Layout::BlockLayoutState::LeadingTrim { };
480-
if (leadingTrimSides.contains(RenderLayoutState::LeadingTrimSide::Start))
482+
if (layoutState->hasLeadingTrimStart())
481483
leadingTrimForIFC.add(Layout::BlockLayoutState::LeadingTrimSide::Start);
482-
if (leadingTrimSides.contains(RenderLayoutState::LeadingTrimSide::End))
484+
if (layoutState->hasLeadingTrimEnd(rootRenderer))
483485
leadingTrimForIFC.add(Layout::BlockLayoutState::LeadingTrimSide::End);
484486
return leadingTrimForIFC;
485487
}

Source/WebCore/rendering/RenderBlockFlow.cpp

Lines changed: 127 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -480,40 +480,6 @@ void RenderBlockFlow::setChildrenInline(bool value)
480480
RenderBlock::setChildrenInline(value);
481481
}
482482

483-
void RenderBlockFlow::setLeadingTrimForSubtree()
484-
{
485-
auto* layoutState = view().frameView().layoutContext().layoutState();
486-
if (!layoutState)
487-
return;
488-
auto leadingTrim = style().leadingTrim();
489-
if (leadingTrim == LeadingTrim::Normal) {
490-
if (borderAndPaddingStart()) {
491-
// For block containers: trim the block-start side of the first formatted line to the corresponding
492-
// text-edge metric of its root inline box. If there is no such line, or if there is intervening non-zero padding or borders,
493-
// there is no effect.
494-
layoutState->resetLeadingTrim();
495-
}
496-
return;
497-
}
498-
// FIXME: Add support for nested leading trims if applicable.
499-
layoutState->resetLeadingTrim();
500-
if (leadingTrim == LeadingTrim::Start || leadingTrim == LeadingTrim::Both)
501-
layoutState->addLeadingTrim(RenderLayoutState::LeadingTrimSide::Start);
502-
if (leadingTrim == LeadingTrim::End || leadingTrim == LeadingTrim::Both)
503-
layoutState->addLeadingTrim(RenderLayoutState::LeadingTrimSide::End);
504-
}
505-
506-
void RenderBlockFlow::resetLeadingTrim()
507-
{
508-
auto* layoutState = view().frameView().layoutContext().layoutState();
509-
if (!layoutState || !layoutState->leadingTrim())
510-
return;
511-
if (layoutState->leadingTrim().contains(RenderLayoutState::LeadingTrimSide::Start) && hasLines()) {
512-
// Only the first formatted line is trimmed.
513-
layoutState->removeLeadingTrim(RenderLayoutState::LeadingTrimSide::Start);
514-
}
515-
}
516-
517483
void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
518484
{
519485
ASSERT(needsLayout());
@@ -566,12 +532,7 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH
566532
if (!firstChild() && !isAnonymousBlock())
567533
setChildrenInline(true);
568534
dirtyForLayoutFromPercentageHeightDescendants();
569-
setLeadingTrimForSubtree();
570-
571-
if (childrenInline())
572-
layoutInlineChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom);
573-
else
574-
layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom);
535+
layoutInFlowChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom, maxFloatLogicalBottom);
575536
// Expand our intrinsic height to encompass floats.
576537
LayoutUnit toAdd = borderAndPaddingAfter() + scrollbarLogicalHeight();
577538
if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && createsNewFormattingContext())
@@ -580,7 +541,6 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH
580541
setEverHadLayout(true);
581542
continue;
582543
}
583-
resetLeadingTrim();
584544
break;
585545
} while (true);
586546

@@ -676,6 +636,132 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH
676636
clearNeedsLayout();
677637
}
678638

639+
static RenderBlockFlow* firstInlineFormattingContextRoot(const RenderBlockFlow& enclosingBlockContainer)
640+
{
641+
// FIXME: Remove this after implementing "last line damage".
642+
for (auto* child = enclosingBlockContainer.firstChild(); child; child = child->previousSibling()) {
643+
if (!is<RenderBlockFlow>(child) || downcast<RenderBlockFlow>(*child).createsNewFormattingContext())
644+
continue;
645+
auto& blockContainer = downcast<RenderBlockFlow>(*child);
646+
if (blockContainer.hasLines())
647+
return &blockContainer;
648+
if (auto* descendantRoot = firstInlineFormattingContextRoot(blockContainer))
649+
return descendantRoot;
650+
}
651+
return nullptr;
652+
}
653+
654+
static RenderBlockFlow* lastInlineFormattingContextRoot(const RenderBlockFlow& enclosingBlockContainer)
655+
{
656+
for (auto* child = enclosingBlockContainer.lastChild(); child; child = child->previousSibling()) {
657+
if (!is<RenderBlockFlow>(child) || downcast<RenderBlockFlow>(*child).createsNewFormattingContext())
658+
continue;
659+
auto& blockContainer = downcast<RenderBlockFlow>(*child);
660+
if (blockContainer.hasLines())
661+
return &blockContainer;
662+
if (auto* descendantRoot = lastInlineFormattingContextRoot(blockContainer))
663+
return descendantRoot;
664+
}
665+
return nullptr;
666+
}
667+
668+
class LeadingTrimmer {
669+
public:
670+
LeadingTrimmer(const RenderBlockFlow& flow, const RenderBlockFlow* inlineFormattingContextRootForLeadingTrimEnd = nullptr)
671+
: m_flow(flow)
672+
{
673+
setLeadingTrimForSubtree(inlineFormattingContextRootForLeadingTrimEnd);
674+
}
675+
676+
~LeadingTrimmer()
677+
{
678+
adjustLeadingTrimAfterLayout();
679+
}
680+
681+
private:
682+
void setLeadingTrimForSubtree(const RenderBlockFlow* inlineFormattingContextRootForLeadingTrimEnd);
683+
void adjustLeadingTrimAfterLayout();
684+
685+
const RenderBlockFlow& m_flow;
686+
};
687+
688+
void LeadingTrimmer::setLeadingTrimForSubtree(const RenderBlockFlow* inlineFormattingContextRootForLeadingTrimEnd)
689+
{
690+
auto* layoutState = m_flow.view().frameView().layoutContext().layoutState();
691+
if (!layoutState)
692+
return;
693+
auto leadingTrim = m_flow.style().leadingTrim();
694+
if (leadingTrim == LeadingTrim::Normal) {
695+
if (m_flow.borderAndPaddingStart()) {
696+
// For block containers: trim the block-start side of the first formatted line to the corresponding
697+
// text-edge metric of its root inline box. If there is no such line, or if there is intervening non-zero padding or borders,
698+
// there is no effect.
699+
layoutState->resetLeadingTrim();
700+
}
701+
return;
702+
}
703+
// FIXME: Add support for nested leading trims if applicable.
704+
layoutState->resetLeadingTrim();
705+
auto applyLeadingTrimStart = leadingTrim == LeadingTrim::Start || leadingTrim == LeadingTrim::Both;
706+
auto applyLeadingTrimEnd = (leadingTrim == LeadingTrim::End || leadingTrim == LeadingTrim::Both) && inlineFormattingContextRootForLeadingTrimEnd;
707+
if (applyLeadingTrimEnd) {
708+
layoutState->addLeadingTrimEnd(*inlineFormattingContextRootForLeadingTrimEnd);
709+
// FIXME: Instead we should just damage the last line.
710+
if (auto* firstFormattedLineRoot = firstInlineFormattingContextRoot(m_flow); firstFormattedLineRoot && firstFormattedLineRoot != inlineFormattingContextRootForLeadingTrimEnd) {
711+
// When we run a "last line" layout on the last formatting context, we should not trim the first line ever (see FIXME).
712+
applyLeadingTrimStart = false;
713+
}
714+
}
715+
if (applyLeadingTrimStart)
716+
layoutState->addLeadingTrimStart();
717+
}
718+
719+
void LeadingTrimmer::adjustLeadingTrimAfterLayout()
720+
{
721+
auto* layoutState = m_flow.view().frameView().layoutContext().layoutState();
722+
if (!layoutState)
723+
return;
724+
if (m_flow.style().leadingTrim() != LeadingTrim::Normal)
725+
return layoutState->resetLeadingTrim();
726+
// This is propagated leading-trim.
727+
if (layoutState->hasLeadingTrimStart() && m_flow.hasLines()) {
728+
// Only the first formatted line is trimmed.
729+
layoutState->removeLeadingTrimStart();
730+
}
731+
}
732+
733+
void RenderBlockFlow::layoutInFlowChildren(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom, LayoutUnit& maxFloatLogicalBottom)
734+
{
735+
if (childrenInline()) {
736+
auto leadingTrimmer = LeadingTrimmer { *this, this };
737+
layoutInlineChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom);
738+
return;
739+
}
740+
741+
{
742+
// With block children, there's no way to tell what the last formatted line is until after we finished laying out the subtree.
743+
auto leadingTrimmer = LeadingTrimmer { *this };
744+
layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom);
745+
}
746+
747+
auto handleLeadingTrimEnd = [&] {
748+
auto hasLeadingTrimEnd = style().leadingTrim() == LeadingTrim::End || style().leadingTrim() == LeadingTrim::Both;
749+
if (!hasLeadingTrimEnd)
750+
return;
751+
// Dirty the last formatted line (in the last IFC) and issue relayout with forcing trimming the last line.
752+
if (auto* rootForLastFormattedLine = lastInlineFormattingContextRoot(*this)) {
753+
for (RenderBlock* ancestor = rootForLastFormattedLine; ancestor && ancestor != this; ancestor = ancestor->containingBlock()) {
754+
// FIXME: We should be able to damage the last line only.
755+
ancestor->setNeedsLayout(MarkOnlyThis);
756+
}
757+
758+
auto leadingTrimmer = LeadingTrimmer { *this, rootForLastFormattedLine };
759+
layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom);
760+
}
761+
};
762+
handleLeadingTrimEnd();
763+
}
764+
679765
void RenderBlockFlow::layoutBlockChildren(bool relayoutChildren, LayoutUnit& maxFloatLogicalBottom)
680766
{
681767
LayoutUnit beforeEdge = borderAndPaddingBefore();

Source/WebCore/rendering/RenderBlockFlow.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class RenderBlockFlow : public RenderBlock {
6666

6767
// RenderBlockFlow always contains either lines or paragraphs. When the children are all blocks (e.g. paragraphs), we call layoutBlockChildren.
6868
// When the children are all inline (e.g., lines), we call layoutInlineChildren.
69+
void layoutInFlowChildren(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom, LayoutUnit& maxFloatLogicalBottom);
6970
void layoutBlockChildren(bool relayoutChildren, LayoutUnit& maxFloatLogicalBottom);
7071
void layoutInlineChildren(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom);
7172

@@ -526,8 +527,8 @@ class RenderBlockFlow : public RenderBlock {
526527
void computeInlinePreferredLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const;
527528
void adjustInitialLetterPosition(RenderBox& childBox, LayoutUnit& logicalTopOffset, LayoutUnit& marginBeforeOffset);
528529

529-
void setLeadingTrimForSubtree();
530-
void resetLeadingTrim();
530+
void setLeadingTrimForSubtree(const RenderBlockFlow* inlineFormattingContextRootForLeadingTrimEnd = nullptr);
531+
void adjustLeadingTrimAfterLayout();
531532

532533
#if ENABLE(TEXT_AUTOSIZING)
533534
int m_widthForTextAutosizing;

Source/WebCore/rendering/RenderElement.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ class RenderElement : public RenderObject {
310310
void paintFocusRing(const PaintInfo&, const RenderStyle&, const Vector<LayoutRect>& focusRingRects) const;
311311

312312
virtual bool establishesIndependentFormattingContext() const;
313+
bool createsNewFormattingContext() const;
313314

314315
protected:
315316
enum BaseTypeFlag {
@@ -363,8 +364,6 @@ class RenderElement : public RenderObject {
363364

364365
bool isVisibleInViewport() const;
365366

366-
bool createsNewFormattingContext() const;
367-
368367
bool shouldApplyLayoutOrPaintContainment(bool) const;
369368
bool shouldApplySizeOrStyleContainment(bool) const;
370369

Source/WebCore/rendering/RenderLayoutState.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ RenderLayoutState::RenderLayoutState(RenderElement& renderer, IsPaginated isPagi
6262
}
6363
}
6464

65-
RenderLayoutState::RenderLayoutState(const FrameViewLayoutContext::LayoutStateStack& layoutStateStack, RenderBox& renderer, const LayoutSize& offset, LayoutUnit pageLogicalHeight, bool pageLogicalHeightChanged, std::optional<size_t> maximumLineCountForLineClamp, std::optional<size_t> visibleLineCountForLineClamp, LeadingTrim leadingTrim)
65+
RenderLayoutState::RenderLayoutState(const FrameViewLayoutContext::LayoutStateStack& layoutStateStack, RenderBox& renderer, const LayoutSize& offset, LayoutUnit pageLogicalHeight, bool pageLogicalHeightChanged, std::optional<size_t> maximumLineCountForLineClamp, std::optional<size_t> visibleLineCountForLineClamp, std::optional<LeadingTrim> leadingTrim)
6666
: m_clipped(false)
6767
, m_isPaginated(false)
6868
, m_pageLogicalHeightChanged(false)

Source/WebCore/rendering/RenderLayoutState.h

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "FrameViewLayoutContext.h"
2929
#include "LayoutRect.h"
3030
#include <wtf/Noncopyable.h>
31+
#include <wtf/WeakPtr.h>
3132

3233
namespace WebCore {
3334

@@ -42,9 +43,9 @@ class RenderLayoutState {
4243
WTF_MAKE_NONCOPYABLE(RenderLayoutState); WTF_MAKE_FAST_ALLOCATED;
4344

4445
public:
45-
enum LeadingTrimSide : uint8_t {
46-
Start = 1 << 0,
47-
End = 1 << 1
46+
struct LeadingTrim {
47+
bool trimFirstFormattedLine { false };
48+
WeakPtr<const RenderBlockFlow> trimLastFormattedLineOnTarget;
4849
};
4950

5051
RenderLayoutState()
@@ -57,7 +58,7 @@ class RenderLayoutState {
5758
#endif
5859
{
5960
}
60-
RenderLayoutState(const FrameViewLayoutContext::LayoutStateStack&, RenderBox&, const LayoutSize& offset, LayoutUnit pageHeight, bool pageHeightChanged, std::optional<size_t> maximumLineCountForLineClamp, std::optional<size_t> visibleLineCountForLineClamp, OptionSet<LeadingTrimSide>);
61+
RenderLayoutState(const FrameViewLayoutContext::LayoutStateStack&, RenderBox&, const LayoutSize& offset, LayoutUnit pageHeight, bool pageHeightChanged, std::optional<size_t> maximumLineCountForLineClamp, std::optional<size_t> visibleLineCountForLineClamp, std::optional<LeadingTrim>);
6162
enum class IsPaginated { No, Yes };
6263
explicit RenderLayoutState(RenderElement&, IsPaginated = IsPaginated::No);
6364

@@ -100,11 +101,14 @@ class RenderLayoutState {
100101
void setVisibleLineCountForLineClamp(size_t visibleLineCount) { m_visibleLineCountForLineClamp = visibleLineCount; }
101102
std::optional<size_t> visibleLineCountForLineClamp() const { return m_visibleLineCountForLineClamp; }
102103

103-
using LeadingTrim = OptionSet<LeadingTrimSide>;
104-
bool hasLeadingTrim() const { return !leadingTrim().isEmpty(); }
105-
LeadingTrim leadingTrim() const { return m_leadingTrim; }
106-
void addLeadingTrim(LeadingTrimSide leadingTrim) { m_leadingTrim.add(leadingTrim); }
107-
void removeLeadingTrim(LeadingTrimSide leadingTrim) { m_leadingTrim.remove(leadingTrim); }
104+
std::optional<LeadingTrim> leadingTrim() { return m_leadingTrim; }
105+
bool hasLeadingTrimStart() const { return m_leadingTrim && m_leadingTrim->trimFirstFormattedLine; }
106+
bool hasLeadingTrimEnd(const RenderBlockFlow& candidate) const { return m_leadingTrim && m_leadingTrim->trimLastFormattedLineOnTarget.get() == &candidate; }
107+
108+
void addLeadingTrimStart();
109+
void removeLeadingTrimStart();
110+
111+
void addLeadingTrimEnd(const RenderBlockFlow& targetInlineFormattingContext);
108112
void resetLeadingTrim() { m_leadingTrim = { }; }
109113

110114
private:
@@ -149,7 +153,7 @@ class RenderLayoutState {
149153
LayoutSize m_lineGridPaginationOrigin;
150154
std::optional<size_t> m_maximumLineCountForLineClamp;
151155
std::optional<size_t> m_visibleLineCountForLineClamp;
152-
LeadingTrim m_leadingTrim;
156+
std::optional<LeadingTrim> m_leadingTrim;
153157
#if ASSERT_ENABLED
154158
RenderElement* m_renderer { nullptr };
155159
#endif
@@ -198,6 +202,30 @@ class PaginatedLayoutStateMaintainer {
198202
bool m_pushed { false };
199203
};
200204

205+
inline void RenderLayoutState::addLeadingTrimStart()
206+
{
207+
if (m_leadingTrim) {
208+
m_leadingTrim->trimFirstFormattedLine = true;
209+
return;
210+
}
211+
m_leadingTrim = { true, { } };
212+
}
213+
214+
inline void RenderLayoutState::removeLeadingTrimStart()
215+
{
216+
ASSERT(m_leadingTrim && m_leadingTrim->trimFirstFormattedLine);
217+
m_leadingTrim->trimFirstFormattedLine = false;
218+
}
219+
220+
inline void RenderLayoutState::addLeadingTrimEnd(const RenderBlockFlow& targetInlineFormattingContext)
221+
{
222+
if (m_leadingTrim) {
223+
m_leadingTrim->trimLastFormattedLineOnTarget = &targetInlineFormattingContext;
224+
return;
225+
}
226+
m_leadingTrim = { false, &targetInlineFormattingContext };
227+
}
228+
201229
inline void RenderLayoutState::resetLineClamp()
202230
{
203231
m_maximumLineCountForLineClamp = { };

0 commit comments

Comments
 (0)