[LFC] Remove redundant InlineFormattingContext::computeBorderAndPadding
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineFormattingContext.cpp
index f872527..fb982b3 100644 (file)
@@ -28,8 +28,6 @@
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
-#include "FloatingContext.h"
-#include "FloatingState.h"
 #include "InlineFormattingState.h"
 #include "InlineLineBreaker.h"
 #include "InlineRunProvider.h"
@@ -48,302 +46,167 @@ namespace Layout {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
 
-InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, FormattingState& formattingState)
+InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState)
     : FormattingContext(formattingContextRoot, formattingState)
 {
 }
 
+static inline const Box* nextInPreOrder(const Box& layoutBox, const Container& root)
+{
+    const Box* nextInPreOrder = nullptr;
+    if (!layoutBox.establishesFormattingContext() && is<Container>(layoutBox) && downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
+        return downcast<Container>(layoutBox).firstInFlowOrFloatingChild();
+
+    for (nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &root; nextInPreOrder = nextInPreOrder->parent()) {
+        if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling())
+            return nextSibling;
+    }
+    return nullptr;
+}
+
 void InlineFormattingContext::layout() const
 {
     if (!is<Container>(root()))
         return;
 
     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
+    auto& root = downcast<Container>(this->root());
+    auto usedValues = UsedHorizontalValues { layoutState().displayBoxForLayoutBox(root).contentBoxWidth() };
+    auto* layoutBox = root.firstInFlowOrFloatingChild();
+    // Compute width/height for non-text content and margin/border/padding for inline containers.
+    while (layoutBox) {
+        if (layoutBox->establishesFormattingContext())
+            layoutFormattingContextRoot(*layoutBox, usedValues);
+        else if (is<Container>(*layoutBox)) {
+            auto& inlineContainer = downcast<InlineContainer>(*layoutBox);
+            computeMargin(inlineContainer, usedValues);
+            computeBorderAndPadding(inlineContainer, usedValues);
+        } else if (layoutBox->isReplaced())
+            computeWidthAndHeightForReplacedInlineBox(*layoutBox, usedValues);
+        layoutBox = nextInPreOrder(*layoutBox, root);
+    }
 
     InlineRunProvider inlineRunProvider;
     collectInlineContent(inlineRunProvider);
-    // Compute width/height for non-text content.
-    for (auto& inlineRun : inlineRunProvider.runs()) {
-        if (inlineRun.isText())
-            continue;
-
-        auto& layoutBox = inlineRun.inlineItem().layoutBox();
-        if (layoutBox.establishesFormattingContext()) {
-            layoutFormattingContextRoot(layoutBox);
-            continue;
-        }
-        computeWidthAndHeightForReplacedInlineBox(layoutBox);
-    }
-
-    layoutInlineContent(inlineRunProvider);
-    LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root() << ")");
+    LineLayout(*this).layout(inlineRunProvider);
+    LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root << ")");
 }
 
-static bool isTrimmableContent(const InlineLineBreaker::Run& run)
+void InlineFormattingContext::computeIntrinsicWidthConstraints() const
 {
-    return run.content.isWhitespace() && run.content.style().collapseWhiteSpace();
-}
+    ASSERT(is<Container>(root()));
 
