[LFC][IFC] Move the collapsed bit from InlineItems to runs
https://bugs.webkit.org/show_bug.cgi?id=203183

Reviewed by Antti Koivisto.
<rdar://problem/56437181>

Let's not store the collapsed bit on the InlineTextItem. All we need to know is whether the InlineTextItem content is collapsible or not.
Also when only the white-space property changes (going from preserve whitespace to not and vice versa) we don't actually need to rebuild the
InlinItem list since they don't carry any layout dependent information.
This patch also fixes leading/trailing content preservation.

* layout/inlineformatting/InlineLine.cpp:
(WebCore::Layout::Line::Run::canBeExtended const):
(WebCore::Layout::shouldPreserveTrailingContent):
(WebCore::Layout::shouldPreserveLeadingContent):
(WebCore::Layout::Line::appendTextContent):
* layout/inlineformatting/InlineLine.h:
(WebCore::Layout::Line::Run::isCollapsed const):
(WebCore::Layout::Line::Run::setIsCollapsed):
* layout/inlineformatting/InlineLineLayout.cpp:
(WebCore::Layout::inlineItemWidth):
* layout/inlineformatting/InlineTextItem.cpp:
(WebCore::Layout::InlineTextItem::createAndAppendTextItems):
(WebCore::Layout::InlineTextItem::InlineTextItem):
(WebCore::Layout::InlineTextItem::split const):
* layout/inlineformatting/InlineTextItem.h:
(WebCore::Layout::InlineTextItem::isCollapsible const):
(WebCore::Layout::InlineTextItem::isCollapsed const): Deleted.
* layout/inlineformatting/text/TextUtil.cpp:
(WebCore::Layout::TextUtil::isTrimmableContent): Deleted.
* layout/inlineformatting/text/TextUtil.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251329 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 0ad2152..12d8a96 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,37 @@
+2019-10-20  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Move the collapsed bit from InlineItems to runs
+        https://bugs.webkit.org/show_bug.cgi?id=203183
+
+        Reviewed by Antti Koivisto.
+        <rdar://problem/56437181>
+
+        Let's not store the collapsed bit on the InlineTextItem. All we need to know is whether the InlineTextItem content is collapsible or not.
+        Also when only the white-space property changes (going from preserve whitespace to not and vice versa) we don't actually need to rebuild the
+        InlinItem list since they don't carry any layout dependent information.
+        This patch also fixes leading/trailing content preservation. 
+
+        * layout/inlineformatting/InlineLine.cpp:
+        (WebCore::Layout::Line::Run::canBeExtended const):
+        (WebCore::Layout::shouldPreserveTrailingContent):
+        (WebCore::Layout::shouldPreserveLeadingContent):
+        (WebCore::Layout::Line::appendTextContent):
+        * layout/inlineformatting/InlineLine.h:
+        (WebCore::Layout::Line::Run::isCollapsed const):
+        (WebCore::Layout::Line::Run::setIsCollapsed):
+        * layout/inlineformatting/InlineLineLayout.cpp:
+        (WebCore::Layout::inlineItemWidth):
+        * layout/inlineformatting/InlineTextItem.cpp:
+        (WebCore::Layout::InlineTextItem::createAndAppendTextItems):
+        (WebCore::Layout::InlineTextItem::InlineTextItem):
+        (WebCore::Layout::InlineTextItem::split const):
+        * layout/inlineformatting/InlineTextItem.h:
+        (WebCore::Layout::InlineTextItem::isCollapsible const):
+        (WebCore::Layout::InlineTextItem::isCollapsed const): Deleted.
+        * layout/inlineformatting/text/TextUtil.cpp:
+        (WebCore::Layout::TextUtil::isTrimmableContent): Deleted.
+        * layout/inlineformatting/text/TextUtil.h:
+
 2019-10-19  Chris Dumez  <cdumez@apple.com>
 
         FileReader should not prevent entering the back/forward cache
