Don't reuse generated breaks when space sequences become non-leading

The NNInlineItemsBuilder class is the responsible of creating the
NGInlineItems list form the text nodes in the DOM tree. A special part
of this logic is the one that inserts control items after a leading
space sequence, in order to introduce forced-breaks in that position.

When these leading space sequences are not in the paragraph's first
line it means that they are preceded by a line-break element. Removing
such break element triggers a new layout, since the DOM tree has
changed.

The NGInlineItemsBuilder logic tries to reuse the existing items when
processing a new text layout. However, if a line-break element is
removed, we should also remove any control item previously added to
introduce forced-breaks, as commented before. The current code doesn't
do that, leading to the buggy behavior described on the bug.

This CL introduces an additional step in the AppendTextReusing function
to detect these kind of "orphan" control items and ignore them when
building the new items list.

Bug: 1263567
Change-Id: Ie9ebadd4952efb491b983fba1cd2c6a7d7d1bf0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3290413
Commit-Queue: Javier Fernandez <jfernandez@igalia.com>
Reviewed-by: Yoshifumi Inoue <yosin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#944940}
diff --git a/editing/run/caret-navigation-after-removing-line-break.html b/editing/run/caret-navigation-after-removing-line-break.html
new file mode 100644
index 0000000..9855c3b
--- /dev/null
+++ b/editing/run/caret-navigation-after-removing-line-break.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<style>
+    .wrap span {
+        white-space: pre-wrap;
+    }
+</style>
+<div class="wrap">
+    <span id="initial" contenteditable="true"> abcd </span>111<span id="before" contenteditable="true"> efgh </span>222<br><span id="after" contenteditable="true"> ijkl </span>333<span id="next" contenteditable="true"> mnop </span>444<span id="last" contenteditable="true"> qrst </span>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script>
+const KEY_CODE_MAP = {
+  'ArrowLeft':  '\uE012',
+  'ArrowUp':    '\uE013',
+  'ArrowRight': '\uE014',
+  'ArrowDown':  '\uE015',
+  'Tab': '\uE004',
+  'S': '\u0053',
+};
+
+function keyPress(target, key) {
+  const code = KEY_CODE_MAP[key];
+  return test_driver.send_keys(target, code);
+}
+
+function deletebr() {
+  let el;
+  el = document.querySelector('br');
+  if (el) {
+    el.remove();
+  }
+}
+
+// Delete the <br> element so that we get a single line
+deletebr();
+
+const s = getSelection();
+promise_test(async t => {
+  initial.focus();
+  let node = initial.firstChild;
+  assert_equals(s.anchorNode, node, "Focus must be at the span with 'abcd'");
+  assert_equals(s.anchorOffset, 0, "Caret must be at the start of the 'abcd' text");
+
+  await keyPress(initial, "Tab");
+  node = before.firstChild
+  assert_equals(s.anchorNode, node, "Caret moved to span with 'efgh'");
+  assert_equals(s.anchorOffset, 0, "Caret must be at the start of the 'efgh' text");
+
+  await keyPress(before, "Tab");
+  node = after.firstChild
+  assert_equals(s.anchorNode, node, "Focus must be at the span with 'ijkl'");
+  assert_equals(s.anchorOffset, 0, "Caret must be at the start of the 'efgh' text");
+
+  await keyPress(after, "Tab");
+  node = next.firstChild
+  assert_equals(s.anchorNode, node, "Focus must be at the span with 'mnop'");
+  assert_equals(s.anchorOffset, 0, "Caret must be at the start of the 'efgh' text");
+
+}, "Navigate after deleting <br>");
+
+promise_test(async t => {
+  initial.focus();
+  await keyPress(initial, "Tab");
+  await keyPress(before, "Tab");
+  await keyPress(after, "Tab");
+  await keyPress(next, "ArrowRight");
+  await keyPress(next, "ArrowRight");
+  await keyPress(next, "ArrowRight");
+
+  await keyPress(next, "S");
+  assert_equals(next.firstChild.textContent, " mnSop ", "Inserting a 'S' char betwen 'n' and 'o'");
+}, "Insert text after deleting <br>")
+
+</script>