Changeset 257291 in webkit


Ignore:
Timestamp:
Feb 24, 2020 5:42:47 PM (4 years ago)
Author:
Alan Bujtas
Message:

[LFC][IFC][Floats] Fix float boxes embedded to unbreakable inline runs.
https://bugs.webkit.org/show_bug.cgi?id=208112
<rdar://problem/59709701>

Reviewed by Antti Koivisto.

This patch fixes the cases when the float is embedded to otherwise unbreakable inline content.
e.g. "text_<div style="float: left"></div>_content"

The logic goes like this:

  1. collect the floats inside the unbreakable candidate content
  2. mark them intrusive if they potentially influence the current line
  3. at handleFloatsAndInlineContent(), adjust available width with the intrusive floats first
  4. feed the inline content to the LineBreaker
  5. commit the float content based on the line breaking result (commit none, partially, all).

(Note that this algorithm produces a different layout compared to WebKit trunk. It mostly matches FireFox though.)

  • layout/inlineformatting/InlineFormattingContext.cpp:

(WebCore::Layout::InlineFormattingContext::computedIntrinsicWidthForConstraint const):
(WebCore::Layout::InlineFormattingContext::setDisplayBoxesForLine):

  • layout/inlineformatting/InlineLineBuilder.cpp:

(WebCore::Layout::LineBuilder::moveLogicalLeft):
(WebCore::Layout::LineBuilder::moveLogicalRight):

  • layout/inlineformatting/LineLayoutContext.cpp:

(WebCore::Layout::isAtSoftWrapOpportunity):
(WebCore::Layout::nextWrapOpportunity):
(WebCore::Layout::LineCandidate::FloatContent::append):
(WebCore::Layout::LineLayoutContext::layoutLine):
(WebCore::Layout::LineLayoutContext::close):
(WebCore::Layout::LineLayoutContext::nextContentForLine):
(WebCore::Layout::LineLayoutContext::addIntrusiveFloats):
(WebCore::Layout::LineLayoutContext::revertIntrusiveFloats):
(WebCore::Layout::LineLayoutContext::handleFloatsAndInlineContent):
(WebCore::Layout::isLineConsideredEmpty): Deleted.
(WebCore::Layout::LineLayoutContext::tryAddingFloatContent): Deleted.
(WebCore::Layout::LineLayoutContext::tryAddingInlineItems): Deleted.

  • layout/inlineformatting/LineLayoutContext.h:
