Use float geometry for first text fragment viewport rect

#40384509 CL 7614274 In Review

Summary

TextFragmentHandler::ExtractFirstFragmentRect() computes the viewport-relative bounding box of the first text fragment highlight and returns it to the browser process via Mojo. Before this CL, the computation used integer geometry (gfx::Rect / ComputeTextRect / FrameToViewport(gfx::Point)), which truncates fractional values at each intermediate step. This causes a ~2px error in the reported rect when the page is zoomed.

The fix switches the pipeline to float geometry (gfx::RectF / ComputeTextRectF / FrameToViewport(gfx::PointF)) and only rounds to integer at the final step via gfx::ToEnclosingRect(). This is part of the broader FractionalScrollOffsets effort.

What Changed

Before (integer pipeline)

gfx::Rect bb = ComputeTextRect(...);
gfx::Point tl = FrameToViewport(
    bb.origin());          // int
gfx::Point br = FrameToViewport(
    bb.bottom_right());    // int
rect = gfx::Rect(tl, br - tl);

After (float pipeline)

gfx::RectF bb = ComputeTextRectF(...);
gfx::PointF tl = FrameToViewport(
    bb.origin());          // float
gfx::PointF br = FrameToViewport(
    bb.bottom_right());    // float
rect = ToEnclosingRect(
    gfx::RectF(tl, br - tl));

Visible effect (unit test)

ExtractFirstTextFragmentRectScroll loads a page at initial-scale=4 with text at top:2000px and highlights "test page":

// Without FractionalScrollOffsets:
EXPECT_EQ(gfx::Rect(432, 296, 360, 40), rect);

// With FractionalScrollOffsets:
EXPECT_EQ(gfx::Rect(432, 298, 360, 40), rect);

The 2px Y difference comes from integer truncation at intermediate coordinate conversions.

Live Reproducer

The reproducer runs 4 automated RED/GREEN tests that detect whether FractionalScrollOffsets is active. They test sub-pixel scroll precision, snap accuracy, and fractional delta accumulation -- the actual user-visible consequences of this 12-year-old issue.

▶ Run RED/GREEN tests

How to go from RED to GREEN

By default all tests will FAIL (FSO is disabled). Launch Chrome with the flag to see them go GREEN:

out/Default/chrome --enable-blink-features=FractionalScrollOffsets \
  --user-data-dir=/tmp/fso-test \
  "https://static.januschka.com/i-40384509/reproducer.html"

Manual Verification

Step 1 -- Build Chrome with and without the CL

Build two versions of content_shell or Chrome:

# With the CL (current branch):
autoninja -C out/Default content_shell

# Without the CL (before the change):
git stash && autoninja -C out/Default content_shell

Step 2 -- Run the unit test

The most reliable verification is the C++ unit test that checks the exact rect values:

tools/autotest.py -C out/Default \
  --gtest_filter="TextFragmentHandlerTest.ExtractFirstTextFragmentRectScroll" \
  third_party/blink/renderer/core/fragment_directive/text_fragment_handler_test.cc

Expected: Test passes. With fractional scroll offsets enabled, the rect should be (432, 298, 360, 40); without, (432, 296, 360, 40).

Step 3 -- Visual check (DevTools mobile emulation)

initial-scale=4 in viewport meta is a mobile feature -- desktop Chrome ignores it. Text fragment directives (#:~:text=) only activate on a fresh top-level navigation. To get both working:

  1. Launch your patched Chrome:
    out/Default/chrome --user-data-dir=/tmp/test-profile
  2. Open DevTools (F12), click the device toolbar toggle (Ctrl+Shift+M).
  3. Pick any mobile device (e.g. "iPhone 14 Pro Max") or set a custom size.
  4. Paste the full URL in the address bar and press Enter:
    https://static.januschka.com/i-40384509/reproducer.html#:~:text=test,page
  5. Expected result:
    • Page zooms to scale=4
    • Browser scrolls down and highlights "test page" in yellow
    • HUD shows scrollY > 500 and scale ~4
    • Status reads "Setup looks correct"

Note: Chrome strips :~:text= from the URL after processing -- the hash appearing empty is expected.

Step 4 -- Verify via content_shell

content_shell honors viewport meta natively (no DevTools emulation needed):

autoninja -C out/Default content_shell
out/Default/content_shell \
  "https://static.januschka.com/i-40384509/reproducer.html#:~:text=test,page"

This gives the exact same conditions as the C++ unit test: viewport enabled, initial-scale=4, text fragment navigation.

Files Changed

text_fragment_handler.cc

ExtractFirstFragmentRect(): switch from ComputeTextRect (int) to ComputeTextRectF (float), and from FrameToViewport(Point) to FrameToViewport(PointF).

visible_units.cc / .h

Add new ComputeTextRectF() overloads that return gfx::RectF instead of gfx::Rect, preserving float precision from the underlying quad bounding boxes.

text_fragment_handler_test.cc

Update ExtractFirstTextFragmentRectScroll to branch on FractionalScrollOffsetsEnabled(), expecting (432, 298, ...) vs (432, 296, ...).