-void InlineFormattingContext::initializeNewLine(Line& line) const
-{
-    auto& formattingRoot = downcast<Container>(root());
-    auto& formattingRootDisplayBox = layoutState().displayBoxForLayoutBox(formattingRoot);
-
-    auto lineLogicalLeft = formattingRootDisplayBox.contentBoxLeft();
-    auto lineLogicalTop = line.isFirstLine() ? formattingRootDisplayBox.contentBoxTop() : line.logicalBottom();
-    auto availableWidth = formattingRootDisplayBox.contentBoxWidth();
-
-    // Check for intruding floats and adjust logical left/available width for this line accordingly.
-    auto& floatingState = formattingState().floatingState();
-    if (!floatingState.isEmpty()) {
-        auto floatConstraints = floatingState.constraints({ lineLogicalTop }, formattingRoot);
-        // Check if these constraints actually put limitation on the line.
-        if (floatConstraints.left && *floatConstraints.left <= formattingRootDisplayBox.contentBoxLeft())
-            floatConstraints.left = { };
-
-        if (floatConstraints.right && *floatConstraints.right >= formattingRootDisplayBox.contentBoxRight())
-            floatConstraints.right = { };
-
-        if (floatConstraints.left && floatConstraints.right) {
-            ASSERT(*floatConstraints.left < *floatConstraints.right);
-            availableWidth = *floatConstraints.right - *floatConstraints.left;
-            lineLogicalLeft = *floatConstraints.left;
-        } else if (floatConstraints.left) {
-            ASSERT(*floatConstraints.left > lineLogicalLeft);
-            availableWidth -= (*floatConstraints.left - lineLogicalLeft);
-            lineLogicalLeft = *floatConstraints.left;
-        } else if (floatConstraints.right) {
-            ASSERT(*floatConstraints.right > lineLogicalLeft);
-            availableWidth = *floatConstraints.right - lineLogicalLeft;
+    auto& layoutState = this->layoutState();
+    auto& root = downcast<Container>(this->root());
+    ASSERT(!layoutState.formattingStateForBox(root).intrinsicWidthConstraints(root));
+
+    Vector<const Box*> formattingContextRootList;
+    auto usedValues = UsedHorizontalValues { };
+    auto* layoutBox = root.firstInFlowOrFloatingChild();
+    while (layoutBox) {
+        if (layoutBox->establishesFormattingContext()) {
+            formattingContextRootList.append(layoutBox);
+            if (layoutBox->isFloatingPositioned())
+                computeIntrinsicWidthForFloatBox(*layoutBox);
+            else if (layoutBox->isInlineBlockBox())
+                computeIntrinsicWidthForInlineBlock(*layoutBox);
+            else
+                ASSERT_NOT_REACHED();
+        } else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) {
+            computeBorderAndPadding(*layoutBox, usedValues);
+            // inline-block and replaced.
+            auto needsWidthComputation = layoutBox->isReplaced() || layoutBox->establishesFormattingContext();
+            if (needsWidthComputation)
+                computeWidthAndMargin(*layoutBox, usedValues);
+            else {
+                // Simple inline container with no intrinsic width <span>.
+                computeMargin(*layoutBox, usedValues);
+            }
         }
+        layoutBox = nextInPreOrder(*layoutBox, root);
     }
 
-    line.init({ lineLogicalLeft, lineLogicalTop }, availableWidth, formattingRoot.style().computedLineHeight());
-}
-
-void InlineFormattingContext::splitInlineRunIfNeeded(const InlineRun& inlineRun, InlineRuns& splitRuns) const
-{
-    ASSERT(inlineRun.textContext());
-    ASSERT(inlineRun.overlapsMultipleInlineItems());
-    // In certain cases, a run can overlap multiple inline elements like this:
-    // <span>normal text content</span><span style="position: relative; left: 10px;">but this one needs a dedicated run</span><span>end of text</span>
-    // The content above generates one long run <normal text contentbut this one needs dedicated runend of text>
-    // However, since the middle run is positioned, it needs to be moved independently from the rest of the content, hence it needs a dedicated inline run.
-
-    // 1. Start with the first inline item (element) and travers the list until
-    // 2. either find an inline item that needs a dedicated run or we reach the end of the run
-    // 3. Create dedicate inline runs.
-    auto& inlineContent = inlineFormattingState().inlineContent();
-    auto contentStart = inlineRun.logicalLeft();
-    auto startPosition = inlineRun.textContext()->start();
-    auto remaningLength = inlineRun.textContext()->length();
-
-    struct Uncommitted {
-        const InlineItem* firstInlineItem { nullptr };
-        const InlineItem* lastInlineItem { nullptr };
-        unsigned length { 0 };
-    };
-    std::optional<Uncommitted> uncommitted;
-
-    auto commit = [&] {
-        if (!uncommitted)
-            return;
-
-        contentStart += uncommitted->firstInlineItem->nonBreakableStart();
-
-        auto runWidth = Geometry::runWidth(inlineContent, *uncommitted->firstInlineItem, startPosition, uncommitted->length, contentStart);
-        auto run = InlineRun { { inlineRun.logicalTop(), contentStart, runWidth, inlineRun.logicalHeight() }, *uncommitted->firstInlineItem };
-        run.setTextContext({ startPosition, uncommitted->length });
-        splitRuns.append(run);
-
-        contentStart += runWidth + uncommitted->lastInlineItem->nonBreakableEnd();
-        remaningLength -= uncommitted->length;
+    InlineRunProvider inlineRunProvider;
+    collectInlineContent(inlineRunProvider);
 
-        startPosition = 0;
-        uncommitted = { };
-    };
+    auto maximumLineWidth = [&](auto availableWidth) {
+        LayoutUnit maxContentLogicalRight;
+        auto lineBreaker = InlineLineBreaker { layoutState, formattingState().inlineContent(), inlineRunProvider.runs() };
+        LayoutUnit lineLogicalRight;
 
-    for (auto iterator = inlineContent.find(const_cast<InlineItem*>(&inlineRun.inlineItem())); iterator != inlineContent.end() && remaningLength > 0; ++iterator) {
-        auto& inlineItem = **iterator;
-
-        // Skip all non-inflow boxes (floats, out-of-flow positioned elements). They don't participate in the inline run context.
-        if (!inlineItem.layoutBox().isInFlow())
-            continue;
-
-        auto currentLength = [&] {
-            return std::min(remaningLength, inlineItem.textContent().length() - startPosition);
-        };
-
-        // 1. Break before/after -> requires dedicated run -> commit what we've got so far and also commit the current inline element as a separate inline run.
-        // 2. Break at the beginning of the inline element -> commit what we've got so far. Current element becomes the first uncommitted.
-        // 3. Break at the end of the inline element -> commit what we've got so far including the current element.
-        // 4. Inline element does not require run breaking -> add current inline element to uncommitted. Jump to the next element.
-        auto detachingRules = inlineItem.detachingRules();
-
-        // #1
-        if (detachingRules.containsAll({ InlineItem::DetachingRule::BreakAtStart, InlineItem::DetachingRule::BreakAtEnd })) {
-            commit();
-            uncommitted = Uncommitted { &inlineItem, &inlineItem, currentLength() };
-            commit();
-            continue;
+        // Switch to the min/max formatting root width values before formatting the lines.
+        for (auto* formattingRoot : formattingContextRootList) {
+            auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraints(*formattingRoot);
+            layoutState.displayBoxForLayoutBox(*formattingRoot).setContentBoxWidth(availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum);
         }
 
-        // #2
-        if (detachingRules.contains(InlineItem::DetachingRule::BreakAtStart))
-            commit();
-
-        // Add current inline item to uncommitted.
-        // #3 and #4
-        if (!uncommitted)
-            uncommitted = Uncommitted { &inlineItem, &inlineItem, 0 };
-        uncommitted->length += currentLength();
-        uncommitted->lastInlineItem = &inlineItem;
-
-        // #3
-        if (detachingRules.contains(InlineItem::DetachingRule::BreakAtEnd))
-            commit();
-    }
-    // Either all inline elements needed dedicated runs or neither of them.
-    if (!remaningLength || remaningLength == inlineRun.textContext()->length())
-        return;
-
-    commit();
-}
+        while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
+            if (run->position == InlineLineBreaker::Run::Position::LineBegin)
+                lineLogicalRight = 0;
+            lineLogicalRight += run->width;
 
-void InlineFormattingContext::createFinalRuns(Line& line) const
-{
-    auto& inlineFormattingState = this->inlineFormattingState();
-    for (auto& inlineRun : line.runs()) {
-        if (inlineRun.overlapsMultipleInlineItems()) {
-            InlineRuns splitRuns;
-            splitInlineRunIfNeeded(inlineRun, splitRuns);
-            for (auto& splitRun : splitRuns)
-                inlineFormattingState.appendInlineRun(splitRun);
-
-            if (!splitRuns.isEmpty())
-                continue;
+            maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
         }
+        return maxContentLogicalRight;
+    };
 
-        auto finalRun = [&] {
-            auto& inlineItem = inlineRun.inlineItem();
-            if (inlineItem.detachingRules().isEmpty())
-                return inlineRun;
-
-            InlineRun adjustedRun = inlineRun;
-            auto width = inlineRun.logicalWidth() - inlineItem.nonBreakableStart() - inlineItem.nonBreakableEnd();
-            adjustedRun.setLogicalLeft(inlineRun.logicalLeft() + inlineItem.nonBreakableStart());
-            adjustedRun.setLogicalWidth(width);
-            return adjustedRun;
-        };
-
-        inlineFormattingState.appendInlineRun(finalRun());
-    }
+    auto intrinsicWidthConstraints = FormattingContext::IntrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
+    layoutState.formattingStateForBox(root).setIntrinsicWidthConstraints(root, intrinsicWidthConstraints);
 }
 
-void InlineFormattingContext::postProcessInlineRuns(Line& line, IsLastLine isLastLine) const
+void InlineFormattingContext::computeIntrinsicWidthForFloatBox(const Box& layoutBox) const
 {
-    Geometry::alignRuns(root().style().textAlign(), line, isLastLine);
-    auto firstRunIndex = inlineFormattingState().inlineRuns().size();
-    createFinalRuns(line);
+    ASSERT(layoutBox.isFloatingPositioned());
+    auto& layoutState = this->layoutState();
 
-    placeInFlowPositionedChildren(firstRunIndex);
-}
+    auto usedHorizontalValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedHorizontalValues);
+    computeMargin(layoutBox, usedHorizontalValues);
+    layoutState.createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
 
-void InlineFormattingContext::closeLine(Line& line, IsLastLine isLastLine) const
-{
-    line.close();
-    if (!line.hasContent())
-        return;
+    auto usedVerticalValues = UsedVerticalValues { };
+    auto heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedVerticalValues, usedHorizontalValues);
 
-    postProcessInlineRuns(line, isLastLine);
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxHeight(heightAndMargin.height);
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
 }
 
