mirror of
https://github.com/lexmount/moli.git
synced 2026-09-25 08:01:28 +00:00
188 lines
6.2 KiB
Python
188 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Pin Phase 22 containment geometry and paint semantics to Chromium.
|
|
|
|
Run with:
|
|
|
|
uv run --project moli-benchmark --with pillow \
|
|
python scripts/layout-phase22-containment-chromium-differential.py
|
|
|
|
The paired Rust regression is
|
|
`containment_matches_chromium_containing_block_eligibility_and_paint_clip`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import html
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from PIL import Image
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURE = ROOT / "scripts" / "fixtures" / "layout-phase22-containment.html"
|
|
DEFAULT_CHROMIUM = Path(
|
|
os.environ.get(
|
|
"CHROMIUM_BINARY",
|
|
str(Path.home() / "chromium" / "src" / "out" / "Default" / "chrome"),
|
|
)
|
|
).expanduser()
|
|
|
|
EXPECTED_GEOMETRY: dict[str, list[float]] = {
|
|
"layout": [20, 20, 160, 100],
|
|
"layout-abs": [146, 32, 20, 16],
|
|
"layout-fixed": [36, 34, 18, 14],
|
|
"paint-abs": [346, 32, 20, 16],
|
|
"paint-fixed": [236, 34, 18, 14],
|
|
"content-abs": [546, 32, 20, 16],
|
|
"content-fixed": [436, 34, 18, 14],
|
|
"strict-abs": [746, 32, 20, 16],
|
|
"strict-fixed": [636, 34, 18, 14],
|
|
"inline-abs": [725, 186, 20, 15],
|
|
"shadow-root": [140, 180, 200, 100],
|
|
"shadow-action": [302, 189, 30, 20],
|
|
"will-contain-abs": [133, 328, 20, 15],
|
|
"will-contain-fixed": [29, 330, 18, 14],
|
|
"will-position-abs": [293, 328, 20, 15],
|
|
"will-position-fixed": [9, 10, 18, 14],
|
|
"will-transform-abs": [453, 328, 20, 15],
|
|
"will-transform-fixed": [349, 330, 18, 14],
|
|
"content-auto": [380, 500, 140, 80],
|
|
"content-auto-abs": [493, 508, 20, 15],
|
|
"content-auto-fixed": [389, 510, 18, 14],
|
|
"plain-bfc": [20, 430, 100, 0],
|
|
"layout-bfc": [160, 430, 100, 30],
|
|
"paint-bfc": [240, 460, 100, 30],
|
|
}
|
|
|
|
EXPECTED_OFFSET_PARENTS: dict[str, str | None] = {
|
|
"layout-abs": "layout",
|
|
"layout-fixed": "layout",
|
|
"paint-abs": "paint",
|
|
"paint-fixed": "paint",
|
|
"content-abs": "content",
|
|
"content-fixed": "content",
|
|
"strict-abs": "strict",
|
|
"strict-fixed": "strict",
|
|
"inline-abs": "inline-outer",
|
|
"will-contain-abs": "will-contain",
|
|
"will-contain-fixed": "will-contain",
|
|
"will-position-abs": "will-position",
|
|
"will-position-fixed": None,
|
|
"will-transform-abs": "will-transform",
|
|
"will-transform-fixed": "will-transform",
|
|
"content-auto-abs": "content-auto",
|
|
"content-auto-fixed": "content-auto",
|
|
"row-abs": "table",
|
|
"cell-abs": "table-cell-contained",
|
|
"shadow-action": "root",
|
|
}
|
|
|
|
SAMPLE_POINTS: dict[str, tuple[int, int]] = {
|
|
"layout-overflow": (10, 105),
|
|
"paint-clipped": (210, 105),
|
|
"paint-inside": (230, 105),
|
|
"content-clipped": (410, 105),
|
|
"content-inside": (430, 105),
|
|
"strict-clipped": (610, 105),
|
|
"strict-inside": (630, 105),
|
|
}
|
|
|
|
EXPECTED_PIXELS: dict[str, list[int]] = {
|
|
"layout-overflow": [0, 255, 0, 255],
|
|
"paint-clipped": [255, 255, 255, 255],
|
|
"paint-inside": [0, 255, 0, 255],
|
|
"content-clipped": [255, 255, 255, 255],
|
|
"content-inside": [0, 255, 0, 255],
|
|
"strict-clipped": [255, 255, 255, 255],
|
|
"strict-inside": [0, 255, 0, 255],
|
|
}
|
|
|
|
|
|
def run_chromium(binary: Path, *arguments: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[
|
|
str(binary),
|
|
"--headless=new",
|
|
"--disable-background-networking",
|
|
"--disable-default-apps",
|
|
"--disable-gpu",
|
|
"--hide-scrollbars",
|
|
"--no-first-run",
|
|
"--no-sandbox",
|
|
"--window-size=800,600",
|
|
*arguments,
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
|
|
def assert_close(label: str, actual: Any, expected: Any) -> None:
|
|
if isinstance(expected, (int, float)) and not isinstance(expected, bool):
|
|
if not isinstance(actual, (int, float)) or abs(actual - expected) > 0.05:
|
|
raise AssertionError(f"{label}: expected {expected}, got {actual}")
|
|
return
|
|
if isinstance(expected, list):
|
|
if not isinstance(actual, list) or len(actual) != len(expected):
|
|
raise AssertionError(f"{label}: expected {expected}, got {actual}")
|
|
for index, (actual_item, expected_item) in enumerate(zip(actual, expected)):
|
|
assert_close(f"{label}[{index}]", actual_item, expected_item)
|
|
return
|
|
if actual != expected:
|
|
raise AssertionError(f"{label}: expected {expected!r}, got {actual!r}")
|
|
|
|
|
|
def measure(binary: Path) -> dict[str, Any]:
|
|
uri = FIXTURE.resolve().as_uri()
|
|
dumped = run_chromium(binary, "--dump-dom", uri).stdout
|
|
match = re.search(r'<pre id="output">(.*?)</pre>', dumped, re.DOTALL)
|
|
if match is None:
|
|
raise RuntimeError("Chromium dump did not contain the fixture output")
|
|
observable = json.loads(html.unescape(match.group(1)))
|
|
|
|
with tempfile.TemporaryDirectory(prefix="moli-phase22-containment-") as directory:
|
|
screenshot = Path(directory) / "capture.png"
|
|
run_chromium(binary, f"--screenshot={screenshot}", uri)
|
|
image = Image.open(screenshot).convert("RGBA")
|
|
pixels = {
|
|
name: list(image.getpixel(point)) for name, point in SAMPLE_POINTS.items()
|
|
}
|
|
return {**observable, "samplePoints": SAMPLE_POINTS, "pixels": pixels}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--chromium", type=Path, default=DEFAULT_CHROMIUM)
|
|
parser.add_argument("--record", action="store_true")
|
|
args = parser.parse_args()
|
|
measured = measure(args.chromium)
|
|
if not args.record:
|
|
for name, expected in EXPECTED_GEOMETRY.items():
|
|
assert_close(f"geometry.{name}", measured["geometry"].get(name), expected)
|
|
for name, expected in EXPECTED_OFFSET_PARENTS.items():
|
|
assert_close(f"offsets.{name}", measured["offsets"].get(name), expected)
|
|
assert_close("pixels", measured["pixels"], EXPECTED_PIXELS)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"status": "recorded" if args.record else "passed",
|
|
"chromium": str(args.chromium),
|
|
**measured,
|
|
},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|