diff --git a/Source/WebCore/layout/inlineformatting/InlineLine.cpp b/Source/WebCore/layout/inlineformatting/InlineLine.cpp
index ad89be9..059632c 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLine.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLine.cpp
@@ -54,7 +54,10 @@
 {
     if (!isText())
         return false;
-    return !downcast<InlineTextItem>(m_inlineItem).isCollapsed() && !isVisuallyEmpty();
+    // Non-collapsed text runs can be merged into one continuous run.
+    if (isVisuallyEmpty())
+        return false;
+    return !isCollapsed();
 }
 
 Line::Line(const InlineFormattingContext& inlineFormattingContext, const InitialConstraints& initialConstraints, Optional<TextAlignMode> horizontalAlignment, SkipAlignment skipAlignment)
@@ -79,6 +82,22 @@
     return !(boxGeometry.horizontalBorder() || (boxGeometry.horizontalPadding() && boxGeometry.horizontalPadding().value()));
 }
 
+static bool shouldPreserveTrailingContent(const InlineTextItem& inlineTextItem)
+{
+    if (!inlineTextItem.isWhitespace())
+        return true;
+    auto whitespace = inlineTextItem.style().whiteSpace();
+    return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap;
+}
+
+static bool shouldPreserveLeadingContent(const InlineTextItem& inlineTextItem)
+{
+    if (!inlineTextItem.isWhitespace())
+        return true;
+    auto whitespace = inlineTextItem.style().whiteSpace();
+    return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
+}
+
 bool Line::isVisuallyEmpty() const
 {
     // FIXME: This should be cached instead -as the inline items are being added.
@@ -324,7 +343,7 @@
 
 void Line::appendTextContent(const InlineTextItem& inlineItem, LayoutUnit logicalWidth)
 {
-    auto isTrimmable = TextUtil::isTrimmableContent(inlineItem);
+    auto isTrimmable = !shouldPreserveTrailingContent(inlineItem);
     if (!isTrimmable)
         m_trimmableContent.clear();
 
@@ -334,18 +353,22 @@
             ASSERT(!logicalWidth);
             return true;
         }
-        if (!isTrimmable)
-            return false;
         // Leading whitespace.
         if (m_runList.isEmpty())
-            return true;
-        // Check if the last item is trimmable as well.
-        for (int index = m_runList.size() - 1; index >= 0; --index) {
-            auto& run = m_runList[index];
+            return !shouldPreserveLeadingContent(inlineItem);
+
+        if (!inlineItem.isCollapsible())
+            return false;
+        // Check if the last item is collapsed as well.
+        for (auto i = m_runList.size(); i--;) {
+            auto& run = m_runList[i];
             if (run->isBox())
                 return false;
+            // When the previous text run is collapsed, this collapsible run collapses completely.
             if (run->isText())
-                return run->isWhitespace() && run->layoutBox().style().collapseWhiteSpace();
+                return run->isCollapsed();
+            // Collapsing works across inline containers: "<span>  </span> " <- the trailing whitespace collapses completely.
+            // Not that when the inline container has preserve whitespace style, "<span style="white-space: pre">  </span> " <- this whitespace stays around. 
             ASSERT(run->isContainerStart() || run->isContainerEnd());
         }
         return true;
@@ -359,11 +382,15 @@
         logicalRect.setHeight(inlineItemContentHeight(inlineItem));
     }
 
+    auto collapseRun = inlineItem.isCollapsible();
     auto contentStart = inlineItem.start();
-    auto contentLength = inlineItem.isCollapsed() ? 1 : inlineItem.length();
+    auto contentLength =  collapseRun ? 1 : inlineItem.length();
     auto textContent = inlineItem.layoutBox().textContent().substring(contentStart, contentLength);
     auto lineItem = makeUnique<Run>(inlineItem, Display::Run { inlineItem.style(), logicalRect, Display::Run::TextContext { contentStart, contentLength, textContent } });
+
     auto isVisuallyEmpty = willCollapseCompletely();
+    if (collapseRun)
+        lineItem->setIsCollapsed();
     if (isVisuallyEmpty)
         lineItem->setVisuallyIsEmpty();
     else if (isTrimmable)
diff --git a/Source/WebCore/layout/inlineformatting/InlineLine.h b/Source/WebCore/layout/inlineformatting/InlineLine.h
index 090532c..1b1f6c7 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLine.h
+++ b/Source/WebCore/layout/inlineformatting/InlineLine.h
@@ -74,6 +74,7 @@
 
         const Display::Rect& logicalRect() const { return m_displayRun.logicalRect(); }
         bool isVisuallyEmpty() const { return m_isVisuallyEmpty; }
+        bool isCollapsed() const { return m_isCollapsed; }
 
         bool isText() const { return m_inlineItem.isText(); }
         bool isBox() const { return m_inlineItem.isBox(); }
@@ -90,12 +91,14 @@
         void expand(const Run&);
 
         void setVisuallyIsEmpty() { m_isVisuallyEmpty = true; }
+        void setIsCollapsed() { m_isCollapsed = true; }
 
         bool isWhitespace() const;
         bool canBeExtended() const;
 
         const InlineItem& m_inlineItem;
         Display::Run m_displayRun;
+        bool m_isCollapsed { false };
         bool m_isVisuallyEmpty { false };
     };
     using RunList = Vector<std::unique_ptr<Run>>;
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineLayout.cpp b/Source/WebCore/layout/inlineformatting/InlineLineLayout.cpp
index 16c856a..4be8632 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineLayout.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLineLayout.cpp
@@ -41,7 +41,7 @@
 
     if (is<InlineTextItem>(inlineItem)) {
         auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
-        auto end = inlineTextItem.isCollapsed() ? inlineTextItem.start() + 1 : inlineTextItem.end();
+        auto end = inlineTextItem.isCollapsible() ? inlineTextItem.start() + 1 : inlineTextItem.end();
         return TextUtil::width(inlineTextItem.layoutBox(), inlineTextItem.start(), end, contentLogicalLeft);
     }
 
