blob: a358483a46f36ea5d52342a44145f783c0677390 [file] [log] [blame]
/*
* Copyright (C) 2000 Lars Knoll ([email protected])
* Copyright (C) 2003-2023 Apple Inc. All right reserved.
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2013 ChangSeok Oh <[email protected]>
* Copyright (C) 2013 Adobe Systems Inc. All right reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "LegacyLineLayout.h"
#include "AXObjectCache.h"
#include "BidiResolver.h"
#include "BreakingContext.h"
#include "DocumentView.h"
#include "FloatingObjects.h"
#include "InlineIteratorBoxInlines.h"
#include "InlineIteratorTextBox.h"
#include "InlineTextBoxStyle.h"
#include "InlineWalker.h"
#include "LegacyInlineIterator.h"
#include "LegacyInlineTextBox.h"
#include "LineInfo.h"
#include "LineInlineHeaders.h"
#include "Logging.h"
#include "RenderBlockFlowInlines.h"
#include "RenderFragmentContainer.h"
#include "RenderFragmentedFlow.h"
#include "RenderLayoutState.h"
#include "RenderLineBreak.h"
#include "RenderObjectInlines.h"
#include "RenderSVGText.h"
#include "RenderView.h"
#include "SVGElementTypeHelpers.h"
#include "SVGRootInlineBox.h"
#include "Settings.h"
#include <wtf/StdLibExtras.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(LegacyLineLayout);
LegacyLineLayout::LegacyLineLayout(RenderBlockFlow& flow)
: m_flow(flow)
{
}
LegacyLineLayout::~LegacyLineLayout()
{
deleteLegacyRootBox(true);
}
static void determineDirectionality(TextDirection& dir, LegacyInlineIterator iter)
{
while (!iter.atEnd()) {
if (iter.atParagraphSeparator())
return;
if (char16_t current = iter.current()) {
UCharDirection charDirection = u_charDirection(current);
if (charDirection == U_LEFT_TO_RIGHT) {
dir = TextDirection::LTR;
return;
}
if (charDirection == U_RIGHT_TO_LEFT || charDirection == U_RIGHT_TO_LEFT_ARABIC) {
dir = TextDirection::RTL;
return;
}
}
iter.increment();
}
}
inline std::unique_ptr<BidiRun> createRun(int start, int end, RenderObject& obj, InlineBidiResolver& resolver)
{
return makeUnique<BidiRun>(start, end, obj, resolver.context(), resolver.dir());
}
bool LegacyLineLayout::shouldSkipCreatingRunsForObject(RenderObject& object)
{
if (is<RenderText>(object))
return false;
auto& renderElement = downcast<RenderElement>(object);
if (renderElement.isFloating())
return true;
if (renderElement.isOutOfFlowPositioned() && !renderElement.style().isOriginalDisplayInlineType() && !renderElement.container()->isRenderInline())
return true;
return false;
}
void LegacyLineLayout::appendRunsForObject(BidiRunList<BidiRun>* runs, int start, int end, RenderObject& obj, InlineBidiResolver& resolver)
{
if (start > end || shouldSkipCreatingRunsForObject(obj))
return;
LineWhitespaceCollapsingState& lineWhitespaceCollapsingState = resolver.whitespaceCollapsingState();
bool haveNextTransition = (lineWhitespaceCollapsingState.currentTransition() < lineWhitespaceCollapsingState.numTransitions());
LegacyInlineIterator nextTransition;
if (haveNextTransition)
nextTransition = lineWhitespaceCollapsingState.transitions()[lineWhitespaceCollapsingState.currentTransition()];
if (lineWhitespaceCollapsingState.betweenTransitions()) {
if (!haveNextTransition || (&obj != nextTransition.renderer()))
return;
// This is a new start point. Stop ignoring objects and
// adjust our start.
start = nextTransition.offset();
lineWhitespaceCollapsingState.incrementCurrentTransition();
if (start < end) {
appendRunsForObject(runs, start, end, obj, resolver);
return;
}
} else {
if (!haveNextTransition || (&obj != nextTransition.renderer())) {
if (runs)
runs->appendRun(createRun(start, end, obj, resolver));
return;
}
// An end transition has been encountered within our object. We need to append a run with our endpoint.
if (static_cast<int>(nextTransition.offset() + 1) <= end) {
lineWhitespaceCollapsingState.incrementCurrentTransition();
// The end of the line is before the object we're inspecting. Skip everything and return
if (nextTransition.refersToEndOfPreviousNode())
return;
if (static_cast<int>(nextTransition.offset() + 1) > start && runs)
runs->appendRun(createRun(start, nextTransition.offset() + 1, obj, resolver));
appendRunsForObject(runs, nextTransition.offset() + 1, end, obj, resolver);
} else if (runs)
runs->appendRun(createRun(start, end, obj, resolver));
}
}
std::unique_ptr<LegacyRootInlineBox> LegacyLineLayout::createRootInlineBox()
{
if (CheckedPtr svgText = dynamicDowncast<RenderSVGText>(m_flow)) {
auto box = makeUnique<SVGRootInlineBox>(*svgText);
box->setHasVirtualLogicalHeight();
return box;
}
return makeUnique<LegacyRootInlineBox>(m_flow);
}
LegacyRootInlineBox* LegacyLineLayout::createAndAppendRootInlineBox()
{
m_legacyRootInlineBox = createRootInlineBox();
if (AXObjectCache::accessibilityEnabled()) [[unlikely]] {
if (AXObjectCache* cache = m_flow.document().existingAXObjectCache())
cache->deferRecomputeIsIgnored(m_flow.element());
}
return m_legacyRootInlineBox.get();
}
LegacyInlineBox* LegacyLineLayout::createInlineBoxForRenderer(RenderObject* renderer)
{
if (renderer == &m_flow)
return createAndAppendRootInlineBox();
if (CheckedPtr textRenderer = dynamicDowncast<RenderSVGInlineText>(*renderer))
return textRenderer->createInlineTextBox();
if (CheckedPtr renderInline = dynamicDowncast<RenderInline>(renderer))
return renderInline->createAndAppendInlineFlowBox();
ASSERT_NOT_REACHED();
return nullptr;
}
static inline void dirtyLineBoxesForRenderer(RenderObject& renderer)
{
if (CheckedPtr renderText = dynamicDowncast<RenderSVGInlineText>(renderer))
renderText->deleteLegacyLineBoxes();
else if (CheckedPtr renderInline = dynamicDowncast<RenderInline>(renderer))
renderInline->deleteLegacyLineBoxes();
}
static bool parentIsConstructedOrHaveNext(LegacyInlineFlowBox* parentBox)
{
do {
if (parentBox->isConstructed() || parentBox->nextOnLine())
return true;
parentBox = parentBox->parent();
} while (parentBox);
return false;
}
LegacyInlineFlowBox* LegacyLineLayout::createLineBoxes(RenderObject* obj, const LineInfo& lineInfo, LegacyInlineBox* childBox)
{
// See if we have an unconstructed line box for this object that is also
// the last item on the line.
unsigned lineDepth = 1;
LegacyInlineFlowBox* parentBox = nullptr;
LegacyInlineFlowBox* result = nullptr;
do {
RenderInline* inlineFlow = obj != &m_flow ? &downcast<RenderInline>(*obj) : nullptr;
// Get the last box we made for this render object.
parentBox = inlineFlow ? inlineFlow->lastLegacyInlineBox() : downcast<RenderBlockFlow>(*obj).legacyRootBox();
// If this box or its ancestor is constructed then it is from a previous line, and we need
// to make a new box for our line. If this box or its ancestor is unconstructed but it has
// something following it on the line, then we know we have to make a new box
// as well. In this situation our inline has actually been split in two on
// the same line (this can happen with very fancy language mixtures).
bool constructedNewBox = false;
bool canUseExistingParentBox = parentBox && !parentIsConstructedOrHaveNext(parentBox);
if (!canUseExistingParentBox) {
// We need to make a new box for this render object. Once
// made, we need to place it at the end of the current line.
LegacyInlineBox* newBox = createInlineBoxForRenderer(obj);
parentBox = downcast<LegacyInlineFlowBox>(newBox);
parentBox->setIsFirstLine(lineInfo.isFirstLine());
parentBox->setIsHorizontal(m_flow.isHorizontalWritingMode());
constructedNewBox = true;
}
if (constructedNewBox || canUseExistingParentBox) {
if (!result)
result = parentBox;
// If we have hit the block itself, then |box| represents the root
// inline box for the line, and it doesn't have to be appended to any parent
// inline.
if (childBox)
parentBox->addToLine(childBox);
if (!constructedNewBox || obj == &m_flow)
break;
childBox = parentBox;
}
// If we've exceeded our line depth, then jump straight to the root and skip all the remaining
// intermediate inline flows.
obj = (++lineDepth >= cMaxLineDepth) ? &m_flow : obj->parent();
} while (true);
return result;
}
LegacyRootInlineBox* LegacyLineLayout::constructLine(BidiRunList<BidiRun>& bidiRuns, const LineInfo& lineInfo)
{
if (legacyRootBox()) {
// Refuse to create multiple lines for svg content. There should not need to be more than one.
ASSERT_NOT_REACHED();
return nullptr;
}
ASSERT(bidiRuns.firstRun());
LegacyInlineFlowBox* parentBox = 0;
for (BidiRun* r = bidiRuns.firstRun(); r; r = r->next()) {
if (lineInfo.isEmpty())
continue;
LegacyInlineBox* box = createInlineBoxForRenderer(&r->renderer());
r->setBox(box);
// If we have no parent box yet, or if the run is not simply a sibling,
// then we need to construct inline boxes as necessary to properly enclose the
// run's inline box. Segments can only be siblings at the root level, as
// they are positioned separately.
if (!parentBox || &parentBox->renderer() != r->renderer().parent()) {
// Create new inline boxes all the way back to the appropriate insertion point.
RenderObject* parentToUse = r->renderer().parent();
parentBox = createLineBoxes(parentToUse, lineInfo, box);
} else {
// Append the inline box to this line.
parentBox->addToLine(box);
}
box->setBidiLevel(r->level());
if (auto* textBox = dynamicDowncast<LegacyInlineTextBox>(*box)) {
textBox->setStart(r->m_start);
textBox->setLen(r->m_stop - r->m_start);
}
}
// We should have a root inline box. It should be unconstructed and
// be the last continuation of our line list.
ASSERT(legacyRootBox() && !legacyRootBox()->isConstructed());
// Now mark the line boxes as being constructed.
legacyRootBox()->setConstructed();
return legacyRootBox();
}
void LegacyLineLayout::removeInlineBox(BidiRun& run, const LegacyRootInlineBox& rootLineBox) const
{
auto* inlineBox = run.box();
#if ASSERT_ENABLED
auto* inlineParent = inlineBox->parent();
while (inlineParent && inlineParent != &rootLineBox) {
ASSERT(!inlineParent->isDirty());
inlineParent = inlineParent->parent();
}
ASSERT(!rootLineBox.isDirty());
#endif
auto* parent = inlineBox->parent();
inlineBox->removeFromParent();
auto& renderer = run.renderer();
if (CheckedPtr textRenderer = dynamicDowncast<RenderSVGInlineText>(renderer))
textRenderer->removeTextBox(downcast<LegacyInlineTextBox>(*inlineBox));
delete inlineBox;
run.setBox(nullptr);
// removeFromParent() unnecessarily dirties the ancestor subtree.
auto* ancestor = parent;
while (ancestor) {
ancestor->markDirty(false);
if (ancestor == &rootLineBox)
break;
ancestor = ancestor->parent();
}
}
void LegacyLineLayout::removeEmptyTextBoxesAndUpdateVisualReordering(LegacyRootInlineBox* lineBox, BidiRun* firstRun)
{
for (auto* run = firstRun; run; run = run->next()) {
if (auto* inlineTextBox = dynamicDowncast<LegacyInlineTextBox>(*run->box())) {
auto& renderer = inlineTextBox->renderer();
if (!inlineTextBox->hasTextContent()) {
removeInlineBox(*run, *lineBox);
continue;
}
if (!inlineTextBox->isLeftToRightDirection())
renderer.setNeedsVisualReordering();
}
}
}
static inline void notifyResolverToResumeInIsolate(InlineBidiResolver& resolver, const RenderInline* root, RenderObject* startObject)
{
if (root != startObject) {
RenderObject* parent = startObject->parent();
notifyResolverToResumeInIsolate(resolver, root, parent);
notifyObserverEnteredObject(&resolver, startObject);
}
}
static inline void setUpResolverToResumeInIsolate(InlineBidiResolver& resolver, InlineBidiResolver& topResolver, BidiRun& isolatedRun, const RenderInline* root, RenderObject* startObject)
{
// Set up m_whitespaceCollapsingState
resolver.whitespaceCollapsingState() = topResolver.whitespaceCollapsingState();
resolver.whitespaceCollapsingState().setCurrentTransition(topResolver.whitespaceCollapsingTransitionForIsolatedRun(isolatedRun));
// Set up m_nestedIsolateCount
notifyResolverToResumeInIsolate(resolver, root, startObject);
}
// FIXME: BidiResolver should have this logic.
static inline void constructBidiRunsForSegment(InlineBidiResolver& topResolver, BidiRunList<BidiRun>& bidiRuns, const LegacyInlineIterator& endOfRuns, VisualDirectionOverride override)
{
// FIXME: We should pass a BidiRunList into createBidiRunsForLine instead
// of the resolver owning the runs.
ASSERT(&topResolver.runs() == &bidiRuns);
ASSERT(topResolver.position() != endOfRuns);
topResolver.createBidiRunsForLine(endOfRuns, override);
while (!topResolver.isolatedRuns().isEmpty()) {
// It does not matter which order we resolve the runs as long as we resolve them all.
auto isolatedRun = WTF::move(topResolver.isolatedRuns().last());
topResolver.isolatedRuns().removeLast();
RenderObject& startObject = isolatedRun.object;
// Only inlines make sense with unicode-bidi: isolate (blocks are already isolated).
// FIXME: Because enterIsolate is not passed a RenderObject, we have to crawl up the
// tree to see which parent inline is the isolate. We could change enterIsolate
// to take a RenderObject and do this logic there, but that would be a layering
// violation for BidiResolver (which knows nothing about RenderObject).
RenderInline* isolatedInline = downcast<RenderInline>(highestContainingIsolateWithinRoot(startObject, &isolatedRun.root));
ASSERT(isolatedInline);
InlineBidiResolver isolatedResolver;
auto unicodeBidi = isolatedInline->style().unicodeBidi();
TextDirection direction;
if (unicodeBidi == UnicodeBidi::Plaintext)
determineDirectionality(direction, LegacyInlineIterator(isolatedInline, &isolatedRun.object, 0));
else {
ASSERT(unicodeBidi == UnicodeBidi::Isolate || unicodeBidi == UnicodeBidi::IsolateOverride);
direction = isolatedInline->writingMode().bidiDirection();
}
isolatedResolver.setStatus(BidiStatus(direction, isOverride(unicodeBidi)));
setUpResolverToResumeInIsolate(isolatedResolver, topResolver, isolatedRun.runToReplace, isolatedInline, &startObject);
// The starting position is the beginning of the first run within the isolate that was identified
// during the earlier call to createBidiRunsForLine. This can be but is not necessarily the
// first run within the isolate.
LegacyInlineIterator iter = LegacyInlineIterator(isolatedInline, &startObject, isolatedRun.position);
isolatedResolver.setPositionIgnoringNestedIsolates(iter);
// We stop at the next end of line; we may re-enter this isolate in the next call to constructBidiRuns().
isolatedResolver.createBidiRunsForLine(endOfRuns, NoVisualOverride);
// Note that we do not delete the runs from the resolver.
// We're not guaranteed to get any BidiRuns in the previous step. If we don't, we allow the placeholder
// itself to be turned into an InlineBox. We can't remove it here without potentially losing track of
// the logically last run.
if (isolatedResolver.runs().runCount())
bidiRuns.replaceRunWithRuns(&isolatedRun.runToReplace, isolatedResolver.runs());
// If we encountered any nested isolate runs, just move them
// to the top resolver's list for later processing.
while (!isolatedResolver.isolatedRuns().isEmpty()) {
auto runWithContext = WTF::move(isolatedResolver.isolatedRuns().last());
isolatedResolver.isolatedRuns().removeLast();
topResolver.setWhitespaceCollapsingTransitionForIsolatedRun(runWithContext.runToReplace, isolatedResolver.whitespaceCollapsingTransitionForIsolatedRun(runWithContext.runToReplace));
topResolver.isolatedRuns().append(WTF::move(runWithContext));
}
}
}
// This function constructs line boxes for all of the text runs in the resolver and computes their position.
LegacyRootInlineBox* LegacyLineLayout::createLineBoxesFromBidiRuns(unsigned bidiLevel, BidiRunList<BidiRun>& bidiRuns, const LegacyInlineIterator& end, LineInfo& lineInfo)
{
if (!bidiRuns.runCount())
return nullptr;
// FIXME: Why is this only done when we had runs?
lineInfo.setLastLine(!end.renderer());
LegacyRootInlineBox* lineBox = constructLine(bidiRuns, lineInfo);
if (!lineBox)
return nullptr;
lineBox->setBidiLevel(bidiLevel);
removeEmptyTextBoxesAndUpdateVisualReordering(lineBox, bidiRuns.firstRun());
GlyphOverflowAndFallbackFontsMap textBoxDataMap;
lineBox->computeOverflow(lineBox->lineTop(), lineBox->lineBottom(), textBoxDataMap);
return lineBox;
}
static void repaintSelfPaintInlineBoxes(const LegacyRootInlineBox& rootInlineBox)
{
if (rootInlineBox.hasSelfPaintInlineBox()) {
for (auto* inlineBox = rootInlineBox.firstChild(); inlineBox; inlineBox = inlineBox->nextOnLine()) {
if (auto* renderer = dynamicDowncast<RenderLayerModelObject>(inlineBox->renderer()); renderer && renderer->hasSelfPaintingLayer())
renderer->repaint();
}
}
}
void LegacyLineLayout::layoutRunsAndFloats(bool hasInlineChild)
{
deleteLegacyRootBox(true);
TextDirection direction = style().writingMode().bidiDirection();
if (style().unicodeBidi() == UnicodeBidi::Plaintext)
determineDirectionality(direction, LegacyInlineIterator(&m_flow, firstInlineRendererSkippingEmpty(m_flow), 0));
InlineBidiResolver resolver;
resolver.setStatus(BidiStatus(direction, isOverride(style().unicodeBidi())));
LegacyInlineIterator iter = LegacyInlineIterator(&m_flow, firstInlineRendererSkippingEmpty(m_flow, &resolver), 0);
resolver.setPosition(iter, numberOfIsolateAncestors(iter));
if (hasInlineChild && !m_flow.selfNeedsLayout()) {
m_flow.setNeedsLayout(MarkOnlyThis); // Mark as needing a full layout to force us to repaint.
if (!layoutContext().needsFullRepaint() && m_flow.cachedLayerClippedOverflowRect()) {
// Because we waited until we were already inside layout to discover
// that the block really needed a full layout, we missed our chance to repaint the layer
// before layout started. Luckily the layer has cached the repaint rect for its original
// position and size, and so we can use that to make a repaint happen now.
m_flow.repaintUsingContainer(m_flow.containerForRepaint().renderer.get(), *m_flow.cachedLayerClippedOverflowRect());
}
}
layoutRunsAndFloatsInRange(resolver);
if (legacyRootBox())
repaintSelfPaintInlineBoxes(*legacyRootBox());
}
void LegacyLineLayout::layoutRunsAndFloatsInRange(InlineBidiResolver& resolver)
{
const RenderStyle& styleToUse = style();
LineWhitespaceCollapsingState& lineWhitespaceCollapsingState = resolver.whitespaceCollapsingState();
LegacyInlineIterator end = resolver.position();
RenderTextInfo renderTextInfo;
LineInfo lineInfo { };
LineBreaker lineBreaker(m_flow);
while (!end.atEnd()) {
lineWhitespaceCollapsingState.reset();
lineInfo.setEmpty(true);
lineInfo.resetRunsFromLeadingWhitespace();
end = lineBreaker.nextLineBreak(resolver, lineInfo, renderTextInfo);
renderTextInfo.lineBreakIteratorFactory.priorContext().reset();
if (resolver.position().atEnd()) {
// FIXME: We shouldn't be creating any runs in nextLineBreak to begin with!
// Once BidiRunList is separated from BidiResolver this will not be needed.
resolver.runs().clear();
resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed).
resolver.setPosition(LegacyInlineIterator(resolver.position().root(), 0, 0), 0);
break;
}
ASSERT(end != resolver.position());
if (!lineInfo.isEmpty()) {
VisualDirectionOverride override = (styleToUse.rtlOrdering() == Order::Visual ? (styleToUse.writingMode().isBidiLTR() ? VisualLeftToRightOverride : VisualRightToLeftOverride) : NoVisualOverride);
if (styleToUse.unicodeBidi() == UnicodeBidi::Plaintext && !resolver.context()->parent()) {
TextDirection direction = styleToUse.writingMode().bidiDirection();
determineDirectionality(direction, resolver.position());
resolver.setStatus(BidiStatus(direction, isOverride(styleToUse.unicodeBidi())));
}
// FIXME: This ownership is reversed. We should own the BidiRunList and pass it to createBidiRunsForLine.
BidiRunList<BidiRun>& bidiRuns = resolver.runs();
constructBidiRunsForSegment(resolver, bidiRuns, end, override);
ASSERT(resolver.position() == end);
// Now that the runs have been ordered, we create the line boxes.
createLineBoxesFromBidiRuns(resolver.status().context->level(), bidiRuns, end, lineInfo);
bidiRuns.clear();
resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed).
}
if (!lineInfo.isEmpty())
lineInfo.setFirstLine(false);
lineWhitespaceCollapsingState.reset();
resolver.setPosition(end, numberOfIsolateAncestors(end));
}
}
void LegacyLineLayout::layoutLineBoxes()
{
m_flow.setLogicalHeight(0_lu);
deleteLegacyRootBox();
if (m_flow.firstChild()) {
// In full layout mode, clear the line boxes of children upfront. Otherwise,
// siblings can run into stale root lineboxes during layout. Then layout
// the replaced elements later. In partial layout mode, line boxes are not
// deleted and only dirtied. In that case, we can layout the replaced
// elements at the same time.
bool hasInlineChild = false;
for (InlineWalker walker(m_flow); !walker.atEnd(); walker.advance()) {
RenderObject& o = *walker.current();
if (!hasInlineChild && o.isInline())
hasInlineChild = true;
dirtyLineBoxesForRenderer(o);
o.clearNeedsLayout();
}
layoutRunsAndFloats(hasInlineChild);
}
if (!legacyRootBox() && m_flow.hasLineIfEmpty())
m_flow.setLogicalHeight(m_flow.logicalHeight() + m_flow.lineHeight());
}
void LegacyLineLayout::addOverflowFromInlineChildren()
{
if (auto* rootBox = legacyRootBox()) {
LayoutRect childVisualOverflowRect = rootBox->visualOverflowRect(rootBox->lineTop(), rootBox->lineBottom());
m_flow.addVisualOverflow(childVisualOverflowRect);
}
}
size_t LegacyLineLayout::lineCount() const
{
return legacyRootBox() ? 1 : 0;
}
const RenderStyle& LegacyLineLayout::style() const
{
return m_flow.style();
}
const LocalFrameViewLayoutContext& LegacyLineLayout::layoutContext() const
{
return m_flow.view().frameView().layoutContext();
}
void LegacyLineLayout::shiftLineBy(LayoutUnit shiftX, LayoutUnit shiftY)
{
if (m_legacyRootInlineBox)
m_legacyRootInlineBox->adjustPosition(shiftX, shiftY);
}
void LegacyLineLayout::deleteLegacyRootBox(bool runCleanup)
{
if (!m_legacyRootInlineBox)
return;
if (!runCleanup) {
m_legacyRootInlineBox = { };
return;
}
auto* rootInlineBoxToDestroy = m_legacyRootInlineBox.release();
rootInlineBoxToDestroy->deleteLine();
}
}