mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(geometry): preserve OOPIF scale with occupied scrollbars
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# OOPIF occupied-scrollbar regression
|
||||
|
||||
An iframe's content quad includes its child viewport's scrollbar space, while
|
||||
`Page.getLayoutMetrics().cssLayoutViewport` excludes that space. Mapping the latter
|
||||
onto the whole quad stretches positions and sizes. The full target-local viewport
|
||||
must determine scale; the visible viewport must still clip content at every OOPIF
|
||||
boundary and constrain target-local action points.
|
||||
|
||||
This fixture alternates loopback hostnames across two nested OOPIFs. It includes
|
||||
scrolling, borders, padding, scaled iframe owners, a partially clipped button and
|
||||
a fully clipped button. Custom scrollbars occupy different widths and heights.
|
||||
The `scrollbars` query parameter accepts `both` (default), `vertical`, `horizontal`
|
||||
or `none`.
|
||||
|
||||
Run the numerical regression with Node 22+ and a local Chrome executable:
|
||||
|
||||
```sh
|
||||
BSK_GEOMETRY_CHROME=/path/to/chrome pnpm --filter @browser-skill/extension exec vitest run \
|
||||
src/tools/__tests__/snapshot-coordinates.browser.test.ts
|
||||
```
|
||||
|
||||
The existing browser runner is shared with the snapshot-unit regression. The test:
|
||||
|
||||
- Verifies actual OOPIF targets and occupied scrollbar dimensions.
|
||||
- Compares snapshot and live geometry with independent DOM rectangles and clipping.
|
||||
- Dispatches real root-target clicks and verifies the intended frame/button received them.
|
||||
- Rejects fully clipped controls and checks snapshot measurement reuse per target.
|
||||
- Covers five device-scale/browser-zoom combinations plus single-axis and no-scrollbar cases.
|
||||
|
||||
The runner creates and removes isolated browser profiles. It is opt-in and skipped
|
||||
in normal unit runs when `BSK_GEOMETRY_CHROME` is unset.
|
||||
|
||||
Run the CLI fixture smoke with a connected test extension:
|
||||
|
||||
```sh
|
||||
BSK_AUTO_UPDATE=off pnpm eval:browser smoke --case oopif-scrollbars --bsk ./target/debug/bsk
|
||||
```
|
||||
|
||||
The smoke assertions verify both nested frames have occupied scrollbars, that the
|
||||
marker is observed and that the session is closed. Numeric geometry and actual
|
||||
click assertions are exercised by the browser test above, not by smoke alone.
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "../../../schemas/case.schema.json",
|
||||
"schemaVersion": 1,
|
||||
"id": "oopif-scrollbars",
|
||||
"title": "Nested OOPIFs with occupied scrollbars and clipped controls",
|
||||
"suite": "regression",
|
||||
"tags": ["geometry", "iframe", "scroll"],
|
||||
"fixture": { "startPath": "/oopif-scrollbars" },
|
||||
"prompts": {
|
||||
"en": "Open {url}, observe the page and its frames, report the OOPIF-SCROLLBARS marker, then close the session.",
|
||||
"zh-CN": "打开 {url},观察页面及其 iframe,报告 OOPIF-SCROLLBARS 标记,然后关闭会话。"
|
||||
},
|
||||
"coverage": ["session.start", "session.stop", "page.navigate", "inspect.observe"],
|
||||
"assertions": {
|
||||
"site": [
|
||||
{
|
||||
"label": "nested frame fixture settled",
|
||||
"type": "geometry.ready",
|
||||
"where": { "data.root": true },
|
||||
"minCount": 1
|
||||
},
|
||||
{
|
||||
"label": "both child frames have occupied scrollbars",
|
||||
"type": "geometry.scrollbars",
|
||||
"where": { "data.vertical": true, "data.horizontal": true },
|
||||
"minCount": 2
|
||||
}
|
||||
],
|
||||
"response": [{ "label": "observed the fixture marker", "includes": "OOPIF-SCROLLBARS" }],
|
||||
"adapter": [{ "label": "browser session was closed", "key": "sessionStopped" }]
|
||||
},
|
||||
"smoke": {
|
||||
"steps": [
|
||||
{ "action": "navigate" },
|
||||
{ "action": "wait-site-event", "type": "geometry.ready", "where": { "root": true } },
|
||||
{ "action": "observe" }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { page, withRun } from "../../../lib/fixtures.mjs";
|
||||
|
||||
export default {
|
||||
id: "oopif-scrollbars",
|
||||
routes: ["/oopif-scrollbars", "/oopif-scrollbars/frame", "/oopif-scrollbars/nested"],
|
||||
render({ pathname, runId, query }) {
|
||||
const root = pathname === "/oopif-scrollbars";
|
||||
const nested = pathname.endsWith("/nested");
|
||||
const mode = query.get("scrollbars") ?? "both";
|
||||
return page({
|
||||
title: "OOPIF scrollbar geometry regression",
|
||||
body: `
|
||||
<style>
|
||||
main { margin: 0; width: auto; }
|
||||
body { width: 2000px; height: 2400px; }
|
||||
html { overflow-anchor: none; }
|
||||
${!root ? `html { overflow-x: ${mode === "vertical" || mode === "none" ? "hidden" : "scroll"}; overflow-y: ${mode === "horizontal" || mode === "none" ? "hidden" : "scroll"}; } html::-webkit-scrollbar { width: 17px; height: 11px; }` : ""}
|
||||
button { position: absolute; padding: 0; border: 0; border-radius: 0; }
|
||||
#probe { left: ${root ? 180 : nested ? 30 : 100}px; top: ${root ? 360 : nested ? 40 : 160}px; width: 60px; height: 20px; }
|
||||
#edge { position: fixed; right: -20px; bottom: -15px; width: 80px; height: 60px; }
|
||||
#outside { position: fixed; left: calc(100% + 2px); top: calc(100% + 2px); width: 10px; height: 10px; }
|
||||
iframe { position: absolute; box-sizing: content-box; width: 420px; height: 320px;
|
||||
left: 340px; top: 400px; border: 6px solid black; padding: 8px; transform: scale(1.2); transform-origin: 0 0; }
|
||||
#nested { left: 150px; top: 210px; width: 200px; height: 120px; border-width: 4px; padding: 6px; transform: scale(0.8); }
|
||||
</style>
|
||||
<button id="probe" data-geometry-probe>OOPIF-SCROLLBARS</button>
|
||||
${!root ? '<button id="edge" data-geometry-probe>EDGE</button><button id="outside" data-geometry-probe>OUTSIDE</button>' : ""}
|
||||
${!nested ? `<iframe id="${root ? "cross" : "nested"}" name="${root ? "cross" : "nested"}" title="${root ? "outer" : "nested"} cross-site frame"></iframe>` : ""}
|
||||
`,
|
||||
script: `
|
||||
history.scrollRestoration = "manual";
|
||||
const child = document.querySelector("iframe");
|
||||
if (child) {
|
||||
const url = new URL(${JSON.stringify(withRun(root ? "/oopif-scrollbars/frame" : "/oopif-scrollbars/nested", runId, { scrollbars: mode }))}, location.href);
|
||||
url.hostname = location.hostname === "127.0.0.1" ? "localhost" : "127.0.0.1";
|
||||
child.src = url.href;
|
||||
}
|
||||
document.addEventListener("click", event => {
|
||||
const probe = event.target.closest("[data-geometry-probe]");
|
||||
if (probe) browserEval.send("geometry.clicked", { probe: probe.id });
|
||||
});
|
||||
window.addEventListener("load", () => {
|
||||
scrollTo(${root ? "80, 240" : nested ? "10, 20" : "40, 100"});
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
document.documentElement.dataset.geometryReady = "true";
|
||||
if (!${root}) browserEval.send("geometry.scrollbars", {
|
||||
vertical: innerWidth > document.documentElement.clientWidth,
|
||||
horizontal: innerHeight > document.documentElement.clientHeight,
|
||||
});
|
||||
browserEval.send("geometry.ready", { root: ${root} });
|
||||
}));
|
||||
});
|
||||
`,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -33,12 +33,9 @@ BSK_AUTO_UPDATE=off pnpm eval:browser smoke --case snapshot-coordinates --bsk ./
|
||||
Smoke alone does **not** certify coordinate accuracy; CLI observations do not expose raw boxes.
|
||||
Use the geometric test above for the regression's numeric assertions.
|
||||
|
||||
## Separate existing boundary
|
||||
## Related scrollbar regression
|
||||
|
||||
The OOPIF root uses non-occupying scrollbars in the unit-conversion regression. Root and
|
||||
same-process frames retain normal scrollbars. Append `&classic-scrollbars` to the fixture URL
|
||||
to reproduce the separate existing OOPIF projection issue: `targetProjection()` maps the CSS
|
||||
layout viewport (which excludes occupying scrollbars) onto the entire owner content quad.
|
||||
This inflates the projected coordinates when scrollbars occupy space. Main already has that
|
||||
mapping; fixing it also changes shared live geometry and is outside this snapshot-unit fix.
|
||||
The test does not relax its numeric tolerance to absorb that error.
|
||||
The unit-conversion fixture keeps non-occupying OOPIF scrollbars by default. The same
|
||||
browser test also runs the dedicated [OOPIF scrollbar fixture](../oopif-scrollbars/README.md),
|
||||
which covers occupied scrollbars, two OOPIF boundaries, clipping and real clicks.
|
||||
Append `&classic-scrollbars` to this fixture's URL to enable its normal OOPIF scrollbars.
|
||||
|
||||
@@ -41,22 +41,23 @@ test("case manifests are discovered, ordered, and grouped into suites", () => {
|
||||
"diagnostics",
|
||||
"mobile-emulation",
|
||||
"generated-form",
|
||||
"oopif-scrollbars",
|
||||
"snapshot-coordinates",
|
||||
],
|
||||
);
|
||||
assert.deepEqual(repositorySummary(cases, fixtureRegistry).suites, {
|
||||
core: 6,
|
||||
matrix: 1,
|
||||
regression: 1,
|
||||
regression: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("repository validation links every case to a fixture and valid workflow evidence", () => {
|
||||
assert.deepEqual(validateRepositoryCases(cases, fixtureRegistry), []);
|
||||
const summary = repositorySummary(cases, fixtureRegistry);
|
||||
assert.equal(summary.cases, 8);
|
||||
assert.equal(summary.fixtureModules, 9);
|
||||
assert.equal(summary.fixtureRoutes, 15);
|
||||
assert.equal(summary.cases, 9);
|
||||
assert.equal(summary.fixtureModules, 10);
|
||||
assert.equal(summary.fixtureRoutes, 18);
|
||||
});
|
||||
|
||||
test("manifest validation rejects unknown operations and incomplete workflow steps", () => {
|
||||
|
||||
Reference in New Issue
Block a user