[LFC] Remove redundant InlineFormattingContext::computeBorderAndPadding
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineFormattingContext.cpp
index b2925e8..fb982b3 100644 (file)
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
-#include "FloatingState.h"
 #include "InlineFormattingState.h"
 #include "InlineLineBreaker.h"
 #include "InlineRunProvider.h"
 #include "LayoutBox.h"
 #include "LayoutContainer.h"
-#include "LayoutFormattingState.h"
 #include "LayoutInlineBox.h"
 #include "LayoutInlineContainer.h"
+#include "LayoutState.h"
 #include "Logging.h"
+#include "Textutil.h"
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/text/TextStream.h>
 
@@ -46,258 +46,336 @@ namespace Layout {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
 
-InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot)
-    : FormattingContext(formattingContextRoot)
+InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState)
+    : FormattingContext(formattingContextRoot, formattingState)
 {
 }
 
-void InlineFormattingContext::layout(LayoutState& layoutState, FormattingState& formattingState) const
+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 -> layout context(" << &layoutState << ") formatting root(" << &root() << ")");
-
-    auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState);
-    InlineRunProvider inlineRunProvider(inlineFormattingState);
-    auto& formattingRoot = downcast<Container>(root());
-    auto* layoutBox = formattingRoot.firstInFlowOrFloatingChild();
-    // Casually walk through the block's descendants and place the inline boxes one after the other as much as we can (yeah, I am looking at you floats).
+    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(layoutState, *layoutBox);
-            // Formatting context roots take care of their entire subtree. Continue with next sibling.
-            inlineRunProvider.append(*layoutBox);
-            layoutBox = layoutBox->nextInFlowOrFloatingSibling();
-            continue;
-        }
-
-        if (is<Container>(layoutBox)) {
-            ASSERT(is<InlineContainer>(layoutBox));
-            layoutBox = downcast<Container>(*layoutBox).firstInFlowOrFloatingChild();
-            continue;
-        }
-
-        inlineRunProvider.append(*layoutBox);
-        computeWidthAndHeightForInlineBox(layoutState, *layoutBox);
-
-        for (; layoutBox; layoutBox = layoutBox->parent()) {
-            if (layoutBox == &formattingRoot) {
-                layoutBox = nullptr;
-                break;
-            }
-            if (auto* nextSibling = layoutBox->nextInFlowOrFloatingSibling()) {
-                layoutBox = nextSibling;
-                break;
-            }
-        }
-        ASSERT(!layoutBox || layoutBox->isDescendantOf(formattingRoot));
+        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);
     }
 
-    layoutInlineContent(layoutState, inlineFormattingState, inlineRunProvider);
-
-    LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> layout context(" << &layoutState << ") formatting root(" << &root() << ")");
+    InlineRunProvider inlineRunProvider;
+    collectInlineContent(inlineRunProvider);
+    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(const LayoutState& layoutState, InlineFormattingState& inlineFormattingState, 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 = inlineFormattingState.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);
     }
 
-    Display::Box::Rect logicalRect;
-    logicalRect.setTop(lineLogicalTop);
-    logicalRect.setLeft(lineLogicalLeft);
-    logicalRect.setWidth(availableWidth);
-    logicalRect.setHeight(formattingRoot.style().computedLineHeight());
+    InlineRunProvider inlineRunProvider;
+    collectInlineContent(inlineRunProvider);
 
-    line.init(logicalRect);
-}
-
-void InlineFormattingContext::layoutInlineContent(const LayoutState& layoutState, InlineFormattingState& inlineFormattingState, const InlineRunProvider& inlineRunProvider) const
-{
-    auto floatingContext = FloatingContext { inlineFormattingState.floatingState() };
+    auto maximumLineWidth = [&](auto availableWidth) {
+        LayoutUnit maxContentLogicalRight;
+        auto lineBreaker = InlineLineBreaker { layoutState, formattingState().inlineContent(), inlineRunProvider.runs() };
+        LayoutUnit lineLogicalRight;
 
-    Line line(inlineFormattingState, root());
-    initializeNewLine(layoutState, inlineFormattingState, 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(layoutState, floatingContext, line, floatBox);
-            inlineFormattingState.floatingState().append(floatBox);
+        // 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);
+        }
 
-            auto floatBoxWidth = layoutState.displayBoxForLayoutBox(floatBox).width();
-            // Shrink availble space for current line and move existing inline runs.
-            floatBox.isLeftFloatingPositioned() ? line.adjustLogicalLeft(floatBoxWidth) : line.adjustLogicalRight(floatBoxWidth);
+        while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
+            if (run->position == InlineLineBreaker::Run::Position::LineBegin)
+                lineLogicalRight = 0;
+            lineLogicalRight += run->width;
 
-            generatesInlineRun = false;
+            maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
         }
+        return maxContentLogicalRight;
+    };
 