-void InlineFormattingContext::appendContentToLine(Line& line, const InlineRunProvider::Run& run, const LayoutSize& runSize) const
+void InlineFormattingContext::computeIntrinsicWidthForInlineBlock(const Box& layoutBox) const
 {
-    auto lastRunType = line.lastRunType();
-    line.appendContent(run, runSize);
+    ASSERT(layoutBox.isInlineBlockBox());
 
-    if (root().style().textAlign() == TextAlignMode::Justify)
-        Geometry::computeExpansionOpportunities(line, run, lastRunType.value_or(InlineRunProvider::Run::Type::NonWhitespace));
+    auto usedValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeMargin(layoutBox, usedValues);
+    layoutState().createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
 }
 
-void InlineFormattingContext::layoutInlineContent(const InlineRunProvider& inlineRunProvider) const
+void InlineFormattingContext::computeMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
 {
-    auto& layoutState = this->layoutState();
-    auto& inlineFormattingState = this->inlineFormattingState();
-    auto floatingContext = FloatingContext { inlineFormattingState.floatingState() };
-
-    Line line;
-    initializeNewLine(line);
-
-    InlineLineBreaker lineBreaker(layoutState, inlineFormattingState.inlineContent(), inlineRunProvider.runs());
-    while (auto run = lineBreaker.nextRun(line.contentLogicalRight(), line.availableWidth(), !line.hasContent())) {
-        auto isFirstRun = run->position == InlineLineBreaker::Run::Position::LineBegin;
-        auto isLastRun = run->position == InlineLineBreaker::Run::Position::LineEnd;
-        auto generatesInlineRun = true;
-
-        // Position float and adjust the runs on line.
-        if (run->content.isFloat()) {
-            auto& floatBox = run->content.inlineItem().layoutBox();
-            computeFloatPosition(floatingContext, line, floatBox);
-            inlineFormattingState.floatingState().append(floatBox);
-
-            auto floatBoxWidth = layoutState.displayBoxForLayoutBox(floatBox).marginBox().width();
-            // Shrink availble space for current line and move existing inline runs.
-            floatBox.isLeftFloatingPositioned() ? line.adjustLogicalLeft(floatBoxWidth) : line.adjustLogicalRight(floatBoxWidth);
-
-            generatesInlineRun = false;
-        }
-
-        // 1. Initialize new line if needed.
-        // 2. Append inline run unless it is skipped.
-        // 3. Close current line if needed.
-        if (isFirstRun) {
-            // When the first run does not generate an actual inline run, the next run comes in first-run as well.
-            // No need to spend time on closing/initializing.
-            // Skip leading whitespace.
-            if (!generatesInlineRun || isTrimmableContent(*run))
-                continue;
-
-            if (line.hasContent()) {
-                // Previous run ended up being at the line end. Adjust the line accordingly.
-                if (!line.isClosed())
-                    closeLine(line, IsLastLine::No);
-                initializeNewLine(line);
-            }
-         }
-
-        if (generatesInlineRun) {
-            auto width = run->width;
-            auto height = run->content.isText() ? LayoutUnit(root().style().computedLineHeight()) : layoutState.displayBoxForLayoutBox(run->content.inlineItem().layoutBox()).height(); 
-            appendContentToLine(line, run->content, { width, height });
-        }
-
-        if (isLastRun)
-            closeLine(line, IsLastLine::No);
-    }
-
-    closeLine(line, IsLastLine::Yes);
+    auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues);
+    auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
+    displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
+    displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
 }
 
