1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
// Another extension's prompt (1Password) blocks the debugger on a tab. Nothing closes it
// automatically, because the user may be using it; the agent is told what is blocking and decides:
// chrome_clear_overlay when it is only in the way, leave it when it is waiting for the user.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { test } from "node:test";
const workerSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/browser-extension/service_worker.js", import.meta.url), "utf8");
const indexSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/index.ts", import.meta.url), "utf8");
const BLOCK = "Cannot access a chrome-extension:// URL of different extension";
const frame = { id: "F", tabId: 2, type: "other", url: "chrome-extension://aeblf/inline/universal-sign-on/x.html?tabId=2#t", extensionId: undefined };
function worker({ targets = [frame], clears = true, scriptResult = { removed: ["chrome-extension://aeblf/inline/universal-sign-on/x.html"] } } = {}) {
const calls = [];
const listener = { addListener() {}, removeListener() {} };
let cleared = false;
const w = {
console, setTimeout, clearTimeout, setInterval: () => 0, navigator: { userAgent: "unit-test" }, fetch: async () => { throw new Error("offline"); },
chrome: {
runtime: { id: "ours", getManifest: () => ({ version: "0.0.0" }), onInstalled: listener, onStartup: listener },
alarms: { create() {}, onAlarm: listener }, action: { onClicked: listener }, webNavigation: { onCommitted: listener },
debugger: { onDetach: listener, onEvent: listener, getTargets: (cb) => cb(cleared ? [] : targets) },
storage: { session: { get: async () => ({}), set: async () => {} } },
scripting: { executeScript: async (options) => { calls.push({ method: "scripting", options }); if (clears) cleared = true; return [{ result: scriptResult }]; } },
tabs: { get: async () => ({ id: 2, url: "https://news.ycombinator.com/login", title: "login" }), query: async () => [{ id: 2, url: "https://news.ycombinator.com/login", title: "login" }] },
},
};
w.self = w;
vm.runInNewContext(workerSource, w);
w.sleep = async () => {};
w.getTabByParams = async () => ({ id: 2, url: "https://news.ycombinator.com/login", title: "login" });
w.attachDebugger = async () => { if (!cleared) throw new Error(BLOCK); };
return { w, calls, isCleared: () => cleared };
}
test("a blocked input is not dismissed with Escape: the agent is told and decides", async () => {
const { w, calls } = worker();
w.cdpRaw = async (_tab, method) => { calls.push({ method }); throw new Error(`${method}: ${BLOCK}`); };
await assert.rejects(
() => w.cdp(2, "Input.dispatchMouseEvent", {}),
(e) => /only in your way/.test(e.message) && /waiting for the user to sign in or approve/.test(e.message) && /leave it alone/.test(e.message) && /chrome_clear_overlay/.test(e.message) && /aeblf/.test(e.message),
);
assert.deepEqual(calls.map((c) => c.method), ["Input.dispatchMouseEvent"]);
});
test("overlay.clear removes the frame and reports the tab usable", async () => {
const { w, calls } = worker();
const result = await w.dispatch("overlay.clear", { targetId: "2" });
assert.equal(result.cleared, true);
assert.deepEqual(result.removedFrames, ["chrome-extension://aeblf/inline/universal-sign-on/x.html"]);
assert.equal(result.debuggerAttachable, true);
assert.equal(calls[0].options.args[0], "ours");
assert.equal(calls[0].options.world, undefined);
});
test("overlay.clear says so when the tab is still blocked (toolbar popup, desktop app)", async () => {
const { w } = worker({ clears: false, scriptResult: { removed: [] } });
const result = await w.dispatch("overlay.clear", { targetId: "2" });
assert.equal(result.cleared, false);
assert.equal(result.debuggerAttachable, false);
assert.deepEqual(result.remainingForeignTargets, ["chrome-extension://aeblf/inline/universal-sign-on/x.html"]);
});
test("the injected clean-up removes a host whose closed shadow root holds a foreign frame, and loose foreign frames, but not ours or the page's", () => {
const { w } = worker();
const frame = (src) => ({ tagName: "IFRAME", src, isConnected: true, removed: false, remove() { this.removed = true; } });
const foreign = frame("chrome-extension://aeblf/inline/a.html?x=1");
const ours = frame("chrome-extension://ours/panel.html");
const page = frame("https://example.com/embed");
const inClosedRoot = frame("chrome-extension://aeblf/inline/universal-sign-on.html?tabId=2");
const host = { tagName: "COM-1PASSWORD-USO", isConnected: true, removed: false, remove() { this.removed = true; }, shadowRoot: null, closedRoot: { querySelectorAll: () => [inClosedRoot] } };
const plain = { tagName: "DIV", isConnected: true, shadowRoot: null };
let blurred = false, escaped = false;
const sandbox = {
document: { querySelectorAll: (sel) => (sel === "iframe" ? [foreign, ours, page] : [foreign, ours, page, host, plain]), activeElement: { blur: () => { blurred = true; } }, body: {}, dispatchEvent: (e) => { escaped = e.key === "Escape"; } },
chrome: { dom: { openOrClosedShadowRoot: (el) => el.closedRoot ?? null } },
KeyboardEvent: class { constructor(type, init) { this.type = type; Object.assign(this, init); } },
Array, Set,
};
const result = vm.runInNewContext(`(${w.clearForeignOverlays.toString()})("ours")`, sandbox);
assert.deepEqual([...result.removed], ["chrome-extension://aeblf/inline/a.html", "<com-1password-uso> holding chrome-extension://aeblf/inline/universal-sign-on.html"]);
assert.ok(foreign.removed && host.removed);
assert.equal(ours.removed || page.removed || inClosedRoot.removed, false);
assert.ok(blurred && escaped);
});
test("the tool and the primer let the agent decide: clear it when in the way, leave it when waiting for the user", () => {
assert.match(indexSource, /name: "chrome_clear_overlay"/);
assert.match(indexSource, /Decide for yourself whether to use it/);
assert.match(indexSource, /do not clear it: tell the user what you are waiting for/);
assert.match(indexSource, /11\. If a tool fails saying another extension[^`]*decide whether it is in your way or part of the task/);
assert.match(indexSource, /"chrome_clear_overlay",\n\] as const/);
});
|