-        // 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;
+    auto intrinsicWidthConstraints = FormattingContext::IntrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
+    layoutState.formattingStateForBox(root).setIntrinsicWidthConstraints(root, intrinsicWidthConstraints);
+}
 
-            if (line.hasContent()) {
-                // Previous run ended up being at the line end. Adjust the line accordingly.
-                if (!line.isClosed())
-                    line.close(Line::LastLine::No);
-                initializeNewLine(layoutState, inlineFormattingState, line);
-            }
-         }
+void InlineFormattingContext::computeIntrinsicWidthForFloatBox(const Box& layoutBox) const
+{
+    ASSERT(layoutBox.isFloatingPositioned());
+    auto& layoutState = this->layoutState();
 
-        if (generatesInlineRun)
-             line.appendContent(*run);
+    auto usedHorizontalValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedHorizontalValues);
+    computeMargin(layoutBox, usedHorizontalValues);
+    layoutState.createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
 
-        if (isLastRun)
-            line.close(Line::LastLine::No);
-    }
+    auto usedVerticalValues = UsedVerticalValues { };
+    auto heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedVerticalValues, usedHorizontalValues);
 
-    line.close(Line::LastLine::Yes);
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxHeight(heightAndMargin.height);
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
 }
 
-void InlineFormattingContext::layoutFormattingContextRoot(LayoutState& layoutState, const Box& layoutBox) const
+void InlineFormattingContext::computeIntrinsicWidthForInlineBlock(const Box& layoutBox) const
 {
-    ASSERT(layoutBox.isFloatingPositioned() || layoutBox.isInlineBlockBox());
-    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    ASSERT(layoutBox.isInlineBlockBox());
 
-    auto computeWidthAndMargin = [&]() {
-        WidthAndMargin widthAndMargin;
+    auto usedValues = UsedHorizontalValues { };
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeMargin(layoutBox, usedValues);
+    layoutState().createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
+}
 
-        if (layoutBox.isFloatingPositioned())
-            widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, *this, layoutBox);
-        else if (layoutBox.isInlineBlockBox())
-            widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox);
-        else
-            ASSERT_NOT_REACHED();
+void InlineFormattingContext::computeMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
+{
+    auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues);
+    auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
+    displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
+    displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
+}
 
-        displayBox.setContentBoxWidth(widthAndMargin.width);
-        displayBox.setHorizontalMargin(widthAndMargin.margin);
-        displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
-    };
+void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
+{
+    auto& layoutState = this->layoutState();
+    WidthAndMargin widthAndMargin;
+    if (layoutBox.isFloatingPositioned())
+        widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
+    else if (layoutBox.isInlineBlockBox())
+        widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues);
+    else if (layoutBox.replaced())
+        widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
+    else
+        ASSERT_NOT_REACHED();
 
-    auto computeHeightAndMargin = [&]() {
-        HeightAndMargin heightAndMargin;
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxWidth(widthAndMargin.width);
+    displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
+    displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
+}
 
-        if (layoutBox.isFloatingPositioned())
-            heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox);
-        else if (layoutBox.isInlineBlockBox())
-            heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
-        else
-            ASSERT_NOT_REACHED();
+void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
+{
+    auto& layoutState = this->layoutState();
+
+    HeightAndMargin heightAndMargin;
+    if (layoutBox.isFloatingPositioned())
+        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, { });
+    else
+        ASSERT_NOT_REACHED();
 
