Support replaced content in repeated (NG) table sections.

For now we attempt to only support regular images, and prevent any other
type of replaced content (including form elements) and scrollable
containers from being repeated, so that we only paint / hit-test in the
first fragmentainer. Or at least don't crash.

This requires us to simplify the PaintInfo::FragmentToPaint() logic. We
previously had some special code that DCHECKed that legacy objects don't
create multiple fragments (and simply returned the first FragmentData),
but since multiple fragments is now possible, we'll allow it, under the
assumption that someone has set the correct fragment ID in PaintInfo
when looking up FragmentData via LayoutObject.

Also had to make some changes in pre-paint, so that we don't leave
fragment traversal at NG objects, even if CanTraversePhysicalFragments()
is false. This is mainly to avoid DCHECK failures -- we'll fail to get
this right for certain types of repeated replaced content, but that's
fine, as long as CanPaintMultipleFragments() returns false in such
cases.

This CL makes two existing tests pass. Also added a "tentative"
correctness test for repeated images, and added a crash test for a lot
of other types of repeated replaced content, and also repeated
scrollable containers, as there are quite a few reasons why the engine
doesn't support repeating those correctly.

Bug: 1334547
Change-Id: Ia86a668b842f4566957b028a03dc51e641a58652
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3826300
Reviewed-by: Ian Kilpatrick <ikilpatrick@chromium.org>
Commit-Queue: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: Alison Maher <almaher@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1034415}
diff --git a/css/css-break/table/repeated-section/image.tentative.html b/css/css-break/table/repeated-section/image.tentative.html
new file mode 100644
index 0000000..72fea01
--- /dev/null
+++ b/css/css-break/table/repeated-section/image.tentative.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-tables-3/#repeated-headers">
+<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
+<style>
+  .table {
+    display: table;
+    width: 100%;
+  }
+  .header {
+    display: table-header-group;
+    break-inside: avoid;
+  }
+  .filler {
+    display: table-row;
+    break-inside: avoid;
+    height: 350px;
+  }
+  img {
+    width: 100%;
+    height: 100px;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:4; gap:0; column-fill:auto; height:500px;">
+    <div class="table">
+      <div class="header">
+        <!-- The image src is a 1x1 green pixel. -->
+        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGhg+A8AAoQBgNXA8F0AAAAASUVORK5CYII=">
+      </div>
+      <div class="filler"></div>
+      <div class="filler"></div>
+      <div class="filler"></div>
+      <div class="filler"></div>
+    </div>
+  </div>
+</div>
diff --git a/css/css-break/table/repeated-section/special-elements-crash.html b/css/css-break/table/repeated-section/special-elements-crash.html
new file mode 100644
index 0000000..469e534
--- /dev/null
+++ b/css/css-break/table/repeated-section/special-elements-crash.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-tables-3/#repeated-headers">
+<style>
+  body {
+    margin: 0;
+  }
+  .table {
+    display: table;
+    width: 100%;
+  }
+  .header {
+    display: table-header-group;
+    break-inside: avoid;
+  }
+  .filler {
+    display: table-row;
+    break-inside: avoid;
+    height: 2600px;
+  }
+  .header > * {
+    /* Don't make stuff too tall. We want everything (in the header) to be
+       within the viewport. */
+    height: 10px;
+  }
+</style>
+<div style="columns:3; column-fill:auto; width:600px; height:5000px;">
+  <div class="table">
+    <div class="header">
+      <input type="text" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+      <input type="submit" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+      <input type="radio">
+      <input type="checkbox">
+      <input type="date">
+      <input type="range">
+      <input type="number">
+      <input type="color">
+      <input type="password">
+      <canvas id="canvas" style="width:40px; height:40px;"></canvas>
+      <iframe src="data:text/html,<div style='position:absolute; height:200px;'>x</div>" style="width:100px; height:40px;"></iframe>
+      <div style="overflow:scroll; width:100px; height:30px;">
+        <div style="height:200px;"></div>
+      </div>
+      <select size="0"><option>xxx</option></select>
+      <select size="5"><option>xxx</option></select>
+      <svg style="width:100px; height:30px;">
+        <circle cx="20" cy="15" r="7" fill="hotpink"/>
+      </svg>
+      <textarea style="width:50px; height:30px;">
+        xxxxxxxxxx xxxxxxxxx xxxxxxxx xxxxxxxxx xxxxxxx xxxxxx
+      </textarea>
+      <video style="width:100px; height:50px;" controls></video>
+      <video style="width:100px; height:50px;"></video>
+      <meter></meter>
+      <button>xxxxxx</button>
+      <fieldset><legend>epic</legend></fieldset>
+      <!-- The image src is a 1x1 green pixel. -->
+      <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGhg+A8AAoQBgNXA8F0AAAAASUVORK5CYII=">
+    </div>
+    <div class="filler"></div>
+    <div class="filler"></div>
+    <div class="filler"></div>
+  </div>
+</div>
+<script>
+  var ctx = canvas.getContext('2d');
+  ctx.fillStyle = "hotpink";
+  ctx.fillRect(0, 0, 50, 50);
+
+  function paintDone() {
+    return new Promise(function(resolve) {
+      requestAnimationFrame(()=> {
+        requestAnimationFrame(()=> {
+          resolve();
+        });
+      });
+    });
+  }
+  async function toggleStyles(elements) {
+    for (const display of ['block', 'inline-block', 'inline']) {
+      for (const position of ['static', 'relative']) {
+        for (const cssfloat of ['none', 'left']) {
+          for (var elm of elements) {
+            elm.style.display = display;
+            elm.style.position = position;
+            elm.style.cssFloat = cssfloat;
+          }
+          await paintDone();
+          for (var elm of elements) {
+            var rect = elm.getClientRects()[0];
+            var x = rect.left;
+            var y = rect.top;
+            x += 2;
+            y += 2;
+            document.elementFromPoint(x, y);
+            document.elementFromPoint(x + 200, y);
+            document.elementFromPoint(x + 400, y);
+          }
+        }
+      }
+    }
+  }
+
+  toggleStyles(document.querySelectorAll(".header > *"));
+</script>