diff --git a/Source/WebCore/layout/inlineformatting/InlineTextItem.cpp b/Source/WebCore/layout/inlineformatting/InlineTextItem.cpp
index 5e6c256..2c94efb 100644
--- a/Source/WebCore/layout/inlineformatting/InlineTextItem.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineTextItem.cpp
@@ -89,40 +89,37 @@
 {
     auto text = inlineBox.textContent();
     if (!text.length())
-        return inlineContent.append(makeUnique<InlineTextItem>(inlineBox, 0, 0, false, false));
+        return inlineContent.append(makeUnique<InlineTextItem>(inlineBox, 0, 0, false));
 
     auto& style = inlineBox.style();
     auto preserveNewline = style.preserveNewline();
-    auto collapseWhiteSpace = style.collapseWhiteSpace();
     LazyLineBreakIterator lineBreakIterator(text);
     unsigned currentPosition = 0;
     while (currentPosition < text.length()) {
         // Soft linebreak?
         if (isSoftLineBreak(text[currentPosition], preserveNewline)) {
-            inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, 1, true, false));
+            inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, 1, true));
             ++currentPosition;
             continue;
         }
         if (isWhitespaceCharacter(text[currentPosition], preserveNewline)) {
             auto length = moveToNextNonWhitespacePosition(text, currentPosition, preserveNewline);
-            auto isCollapsed = collapseWhiteSpace && length > 1;
-            inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, length, true, isCollapsed));
+            inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, length, true));
             currentPosition += length;
             continue;
         }
 
         auto length = moveToNextBreakablePosition(currentPosition, lineBreakIterator, style);
