[eric] macos: pure property-tested clamp_decision for the mouse-clamp addon (5M-case test)

This commit is contained in:
ciregenz
2026-06-13 22:27:34 -07:00
parent 3748f23666
commit 67e08f6fe4
5 changed files with 138 additions and 8 deletions
@@ -0,0 +1,44 @@
// Pure geometry for the off-window mouse-release clamp: no AppKit, no state, no I/O,
// so the SAME code that ships in production (mouseclamp.mm includes this) can be
// hammered by a standalone property test (clamp_decision_test.c). Keeping the
// decision here, not inline in the .mm, is the whole point: the misfire risk lives
// in this arithmetic, so this arithmetic is what the machine must prove.
//
// Contract (mirrors NSPointInRect: min-edge inclusive, max-edge exclusive):
// release INSIDE the window -> {clamp=false}, location returned UNCHANGED.
// release OUTSIDE the window -> {clamp=true}, location snapped strictly inside
// the content rect (so Chromium's hit-test can
// never return null, which is the crash).
#ifndef MOUSECLAMP_DECISION_H
#define MOUSECLAMP_DECISION_H
#include <math.h>
#include <stdbool.h>
typedef struct {
bool clamp; // replace the event with one at (x,y)? false => leave it alone
double x, y; // snapped location; meaningful only when clamp == true
} ClampDecision;
// p = release location in window coords
// w = window size (winW, winH)
// c = content rect in window coords (cx, cy, cw, ch)
static inline ClampDecision clamp_decision(double px, double py,
double winW, double winH,
double cx, double cy,
double cw, double ch) {
ClampDecision d;
// inside the window (titlebar included) -> untouched, byte-for-byte the same event
if (px >= 0.0 && px < winW && py >= 0.0 && py < winH) {
d.clamp = false;
d.x = px;
d.y = py;
return d;
}
d.clamp = true;
d.x = fmin(fmax(px, cx + 1.0), cx + cw - 1.0);
d.y = fmin(fmax(py, cy + 1.0), cy + ch - 1.0);
return d;
}
#endif // MOUSECLAMP_DECISION_H
@@ -0,0 +1,73 @@
// Property test for clamp_decision() (the only misfire-prone part of the fix).
// Compiles with plain clang, no node/electron/AppKit, so it runs anywhere fast.
// Hammers millions of random window/content/point combinations and asserts the
// three invariants the fix's safety rests on. Run via ./run-tests.sh.
#include "clamp_decision.h"
#include <stdio.h>
#include <stdlib.h>
static unsigned long g_seed = 88172645463325252ULL;
static double frnd(double lo, double hi) { // xorshift -> [lo,hi)
g_seed ^= g_seed << 13; g_seed ^= g_seed >> 7; g_seed ^= g_seed << 17;
return lo + (hi - lo) * ((double)(g_seed >> 11) / (double)(1ULL << 53));
}
static long fails = 0;
static void check(int cond, const char *msg,
double px, double py, double w, double h,
double cx, double cy, double cw, double ch, ClampDecision d) {
if (cond) return;
if (fails < 12)
fprintf(stderr,
"FAIL %s: p=(%.2f,%.2f) win=(%.2f,%.2f) content=(%.2f,%.2f,%.2f,%.2f) "
"=> clamp=%d (%.2f,%.2f)\n",
msg, px, py, w, h, cx, cy, cw, ch, d.clamp, d.x, d.y);
fails++;
}
static void run(double px, double py, double w, double h,
double cx, double cy, double cw, double ch) {
ClampDecision d = clamp_decision(px, py, w, h, cx, cy, cw, ch);
int inside = (px >= 0.0 && px < w && py >= 0.0 && py < h);
if (inside) {
// INVARIANT 1 (no misfire on normal clicks): in-window release is untouched.
check(!d.clamp, "inside-must-not-clamp", px, py, w, h, cx, cy, cw, ch, d);
check(d.x == px && d.y == py, "inside-must-be-identical", px, py, w, h, cx, cy, cw, ch, d);
} else {
// INVARIANT 2 (crash-safe): off-window release is clamped strictly inside the
// content rect, so Chromium's hit-test can never return null.
check(d.clamp, "outside-must-clamp", px, py, w, h, cx, cy, cw, ch, d);
check(d.x >= cx + 1.0 && d.x <= cx + cw - 1.0, "x-in-content", px, py, w, h, cx, cy, cw, ch, d);
check(d.y >= cy + 1.0 && d.y <= cy + ch - 1.0, "y-in-content", px, py, w, h, cx, cy, cw, ch, d);
}
// INVARIANT 3: never produces NaN/inf.
check(d.x == d.x && d.y == d.y, "no-nan", px, py, w, h, cx, cy, cw, ch, d);
}
int main(void) {
// Hand-picked edges: exact corners, the off-by-one boundaries, second-display
// negatives, the literal 650->499 case I verified on real events.
run(0, 0, 500, 350, 0, 0, 500, 350); // top-left corner (inside)
run(499, 349, 500, 350, 0, 0, 500, 350); // last inside pixel
run(500, 175, 500, 350, 0, 0, 500, 350); // exactly on right edge (outside)
run(650, 175, 500, 350, 0, 0, 500, 350); // the verified real case -> expect x=499
run(-300, -900, 500, 350, 0, 0, 500, 350); // second display up-left (negatives)
run(99999, 99999, 500, 350, 0, 0, 500, 350); // far off-window
const long N = 5000000;
for (long i = 0; i < N; i++) {
double w = frnd(80, 4000), h = frnd(80, 4000);
double cw = frnd(50, w), ch = frnd(50, h);
double cx = frnd(0, w - cw), cy = frnd(0, h - ch);
double px = frnd(-3000, w + 3000), py = frnd(-3000, h + 3000);
run(px, py, w, h, cx, cy, cw, ch);
}
if (fails) {
fprintf(stderr, "\nclamp_decision: %ld FAILURES across %ld cases\n", fails, N + 6);
return 1;
}
printf("clamp_decision: ALL %ld cases pass (invariants: in-window untouched, "
"off-window snapped strictly inside content, no NaN)\n", N + 6);
return 0;
}
+8 -7
View File
@@ -12,7 +12,7 @@
#include <node_api.h>
#import <Cocoa/Cocoa.h>
#include <math.h>
#include "clamp_decision.h"
static id gMonitor = nil;
@@ -28,15 +28,16 @@ static NSEvent *ClampOffWindowRelease(NSEvent *event) {
if (!content) return event;
NSPoint p = [event locationInWindow];
NSSize ws = [win frame].size;
// act only when the release is truly off the window (the crash case); any
// release inside the window, titlebar included, is left exactly as-is
if (NSPointInRect(p, NSMakeRect(0.0, 0.0, ws.width, ws.height))) return event;
NSRect cb = [content frame];
CGFloat x = fmin(fmax(p.x, NSMinX(cb) + 1.0), NSMaxX(cb) - 1.0);
CGFloat y = fmin(fmax(p.y, NSMinY(cb) + 1.0), NSMaxY(cb) - 1.0);
// all the misfire-prone arithmetic lives in clamp_decision() so the property
// test exercises the exact code that ships
ClampDecision d = clamp_decision(p.x, p.y, ws.width, ws.height,
cb.origin.x, cb.origin.y,
cb.size.width, cb.size.height);
if (!d.clamp) return event;
NSEvent *clamped =
[NSEvent mouseEventWithType:t
location:NSMakePoint(x, y)
location:NSMakePoint(d.x, d.y)
modifierFlags:[event modifierFlags]
timestamp:[event timestamp]
windowNumber:[event windowNumber]
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Compile + run the clamp_decision property test (plain clang, no electron deps).
set -euo pipefail
if ! command -v clang >/dev/null 2>&1; then
echo "clang not found; skipping clamp_decision property test"; exit 0
fi
HERE="$(cd "$(dirname "$0")" && pwd)"
BIN="$(mktemp -t clamp_decision_test)"
trap 'rm -f "$BIN"' EXIT
clang -O2 -Wall -Wextra -Werror -o "$BIN" "$HERE/clamp_decision_test.c"
"$BIN"
+2 -1
View File
@@ -14,7 +14,8 @@
"dist:win": "electron-builder --win --x64 --publish never",
"dist:win:publish": "electron-builder --win --x64 --publish always",
"dist:all": "electron-builder --mac --win --linux",
"test": "node --test affiliateTracking.test.js"
"test": "node --test affiliateTracking.test.js",
"test:mouseclamp": "bash native/mouseclamp/run-tests.sh"
},
"dependencies": {
"electron-updater": "6.8.3",