Location:
trunk/Source/WebCore
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r257290 r257291  
     12020-02-24  Zalan Bujtas  <zalan@apple.com>
     2
     3        [LFC][IFC][Floats] Fix float boxes embedded to unbreakable inline runs.
     4        https://bugs.webkit.org/show_bug.cgi?id=208112
     5        <rdar://problem/59709701>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        This patch fixes the cases when the float is embedded to otherwise unbreakable inline content.
     10        e.g. "text_<div style="float: left"></div>_content"
     11
     12        The logic goes like this:
     13        1. collect the floats inside the unbreakable candidate content
     14        2. mark them intrusive if they potentially influence the current line
     15        3. at handleFloatsAndInlineContent(), adjust available width with the intrusive floats first
     16        4. feed the inline content to the LineBreaker
     17        6. commit the float content based on the line breaking result (commit none, partially, all).
     18        (Note that this algorithm produces a different layout compared to WebKit trunk. It mostly matches FireFox though.)
     19
     20        * layout/inlineformatting/InlineFormattingContext.cpp:
     21        (WebCore::Layout::InlineFormattingContext::computedIntrinsicWidthForConstraint const):
     22        (WebCore::Layout::InlineFormattingContext::setDisplayBoxesForLine):
     23        * layout/inlineformatting/InlineLineBuilder.cpp:
     24        (WebCore::Layout::LineBuilder::moveLogicalLeft):
     25        (WebCore::Layout::LineBuilder::moveLogicalRight):
     26        * layout/inlineformatting/LineLayoutContext.cpp:
     27        (WebCore::Layout::isAtSoftWrapOpportunity):
     28        (WebCore::Layout::nextWrapOpportunity):
     29        (WebCore::Layout::LineCandidate::FloatContent::append):
     30        (WebCore::Layout::LineLayoutContext::layoutLine):
     31        (WebCore::Layout::LineLayoutContext::close):
     32        (WebCore::Layout::LineLayoutContext::nextContentForLine):
     33        (WebCore::Layout::LineLayoutContext::addIntrusiveFloats):
     34        (WebCore::Layout::LineLayoutContext::revertIntrusiveFloats):
     35        (WebCore::Layout::LineLayoutContext::handleFloatsAndInlineContent):
     36        (WebCore::Layout::isLineConsideredEmpty): Deleted.
     37        (WebCore::Layout::LineLayoutContext::tryAddingFloatContent): Deleted.
     38        (WebCore::Layout::LineLayoutContext::tryAddingInlineItems): Deleted.
     39        * layout/inlineformatting/LineLayoutContext.h:
     40
    1412020-02-24  Nikos Mouchtaris  <nmouchtaris@apple.com>
    242
  • trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp

    r256928 r257291  
    241241        // Only the horiztonal available width is constrained when computing intrinsic width.
    242242        lineBuilder.initialize(LineBuilder::Constraints { { }, horizontalConstraints.logicalWidth, false, { } });
    243         auto lineContent = lineLayoutContext.layoutLine(lineBuilder, layoutRange , { });
    244 
     243        auto lineContent = lineLayoutContext.layoutLine(lineBuilder, layoutRange, { });
    245244        layoutRange.start = *lineContent.trailingInlineItemIndex + 1;
    246         InlineLayoutUnit floatsWidth = 0;
    247         for (auto& floatItem : lineContent.floats)
    248             floatsWidth += geometryForBox(floatItem->layoutBox()).marginBoxWidth();
    249         maximumLineWidth = std::max(maximumLineWidth, floatsWidth + lineContent.lineBox.logicalWidth());
     245        // FIXME: Use line logical left and right to take floats into account.
     246        maximumLineWidth = std::max(maximumLineWidth, lineContent.lineBox.logicalWidth());
    250247    }
    251248    return maximumLineWidth;
     
    438435        auto floatingContext = FloatingContext { root(), *this, formattingState.floatingState() };
    439436        // Move floats to their final position.
    440         for (const auto& floatItem : lineContent.floats) {
    441             auto& floatBox = floatItem->layoutBox();
     437        for (const auto& floatCandidate : lineContent.floats) {
     438            auto& floatBox = floatCandidate.item->layoutBox();
    442439            auto& displayBox = formattingState.displayBox(floatBox);
    443440            // Set static position first.
    444             displayBox.setTopLeft({ lineBox.logicalLeft(), lineBox.logicalTop() });
     441            auto verticalStaticPosition = floatCandidate.isIntrusive == LineLayoutContext::LineContent::Float::Intrusive::Yes ? lineBox.logicalTop() : lineBox.logicalBottom();
     442            displayBox.setTopLeft({ lineBox.logicalLeft(), verticalStaticPosition });
    445443            // Float it.
    446444            displayBox.setTopLeft(floatingContext.positionForFloat(floatBox, horizontalConstraints));
  • trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp

    r257236 r257291  
    7373    // An incoming <img> box would enable us to commit the "<span>prior_continuous_content</span>" content
    7474    // but an incoming text content would not necessarily.
    75     ASSERT(current.isText() || current.isBox() || current.isFloat());
    76     ASSERT(next.isText() || next.isBox() || next.isFloat());
     75    ASSERT(current.isText() || current.isBox());
     76    ASSERT(next.isText() || next.isBox());
    7777    if (current.isText() && next.isText()) {
    7878        auto& currentInlineTextItem = downcast<InlineTextItem>(current);
     
    102102        return true;
    103103    }
    104     if (current.isFloat() || next.isFloat()) {
    105         // Floats are not part of the inline content. We should treat them as if they were not here as far as wrap opportunitues.
    106         // [text][float box][text] is essentially just [text][text]
    107         return false;
    108     }
    109104    ASSERT_NOT_REACHED();
    110105    return true;
     
    124119
    125120    auto inlineItemIndexWithContent = [&] (auto index) {
    126         // Break at the first text/box/line break inline item.
     121        // Note that floats are not part of the inline content. We should treat them as if they were not here as far as wrap opportunities are concerned.
     122        // [text][float box][text] is essentially just [text][text]
    127123        for (; index < layoutRange.end; ++index) {
    128124            auto& inlineItem = inlineContent[index];
    129             if (inlineItem.isText() || inlineItem.isBox() || inlineItem.isFloat())
     125            if (inlineItem.isText() || inlineItem.isBox())
    130126                return index;
    131127            if (inlineItem.isLineBreak()) {
     
    198194
    199195    struct FloatContent {
    200         void append(const InlineItem& floatItem) { m_floatList.append(&floatItem); }
    201 
    202         using FloatList = Vector<const InlineItem*>;
     196        void append(const InlineItem& floatItem, InlineLayoutUnit logicalWidth, bool isIntrusive);
     197
     198        struct Float {
     199            const InlineItem* item { nullptr };
     200            InlineLayoutUnit logicalWidth { 0 };
     201            bool isIntrusive { true };
     202        };
     203        using FloatList = Vector<Float>;
    203204        const FloatList& list() const { return m_floatList; }
    204 
    205         void reset() { m_floatList.clear(); }
     205        InlineLayoutUnit intrusiveWidth() const { return m_intrusiveWidth; }
     206
     207        void reset();
    206208
    207209    private:
    208210        FloatList m_floatList;
     211        InlineLayoutUnit m_intrusiveWidth { 0 };
    209212    };
    210213    // Candidate content is a collection of inline items and/or float boxes.
     
    219222}
    220223
     224inline void LineCandidate::InlineContent::reset()
     225{
     226    m_LogicalWidth = { };
     227    m_inlineRuns.clear();
     228    m_trailingLineBreak = { };
     229}
     230
     231inline void LineCandidate::FloatContent::append(const InlineItem& floatItem, InlineLayoutUnit logicalWidth, bool isIntrusive)
     232{
     233    if (isIntrusive)
     234        m_intrusiveWidth += logicalWidth;
     235    m_floatList.append({ &floatItem, logicalWidth, isIntrusive });
     236}
     237
     238inline void LineCandidate::FloatContent::reset()
     239{
     240    m_floatList.clear();
     241    m_intrusiveWidth = { };
     242}
     243
    221244inline void LineCandidate::reset()
    222245{
    223246    floatContent.reset();
    224247    inlineContent.reset();
    225 }
    226 
    227 inline void LineCandidate::InlineContent::reset()
    228 {
    229     m_LogicalWidth = 0;
    230     m_inlineRuns.clear();
    231     m_trailingLineBreak = nullptr;
    232248}
    233249
     
    262278    // Non-replaced inline box (e.g. inline-block)
    263279    return boxGeometry.width();
    264 }
    265 
    266 static inline bool isLineConsideredEmpty(const LineBuilder& line)
    267 {
    268     return line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
    269280}
    270281
     
    290301        // 3. Check if the content fits the line and commit the content accordingly (full, partial or not commit at all).
    291302        // 4. Return if we are at the end of the line either by not being able to fit more content or because of an explicit line break.
    292         nextContentForLine(lineCandidate, currentItemIndex, layoutRange, partialLeadingContentLength, line.lineBox().logicalWidth());
    293         if (lineCandidate.floatContent.list().isEmpty()) {
    294             // Floats shrink the available horizontal space for the rest of the content, but they are not added on the line.
    295             auto result = tryAddingFloatContent(line, lineCandidate);
    296             committedInlineItemCount += result.committedCount.value;
    297             if (result.isEndOfLine == LineBreaker::IsEndOfLine::Yes) {
    298                 // This float takes up all the horizontal space.
    299                 return close(line, layoutRange, committedInlineItemCount, { });
    300             }
    301         }
     303        nextContentForLine(lineCandidate, currentItemIndex, layoutRange, partialLeadingContentLength, line.availableWidth() + line.trimmableTrailingWidth(), line.lineBox().logicalWidth());
     304        // Now check if we can put this content on the current line.
     305        auto result = handleFloatsAndInlineContent(lineBreaker, line, layoutRange, lineCandidate);
     306        committedInlineItemCount = result.committedCount.isRevert ? result.committedCount.value : committedInlineItemCount + result.committedCount.value;
    302307        auto& inlineContent = lineCandidate.inlineContent;
    303         // Now check if we can put this content on the current line.
    304         auto result = tryAddingInlineItems(lineBreaker, line, layoutRange, lineCandidate);
    305         committedInlineItemCount = result.committedCount.isRevert ? result.committedCount.value : committedInlineItemCount + result.committedCount.value;
    306308        auto inlineContentIsFullyCommitted = inlineContent.runs().size() == result.committedCount.value && !result.partialContent;
    307309        auto isEndOfLine = result.isEndOfLine == LineBreaker::IsEndOfLine::Yes;
     
    317319            return close(line, layoutRange, committedInlineItemCount, result.partialContent);
    318320        }
    319         currentItemIndex = layoutRange.start + committedInlineItemCount;
     321        currentItemIndex = layoutRange.start + committedInlineItemCount + m_floats.size();
    320322        partialLeadingContentLength = { };
    321323    }
     
    327329{
    328330    ASSERT(committedInlineItemCount || line.hasIntrusiveFloat());
    329     if (!committedInlineItemCount)
    330         return LineContent { { }, { }, WTFMove(m_floats), line.close(), line.lineBox() };
    331 
     331    if (!committedInlineItemCount) {
     332        if (m_floats.isEmpty()) {
     333            // We didn't manage to add a run or a float at this vertical position.
     334            return LineContent { { }, { }, WTFMove(m_floats), line.close(), line.lineBox() };
     335        }
     336        auto trailingInlineItemIndex = layoutRange.start + m_floats.size();
     337        return LineContent { trailingInlineItemIndex, { }, WTFMove(m_floats), line.close(), line.lineBox() };
     338    }
    332339    // Adjust hyphenated line count.
    333340    if (partialContent && partialContent->trailingContentHasHyphen)
     
    335342    else
    336343        m_successiveHyphenatedLineCount = 0;
    337     unsigned trailingInlineItemIndex = layoutRange.start + committedInlineItemCount - 1;
     344    ASSERT(committedInlineItemCount);
     345    auto trailingInlineItemIndex = layoutRange.start + committedInlineItemCount + m_floats.size() - 1;
    338346    ASSERT(trailingInlineItemIndex < layoutRange.end);
    339347    auto isLastLineWithInlineContent = [&] {
     
    354362}
    355363
    356 void LineLayoutContext::nextContentForLine(LineCandidate& lineCandidate, unsigned currentInlineItemIndex, const InlineItemRange layoutRange, Optional<unsigned> partialLeadingContentLength, InlineLayoutUnit currentLogicalRight)
     364void LineLayoutContext::nextContentForLine(LineCandidate& lineCandidate, unsigned currentInlineItemIndex, const InlineItemRange layoutRange, Optional<unsigned> partialLeadingContentLength, InlineLayoutUnit availableLineWidth, InlineLayoutUnit currentLogicalRight)
    357365{
    358366    ASSERT(currentInlineItemIndex < layoutRange.end);
     
    375383    }
    376384
     385    auto accumulatedWidth = InlineLayoutUnit { };
    377386    for (auto index = currentInlineItemIndex; index < softWrapOpportunityIndex; ++index) {
    378387        auto& inlineItem = m_inlineItems[index];
    379388        if (inlineItem.isFloat()) {
    380389            // Floats are not part of the line context.
    381             // FIXME: Check if their width should be added to currentLogicalRight.
    382             lineCandidate.floatContent.append(inlineItem);
     390            auto floatWidth = inlineItemWidth(inlineItem, { });
     391            lineCandidate.floatContent.append(inlineItem, floatWidth, floatWidth <= (availableLineWidth - accumulatedWidth));
     392            accumulatedWidth += floatWidth;
    383393            continue;
    384394        }
     
    387397            lineCandidate.inlineContent.appendInlineItem(inlineItem, inlineItenmWidth);
    388398            currentLogicalRight += inlineItenmWidth;
     399            accumulatedWidth += inlineItenmWidth;
    389400            continue;
    390401        }
     
    397408}
    398409
    399 LineLayoutContext::Result LineLayoutContext::tryAddingFloatContent(LineBuilder& line, const LineCandidate& lineCandidate)
     410void LineLayoutContext::commitFloats(LineBuilder& line, const LineCandidate& lineCandidate, CommitIntrusiveFloatsOnly commitIntrusiveOnly)
    400411{
    401412    auto& floatContent = lineCandidate.floatContent;
    402     auto availableLineWidth = line.availableWidth() + line.trimmableTrailingWidth();
    403     auto accumulatedFloatsWidth = InlineLayoutUnit { };
    404     size_t committedCount = 0;
    405 
    406     for (auto* floatCandidate : floatContent.list()) {
    407         auto logicalWidth = inlineItemWidth(*floatCandidate, { });
    408         auto availableWidthForFloat = availableLineWidth - accumulatedFloatsWidth;
    409         accumulatedFloatsWidth += logicalWidth;
    410 
    411         if (availableWidthForFloat < logicalWidth)
    412             return { LineBreaker::IsEndOfLine::Yes, { committedCount, false } };
    413 
    414         // This float can sit on the current line.
    415         auto& floatBox = floatCandidate->layoutBox();
    416         // Shrink available space for current line and move existing inline runs.
     413    auto leftFloatsWidth = InlineLayoutUnit { };
     414    auto rightFloatsWidth = InlineLayoutUnit { };
     415
     416    for (auto& floatCandidate : floatContent.list()) {
     417        if (floatCandidate.isIntrusive && commitIntrusiveOnly == CommitIntrusiveFloatsOnly::Yes)
     418            continue;
     419        if (!floatCandidate.isIntrusive) {
     420            m_floats.append({ LineContent::Float::Intrusive::No, floatCandidate.item });
     421            continue;
     422        }
     423        m_floats.append({ LineContent::Float::Intrusive::Yes, floatCandidate.item });
     424        // This float is intrusive and it shrinks the current line.
     425        // Shrink available space for current line.
     426        if (floatCandidate.item->layoutBox().isLeftFloatingPositioned())
     427            leftFloatsWidth += floatCandidate.logicalWidth;
     428        else
     429            rightFloatsWidth += floatCandidate.logicalWidth;
     430    }
     431    if (leftFloatsWidth || rightFloatsWidth) {
    417432        line.setHasIntrusiveFloat();
    418         if (floatBox.isLeftFloatingPositioned())
    419             line.moveLogicalLeft(logicalWidth);
    420         else
    421             line.moveLogicalRight(logicalWidth);
    422         m_floats.append(floatCandidate);
    423         ++committedCount;
    424     }
    425     return { LineBreaker::IsEndOfLine::No, { committedCount, false } };
    426 }
    427 
    428 LineLayoutContext::Result LineLayoutContext::tryAddingInlineItems(LineBreaker& lineBreaker, LineBuilder& line, const InlineItemRange& layoutRange, const LineCandidate& lineCandidate)
     433        if (leftFloatsWidth)
     434            line.moveLogicalLeft(leftFloatsWidth);
     435        if (rightFloatsWidth)
     436            line.moveLogicalRight(rightFloatsWidth);
     437    }
     438}
     439
     440LineLayoutContext::Result LineLayoutContext::handleFloatsAndInlineContent(LineBreaker& lineBreaker, LineBuilder& line, const InlineItemRange& layoutRange, const LineCandidate& lineCandidate)
    429441{
    430442    auto& inlineContent = lineCandidate.inlineContent;
    431443    auto& candidateRuns = inlineContent.runs();
    432 
    433     if (candidateRuns.isEmpty())
     444    if (candidateRuns.isEmpty()) {
     445        commitFloats(line, lineCandidate);
    434446        return { LineBreaker::IsEndOfLine::No };
     447    }
    435448
    436449    auto shouldDisableHyphenation = [&] {
     
    442455        lineBreaker.setHyphenationDisabled();
    443456
     457    auto& floatContent = lineCandidate.floatContent;
    444458    // Check if this new content fits.
    445     auto lineStatus = LineBreaker::LineStatus { line.availableWidth(), line.trimmableTrailingWidth(), line.isTrailingRunFullyTrimmable(), isLineConsideredEmpty(line) };
     459    auto availableWidth = line.availableWidth() - floatContent.intrusiveWidth();
     460    auto isLineConsideredEmpty = line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
     461    auto lineStatus = LineBreaker::LineStatus { availableWidth, line.trimmableTrailingWidth(), line.isTrailingRunFullyTrimmable(), isLineConsideredEmpty };
    446462    auto result = lineBreaker.shouldWrapInlineContent(candidateRuns, inlineContent.logicalWidth(), lineStatus);
    447463    if (result.lastWrapOpportunityItem)
    448464        m_lastWrapOpportunityItem = result.lastWrapOpportunityItem;
    449465    if (result.action == LineBreaker::Result::Action::Keep) {
    450         // This continuous content can be fully placed on the current line.
     466        // This continuous content can be fully placed on the current line including non-intrusive floats.
    451467        for (auto& run : candidateRuns)
    452468            line.append(run.inlineItem, run.logicalWidth);
     469        commitFloats(line, lineCandidate);
    453470        return { result.isEndOfLine, { candidateRuns.size(), false } };
    454471    }
     
    467484        ASSERT(result.isEndOfLine == LineBreaker::IsEndOfLine::Yes);
    468485        // Commit the combination of full and partial content on the current line.
     486        commitFloats(line, lineCandidate, CommitIntrusiveFloatsOnly::Yes);
    469487        ASSERT(result.partialTrailingContent);
    470488        commitPartialContent(line, candidateRuns, *result.partialTrailingContent);
  • trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h

    r257236 r257291  
    4747        Optional<unsigned> trailingInlineItemIndex;
    4848        Optional<PartialContent> partialContent;
    49         Vector<const InlineItem*> floats;
     49        struct Float {
     50            enum class Intrusive { No, Yes };
     51            Intrusive isIntrusive { Intrusive::Yes };
     52            const InlineItem* item { nullptr };
     53        };
     54        using FloatList = Vector<Float>;
     55        FloatList floats;
    5056        const LineBuilder::RunList runList;
    5157        const LineBoxBuilder lineBox;
     
    6066
    6167private:
    62     void nextContentForLine(LineCandidate&, unsigned inlineItemIndex, const InlineItemRange layoutRange, Optional<unsigned> overflowLength, InlineLayoutUnit currentLogicalRight);
     68    void nextContentForLine(LineCandidate&, unsigned inlineItemIndex, const InlineItemRange layoutRange, Optional<unsigned> overflowLength, InlineLayoutUnit availableLineWidth, InlineLayoutUnit currentLogicalRight);
    6369    struct Result {
    6470        LineBreaker::IsEndOfLine isEndOfLine { LineBreaker::IsEndOfLine::No };
     
    7076        Optional <LineContent::PartialContent> partialContent { };
    7177    };
    72     Result tryAddingFloatContent(LineBuilder&, const LineCandidate&);
    73     Result tryAddingInlineItems(LineBreaker&, LineBuilder&, const InlineItemRange& layoutRange, const LineCandidate&);
     78    enum class CommitIntrusiveFloatsOnly { No, Yes };
     79    void commitFloats(LineBuilder&, const LineCandidate&, CommitIntrusiveFloatsOnly = CommitIntrusiveFloatsOnly::No);
     80    Result handleFloatsAndInlineContent(LineBreaker&, LineBuilder&, const InlineItemRange& layoutRange, const LineCandidate&);
    7481    size_t rebuildLine(LineBuilder&, const InlineItemRange& layoutRange);
    7582    void commitPartialContent(LineBuilder&, const LineBreaker::RunList&, const LineBreaker::Result::PartialTrailingContent&);
     
    8491    const ContainerBox& m_formattingContextRoot;
    8592    const InlineItems& m_inlineItems;
    86     using FloatList = Vector<const InlineItem*>;
    87     FloatList m_floats;
     93    LineContent::FloatList m_floats;
    8894    Optional<InlineTextItem> m_partialLeadingTextItem;
    8995    const InlineItem* m_lastWrapOpportunityItem { nullptr };
Note: See TracChangeset for help on using the changeset viewer.