diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc index 918fad6bd23f3a..1c287c5ada5960 100644 --- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc +++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc @@ -54,11 +54,33 @@ static inline bool IsPunctuationForFirstLetter(UChar32 c) { char_category == WTF::unicode::kPunctuation_Other; } -static inline bool IsSpaceForFirstLetter(UChar c) { - return IsSpaceOrNewline(c) || c == WTF::unicode::kNoBreakSpaceCharacter; +static inline bool IsNewLine(UChar c) { + if (c == 0xA || c == 0xD) { + return true; + } + + return false; } -unsigned FirstLetterPseudoElement::FirstLetterLength(const String& text) { +static inline bool IsSpace(UChar c) { + if (IsNewLine(c)) { + return false; + } + + return IsSpaceOrNewline(c); +} + +static inline bool IsSpaceForFirstLetter(UChar c, bool preserve_breaks) { + return (RuntimeEnabledFeatures:: + CSSFirstLetterNoNewLineAsPrecedingCharEnabled() && + preserve_breaks + ? IsSpace(c) + : IsSpaceOrNewline(c)) || + c == WTF::unicode::kNoBreakSpaceCharacter; +} + +unsigned FirstLetterPseudoElement::FirstLetterLength(const String& text, + bool preserve_breaks) { unsigned length = 0; unsigned text_length = text.length(); @@ -66,17 +88,23 @@ unsigned FirstLetterPseudoElement::FirstLetterLength(const String& text) { return length; // Account for leading spaces first. - while (length < text_length && IsSpaceForFirstLetter(text[length])) + while (length < text_length && + IsSpaceForFirstLetter(text[length], preserve_breaks)) { length++; + } + // Now account for leading punctuation. while (length < text_length && - IsPunctuationForFirstLetter(text.CharacterStartingAt(length))) + IsPunctuationForFirstLetter(text.CharacterStartingAt(length))) { length += LengthOfGraphemeCluster(text, length); + } // Bail if we didn't find a letter before the end of the text or before a // space. - if (IsSpaceForFirstLetter(text[length]) || length == text_length) + if (IsSpaceForFirstLetter(text[length], preserve_breaks) || + IsNewLine(text[length]) || length == text_length) { return 0; + } // Account the next character for first letter. length += LengthOfGraphemeCluster(text, length); @@ -153,7 +181,9 @@ LayoutText* FirstLetterPseudoElement::FirstLetterTextLayoutObject( ? To(first_letter_text_layout_object) ->CompleteText() : layout_text->OriginalText(); - if (FirstLetterLength(str.Impl()) || + bool preserve_breaks = ShouldPreserveBreaks( + first_letter_text_layout_object->StyleRef().GetWhiteSpaceCollapse()); + if (FirstLetterLength(str.Impl(), preserve_breaks) || IsInvalidFirstLetterLayoutObject(first_letter_text_layout_object)) { break; } @@ -248,7 +278,10 @@ void FirstLetterPseudoElement::UpdateTextFragments() { String old_text(remaining_text_layout_object_->CompleteText()); DCHECK(old_text.Impl()); - unsigned length = FirstLetterPseudoElement::FirstLetterLength(old_text); + bool preserve_breaks = ShouldPreserveBreaks( + remaining_text_layout_object_->StyleRef().GetWhiteSpaceCollapse()); + unsigned length = + FirstLetterPseudoElement::FirstLetterLength(old_text, preserve_breaks); remaining_text_layout_object_->SetTextFragment( old_text.Impl()->Substring(length, old_text.length()), length, old_text.length() - length); @@ -371,7 +404,10 @@ void FirstLetterPseudoElement::AttachFirstLetterTextLayoutObjects( // FIXME: This would already have been calculated in firstLetterLayoutObject. // Can we pass the length through? - unsigned length = FirstLetterPseudoElement::FirstLetterLength(old_text); + bool preserve_breaks = ShouldPreserveBreaks( + first_letter_text->StyleRef().GetWhiteSpaceCollapse()); + unsigned length = + FirstLetterPseudoElement::FirstLetterLength(old_text, preserve_breaks); // In case of inline level content made of punctuation, we use // first_letter_text length instead of FirstLetterLength. diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.h b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.h index 9134025a10b4cb..25f497efec6587 100644 --- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.h +++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.h @@ -46,7 +46,8 @@ class CORE_EXPORT FirstLetterPseudoElement final : public PseudoElement { void Trace(Visitor*) const override; static LayoutText* FirstLetterTextLayoutObject(const Element&); - static unsigned FirstLetterLength(const String&); + static unsigned FirstLetterLength(const String&, + bool preserve_breaks = false); void ClearRemainingTextLayoutObject(); LayoutTextFragment* RemainingTextLayoutObject() const { diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 12a66b35252c91..ae3256989021f8 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 @@ -872,6 +872,12 @@ name: "CSSExponentialFunctions", status: "experimental", }, + { + // Don't allow new line as preceding character for ::first-letter. + // See crbug.com/1477217 + name: "CSSFirstLetterNoNewLineAsPrecedingChar", + status: "stable", + }, { name: "CSSFocusVisible", status: "stable", diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-with-preceding-new-line.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-with-preceding-new-line.html new file mode 100644 index 00000000000000..5243b042882328 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-with-preceding-new-line.html @@ -0,0 +1,31 @@ + + + + + CSS Test: ::first-letter with preceding new line + + + + + + + +
+ The second line. + The third line. +
+ + diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/first-letter-with-preceding-new-line-ref.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/first-letter-with-preceding-new-line-ref.html new file mode 100644 index 00000000000000..e3d6b0aa285361 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/first-letter-with-preceding-new-line-ref.html @@ -0,0 +1,20 @@ + + + + + CSS Test: ::first-letter with preceding new line + + + +
+ The second line. + The third line. +
+ +