-        inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, length, false, false));
+        inlineContent.append(makeUnique<InlineTextItem>(inlineBox, currentPosition, length, false));
         currentPosition += length;
     }
 }
 
-InlineTextItem::InlineTextItem(const Box& inlineBox, unsigned start, unsigned length, bool isWhitespace, bool isCollapsed)
+InlineTextItem::InlineTextItem(const Box& inlineBox, unsigned start, unsigned length, bool isWhitespace)
     : InlineItem(inlineBox, Type::Text)
     , m_start(start)
     , m_length(length)
     , m_isWhitespace(isWhitespace)
-    , m_isCollapsed(isCollapsed)
 {
 }
 
@@ -130,7 +127,7 @@
 {
     RELEASE_ASSERT(splitPosition >= this->start());
     RELEASE_ASSERT(splitPosition + length <= end());
-    return makeUnique<InlineTextItem>(layoutBox(), splitPosition, length, isWhitespace(), isCollapsed());
+    return makeUnique<InlineTextItem>(layoutBox(), splitPosition, length, isWhitespace());
 }
 
 }
diff --git a/Source/WebCore/layout/inlineformatting/InlineTextItem.h b/Source/WebCore/layout/inlineformatting/InlineTextItem.h
index 9a9c7c8..45fa6e1 100644
--- a/Source/WebCore/layout/inlineformatting/InlineTextItem.h
+++ b/Source/WebCore/layout/inlineformatting/InlineTextItem.h
@@ -37,14 +37,14 @@
 public:
     static void createAndAppendTextItems(InlineItems&, const Box&);
 
-    InlineTextItem(const Box&, unsigned start, unsigned length, bool isWhitespace, bool isCollapsed);
+    InlineTextItem(const Box&, unsigned start, unsigned length, bool isWhitespace);
 
     unsigned start() const { return m_start; }
     unsigned end() const { return start() + length(); }
     unsigned length() const { return m_length; }
 
     bool isWhitespace() const { return m_isWhitespace; }
-    bool isCollapsed() const { return m_isCollapsed; }
+    bool isCollapsible() const { return isWhitespace() && style().collapseWhiteSpace(); }
 
     std::unique_ptr<InlineTextItem> split(unsigned splitPosition, unsigned length) const;
 
@@ -52,7 +52,6 @@
     unsigned m_start { 0 };
     unsigned m_length { 0 };
     bool m_isWhitespace { false };
-    bool m_isCollapsed { false };
 };
 
 }
diff --git a/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp b/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
index 131ba62..d312e70 100644
--- a/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
+++ b/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
@@ -89,14 +89,6 @@
     return width;
 }
 
-bool TextUtil::isTrimmableContent(const InlineItem& inlineItem)
-{
-    if (!is<InlineTextItem>(inlineItem))
-        return false;
-    auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
-    return inlineTextItem.isWhitespace() && inlineTextItem.style().collapseWhiteSpace();
-}
-
 TextUtil::SplitData TextUtil::split(const Box& inlineBox, unsigned startPosition, unsigned length, LayoutUnit textWidth, LayoutUnit availableWidth, LayoutUnit contentLogicalLeft)
 {
     // FIXME This should take hypens into account.
diff --git a/Source/WebCore/layout/inlineformatting/text/TextUtil.h b/Source/WebCore/layout/inlineformatting/text/TextUtil.h
index f152650..73128e2 100644
--- a/Source/WebCore/layout/inlineformatting/text/TextUtil.h
+++ b/Source/WebCore/layout/inlineformatting/text/TextUtil.h
@@ -36,7 +36,6 @@
 public:
     static LayoutUnit width(const Box&, unsigned from, unsigned to, LayoutUnit contentLogicalLeft);
     static Optional<unsigned> hyphenPositionBefore(const InlineItem&, unsigned from, unsigned length);
-    static bool isTrimmableContent(const InlineItem&);
     struct SplitData {
         unsigned start { 0 };
         unsigned length { 0 };