-void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox) const
+void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
 {
     auto& layoutState = this->layoutState();
-
     WidthAndMargin widthAndMargin;
     if (layoutBox.isFloatingPositioned())
-        widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox);
+        widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
     else if (layoutBox.isInlineBlockBox())
-        widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox);
+        widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues);
     else if (layoutBox.replaced())
-        widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox);
+        widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
     else
         ASSERT_NOT_REACHED();
 
     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
     displayBox.setContentBoxWidth(widthAndMargin.width);
-    displayBox.setHorizontalMargin(widthAndMargin.margin);
-    displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
+    displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
+    displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
 }
 
 void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
@@ -352,28 +215,28 @@ void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
 
     HeightAndMargin heightAndMargin;
     if (layoutBox.isFloatingPositioned())
-        heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox);
+        heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, { }, UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() });
     else if (layoutBox.isInlineBlockBox())
         heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
     else if (layoutBox.replaced())
-        heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox);
+        heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox, { });
     else
         ASSERT_NOT_REACHED();
 
     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
     displayBox.setContentBoxHeight(heightAndMargin.height);
-    displayBox.setVerticalNonCollapsedMargin(heightAndMargin.nonCollapsedMargin);
-    displayBox.setVerticalMargin(heightAndMargin.usedMarginValues());
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
 }
 
