pi-chrome 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Pi Chrome Connector",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
|
|
6
6
|
"permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"],
|
|
7
7
|
"host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
|
|
@@ -67,7 +67,22 @@ async function attachDebugger(tabId) {
|
|
|
67
67
|
entry.detachAt = Date.now() + TRUSTED_IDLE_DETACH_MS;
|
|
68
68
|
return entry;
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
try {
|
|
71
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// Chrome occasionally rejects attach with "Cannot access a chrome-extension:// URL of
|
|
74
|
+
// different extension" right after a navigation, even when the target tab's URL is a
|
|
75
|
+
// normal page. Wait a tick, verify the tab is on a non-privileged URL, and retry once.
|
|
76
|
+
const msg = String(error?.message || error);
|
|
77
|
+
const transient = /Cannot access a chrome-extension|Cannot access contents of|No tab with id|Debugger is not attached|Another debugger|Target closed/i.test(msg);
|
|
78
|
+
if (!transient) throw error;
|
|
79
|
+
await sleep(180);
|
|
80
|
+
const tab = await chrome.tabs.get(tabId).catch(() => null);
|
|
81
|
+
if (!tab || (tab.url || "").startsWith("chrome://") || (tab.url || "").startsWith("chrome-extension://")) {
|
|
82
|
+
throw new Error(`Chrome can't attach the debugger to this tab (${tab?.url ?? "unknown"}). Open a normal http(s) tab and try again.`);
|
|
83
|
+
}
|
|
84
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION);
|
|
85
|
+
}
|
|
71
86
|
// Seed pointer in a plausible "just left the address bar" location.
|
|
72
87
|
const entry = { detachAt: Date.now() + TRUSTED_IDLE_DETACH_MS, pointer: { x: 120 + Math.random() * 200, y: 80 + Math.random() * 120 } };
|
|
73
88
|
attachedTabs.set(tabId, entry);
|
|
@@ -256,9 +271,28 @@ async function trustedClick(params) {
|
|
|
256
271
|
const resolved = await resolveTargetInTab(tab.id, params);
|
|
257
272
|
const point = resolved.rect ? pickInsideRect(resolved.rect) : { x: resolved.x, y: resolved.y };
|
|
258
273
|
await cdpMoveTo(tab.id, point.x, point.y);
|
|
259
|
-
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse" });
|
|
274
|
+
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse", force: 0.5 });
|
|
260
275
|
await sleep(rng(45, 140));
|
|
261
276
|
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mouseReleased", x: point.x, y: point.y, button: "left", buttons: 0, clickCount: 1, pointerType: "mouse" });
|
|
277
|
+
// Reset :focus-visible if the click landed on a focusable element. CDP-driven pointer
|
|
278
|
+
// focus can leave :focus-visible=true in Chromium, which trips heuristics that expect
|
|
279
|
+
// pointer focus to suppress the focus ring (synthetic clicks naturally land on false).
|
|
280
|
+
if (params.selector || params.uid) {
|
|
281
|
+
await chrome.scripting.executeScript({
|
|
282
|
+
target: { tabId: tab.id, frameIds: [0] },
|
|
283
|
+
world: "MAIN",
|
|
284
|
+
func: (sel, uid) => {
|
|
285
|
+
const state = window.__PI_CHROME_STATE__;
|
|
286
|
+
let el = null;
|
|
287
|
+
if (uid && state && state.elements && state.elements[uid]) el = state.elements[uid];
|
|
288
|
+
else if (sel) el = document.querySelector(sel);
|
|
289
|
+
if (el && typeof el.focus === "function" && el === document.activeElement) {
|
|
290
|
+
try { el.blur(); el.focus({ preventScroll: true, focusVisible: false }); } catch {}
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
args: [params.selector ?? null, params.uid ?? null],
|
|
294
|
+
}).catch(() => undefined);
|
|
295
|
+
}
|
|
262
296
|
return { trusted: true, x: point.x, y: point.y, tag: resolved.tag };
|
|
263
297
|
}
|
|
264
298
|
|
|
@@ -320,7 +354,7 @@ async function trustedType(params) {
|
|
|
320
354
|
const resolved = await resolveTargetInTab(tab.id, params);
|
|
321
355
|
const point = resolved.rect ? pickInsideRect(resolved.rect) : { x: resolved.x, y: resolved.y };
|
|
322
356
|
await cdpMoveTo(tab.id, point.x, point.y);
|
|
323
|
-
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse" });
|
|
357
|
+
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse", force: 0.5 });
|
|
324
358
|
await sleep(rng(45, 110));
|
|
325
359
|
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mouseReleased", x: point.x, y: point.y, button: "left", buttons: 0, clickCount: 1, pointerType: "mouse" });
|
|
326
360
|
await sleep(rng(50, 120));
|
|
@@ -344,7 +378,7 @@ async function trustedFill(params) {
|
|
|
344
378
|
await cdpMoveTo(tab.id, point.x, point.y);
|
|
345
379
|
// Triple-click selects all in input fields.
|
|
346
380
|
for (let i = 1; i <= 3; i++) {
|
|
347
|
-
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: i, pointerType: "mouse" });
|
|
381
|
+
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: point.x, y: point.y, button: "left", buttons: 1, clickCount: i, pointerType: "mouse", force: 0.5 });
|
|
348
382
|
await sleep(rng(20, 60));
|
|
349
383
|
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mouseReleased", x: point.x, y: point.y, button: "left", buttons: 0, clickCount: i, pointerType: "mouse" });
|
|
350
384
|
await sleep(rng(20, 60));
|
|
@@ -367,21 +401,36 @@ async function trustedScroll(params) {
|
|
|
367
401
|
const x = resolved.rect ? resolved.rect.left + Math.min(resolved.rect.width, 800) / 2 : resolved.x;
|
|
368
402
|
const y = resolved.rect ? resolved.rect.top + Math.min(resolved.rect.height, 600) / 2 : resolved.y;
|
|
369
403
|
const totalY = params.deltaY || 0, totalX = params.deltaX || 0;
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
const MAX_STEP = 25;
|
|
404
|
+
// Profile mimics a trackpad flick: short ramp-up (~15% of events), then geometric decay
|
|
405
|
+
// with a ~12% drop per event. Gives momentum tail tests something to find, and the small
|
|
406
|
+
// tail deltas (a handful of <20px events) put IntersectionObserver thresholds in range.
|
|
374
407
|
const peak = Math.max(Math.abs(totalY), Math.abs(totalX));
|
|
375
|
-
//
|
|
376
|
-
const
|
|
377
|
-
const n = Math.max(6, Math.min(200, params.steps || Math.max(minN, 12)));
|
|
378
|
-
// Front-loaded but smooth weights: w[i] = 1 + 0.5 * (1 - i/(n-1)) so the first event has
|
|
379
|
-
// weight 1.5, the last has 1.0, average ~1.25; redistribution stays predictable.
|
|
408
|
+
// Aim peak event ~22px so cumulative wheel approach to target seeds low-ratio IO samples.
|
|
409
|
+
const PEAK_TARGET = 22;
|
|
380
410
|
const w = [];
|
|
381
|
-
for
|
|
382
|
-
|
|
383
|
-
|
|
411
|
+
// Build weights for an arbitrary n, then iterate to find an n where peak * (w_peak/sum) <= PEAK_TARGET.
|
|
412
|
+
function build(n) {
|
|
413
|
+
const arr = [];
|
|
414
|
+
const peakIdx = Math.max(1, Math.floor(n * 0.15));
|
|
415
|
+
for (let i = 0; i < n; i++) {
|
|
416
|
+
if (i <= peakIdx) arr.push(0.5 + 0.5 * (i / peakIdx)); // 0.5 → 1.0
|
|
417
|
+
else arr.push(Math.pow(0.88, i - peakIdx)); // ~12% drop per step
|
|
418
|
+
}
|
|
419
|
+
return arr;
|
|
420
|
+
}
|
|
421
|
+
let n = Math.max(12, params.steps || 24);
|
|
422
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
423
|
+
const arr = build(n);
|
|
424
|
+
const s = arr.reduce((a, b) => a + b, 0);
|
|
425
|
+
const peakStep = peak * (Math.max(...arr) / s);
|
|
426
|
+
if (peakStep <= PEAK_TARGET || n >= 240) {
|
|
427
|
+
w.length = 0;
|
|
428
|
+
w.push(...arr);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
n = Math.ceil(n * 1.4);
|
|
384
432
|
}
|
|
433
|
+
if (w.length === 0) w.push(...build(n));
|
|
385
434
|
const sumW = w.reduce((a, b) => a + b, 0);
|
|
386
435
|
for (let i = 0; i < n; i++) {
|
|
387
436
|
const dy = totalY * (w[i] / sumW), dx = totalX * (w[i] / sumW);
|
|
@@ -419,7 +468,7 @@ async function trustedDrag(params) {
|
|
|
419
468
|
const fp = from.rect ? pickInsideRect(from.rect) : { x: from.x, y: from.y };
|
|
420
469
|
const tp = to.rect ? pickInsideRect(to.rect) : { x: to.x, y: to.y };
|
|
421
470
|
await cdpMoveTo(tab.id, fp.x, fp.y);
|
|
422
|
-
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: fp.x, y: fp.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse" });
|
|
471
|
+
await cdp(tab.id, "Input.dispatchMouseEvent", { type: "mousePressed", x: fp.x, y: fp.y, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse", force: 0.5 });
|
|
423
472
|
await sleep(rng(60, 140));
|
|
424
473
|
const steps = params.steps || 20;
|
|
425
474
|
for (let i = 1; i <= steps; i++) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Drive your existing logged-in Chrome from Pi — no re-login, no throwaway profile, watch the agent work in real time (or toggle quiet background mode).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|