Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,11 @@ function LabAvoidKeyboardPage() {
* PROBES
* ============================================================================= */

//the moving frame of the aim's smooth scroll the notice lands on, and how tall it is
const ARRIVAL_FRAME = 3
//the moving frame of the aim's smooth scroll the notice lands on, and how tall it is.
//The first: the aim has committed its destination by then, and the flight is clock-driven
//while frames are not, so on a slow frame (WebKit's first run) the third moving frame
//already sat on the destination and the run proved nothing
const ARRIVAL_FRAME = 1
const ARRIVAL_PX = 100
//a scroll is over after this many frames with nothing moving past half a pixel —
//longer than the 120ms quiet window the no-`scrollend` fallback waits before re-aiming
Expand Down Expand Up @@ -237,7 +240,7 @@ function ArrivalProbe() {
return (
<LabSection
title="Content arriving mid-scroll"
description={`Run focuses the field below and, on frame ${ARRIVAL_FRAME} of the scroll that lifts it, inserts a ${ARRIVAL_PX}px notice above it. The aim chose its destination before the notice existed, so the field must still end up clear: AvoidKeyboard looks again when that scroll ends.`}
description={`Run focuses the field below and, on the first frame of the scroll that lifts it, inserts a ${ARRIVAL_PX}px notice above it. The aim chose its destination before the notice existed, so the field must still end up clear: AvoidKeyboard looks again when that scroll ends.`}
>
<LabActions>
<LabButton onClick={run}>Run: focus, insert mid-scroll</LabButton>
Expand Down
27 changes: 16 additions & 11 deletions playground/e2e/avoid-keyboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,17 @@ test.describe("AvoidKeyboard driven by the keyboard seam", () => {
*
* The aim picks an absolute scrollTop on the focus frame; a notice inserted during
* the 200-450ms flight moves the field down and leaves the destination where it
* was, so the field lands 100px short, under the box's bottom edge. Measured on
* origin/main on both engines: landed 520, clearance -76 (24 with no insertion).
* The fix looks once more when that scroll ends.
* was, so the field lands 100px short, under the box's bottom edge. Measured with
* the second look removed: chromium lands 520, clearance -76 (24 with no
* insertion); WebKit stops the flight where the notice went in, landed 130,
* clearance -466. The fix looks once more when that scroll ends.
*
* The lab's Run button drives it, so a device run is the same code path: it
* focuses the field with `preventScroll` (the browser's own scroll-into-view would
* otherwise travel first), counts the frames on which the scroller has moved, and
* on the third inserts the notice with `flushSync`, then waits for 30 still frames.
* otherwise travel first), and on the first frame the scroller has moved inserts
* the notice with `flushSync`, then waits for 30 still frames. Not a later frame: the
* flight runs on the clock and frames do not, so on a slow frame the third one had
* already reached the destination (WebKit, inserted at 520 of 520).
*
* Both Playwright engines have `scrollend`, and adaptv's iOS floor does not, so the
* test runs twice: once as shipped, and once with every `onscrollend` handler
Expand All @@ -158,7 +161,7 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro
test.use({ viewport: { width: 390, height: 844 } })

for (const mode of ["scrollend", "no scrollend"] as const) {
test(`${mode}: a 100px notice inserted on frame 3 of the scroll still leaves the field clear`, async ({
test(`${mode}: a 100px notice inserted on the first frame of the scroll still leaves the field clear`, async ({
page,
}, testInfo) => {
await page.addInitScript(() => {
Expand Down Expand Up @@ -205,6 +208,13 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro
`ARRIVAL ${testInfo.project.name} ${mode} clearance=${clearance} ${scroll}`,
)

//first, so a field left underneath reads as that and not as a broken premise: with
//no second look the landing is short of the notice, which also fails the premise
expect(
clearance,
"the field's bottom sits at or above the box's bottom once the scroll stops",
).toBeGreaterThanOrEqual(0)

//the premise: the notice went in while the FIRST aim's scroll was still short of its
//destination. That destination is where the field would have landed with no notice,
//which is the landing minus the notice. Comparing against the landing itself is
Expand All @@ -228,11 +238,6 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro
1,
)
await expect(page.getByLabel("Arrival field")).toBeFocused()

expect(
clearance,
"the field's bottom sits at or above the box's bottom once the scroll stops",
).toBeGreaterThanOrEqual(0)
})
}
})
Loading