-void InlineFormattingContext::layoutFormattingContextRoot(const Box& root) const
+void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const
 {
     ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox());
+    ASSERT(usedValues.containingBlockWidth);
 
-    computeBorderAndPadding(root);
-    computeWidthAndMargin(root);
+    computeBorderAndPadding(root, usedValues);
+    computeWidthAndMargin(root, usedValues);
     // Swich over to the new formatting context (the one that the root creates).
-    auto formattingContext = layoutState().createFormattingStateForFormattingRootIfNeeded(root).createFormattingContext(root);
+    auto formattingContext = layoutState().createFormattingContext(root);
     formattingContext->layout();
     // Come back and finalize the root's height and margin.
     computeHeightAndMargin(root);
@@ -381,175 +244,138 @@ void InlineFormattingContext::layoutFormattingContextRoot(const Box& root) const
     formattingContext->layoutOutOfFlowDescendants(root);
 }
 
-void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox) const
+void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const
 {
     ASSERT(!layoutBox.isContainer());
     ASSERT(!layoutBox.establishesFormattingContext());
     ASSERT(layoutBox.replaced());
+    ASSERT(usedValues.containingBlockWidth);
 
-    computeBorderAndPadding(layoutBox);
-    computeWidthAndMargin(layoutBox);
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeWidthAndMargin(layoutBox, usedValues);
     computeHeightAndMargin(layoutBox);
 }
 
