[LFC][IFC] Optimize LineBuilder::appendTextContent for the most common inline content
https://bugs.webkit.org/show_bug.cgi?id=206397
<rdar://problem/58671338>

Reviewed by Antti Koivisto.

~2% progression on PerformanceTests/Layout/line-layout-simple.html.

* layout/inlineformatting/InlineLineBuilder.cpp:
(WebCore::Layout::LineBuilder::LineBuilder):
(WebCore::Layout::shouldPreserveLeadingContent):
(WebCore::Layout::LineBuilder::appendTextContent):
(WebCore::Layout::LineBuilder::appendLineBreak):
(WebCore::Layout::LineBuilder::InlineItemRun::InlineItemRun):
* layout/inlineformatting/InlineLineBuilder.h:
(WebCore::Layout::LineBuilder::InlineItemRun::setIsCollapsed): Deleted.
* layout/inlineformatting/InlineTextItem.cpp:
(WebCore::Layout::InlineTextItem::InlineTextItem):
* layout/inlineformatting/InlineTextItem.h:
(WebCore::Layout::InlineTextItem::isCollapsible const):
* layout/inlineformatting/text/TextUtil.cpp:
(WebCore::Layout::TextUtil::shouldPreserveTrailingWhitespace): Deleted.
* layout/inlineformatting/text/TextUtil.h:
(WebCore::Layout::TextUtil::shouldPreserveTrailingWhitespace):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254750 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index b69b899..140f543 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,30 @@
+2020-01-17  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][IFC] Optimize LineBuilder::appendTextContent for the most common inline content
+        https://bugs.webkit.org/show_bug.cgi?id=206397
+        <rdar://problem/58671338>
+
+        Reviewed by Antti Koivisto.
+
+        ~2% progression on PerformanceTests/Layout/line-layout-simple.html.
+
+        * layout/inlineformatting/InlineLineBuilder.cpp:
+        (WebCore::Layout::LineBuilder::LineBuilder):
+        (WebCore::Layout::shouldPreserveLeadingContent):
+        (WebCore::Layout::LineBuilder::appendTextContent):
+        (WebCore::Layout::LineBuilder::appendLineBreak):
+        (WebCore::Layout::LineBuilder::InlineItemRun::InlineItemRun):
+        * layout/inlineformatting/InlineLineBuilder.h:
+        (WebCore::Layout::LineBuilder::InlineItemRun::setIsCollapsed): Deleted.
+        * layout/inlineformatting/InlineTextItem.cpp:
+        (WebCore::Layout::InlineTextItem::InlineTextItem):
+        * layout/inlineformatting/InlineTextItem.h:
+        (WebCore::Layout::InlineTextItem::isCollapsible const):
+        * layout/inlineformatting/text/TextUtil.cpp:
+        (WebCore::Layout::TextUtil::shouldPreserveTrailingWhitespace): Deleted.
+        * layout/inlineformatting/text/TextUtil.h:
+        (WebCore::Layout::TextUtil::shouldPreserveTrailingWhitespace):
+
 2020-01-17  Antti Koivisto  <antti@apple.com>
 
         [LFC][IFC] Allocate InlineItems in a vector
diff --git a/Source/WebCore/layout/inlineformatting/InlineItem.cpp b/Source/WebCore/layout/inlineformatting/InlineItem.cpp
index 5ca9882..4625a0b 100644
--- a/Source/WebCore/layout/inlineformatting/InlineItem.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineItem.cpp
@@ -38,6 +38,7 @@
     uint8_t enum1;
     uint8_t enum2;
     bool widthBool;
+    bool isCollapsible;
     InlineLayoutUnit width;
     unsigned start;
     unsigned length;
diff --git a/Source/WebCore/layout/inlineformatting/InlineItem.h b/Source/WebCore/layout/inlineformatting/InlineItem.h
index 1064cf9..26ddefc 100644
--- a/Source/WebCore/layout/inlineformatting/InlineItem.h
+++ b/Source/WebCore/layout/inlineformatting/InlineItem.h
@@ -60,6 +60,7 @@
     enum class TextItemType  : uint8_t { Undefined, Whitespace, NonWhitespace };
     TextItemType m_textItemType { TextItemType::Undefined };
     bool m_hasWidth { false };
+    bool m_isCollapsible { false };
     InlineLayoutUnit m_width { };
     unsigned m_length { 0 };
 
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
index 51f9793..7abf426 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
@@ -189,6 +189,7 @@
     , m_collapsibleContent(m_inlineItemRuns)
     , m_horizontalAlignment(horizontalAlignment)
     , m_isIntrinsicSizing(intrinsicSizing == IntrinsicSizing::Yes)
+    , m_shouldIgnoreTrailingLetterSpacing(RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
 {
 }
 
@@ -220,7 +221,7 @@
     m_lineIsVisuallyEmptyBeforeCollapsibleContent = { };
 }
 
