Diwall MCP

English
Download v0.0.1

Marks and masks

A mark is the browser's node, not a rank, and it is checked again at the instant of the click. Masks are drawn on the captured pixels, at any screen scale and page zoom.

A mark is a node, not a rank

A rank says “the third button”. When the page inserts a banner or a line, the third button is another one, and it may be the payment button. The server binds each mark to the node the browser holds for that element in that snapshot. When the model acts:

  1. the server finds the node behind the mark and checks that it is the same node, still in the page; a moved or replaced element is refused with element_stale, a page that changed since the observation with snapshot_stale;
  2. it asks the browser which node lies at the point it is about to press, and refuses with element_obscured when that is neither the target nor one of its descendants: a banner or a modal over the target would take the click;
  3. after the pointer moves and before the press, it asks again, because a menu that opens on hover or a banner that slides in moves the page at that moment.

In every refusal, nothing is clicked, and the model observes again.

Measured on Chromium 141 with a test page that moves each target 60 CSS pixels down at the first movement of the pointer and puts another button in its place: before the third check, 24 clicks out of 24 returned no error and landed on the button put in place of the target; with it, 24 out of 24 are refused. What the checks do not catch is on Limits.

A mask is drawn on the pixels

The marks and the masks are drawn by the server on the captured image, never inside the page. An overlay drawn in the page could be moved, hidden or read by the page’s own scripts; a rectangle painted over the captured pixels covers what the server measured, and the value under it cannot be recovered from the image. If one mask cannot be drawn, no image is published (capture: unavailable); the tree is still served.

Every box is in CSS pixels, so a mask sits on its field whatever the scale of the screen. Measured on Chromium 141, window 1280 × 800, on the password field of the demo page:

ScaleThe browser’s box, and the server’s nowThe server’s before the fix
1.598.7, 154.1, 252.0 × 27.3148.0, 231.2, 378.0 × 41.0
298.7, 154.4, 252.0 × 27.5197.4, 308.9, 504.0 × 55.0

At scales 1, 1.5 and 2, every pixel of the field is now under its mask, and the mask goes no further than two pixels beyond it.

The page zoom (Ctrl and +) changes the size of a CSS pixel; the server multiplies the cut of the image by the zoom the browser reports. At 100, 125, 150 and 200 %, on a headless browser, every pixel of the field is under its mask; before the fix, at 125 % and above, none was, and capture still read ok. The image is now withheld, with capture: unavailable, when the browser gives no usable zoom, when the zoom changes during the capture, or when the image differs from the view by more than two pixels on either axis.

To replay the measures with a browser on your PATH, from the repository:

.venv/bin/python -m pytest -q tests/test_scale_integration.py
.venv/bin/python -m pytest -q tests/test_zoom_integration.py
.venv/bin/python -m pytest -q tests/test_zoom_scrolled_integration.py

The full tables, and what was not measured, are in section 7 of the operator guide.