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.
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);
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));
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.
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.
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"
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
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).
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:
out/Default/chrome --user-data-dir=/tmp/test-profile
F12), click the device
toolbar toggle (Ctrl+Shift+M).https://static.januschka.com/i-40384509/reproducer.html#:~:text=test,page
"test page"
in yellowscrollY > 500 and
scale ~4Note: Chrome strips :~:text= from the
URL after processing -- the hash appearing empty is expected.
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.
ExtractFirstFragmentRect(): switch from
ComputeTextRect (int) to ComputeTextRectF (float),
and from FrameToViewport(Point) to
FrameToViewport(PointF).
Add new ComputeTextRectF() overloads that return
gfx::RectF instead of gfx::Rect, preserving
float precision from the underlying quad bounding boxes.
Update ExtractFirstTextFragmentRectScroll to branch on
FractionalScrollOffsetsEnabled(), expecting
(432, 298, ...) vs (432, 296, ...).