diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-13 01:29:51 -0400 |
|---|---|---|
| committer | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-13 01:29:51 -0400 |
| commit | 6ddb6007df14a865d4226d7ee16e08ff4d3c36ca (patch) | |
| tree | f9cb55e32c3991171b2f8a449de781eb30d04458 | |
| parent | 6dd4b2b0abe30ed11b0969fe838fa24f67b7d6d1 (diff) | |
pi-chrome 0.10.1: chrome_key modifiers param for trusted chords (Cmd+V etc)
| -rw-r--r-- | extensions/chrome-profile-bridge/browser-extension/manifest.json | 2 | ||||
| -rw-r--r-- | extensions/chrome-profile-bridge/browser-extension/service_worker.js | 26 | ||||
| -rw-r--r-- | extensions/chrome-profile-bridge/index.ts | 6 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | test-suite/challenges/10-user-activation.html | 7 |
5 files changed, 35 insertions, 8 deletions
diff --git a/extensions/chrome-profile-bridge/browser-extension/manifest.json b/extensions/chrome-profile-bridge/browser-extension/manifest.json index 969293a..cd5e1b5 100644 --- a/extensions/chrome-profile-bridge/browser-extension/manifest.json +++ b/extensions/chrome-profile-bridge/browser-extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Pi Existing Chrome Profile Bridge", - "version": "0.10.0", + "version": "0.10.1", "description": "Lets Pi control tabs in this existing Chrome profile via a local bridge at 127.0.0.1.", "permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"], "host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"], diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js index beb2dbf..bd00b1e 100644 --- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js +++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js @@ -236,18 +236,36 @@ async function trustedKey(params) { await attachDebugger(tab.id); const key = String(params.key || ""); if (!key) throw new Error("trusted.key: missing key"); + const mods = params.modifiers || {}; + const modBits = cdpModifiersFor(mods); + // Press modifiers in standard order, then key, then release in reverse. + const modOrder = []; + if (mods.metaKey) modOrder.push({ key: "Meta", code: "MetaLeft", vk: 91 }); + if (mods.ctrlKey) modOrder.push({ key: "Control", code: "ControlLeft", vk: 17 }); + if (mods.altKey) modOrder.push({ key: "Alt", code: "AltLeft", vk: 18 }); + if (mods.shiftKey) modOrder.push({ key: "Shift", code: "ShiftLeft", vk: 16 }); + for (const m of modOrder) { + await cdp(tab.id, "Input.dispatchKeyEvent", { type: "keyDown", key: m.key, code: m.code, windowsVirtualKeyCode: m.vk, modifiers: modBits }); + await sleep(rng(6, 18)); + } const info = cdpKeyInfo(key); + // When modifiers are active, browsers usually emit "rawKeyDown" (no text) so chords like Cmd+V don't insert the literal char. + const downType = modBits ? "rawKeyDown" : "keyDown"; await cdp(tab.id, "Input.dispatchKeyEvent", { - type: "keyDown", key: info.key, code: info.code, + type: downType, key: info.key, code: info.code, windowsVirtualKeyCode: info.windowsVirtualKeyCode, nativeVirtualKeyCode: info.windowsVirtualKeyCode, - text: info.text, unmodifiedText: info.text, + text: modBits ? "" : info.text, unmodifiedText: modBits ? "" : info.text, modifiers: modBits, }); await sleep(rng(25, 90)); await cdp(tab.id, "Input.dispatchKeyEvent", { type: "keyUp", key: info.key, code: info.code, - windowsVirtualKeyCode: info.windowsVirtualKeyCode, + windowsVirtualKeyCode: info.windowsVirtualKeyCode, modifiers: modBits, }); - return { trusted: true, key: info.key }; + for (const m of modOrder.reverse()) { + await sleep(rng(5, 18)); + await cdp(tab.id, "Input.dispatchKeyEvent", { type: "keyUp", key: m.key, code: m.code, windowsVirtualKeyCode: m.vk, modifiers: 0 }); + } + return { trusted: true, key: info.key, modifiers: mods }; } async function trustedType(params) { diff --git a/extensions/chrome-profile-bridge/index.ts b/extensions/chrome-profile-bridge/index.ts index 11e1a40..56bf224 100644 --- a/extensions/chrome-profile-bridge/index.ts +++ b/extensions/chrome-profile-bridge/index.ts @@ -882,6 +882,12 @@ Usage rules: promptSnippet: "Press keys in Chrome through the companion extension.", parameters: Type.Object({ key: Type.String(), + modifiers: Type.Optional(Type.Object({ + shiftKey: Type.Optional(Type.Boolean()), + ctrlKey: Type.Optional(Type.Boolean()), + altKey: Type.Optional(Type.Boolean()), + metaKey: Type.Optional(Type.Boolean()), + }, { description: "Modifier keys to hold while pressing the key (chord). Only honoured for trusted-mode presses; synthetic path ignores." })), includeSnapshot: Type.Optional(Type.Boolean({ description: "If true, include a fresh chrome_snapshot result after the keypress." })), maxElements: Type.Optional(Type.Number({ default: MAX_ELEMENTS, description: "Max elements in the included snapshot." })), targetId: Type.Optional(Type.String()), diff --git a/package.json b/package.json index 4fdd66f..7da23c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-chrome", - "version": "0.10.0", + "version": "0.10.1", "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).", "keywords": [ "pi-package", diff --git a/test-suite/challenges/10-user-activation.html b/test-suite/challenges/10-user-activation.html index d0929d8..1a12e39 100644 --- a/test-suite/challenges/10-user-activation.html +++ b/test-suite/challenges/10-user-activation.html @@ -12,15 +12,18 @@ <script> Challenge.init({ id: "user-activation", instructions: "click button; user-activation must unlock gated APIs" }); document.getElementById("go").addEventListener("click", async () => { + // Capture activation state synchronously before any await (activation can be consumed by gated APIs). const ua = navigator.userActivation; - Challenge.log("activation", { isActive: ua?.isActive, hasBeenActive: ua?.hasBeenActive }); + const wasActive = !!ua?.isActive; + const hadBeenActive = !!ua?.hasBeenActive; + Challenge.log("activation", { isActive: wasActive, hasBeenActive: hadBeenActive }); let clip = "skip", fs = "skip"; try { await navigator.clipboard.writeText("pi-chrome-test"); clip = "ok"; } catch (e) { clip = "err:" + e.name; } try { await document.documentElement.requestFullscreen(); fs = "ok"; document.exitFullscreen?.(); } catch (e) { fs = "err:" + e.name; } const bad = []; - if (!ua?.isActive) bad.push("userActivation.isActive === false (synthetic click)"); + if (!wasActive && !hadBeenActive) bad.push("userActivation.isActive/hasBeenActive both false (synthetic click)"); if (clip.startsWith("err")) bad.push("clipboard.writeText " + clip); if (fs.startsWith("err")) bad.push("requestFullscreen " + fs); Challenge.log("gates", { clip, fs }); |
