pi-chrome 0.10.1: chrome_key modifiers param for trusted chords (Cmd+V etc)
Tianren Dong tianrendong.dtr@gmail.com
Wed, 13 May 2026 01:29:51 -0400
5 files changed,
35 insertions(+),
8 deletions(-)
M
extensions/chrome-profile-bridge/browser-extension/manifest.json
→
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/*"],
M
extensions/chrome-profile-bridge/browser-extension/service_worker.js
→
extensions/chrome-profile-bridge/browser-extension/service_worker.js
@@ -236,18 +236,36 @@ if (params.foreground) await bringToFront(tab);
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) {
M
extensions/chrome-profile-bridge/index.ts
→
extensions/chrome-profile-bridge/index.ts
@@ -882,6 +882,12 @@ "Send a keyboard key to an existing Chrome tab (Enter, Escape, Tab, Backspace, Delete, ArrowUp/Down/Left/Right, or one character). By default focuses Chrome and activates the tab so the user can watch; pass background=true to send the key silently. Pass includeSnapshot=true to verify after the keypress.",
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()),
M
package.json
→
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",
M
test-suite/challenges/10-user-activation.html
→
test-suite/challenges/10-user-activation.html
@@ -12,15 +12,18 @@ </main>
<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 });