-void InlineFormattingContext::computeFloatPosition(const FloatingContext& floatingContext, Line& line, const Box& floatBox) const
+static void addDetachingRules(InlineItem& inlineItem, Optional<LayoutUnit> nonBreakableStartWidth, Optional<LayoutUnit> nonBreakableEndWidth)
 {
-    auto& layoutState = this->layoutState();
-    ASSERT(layoutState.hasDisplayBox(floatBox));
-    auto& displayBox = layoutState.displayBoxForLayoutBox(floatBox);
-
-    // Set static position first.
-    displayBox.setTopLeft({ line.contentLogicalRight(), line.logicalTop() });
-    // Float it.
-    displayBox.setTopLeft(floatingContext.positionForFloat(floatBox));
+    OptionSet<InlineItem::DetachingRule> detachingRules;
+    if (nonBreakableStartWidth) {
+        detachingRules.add(InlineItem::DetachingRule::BreakAtStart);
+        inlineItem.addNonBreakableStart(*nonBreakableStartWidth);
+    }
+    if (nonBreakableEndWidth) {
+        detachingRules.add(InlineItem::DetachingRule::BreakAtEnd);
+        inlineItem.addNonBreakableEnd(*nonBreakableEndWidth);
+    }
+    inlineItem.addDetachingRule(detachingRules);
 }
 
-void InlineFormattingContext::placeInFlowPositionedChildren(unsigned fistRunIndex) const
+static InlineItem& createAndAppendInlineItem(InlineRunProvider& inlineRunProvider, InlineContent& inlineContent, const Box& layoutBox)
 {
-    auto& inlineRuns = inlineFormattingState().inlineRuns();
-    for (auto runIndex = fistRunIndex; runIndex < inlineRuns.size(); ++runIndex) {
-        auto& inlineRun = inlineRuns[runIndex];
-
-        auto positionOffset = [&](auto& layoutBox) {
-            // FIXME: Need to figure out whether in-flow offset should stick. This might very well be temporary.
-            std::optional<LayoutSize> offset;
-            for (auto* box = &layoutBox; box != &root(); box = box->parent()) {
-                if (!box->isInFlowPositioned())
-                    continue;
-                offset = offset.value_or(LayoutSize()) + Geometry::inFlowPositionedPositionOffset(layoutState(), *box);
-            }
-            return offset;
-        };
-
-        if (auto offset = positionOffset(inlineRun.inlineItem().layoutBox())) {
-            inlineRun.moveVertically(offset->height());
-            inlineRun.moveHorizontally(offset->width());
-        }
-    }
+    ASSERT(layoutBox.isInlineLevelBox() || layoutBox.isFloatingPositioned());
+    auto inlineItem = std::make_unique<InlineItem>(layoutBox);
+    auto* inlineItemPtr = inlineItem.get();
+    inlineContent.add(WTFMove(inlineItem));
+    inlineRunProvider.append(*inlineItemPtr);
+    return *inlineItemPtr;
 }
 
