Fix line breaking with `break-spaces` in the presence of close tags

With the `white-space: break-spaces` CSS property, white spaces are
always preserved, and they always take space. Combined with the fact
that white space characters have a break opportunity after them, but
not before (as required by UAX#14), this means that a word which
exactly fits the remaining inline size of the line, could not fit in
that line without overflow if it was followed by a space.

Chromium's implementation of `break-spaces`, however, did allow
breaking between a word and a space if there was a closing element
boundary in the way, even if both sides of the boundary had
`white-space: break-spaces`. This is because
`NGLineBreaker::HandleCloseTag` did not seem to be considering the
`break-spaces` class. This CL fixes this.

Bug: 1261435
Change-Id: I010cf99ffb0c2cf03b4ff2547a412cc44c7e1bab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4150327
Reviewed-by: Koji Ishii <kojii@chromium.org>
Commit-Queue: Andreu Botella <abotella@igalia.com>
Cr-Commit-Position: refs/heads/main@{#1096617}
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 965a56a..cc9bb2f 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -2538,13 +2538,28 @@
       item_result->can_break_after = last->can_break_after;
       return;
     }
-    if (was_auto_wrap || last->can_break_after) {
-      item_result->can_break_after =
-          last->can_break_after ||
-          IsBreakableSpace(Text()[item_result->EndOffset()]);
+    if (last->can_break_after) {
+      // A break opportunity before a close tag always propagates to after the
+      // close tag.
+      item_result->can_break_after = true;
       last->can_break_after = false;
       return;
     }
+    if (was_auto_wrap) {
+      // We can break before a breakable space if we either:
+      //   a) allow breaking before a white space, or
+      //   b) the break point is preceded by another breakable space.
+      // TODO(abotella): What if the following breakable space is after an
+      // open tag which has a different white-space value?
+      bool preceded_by_breakable_space =
+          item_result->EndOffset() > 0 &&
+          IsBreakableSpace(Text()[item_result->EndOffset() - 1]);
+      item_result->can_break_after =
+          IsBreakableSpace(Text()[item_result->EndOffset()]) &&
+          (!current_style_->BreakOnlyAfterWhiteSpace() ||
+           preceded_by_breakable_space);
+      return;
+    }
     if (auto_wrap_ && !IsBreakableSpace(Text()[item_result->EndOffset() - 1]))
       ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/break-spaces-011.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/break-spaces-011.html
new file mode 100644
index 0000000..8c40a31a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/break-spaces-011.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Test: line breaking with white-space: break-spaces and element boundaries</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com" />
+<link rel="help" title="3. White Space and Wrapping: the white-space property" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" title="5.1. Line Breaking Details" href="https://drafts.csswg.org/css-text-3/#line-break-details">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-break-spaces">
+<meta name="flags" content="ahem">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="An element boundary doesn't allow breaking if white-space is set to break-spaces at both sides of the boundary">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+div {
+  font: 25px/1 Ahem;
+}
+.fail {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+.fail span { color: green; }
+.test {
+  color: green;
+  width: 4ch;
+  white-space: break-spaces;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="fail">XXXX<br>X<span>X</span>X<span>X</span><br>XX<span>XX</span><br>X<span>X</span>X<span>X</span></div>
+<div class="test"><span>XXXX</span> X X XX<span> </span><span> </span>X X</div>