-        displayBox.setContentBoxHeight(heightAndMargin.height);
-        displayBox.setVerticalNonCollapsedMargin(heightAndMargin.margin);
-        displayBox.setVerticalMargin(heightAndMargin.collapsedMargin.value_or(heightAndMargin.margin));
-    };
+    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
+    displayBox.setContentBoxHeight(heightAndMargin.height);
+    displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
+}
 
-    computeBorderAndPadding(layoutState, layoutBox);
-    computeWidthAndMargin();
+void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const
+{
+    ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox());
+    ASSERT(usedValues.containingBlockWidth);
 
+    computeBorderAndPadding(root, usedValues);
+    computeWidthAndMargin(root, usedValues);
     // Swich over to the new formatting context (the one that the root creates).
-    auto& formattingState = layoutState.createFormattingStateForFormattingRootIfNeeded(layoutBox);
-    formattingState.formattingContext(layoutBox)->layout(layoutState, formattingState);
-
+    auto formattingContext = layoutState().createFormattingContext(root);
+    formattingContext->layout();
     // Come back and finalize the root's height and margin.
-    computeHeightAndMargin();
+    computeHeightAndMargin(root);
+    // Now that we computed the root's height, we can go back and layout the out-of-flow descedants (if any).
+    formattingContext->layoutOutOfFlowDescendants(root);
 }
 
-void InlineFormattingContext::computeWidthAndHeightForInlineBox(LayoutState& layoutState, const Box& layoutBox) const
+void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const
 {
     ASSERT(!layoutBox.isContainer());
     ASSERT(!layoutBox.establishesFormattingContext());
-
-    if (is<InlineBox>(layoutBox) && downcast<InlineBox>(layoutBox).hasTextContent()) {
-        // Text content width is computed during text run generation. -It does not make any sense to measure unprocessed text here, since it will likely be
-        // split up (or concatenated).
-        return;
-    }
-
-    // This is pretty much only for replaced inline boxes atm.
     ASSERT(layoutBox.replaced());
-    computeBorderAndPadding(layoutState, layoutBox);
-
-    auto widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox);
-    auto heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox);
+    ASSERT(usedValues.containingBlockWidth);
 
-    auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
-    displayBox.setContentBoxWidth(widthAndMargin.width);
-    displayBox.setHorizontalMargin(widthAndMargin.margin);
-    displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
-
-    displayBox.setContentBoxHeight(heightAndMargin.height);
-    displayBox.setVerticalNonCollapsedMargin(heightAndMargin.margin);
-    displayBox.setVerticalMargin(heightAndMargin.collapsedMargin.value_or(heightAndMargin.margin));
+    computeBorderAndPadding(layoutBox, usedValues);
+    computeWidthAndMargin(layoutBox, usedValues);
+    computeHeightAndMargin(layoutBox);
 }
 
-void InlineFormattingContext::computeFloatPosition(const LayoutState& layoutState, const FloatingContext& floatingContext, Line& line, const Box& floatBox) const
+static void addDetachingRules(InlineItem& inlineItem, Optional<LayoutUnit> nonBreakableStartWidth, Optional<LayoutUnit> nonBreakableEndWidth)
 {
-    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::computeStaticPosition(const LayoutState&, const Box&) const
+static InlineItem& createAndAppendInlineItem(InlineRunProvider& inlineRunProvider, InlineContent& inlineContent, const Box& layoutBox)
 {
+    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::computeInFlowPositionedPosition(const LayoutState&, const Box&) const
+void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
 {
-}
+    if (!is<Container>(root()))
+        return;
+    auto& root = downcast<Container>(this->root());
+    if (!root.hasInFlowOrFloatingChild())
+        return;
+    // 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);
+    };
 
-FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsicWidthConstraints(LayoutState&, const Box&) const
-{
-    return { };
+    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;
+            }
+
+            // 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;
+
+            if (!container.hasInFlowOrFloatingChild())
+                break;
+            layoutQueue.append(container.firstInFlowOrFloatingChild());
+        }
+
+        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;
+            }
+
+            if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
+                layoutQueue.append(nextSibling);
+                break;
+            }
+        }
+    }
 }
 
 }