-void InlineFormattingContext::collectInlineContentForSubtree(const Box& root, InlineRunProvider& inlineRunProvider) const
+void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
 {
-    // Collect inline content recursively and set breaking rules for the inline elements (for paddings, margins, positioned element etc).
-    auto& inlineFormattingState = this->inlineFormattingState();
-
-    auto createAndAppendInlineItem = [&] {
-        auto inlineItem = std::make_unique<InlineItem>(root);
-        inlineRunProvider.append(*inlineItem);
-        inlineFormattingState.inlineContent().add(WTFMove(inlineItem));
-    };
-
-    if (root.establishesFormattingContext() && &root != &(this->root())) {
-        createAndAppendInlineItem();
-        auto& inlineRun = *inlineFormattingState.inlineContent().last();
-
-        auto horizontalMargins = Geometry::computedNonCollapsedHorizontalMarginValue(layoutState(), root);
-        inlineRun.addDetachingRule({ InlineItem::DetachingRule::BreakAtStart, InlineItem::DetachingRule::BreakAtEnd });
-        inlineRun.addNonBreakableStart(horizontalMargins.left);
-        inlineRun.addNonBreakableEnd(horizontalMargins.right);
-        // Skip formatting root subtree. They are not part of this inline formatting context.
+    if (!is<Container>(root()))
         return;
-    }
-
-    if (!is<Container>(root)) {
-        createAndAppendInlineItem();
+    auto& root = downcast<Container>(this->root());
+    if (!root.hasInFlowOrFloatingChild())
         return;
-    }
-
-    auto* lastInlineBoxBeforeContainer = inlineFormattingState.lastInlineItem();
-    auto* child = downcast<Container>(root).firstInFlowOrFloatingChild();
-    while (child) {
-        collectInlineContentForSubtree(*child, inlineRunProvider);
-        child = child->nextInFlowOrFloatingSibling();
-    }
-
-    // FIXME: Revisit this when we figured out how inline boxes fit the display tree.
-    auto padding = Geometry::computedPadding(layoutState(), root);
-    auto border = Geometry::computedBorder(layoutState(), root);
-    auto horizontalMargins = Geometry::computedNonCollapsedHorizontalMarginValue(layoutState(), root);
-    // Setup breaking boundaries for this subtree.
-    auto* lastDescendantInlineBox = inlineFormattingState.lastInlineItem();
-    // Empty container?
-    if (lastInlineBoxBeforeContainer == lastDescendantInlineBox)
-        return;
-
-    auto rootBreaksAtStart = [&] {
-        if (&root == &(this->root()))
-            return false;
-        return (padding && padding->horizontal.left) || border.horizontal.left || horizontalMargins.left || root.isPositioned();
-    };
-
-    auto rootBreaksAtEnd = [&] {
-        if (&root == &(this->root()))
-            return false;
-        return (padding && padding->horizontal.right) || border.horizontal.right || horizontalMargins.right || root.isPositioned();
+    // The logic here is very similar to BFC layout.
+    // 1. Travers down the layout tree and collect "start" unbreakable widths (margin-left, border-left, padding-left)
+    // 2. Create InlineItem per leaf inline box (text nodes, inline-blocks, floats) and set "start" unbreakable width on them. 
+    // 3. Climb back and collect "end" unbreakable width and set it on the last InlineItem.
+    auto& layoutState = this->layoutState();
+    auto& inlineContent = formattingState().inlineContent();
+
+    enum class NonBreakableWidthType { Start, End };
+    auto nonBreakableWidth = [&](auto& container, auto type) {
+        auto& displayBox = layoutState.displayBoxForLayoutBox(container);
+        if (type == NonBreakableWidthType::Start)
+            return displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0);
+        return displayBox.marginEnd() + displayBox.borderRight() + displayBox.paddingRight().valueOr(0);
     };
 
-    if (rootBreaksAtStart()) {
-        InlineItem* firstDescendantInlineBox = nullptr;
-        auto& inlineContent = inlineFormattingState.inlineContent();
-
-        if (lastInlineBoxBeforeContainer) {
-            auto iterator = inlineContent.find(lastInlineBoxBeforeContainer);
-            firstDescendantInlineBox = (*++iterator).get();
-        } else
-            firstDescendantInlineBox = inlineContent.first().get();
-
-        ASSERT(firstDescendantInlineBox);
-        firstDescendantInlineBox->addDetachingRule(InlineItem::DetachingRule::BreakAtStart);
-        auto startOffset = border.horizontal.left + horizontalMargins.left;
-        if (padding)
-            startOffset += padding->horizontal.left;
-        firstDescendantInlineBox->addNonBreakableStart(startOffset);
-    }
-
-    if (rootBreaksAtEnd()) {
-        lastDescendantInlineBox->addDetachingRule(InlineItem::DetachingRule::BreakAtEnd);
-        auto endOffset = border.horizontal.right + horizontalMargins.right;
-        if (padding)
-            endOffset += padding->horizontal.right;
-        lastDescendantInlineBox->addNonBreakableEnd(endOffset);
-    }
-}
-
-void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
-{
-    collectInlineContentForSubtree(root(), inlineRunProvider);
-}
-
-FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsicWidthConstraints() const
-{
-    auto& formattingStateForRoot = layoutState().formattingStateForBox(root());
-    if (auto instrinsicWidthConstraints = formattingStateForRoot.instrinsicWidthConstraints(root()))
-        return *instrinsicWidthConstraints;
-
-    auto& inlineFormattingState = this->inlineFormattingState();
-    InlineRunProvider inlineRunProvider;
-    collectInlineContent(inlineRunProvider);
+    LayoutQueue layoutQueue;
+    layoutQueue.append(root.firstInFlowOrFloatingChild());
+
+    Optional<LayoutUnit> nonBreakableStartWidth;
+    Optional<LayoutUnit> nonBreakableEndWidth;
+    InlineItem* lastInlineItem = nullptr;
+    while (!layoutQueue.isEmpty()) {
+        while (true) {
+            auto& layoutBox = *layoutQueue.last();
+            if (!is<Container>(layoutBox))
+                break;
+            auto& container = downcast<Container>(layoutBox);
+
+            if (container.establishesFormattingContext()) {
+                // Formatting contexts are treated as leaf nodes.
+                auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, container);
+                auto& displayBox = layoutState.displayBoxForLayoutBox(container);
+                auto currentNonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + displayBox.marginStart() + nonBreakableEndWidth.valueOr(0);
+                addDetachingRules(inlineItem, currentNonBreakableStartWidth, displayBox.marginEnd());
+                nonBreakableStartWidth = { };
+                nonBreakableEndWidth = { };
+
+                // Formatting context roots take care of their subtrees. Continue with next sibling if exists.
+                layoutQueue.removeLast();
+                if (!container.nextInFlowOrFloatingSibling())
+                    break;
+                layoutQueue.append(container.nextInFlowOrFloatingSibling());
+                continue;
+            }
 
-    // Compute width for non-text content.
-    for (auto& inlineRun : inlineRunProvider.runs()) {
-        if (inlineRun.isText())
-            continue;
+            // Check if this non-formatting context container has any non-breakable start properties (margin-left, border-left, padding-left)
+            // <span style="padding-left: 5px"><span style="padding-left: 5px">foobar</span></span> -> 5px + 5px
+            auto currentNonBreakableStartWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::Start);
+            if (currentNonBreakableStartWidth || layoutBox.isPositioned())
+                nonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + currentNonBreakableStartWidth;
 
-        computeWidthAndMargin(inlineRun.inlineItem().layoutBox());
-    }
+            if (!container.hasInFlowOrFloatingChild())
+                break;
+            layoutQueue.append(container.firstInFlowOrFloatingChild());
+        }
 