-static bool shouldPreserveLeadingContent(const InlineTextItem& inlineTextItem)
+static inline bool shouldPreserveLeadingContent(const InlineTextItem& inlineTextItem)
 {
     if (!inlineTextItem.isWhitespace())
         return true;
@@ -635,15 +636,12 @@
 
 void LineBuilder::appendTextContent(const InlineTextItem& inlineItem, InlineLayoutUnit logicalWidth)
 {
+    auto isCollapsible = inlineItem.isCollapsible();
     auto willCollapseCompletely = [&] {
-        if (!inlineItem.isCollapsible())
+        if (!isCollapsible)
             return false;
-        // Leading whitespace.
-        if (m_inlineItemRuns.isEmpty())
-            return !shouldPreserveLeadingContent(inlineItem);
         // Check if the last item is collapsed as well.
-        for (auto i = m_inlineItemRuns.size(); i--;) {
-            auto& run = m_inlineItemRuns[i];
+        for (auto& run : WTF::makeReversedRange(m_inlineItemRuns)) {
             if (run.isBox())
                 return false;
             // https://drafts.csswg.org/css-text-3/#white-space-phase-1
@@ -655,34 +653,27 @@
                 return run.isCollapsible();
             ASSERT(run.isContainerStart() || run.isContainerEnd());
         }
-        return true;
+        // Leading whitespace.
+        return !shouldPreserveLeadingContent(inlineItem);
     };
 
     auto collapsesToZeroAdvanceWidth = willCollapseCompletely();
-    auto collapsedRun = inlineItem.isCollapsible() && inlineItem.length() > 1;
-    auto contentStart = inlineItem.start();
+    logicalWidth = collapsesToZeroAdvanceWidth ? 0 : logicalWidth;
+    auto collapsedRun = isCollapsible && inlineItem.length() > 1;
     auto contentLength =  collapsedRun ? 1 : inlineItem.length();
-    m_inlineItemRuns.append({ inlineItem, contentLogicalWidth(), logicalWidth, Display::Run::TextContext { contentStart, contentLength, inlineItem.layoutBox().textContext()->content } });
-    auto& lineRun = m_inlineItemRuns.last();
+    m_inlineItemRuns.append({ inlineItem, contentLogicalWidth(), logicalWidth, collapsedRun, collapsesToZeroAdvanceWidth, Display::Run::TextContext { inlineItem.start(), contentLength, inlineItem.layoutBox().textContext()->content } });
+    m_lineBox.expandHorizontally(logicalWidth);
 
-    if (collapsesToZeroAdvanceWidth)
-        lineRun.setCollapsesToZeroAdvanceWidth();
-
-    if (collapsedRun)
-        lineRun.setIsCollapsed();
-
-    m_lineBox.expandHorizontally(lineRun.logicalWidth());
-
-    // Existing trailing collapsible content can only be expanded if the current run is fully collapsible.
-    auto collapsibleListNeedsReset = !m_collapsibleContent.isEmpty() && !lineRun.isCollapsibleWhitespace();
-    if (collapsibleListNeedsReset)
-        m_collapsibleContent.reset();
-    auto isCollapsible = lineRun.isCollapsibleWhitespace() || lineRun.hasTrailingLetterSpacing();
-    if (isCollapsible) {
+    if (isCollapsible && !TextUtil::shouldPreserveTrailingWhitespace(inlineItem.style())) {
         // If we ever collapse this content, we need to know if the line visibility state needs to be recomputed.
         if (m_collapsibleContent.isEmpty())
             m_lineIsVisuallyEmptyBeforeCollapsibleContent = isVisuallyEmpty();
         m_collapsibleContent.append(m_inlineItemRuns.size() - 1);
+    } else {
+        // Existing trailing collapsible content can only be expanded if the current run is fully collapsible.
+        m_collapsibleContent.reset();
+        if (!m_shouldIgnoreTrailingLetterSpacing && !inlineItem.isWhitespace() && inlineItem.style().letterSpacing() > 0)
+            m_collapsibleContent.append(m_inlineItemRuns.size() - 1);
     }
 }
 
@@ -710,7 +701,7 @@
     // Soft line breaks (preserved new line characters) require inline text boxes for compatibility reasons.
     ASSERT(inlineItem.isSoftLineBreak());
     auto& softLineBreakItem = downcast<InlineSoftLineBreakItem>(inlineItem);
-    m_inlineItemRuns.append({ softLineBreakItem, contentLogicalWidth(), 0_lu, Display::Run::TextContext { softLineBreakItem.position(), 1, softLineBreakItem.layoutBox().textContext()->content } });
+    m_inlineItemRuns.append({ softLineBreakItem, contentLogicalWidth(), 0_lu, false, false, Display::Run::TextContext { softLineBreakItem.position(), 1, softLineBreakItem.layoutBox().textContext()->content } });
 }
 
 void LineBuilder::adjustBaselineAndLineHeight(const Run& run)
@@ -964,11 +955,20 @@
     return 0_lu;
 }
 
-LineBuilder::InlineItemRun::InlineItemRun(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, WTF::Optional<Display::Run::TextContext> textContext)
+LineBuilder::InlineItemRun::InlineItemRun(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth)
     : m_inlineItem(inlineItem)
     , m_logicalLeft(logicalLeft)
     , m_logicalWidth(logicalWidth)
-    , m_textContext(textContext)
+{
+}
+
+LineBuilder::InlineItemRun::InlineItemRun(const InlineItem& inlineItem, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, bool isCollapsed, bool isCollapsedToZeroAdvanceWidth, Display::Run::TextContext&& textContext)
+    : m_inlineItem(inlineItem)
+    , m_logicalLeft(logicalLeft)
+    , m_logicalWidth(logicalWidth)
+    , m_textContext(WTFMove(textContext))
+    , m_isCollapsed(isCollapsed)
+    , m_collapsedToZeroAdvanceWidth(isCollapsedToZeroAdvanceWidth)
 {
 }
 
diff --git a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
index e285db1..f78a614 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
+++ b/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
@@ -161,7 +161,8 @@
 
     class InlineItemRun {
     public:
-        InlineItemRun(const InlineItem&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, WTF::Optional<Display::Run::TextContext> = WTF::nullopt);
+        InlineItemRun(const InlineItem&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, bool isCollapsed, bool isCollapsedToZeroAdvanceWidth, Display::Run::TextContext&&);
+        InlineItemRun(const InlineItem&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth);
 
         const Box& layoutBox() const { return m_inlineItem.layoutBox(); }
         const RenderStyle& style() const { return layoutBox().style(); }
@@ -176,7 +177,6 @@
         bool isLineBreak() const { return m_inlineItem.isLineBreak(); }
         InlineItem::Type type() const { return m_inlineItem.type(); }
 
-        void setIsCollapsed() { m_isCollapsed = true; }
         bool isCollapsed() const { return m_isCollapsed; }
 
         void moveHorizontally(InlineLayoutUnit offset) { m_logicalLeft += offset; }
@@ -242,6 +242,7 @@
     bool m_hasIntrusiveFloat { false };
     Display::LineBox m_lineBox;
     Optional<bool> m_lineIsVisuallyEmptyBeforeCollapsibleContent;
+    bool m_shouldIgnoreTrailingLetterSpacing { false };
 };
 
 inline void LineBuilder::CollapsibleContent::reset()
diff --git a/Source/WebCore/layout/inlineformatting/InlineTextItem.h b/Source/WebCore/layout/inlineformatting/InlineTextItem.h
index 2f724a7..ec24114 100644
--- a/Source/WebCore/layout/inlineformatting/InlineTextItem.h
+++ b/Source/WebCore/layout/inlineformatting/InlineTextItem.h
@@ -46,7 +46,7 @@
     unsigned length() const { return m_length; }
 
     bool isWhitespace() const { return m_textItemType == TextItemType::Whitespace; }
-    bool isCollapsible() const { return isWhitespace() && style().collapseWhiteSpace(); }
+    bool isCollapsible() const { return m_isCollapsible; }
     Optional<InlineLayoutUnit> width() const { return m_hasWidth ? makeOptional(m_width) : Optional<InlineLayoutUnit> { }; }
     bool isEmptyContent() const;
 
@@ -80,6 +80,7 @@
     m_startOrPosition = start;
     m_length = length;
     m_hasWidth = !!width;
+    m_isCollapsible = textItemType == TextItemType::Whitespace && inlineBox.style().collapseWhiteSpace();
     m_width = width.valueOr(0);
     m_textItemType = textItemType;
 }
