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>