-    auto maximumLineWidth = [&](auto availableWidth) {
-        LayoutUnit maxContentLogicalRight;
-        InlineLineBreaker lineBreaker(layoutState(), inlineFormattingState.inlineContent(), inlineRunProvider.runs());
-        LayoutUnit lineLogicalRight;
-        while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
-            if (run->position == InlineLineBreaker::Run::Position::LineBegin)
-                lineLogicalRight = 0;
-            lineLogicalRight += run->width;
+        while (!layoutQueue.isEmpty()) {
+            auto& layoutBox = *layoutQueue.takeLast();
+            if (is<Container>(layoutBox)) {
+                // This is the end of an inline container. Compute the non-breakable end width and add it to the last inline box.
+                // <span style="padding-right: 5px">foobar</span> -> 5px; last inline item -> "foobar"
+                auto currentNonBreakableEndWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::End);
+                if (currentNonBreakableEndWidth || layoutBox.isPositioned())
+                    nonBreakableEndWidth = nonBreakableEndWidth.valueOr(0) + currentNonBreakableEndWidth;
+                // Add it to the last inline box
+                if (lastInlineItem) {
+                    addDetachingRules(*lastInlineItem, { }, nonBreakableEndWidth);
+                    nonBreakableEndWidth = { };
+                }
+            } else {
+                // Leaf inline box
+                auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, layoutBox);
+                // Add start and the (through empty containers) accumulated end width.
+                // <span style="padding-left: 1px">foobar</span> -> nonBreakableStartWidth: 1px;
+                // <span style="padding: 5px"></span>foobar -> nonBreakableStartWidth: 5px; nonBreakableEndWidth: 5px
+                if (nonBreakableStartWidth || nonBreakableEndWidth) {
+                    addDetachingRules(inlineItem, nonBreakableStartWidth.valueOr(0) + nonBreakableEndWidth.valueOr(0), { });
+                    nonBreakableStartWidth = { };
+                    nonBreakableEndWidth = { };
+                }
+                lastInlineItem = &inlineItem;
+            }
 
-            maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
+            if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
+                layoutQueue.append(nextSibling);
+                break;
+            }
         }
-        return maxContentLogicalRight;
-    };
-
-    return FormattingContext::InstrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
+    }
 }
 
 }