diff --git a/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp b/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
index 782f5bd..e8db9d7 100644
--- a/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
+++ b/Source/WebCore/layout/inlineformatting/text/TextUtil.cpp
@@ -128,12 +128,6 @@
     return { startPosition, right - startPosition, leftSideWidth };
 }
 
-bool TextUtil::shouldPreserveTrailingWhitespace(const RenderStyle& style)
-{
-    auto whitespace = style.whiteSpace();
-    return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
-}
-
 unsigned TextUtil::findNextBreakablePosition(LazyLineBreakIterator& lineBreakIterator, unsigned startPosition, const RenderStyle& style)
 {
     auto keepAllWordsForCJK = style.wordBreak() == WordBreak::KeepAll;
diff --git a/Source/WebCore/layout/inlineformatting/text/TextUtil.h b/Source/WebCore/layout/inlineformatting/text/TextUtil.h
index 0e1b695..c40e5ef 100644
--- a/Source/WebCore/layout/inlineformatting/text/TextUtil.h
+++ b/Source/WebCore/layout/inlineformatting/text/TextUtil.h
@@ -56,6 +56,12 @@
     static InlineLayoutUnit fixedPitchWidth(const StringView&, const RenderStyle&, unsigned from, unsigned to, InlineLayoutUnit contentLogicalLeft);
 };
 
+inline bool TextUtil::shouldPreserveTrailingWhitespace(const RenderStyle& style)
+{
+    auto whitespace = style.whiteSpace();
+    return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
+}
+
 }
 }
 #endif