playwriter 0.0.103 → 0.1.0
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.
- package/dist/bippy.js +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +5 -0
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cli-help.test.d.ts +2 -0
- package/dist/cli-help.test.d.ts.map +1 -0
- package/dist/cli-help.test.js +31 -0
- package/dist/cli-help.test.js.map +1 -0
- package/dist/cli.js +13 -5
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +2 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +32 -22
- package/dist/executor.js.map +1 -1
- package/dist/extension/background.js +516 -22
- package/dist/extension/manifest.json +4 -2
- package/dist/extension-connection.test.d.ts.map +1 -1
- package/dist/extension-connection.test.js +79 -0
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ghost-cursor-client.js +170 -83
- package/dist/{recording-ghost-cursor.d.ts → ghost-cursor-controller.d.ts} +15 -10
- package/dist/ghost-cursor-controller.d.ts.map +1 -0
- package/dist/ghost-cursor-controller.js +98 -0
- package/dist/ghost-cursor-controller.js.map +1 -0
- package/dist/ghost-cursor.d.ts.map +1 -1
- package/dist/ghost-cursor.js +42 -26
- package/dist/ghost-cursor.js.map +1 -1
- package/dist/on-mouse-action.test.js +25 -0
- package/dist/on-mouse-action.test.js.map +1 -1
- package/dist/popup-relocation.test.d.ts +7 -0
- package/dist/popup-relocation.test.d.ts.map +1 -0
- package/dist/popup-relocation.test.js +139 -0
- package/dist/popup-relocation.test.js.map +1 -0
- package/dist/prompt.md +13 -12
- package/dist/readability.js +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +101 -1
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-state.d.ts +1 -0
- package/dist/relay-state.d.ts.map +1 -1
- package/dist/relay-state.js.map +1 -1
- package/dist/screen-recording.d.ts +2 -2
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +0 -3
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/package.json +6 -6
- package/src/aria-snapshots/github-interactive.txt +5 -3
- package/src/aria-snapshots/github-raw.txt +8 -5
- package/src/aria-snapshots/hackernews-interactive.txt +241 -242
- package/src/aria-snapshots/hackernews-raw.txt +267 -268
- package/src/aria-snapshots/prosemirror-interactive.txt +3 -1
- package/src/aria-snapshots/prosemirror-raw.txt +4 -1
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/cdp-relay.ts +5 -0
- package/src/cli-help.test.ts +41 -0
- package/src/cli.ts +14 -9
- package/src/executor.ts +33 -22
- package/src/extension-connection.test.ts +88 -0
- package/src/ghost-cursor-client.ts +221 -96
- package/src/{recording-ghost-cursor.ts → ghost-cursor-controller.ts} +50 -34
- package/src/ghost-cursor.ts +54 -41
- package/src/on-mouse-action.test.ts +30 -0
- package/src/popup-relocation.test.ts +163 -0
- package/src/relay-core.test.ts +117 -0
- package/src/relay-state.ts +1 -1
- package/src/screen-recording.ts +3 -6
- package/src/skill.md +13 -12
- package/src/snapshots/shadcn-ui-accessibility-full.md +174 -181
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +6 -14
- package/dist/recording-ghost-cursor.d.ts.map +0 -1
- package/dist/recording-ghost-cursor.js +0 -79
- package/dist/recording-ghost-cursor.js.map +0 -1
|
@@ -27,6 +27,337 @@ var createStoreImpl = (createState) => {
|
|
|
27
27
|
};
|
|
28
28
|
var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
|
|
29
29
|
//#endregion
|
|
30
|
+
//#region src/toolbar/toolbar.ts
|
|
31
|
+
function initPlaywriterToolbar() {
|
|
32
|
+
if (window.__playwriterToolbarInstalled) return;
|
|
33
|
+
window.__playwriterToolbarInstalled = true;
|
|
34
|
+
try {
|
|
35
|
+
if (window !== window.top) return;
|
|
36
|
+
} catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let pinModeActive = false;
|
|
40
|
+
let pinCount = 0;
|
|
41
|
+
let toastTimer = null;
|
|
42
|
+
let overlayEl = null;
|
|
43
|
+
let pinBtn;
|
|
44
|
+
const host = document.createElement("div");
|
|
45
|
+
host.setAttribute("data-playwriter-toolbar", "1");
|
|
46
|
+
host.style.cssText = "position:fixed;top:12px;right:12px;z-index:2147483647;pointer-events:none;font-size:0;line-height:0;";
|
|
47
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
48
|
+
const styleEl = document.createElement("style");
|
|
49
|
+
styleEl.textContent = `
|
|
50
|
+
*,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
51
|
+
.toolbar {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 2px;
|
|
55
|
+
padding: 3px;
|
|
56
|
+
background: #fff;
|
|
57
|
+
border-radius: 10px;
|
|
58
|
+
pointer-events: all;
|
|
59
|
+
user-select: none;
|
|
60
|
+
box-shadow: 0px 0px 0.5px rgba(0,0,0,0.18), 0px 3px 8px rgba(0,0,0,0.1), 0px 1px 3px rgba(0,0,0,0.1);
|
|
61
|
+
}
|
|
62
|
+
.divider {
|
|
63
|
+
width: 1px;
|
|
64
|
+
height: 12px;
|
|
65
|
+
background: rgba(0, 0, 0, 0.08);
|
|
66
|
+
margin: 0 1px;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
}
|
|
69
|
+
.btn {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
width: 26px;
|
|
74
|
+
height: 26px;
|
|
75
|
+
border: none;
|
|
76
|
+
border-radius: 7px;
|
|
77
|
+
background: transparent;
|
|
78
|
+
color: #000;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
transition: background 0.1s;
|
|
81
|
+
padding: 0;
|
|
82
|
+
flex-shrink: 0;
|
|
83
|
+
outline: none;
|
|
84
|
+
}
|
|
85
|
+
.btn:hover {
|
|
86
|
+
background: rgba(0, 0, 0, 0.04);
|
|
87
|
+
}
|
|
88
|
+
.btn.active {
|
|
89
|
+
background: #0d99ff;
|
|
90
|
+
color: #fff;
|
|
91
|
+
}
|
|
92
|
+
.btn.active:hover {
|
|
93
|
+
background: #0d99ff;
|
|
94
|
+
filter: brightness(1.05);
|
|
95
|
+
}
|
|
96
|
+
/* When active, the logo inner cursor path needs to match the blue bg
|
|
97
|
+
so it appears as a "cutout" through the white outer shape */
|
|
98
|
+
.btn.active .logo-inner { fill: #0d99ff; }
|
|
99
|
+
.toast {
|
|
100
|
+
position: fixed;
|
|
101
|
+
background: #0f172a;
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
padding: 9px 18px;
|
|
104
|
+
color: rgba(255, 255, 255, 0.85);
|
|
105
|
+
font-size: 11px;
|
|
106
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
z-index: 1;
|
|
111
|
+
--toast-transform: translateX(-50%);
|
|
112
|
+
animation: toast-in 0.15s ease;
|
|
113
|
+
}
|
|
114
|
+
@keyframes toast-in {
|
|
115
|
+
from { opacity: 0; transform: var(--toast-transform) translateY(4px); }
|
|
116
|
+
to { opacity: 1; transform: var(--toast-transform); }
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
const toolbarEl = document.createElement("div");
|
|
120
|
+
toolbarEl.className = "toolbar";
|
|
121
|
+
toolbarEl.setAttribute("role", "toolbar");
|
|
122
|
+
toolbarEl.setAttribute("aria-label", "Playwriter tools");
|
|
123
|
+
shadow.appendChild(styleEl);
|
|
124
|
+
shadow.appendChild(toolbarEl);
|
|
125
|
+
function showToast(msg, anchorRect) {
|
|
126
|
+
shadow.querySelectorAll(".toast").forEach((el) => {
|
|
127
|
+
el.remove();
|
|
128
|
+
});
|
|
129
|
+
if (toastTimer !== null) clearTimeout(toastTimer);
|
|
130
|
+
const toastEl = document.createElement("div");
|
|
131
|
+
toastEl.className = "toast";
|
|
132
|
+
toastEl.textContent = msg;
|
|
133
|
+
if (anchorRect) {
|
|
134
|
+
const GAP = 8;
|
|
135
|
+
const centerX = anchorRect.left + anchorRect.width / 2;
|
|
136
|
+
const belowY = anchorRect.bottom + GAP;
|
|
137
|
+
const fitsBelow = belowY + 36 < window.innerHeight;
|
|
138
|
+
const top = fitsBelow ? belowY : anchorRect.top - GAP;
|
|
139
|
+
const transformOrigin = fitsBelow ? "top center" : "bottom center";
|
|
140
|
+
toastEl.style.left = Math.max(8, Math.min(centerX, window.innerWidth - 8)) + "px";
|
|
141
|
+
toastEl.style.top = top + "px";
|
|
142
|
+
const baseTransform = fitsBelow ? "translateX(-50%)" : "translateX(-50%) translateY(-100%)";
|
|
143
|
+
toastEl.style.setProperty("--toast-transform", baseTransform);
|
|
144
|
+
toastEl.style.transform = baseTransform;
|
|
145
|
+
toastEl.style.transformOrigin = transformOrigin;
|
|
146
|
+
} else {
|
|
147
|
+
toastEl.style.bottom = "20px";
|
|
148
|
+
toastEl.style.left = "50%";
|
|
149
|
+
toastEl.style.transform = "translateX(-50%)";
|
|
150
|
+
}
|
|
151
|
+
shadow.appendChild(toastEl);
|
|
152
|
+
toastTimer = window.setTimeout(() => {
|
|
153
|
+
toastEl.remove();
|
|
154
|
+
}, 1900);
|
|
155
|
+
}
|
|
156
|
+
function getOverlay() {
|
|
157
|
+
if (!overlayEl) {
|
|
158
|
+
const EDGE = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
|
|
159
|
+
const FILL = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
|
|
160
|
+
const container = document.createElement("div");
|
|
161
|
+
container.setAttribute("data-playwriter-overlay", "1");
|
|
162
|
+
container.style.cssText = [
|
|
163
|
+
"position:fixed",
|
|
164
|
+
"pointer-events:none",
|
|
165
|
+
"z-index:2147483646",
|
|
166
|
+
`background:${FILL}`,
|
|
167
|
+
"display:none"
|
|
168
|
+
].join(";");
|
|
169
|
+
const edgeTop = document.createElement("div");
|
|
170
|
+
edgeTop.style.cssText = `position:absolute;top:0;left:0;width:100%;height:1px;background:${EDGE};`;
|
|
171
|
+
const edgeRight = document.createElement("div");
|
|
172
|
+
edgeRight.style.cssText = `position:absolute;top:0;right:0;width:1px;height:100%;background:${EDGE};`;
|
|
173
|
+
const edgeBottom = document.createElement("div");
|
|
174
|
+
edgeBottom.style.cssText = `position:absolute;bottom:0;left:0;width:100%;height:1px;background:${EDGE};`;
|
|
175
|
+
const edgeLeft = document.createElement("div");
|
|
176
|
+
edgeLeft.style.cssText = `position:absolute;top:0;left:0;width:1px;height:100%;background:${EDGE};`;
|
|
177
|
+
container.appendChild(edgeTop);
|
|
178
|
+
container.appendChild(edgeRight);
|
|
179
|
+
container.appendChild(edgeBottom);
|
|
180
|
+
container.appendChild(edgeLeft);
|
|
181
|
+
document.documentElement.appendChild(container);
|
|
182
|
+
overlayEl = container;
|
|
183
|
+
}
|
|
184
|
+
return overlayEl;
|
|
185
|
+
}
|
|
186
|
+
function positionOverlay(target) {
|
|
187
|
+
const rect = target.getBoundingClientRect();
|
|
188
|
+
if (!rect.width && !rect.height) return;
|
|
189
|
+
const overlay = getOverlay();
|
|
190
|
+
overlay.style.display = "block";
|
|
191
|
+
overlay.style.top = rect.top + "px";
|
|
192
|
+
overlay.style.left = rect.left + "px";
|
|
193
|
+
overlay.style.width = rect.width + "px";
|
|
194
|
+
overlay.style.height = rect.height + "px";
|
|
195
|
+
}
|
|
196
|
+
function hideOverlay() {
|
|
197
|
+
if (overlayEl) overlayEl.style.display = "none";
|
|
198
|
+
}
|
|
199
|
+
function removeOverlay() {
|
|
200
|
+
if (overlayEl) {
|
|
201
|
+
overlayEl.remove();
|
|
202
|
+
overlayEl = null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function getTargetAt(x, y) {
|
|
206
|
+
return document.elementsFromPoint(x, y).find((el) => !el.hasAttribute("data-playwriter-overlay") && !el.hasAttribute("data-playwriter-toolbar") && el !== document.documentElement && el !== document.body) ?? null;
|
|
207
|
+
}
|
|
208
|
+
function isOverToolbar(e) {
|
|
209
|
+
return e.composedPath().some((node) => node === host);
|
|
210
|
+
}
|
|
211
|
+
function flashElement(el) {
|
|
212
|
+
const s = el.style;
|
|
213
|
+
if (!s) return;
|
|
214
|
+
const prevOutline = s.outline;
|
|
215
|
+
const prevOffset = s.outlineOffset;
|
|
216
|
+
s.outline = "1px solid #22c55e";
|
|
217
|
+
s.outlineOffset = "2px";
|
|
218
|
+
window.setTimeout(() => {
|
|
219
|
+
s.outline = prevOutline;
|
|
220
|
+
s.outlineOffset = prevOffset;
|
|
221
|
+
}, 350);
|
|
222
|
+
}
|
|
223
|
+
function copyText(text) {
|
|
224
|
+
navigator.clipboard.writeText(text).catch(() => {
|
|
225
|
+
try {
|
|
226
|
+
const ta = document.createElement("textarea");
|
|
227
|
+
ta.value = text;
|
|
228
|
+
ta.style.cssText = "position:fixed;top:0;left:0;opacity:0;pointer-events:none;";
|
|
229
|
+
document.body.appendChild(ta);
|
|
230
|
+
ta.focus();
|
|
231
|
+
ta.select();
|
|
232
|
+
document.execCommand("copy");
|
|
233
|
+
ta.remove();
|
|
234
|
+
} catch {}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function allocatePinName() {
|
|
238
|
+
const shared = window.__playwriterPinCount;
|
|
239
|
+
if (typeof shared === "number" && shared > pinCount) pinCount = shared;
|
|
240
|
+
pinCount++;
|
|
241
|
+
window.__playwriterPinCount = pinCount;
|
|
242
|
+
return `playwriterPinnedElem${pinCount}`;
|
|
243
|
+
}
|
|
244
|
+
function onMouseMove(e) {
|
|
245
|
+
if (isOverToolbar(e)) {
|
|
246
|
+
hideOverlay();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const target = getTargetAt(e.clientX, e.clientY);
|
|
250
|
+
if (target) positionOverlay(target);
|
|
251
|
+
else hideOverlay();
|
|
252
|
+
}
|
|
253
|
+
function describeElement(el, n) {
|
|
254
|
+
const tag = el.tagName ? el.tagName.toLowerCase() : "";
|
|
255
|
+
const id = el.id || "";
|
|
256
|
+
const cls = typeof el.className === "string" ? el.className : "";
|
|
257
|
+
const role = el.getAttribute("role") || "";
|
|
258
|
+
const aria = el.getAttribute("aria-label") || "";
|
|
259
|
+
const nameAttr = el.getAttribute("name") || "";
|
|
260
|
+
const href = el.getAttribute("href") || "";
|
|
261
|
+
const typeAttr = el.getAttribute("type") || "";
|
|
262
|
+
const text = (el.textContent || "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
263
|
+
const r = el.getBoundingClientRect();
|
|
264
|
+
const rect = `x=${Math.round(r.x)} y=${Math.round(r.y)} w=${Math.round(r.width)} h=${Math.round(r.height)}`;
|
|
265
|
+
const visible = r.width > 0 && r.height > 0;
|
|
266
|
+
return [
|
|
267
|
+
`Pinned #${n} (globalThis.playwriterPinnedElem${n})`,
|
|
268
|
+
`URL: ${location.href}`,
|
|
269
|
+
`Tag: ${tag}`,
|
|
270
|
+
!!id && `ID: ${id}`,
|
|
271
|
+
!!cls && `Classes: ${cls.slice(0, 200)}`,
|
|
272
|
+
!!role && `Role: ${role}`,
|
|
273
|
+
!!aria && `Aria-label: ${aria}`,
|
|
274
|
+
!!nameAttr && `Name: ${nameAttr}`,
|
|
275
|
+
!!href && `Href: ${href.slice(0, 200)}`,
|
|
276
|
+
!!typeAttr && `Type: ${typeAttr}`,
|
|
277
|
+
!!text && `Text: ${text}`,
|
|
278
|
+
`Rect: ${rect}`,
|
|
279
|
+
`Visible: ${visible}`
|
|
280
|
+
].filter((line) => typeof line === "string").join("\n");
|
|
281
|
+
}
|
|
282
|
+
function buildInspectionCode(n, url, summary) {
|
|
283
|
+
return `state.page=context.pages().find(x=>x.url()===${JSON.stringify(url).replace(/'/g, "\\u0027")})||context.pages()[0]; console.log(${JSON.stringify(summary).replace(/'/g, "\\u0027")}+"\\n\\nouterHTML:\\n"+await state.page.evaluate(n=>globalThis["playwriterPinnedElem"+n]?.outerHTML,${n}))`;
|
|
284
|
+
}
|
|
285
|
+
function onClick(e) {
|
|
286
|
+
if (isOverToolbar(e)) return;
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
e.stopImmediatePropagation();
|
|
289
|
+
const target = getTargetAt(e.clientX, e.clientY);
|
|
290
|
+
if (!target) return;
|
|
291
|
+
const name = allocatePinName();
|
|
292
|
+
const n = pinCount;
|
|
293
|
+
window[name] = target;
|
|
294
|
+
flashElement(target);
|
|
295
|
+
const url = location.href;
|
|
296
|
+
copyText("see the element I pinned in the playwriter tab `playwriter -e '" + buildInspectionCode(n, url, describeElement(target, n)) + "'`");
|
|
297
|
+
showToast(`Copied pin #${n}`, target.getBoundingClientRect());
|
|
298
|
+
setPinMode(false);
|
|
299
|
+
}
|
|
300
|
+
function onKeyDown(e) {
|
|
301
|
+
if (e.key === "Escape") setPinMode(false);
|
|
302
|
+
}
|
|
303
|
+
function setPinMode(on) {
|
|
304
|
+
pinModeActive = on;
|
|
305
|
+
pinBtn.classList.toggle("active", on);
|
|
306
|
+
if (on) {
|
|
307
|
+
document.documentElement.style.cursor = "crosshair";
|
|
308
|
+
getOverlay();
|
|
309
|
+
document.addEventListener("mousemove", onMouseMove, {
|
|
310
|
+
capture: true,
|
|
311
|
+
passive: true
|
|
312
|
+
});
|
|
313
|
+
document.addEventListener("click", onClick, true);
|
|
314
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
315
|
+
} else {
|
|
316
|
+
document.documentElement.style.cursor = "";
|
|
317
|
+
hideOverlay();
|
|
318
|
+
document.removeEventListener("mousemove", onMouseMove, true);
|
|
319
|
+
document.removeEventListener("click", onClick, true);
|
|
320
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const CLIPBOARD_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 424 424" aria-hidden="true"><path d="M 0 212 C 0 112.063 0 62.095 31.037 31.037 C 62.116 0 112.063 0 212 0 C 311.937 0 361.905 0 392.942 31.037 C 424 62.116 424 112.063 424 212 C 424 311.937 424 361.905 392.942 392.942 C 361.926 424 311.937 424 212 424 C 112.063 424 62.095 424 31.037 392.942 C 0 361.926 0 311.937 0 212" fill="currentColor"/><path class="logo-inner" d="M 225.732 260.521 L 277.905 312.673 C 283.311 318.1 286.003 320.793 289.014 322.043 C 293.042 323.718 297.557 323.718 301.585 322.043 C 304.596 320.793 307.309 318.1 312.694 312.694 C 318.1 307.288 320.793 304.596 322.043 301.585 C 323.722 297.563 323.722 293.036 322.043 289.014 C 320.793 286.003 318.1 283.29 312.694 277.905 L 260.521 225.732 L 276.442 209.789 C 292.766 193.465 300.907 185.325 298.999 176.548 C 297.07 167.792 286.237 163.785 264.591 155.814 L 192.384 129.208 C 149.2 113.308 127.618 105.358 116.488 116.488 C 105.358 127.618 113.308 149.2 129.208 192.384 L 155.814 264.591 C 163.785 286.237 167.792 297.07 176.548 298.999 C 185.303 300.928 193.465 292.766 209.789 276.442 Z" fill="white"/></svg>`;
|
|
324
|
+
const CLOSE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
325
|
+
pinBtn = document.createElement("button");
|
|
326
|
+
pinBtn.className = "btn";
|
|
327
|
+
pinBtn.setAttribute("aria-label", "Pin element — click any element to copy inspection code for a playwriter -e call");
|
|
328
|
+
pinBtn.setAttribute("title", "Pin element (click to copy inspection code)");
|
|
329
|
+
pinBtn.innerHTML = CLIPBOARD_SVG;
|
|
330
|
+
pinBtn.addEventListener("click", (e) => {
|
|
331
|
+
e.stopPropagation();
|
|
332
|
+
setPinMode(!pinModeActive);
|
|
333
|
+
});
|
|
334
|
+
const dividerEl = document.createElement("div");
|
|
335
|
+
dividerEl.className = "divider";
|
|
336
|
+
dividerEl.setAttribute("aria-hidden", "true");
|
|
337
|
+
const closeBtn = document.createElement("button");
|
|
338
|
+
closeBtn.className = "btn";
|
|
339
|
+
closeBtn.setAttribute("aria-label", "Close Playwriter toolbar");
|
|
340
|
+
closeBtn.setAttribute("title", "Close toolbar");
|
|
341
|
+
closeBtn.innerHTML = CLOSE_SVG;
|
|
342
|
+
closeBtn.addEventListener("click", (e) => {
|
|
343
|
+
e.stopPropagation();
|
|
344
|
+
setPinMode(false);
|
|
345
|
+
host.style.display = "none";
|
|
346
|
+
});
|
|
347
|
+
toolbarEl.appendChild(pinBtn);
|
|
348
|
+
toolbarEl.appendChild(dividerEl);
|
|
349
|
+
toolbarEl.appendChild(closeBtn);
|
|
350
|
+
document.documentElement.appendChild(host);
|
|
351
|
+
window.__playwriterToolbarDestroy = function() {
|
|
352
|
+
setPinMode(false);
|
|
353
|
+
removeOverlay();
|
|
354
|
+
host.remove();
|
|
355
|
+
delete window.__playwriterToolbarInstalled;
|
|
356
|
+
delete window.__playwriterToolbarDestroy;
|
|
357
|
+
delete window.__playwriterPinCount;
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
30
361
|
//#region ../playwriter/src/ghost-browser.ts
|
|
31
362
|
/**
|
|
32
363
|
* Handles ghost-browser commands in the extension.
|
|
@@ -72,6 +403,9 @@ async function handleGhostBrowserCommand(params, chromeApi) {
|
|
|
72
403
|
}
|
|
73
404
|
}
|
|
74
405
|
//#endregion
|
|
406
|
+
//#region ../playwriter/dist/ghost-cursor-client.js?raw
|
|
407
|
+
var ghost_cursor_client_default = "(() => {\n var __defProp = Object.defineProperty;\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n var __hasOwnProp = Object.prototype.hasOwnProperty;\n function __accessProp(key) {\n return this[key];\n }\n var __toCommonJS = (from) => {\n var entry = (__moduleCache ??= new WeakMap).get(from), desc;\n if (entry)\n return entry;\n entry = __defProp({}, \"__esModule\", { value: true });\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (var key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(entry, key))\n __defProp(entry, key, {\n get: __accessProp.bind(from, key),\n enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable\n });\n }\n __moduleCache.set(from, entry);\n return entry;\n };\n var __moduleCache;\n\n // src/ghost-cursor-client.ts\n var exports_ghost_cursor_client = {};\n\n // src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts\n var SCREENSTUDIO_POINTER_MACOS_TAHOE_DATA_URL = \"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjE4IiBoZWlnaHQ9Ijk1OCIgdmlld0JveD0iMCAwIDYxOCA5NTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMzg0XzI3KSI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTI3LjA2MiAzNy4wMzMxTDU0MC42OTYgNDUxLjU1NUM1OTIuNjUzIDUwMy42NiA1NTUuNzk0IDU5Mi41NzQgNDgyLjIyNiA1OTIuNTc0TDQyMS44MzEgNTkyLjU2OUw0ODEuODIxIDczNS4wNTRDNDkyLjMzMSA3NjAuMDIxIDQ5Mi40NzkgNzg3LjY1MiA0ODIuMjY1IDgxMi43NjdDNDcyLjAwMiA4MzcuOTMyIDQ1Mi41NjEgODU3LjU3IDQyNy40OTYgODY4LjA4QzQxNC44NjQgODczLjM1OSA0MDEuNjQgODc2LjAyNCAzODguMTIxIDg3Ni4wMjRDMzQ3LjExNyA4NzYuMDI0IDMxMC4zNTggODUxLjYgMjk0LjQ3IDgxMy44MDRMMjMxLjQyIDY2My45MThMMTkwLjM2OCA3MDAuMzM3QzEzNy4wMjkgNzQ3LjUwOCA1MyA3MDkuNjYzIDUzIDYzOC40MTNWNjcuNjc0NEM1MyAyOC45OTAzIDk5LjcyNjggOS42NDgyOCAxMjcuMDYyIDM3LjAzMzFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEwMi4zMTYgOTkuNjUyQzEwMi4zMTYgOTMuMTg4MiAxMTAuMTYyIDg5LjkzMTYgMTE0LjcwMSA5NC41MjA0TDUwNC44OTcgNDg1LjU1NUM1MjYuMTY0IDUwNi44NzEgNTExLjA2NSA1NDMuMjM2IDQ4MC45NjcgNTQzLjIzNkwzNDcuNTQ2IDU0My4xNjFMNDM2LjM0MiA3NTQuMTQzQzQ0Ny41NDIgNzgwLjc4OCA0MzUuMDA5IDgxMS40MjkgNDA4LjQxNCA4MjIuNTgxQzM4MS43MiA4MzMuNzgxIDM1MS4xMjggODIxLjI5OCAzMzkuOTc3IDc5NC43MDJMMjUwLjI5MyA1ODEuMzUyTDE1OC41MTcgNjYyLjY0NEMxMzcuOTkxIDY4MC44MDEgMTA2LjMxOSA2NjguMTQ1IDEwMi42NjQgNjQyLjMyM0wxMDIuMzE2IDYzNy4zMzFWOTkuNjUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzM4NF8yNyIgeD0iMC4zNCIgeT0iMC43OTkyMTkiIHdpZHRoPSI2MTcuMzIiIGhlaWdodD0iOTU3LjE0NCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz4KPGZlT2Zmc2V0IGR5PSIyOS4yNiIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyNi4zMyIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuNjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd18zODRfMjciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfMzg0XzI3IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPg==\";\n\n // src/ghost-cursor-client.ts\n var isTopFrame = (() => {\n try {\n return window === window.top;\n } catch {\n return false;\n }\n })();\n var CURSOR_ID = \"__playwriter_ghost_cursor__\";\n var SCREENSTUDIO_POINTER_ASPECT_RATIO = 618 / 958;\n var SCREENSTUDIO_HOTSPOT_X_RATIO = 0.14;\n var SCREENSTUDIO_HOTSPOT_Y_RATIO = 0.06;\n var MINIMAL_TRIANGLE_HOTSPOT_X_RATIO = 0.07;\n var MINIMAL_TRIANGLE_HOTSPOT_Y_RATIO = 0.06;\n var MOVE_EASING = \"cubic-bezier(0.65, 0, 0.35, 1)\";\n var PRESS_EASING = \"cubic-bezier(0.23, 1, 0.32, 1)\";\n var PRESS_DURATION_MS = 140;\n var IDLE_HIDE_DELAY_MS = 5000;\n var IDLE_FADE_OUT_MS = 600;\n var DEFAULT_OPTIONS = {\n style: \"minimal\",\n color: \"#111827\",\n size: 22,\n zIndex: 2147483647,\n easing: MOVE_EASING,\n minDurationMs: 220,\n maxDurationMs: 1500,\n speedPxPerMs: 1.2\n };\n var runtime = {\n outerElement: null,\n innerElement: null,\n options: DEFAULT_OPTIONS,\n x: 0,\n y: 0,\n scale: 1,\n hasPosition: false,\n enabled: false,\n idleHidden: false\n };\n var idleHideTimer = null;\n function clamp(options) {\n const { value, min, max } = options;\n return Math.min(max, Math.max(min, value));\n }\n function mergeOptions(options) {\n if (!options) {\n return DEFAULT_OPTIONS;\n }\n return {\n style: options.style ?? DEFAULT_OPTIONS.style,\n color: options.color ?? DEFAULT_OPTIONS.color,\n size: options.size ?? DEFAULT_OPTIONS.size,\n zIndex: options.zIndex ?? DEFAULT_OPTIONS.zIndex,\n easing: options.easing ?? DEFAULT_OPTIONS.easing,\n minDurationMs: options.minDurationMs ?? DEFAULT_OPTIONS.minDurationMs,\n maxDurationMs: options.maxDurationMs ?? DEFAULT_OPTIONS.maxDurationMs,\n speedPxPerMs: options.speedPxPerMs ?? DEFAULT_OPTIONS.speedPxPerMs\n };\n }\n function getCursorDimensions() {\n if (runtime.options.style === \"screenstudio\") {\n const height = runtime.options.size;\n const width = Math.max(10, Math.round(height * SCREENSTUDIO_POINTER_ASPECT_RATIO));\n return { width, height };\n }\n if (runtime.options.style === \"minimal\") {\n const size = Math.max(12, runtime.options.size);\n return { width: size, height: size };\n }\n return { width: runtime.options.size, height: runtime.options.size };\n }\n function getHotspotOffsetPx() {\n const dimensions = getCursorDimensions();\n if (runtime.options.style === \"screenstudio\") {\n return {\n x: Math.round(dimensions.width * SCREENSTUDIO_HOTSPOT_X_RATIO),\n y: Math.round(dimensions.height * SCREENSTUDIO_HOTSPOT_Y_RATIO)\n };\n }\n if (runtime.options.style === \"minimal\") {\n return {\n x: Math.round(dimensions.width * MINIMAL_TRIANGLE_HOTSPOT_X_RATIO),\n y: Math.round(dimensions.height * MINIMAL_TRIANGLE_HOTSPOT_Y_RATIO)\n };\n }\n return {\n x: Math.round(dimensions.width / 2),\n y: Math.round(dimensions.height / 2)\n };\n }\n function getBaseOpacity() {\n if (runtime.options.style === \"screenstudio\") {\n return \"0.95\";\n }\n if (runtime.options.style === \"minimal\") {\n return \"1\";\n }\n return \"0.72\";\n }\n function applyTranslate() {\n if (!runtime.outerElement) {\n return;\n }\n const hotspot = getHotspotOffsetPx();\n runtime.outerElement.style.transform = `translate3d(${runtime.x - hotspot.x}px, ${runtime.y - hotspot.y}px, 0)`;\n }\n function applyScale() {\n if (!runtime.innerElement) {\n return;\n }\n runtime.innerElement.style.transform = `scale(${runtime.scale})`;\n }\n function computeDurationMs(options) {\n if (!runtime.hasPosition) {\n return 0;\n }\n const dx = options.targetX - runtime.x;\n const dy = options.targetY - runtime.y;\n const distance = Math.hypot(dx, dy);\n const rawDurationMs = distance / runtime.options.speedPxPerMs;\n return clamp({\n value: rawDurationMs,\n min: runtime.options.minDurationMs,\n max: runtime.options.maxDurationMs\n });\n }\n function createCursorElement() {\n const outer = document.createElement(\"div\");\n outer.id = CURSOR_ID;\n outer.setAttribute(\"aria-hidden\", \"true\");\n outer.style.position = \"fixed\";\n outer.style.left = \"0\";\n outer.style.top = \"0\";\n outer.style.pointerEvents = \"none\";\n outer.style.zIndex = `${runtime.options.zIndex}`;\n outer.style.transitionProperty = \"transform\";\n outer.style.transitionTimingFunction = runtime.options.easing;\n outer.style.transitionDuration = \"0ms\";\n outer.style.willChange = \"transform\";\n const inner = document.createElement(\"div\");\n inner.style.transitionProperty = \"transform, opacity\";\n inner.style.transitionTimingFunction = PRESS_EASING;\n inner.style.transitionDuration = `${PRESS_DURATION_MS}ms`;\n inner.style.opacity = getBaseOpacity();\n outer.appendChild(inner);\n runtime.outerElement = outer;\n runtime.innerElement = inner;\n applyRuntimeVisualOptions();\n return outer;\n }\n function ensureCursorElement() {\n const existing = document.getElementById(CURSOR_ID);\n if (existing) {\n runtime.outerElement = existing;\n runtime.innerElement = existing.firstElementChild || null;\n return existing;\n }\n const outer = createCursorElement();\n const root = document.documentElement || document.body;\n root.appendChild(outer);\n return outer;\n }\n function applyRuntimeVisualOptions() {\n if (!runtime.innerElement) {\n return;\n }\n const dimensions = getCursorDimensions();\n runtime.innerElement.style.width = `${dimensions.width}px`;\n runtime.innerElement.style.height = `${dimensions.height}px`;\n if (runtime.outerElement) {\n runtime.outerElement.style.zIndex = `${runtime.options.zIndex}`;\n runtime.outerElement.style.transitionTimingFunction = runtime.options.easing;\n }\n const hotspot = getHotspotOffsetPx();\n runtime.innerElement.style.transformOrigin = `${hotspot.x}px ${hotspot.y}px`;\n if (runtime.options.style === \"screenstudio\") {\n runtime.innerElement.style.borderRadius = \"0\";\n runtime.innerElement.style.border = \"none\";\n runtime.innerElement.style.backgroundColor = \"transparent\";\n runtime.innerElement.style.backgroundImage = `url(\"${SCREENSTUDIO_POINTER_MACOS_TAHOE_DATA_URL}\")`;\n runtime.innerElement.style.backgroundRepeat = \"no-repeat\";\n runtime.innerElement.style.backgroundPosition = \"left top\";\n runtime.innerElement.style.backgroundSize = \"contain\";\n runtime.innerElement.style.backdropFilter = \"none\";\n runtime.innerElement.style.filter = \"none\";\n runtime.innerElement.style.boxShadow = \"none\";\n runtime.innerElement.style.opacity = getBaseOpacity();\n return;\n }\n if (runtime.options.style === \"minimal\") {\n const triangleSvg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"-1 -1 26 26\"><path fill=\"white\" stroke=\"${runtime.options.color}\" stroke-width=\"1.5\" stroke-linejoin=\"round\" d=\"m23.284 19.124l-6.866-6.895a.4.4 0 0 1-.118-.296a.43.43 0 0 1 .163-.282l4.439-3.077a1.48 1.48 0 0 0 .621-1.48a1.48 1.48 0 0 0-1.036-1.198L1.623.302a1.14 1.14 0 0 0-1.11.282A1.13 1.13 0 0 0 .29 1.649L5.928 20.44a1.48 1.48 0 0 0 1.183 1.035a1.48 1.48 0 0 0 1.48-.621l3.078-4.44a.37.37 0 0 1 .31-.118a.43.43 0 0 1 .296.104l6.91 6.91a1.48 1.48 0 0 0 2.087 0l2.086-2.086a1.48 1.48 0 0 0-.074-2.101\"/></svg>`;\n const triangleDataUrl = `url(\"data:image/svg+xml,${encodeURIComponent(triangleSvg)}\")`;\n runtime.innerElement.style.borderRadius = \"0\";\n runtime.innerElement.style.border = \"none\";\n runtime.innerElement.style.backgroundColor = \"transparent\";\n runtime.innerElement.style.backgroundImage = triangleDataUrl;\n runtime.innerElement.style.backgroundRepeat = \"no-repeat\";\n runtime.innerElement.style.backgroundSize = \"contain\";\n runtime.innerElement.style.backgroundPosition = \"left top\";\n runtime.innerElement.style.backdropFilter = \"none\";\n runtime.innerElement.style.boxShadow = \"none\";\n runtime.innerElement.style.filter = \"drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4))\";\n runtime.innerElement.style.opacity = getBaseOpacity();\n return;\n }\n runtime.innerElement.style.borderRadius = \"999px\";\n runtime.innerElement.style.border = \"none\";\n runtime.innerElement.style.backgroundColor = runtime.options.color;\n runtime.innerElement.style.backgroundImage = \"none\";\n runtime.innerElement.style.backdropFilter = \"none\";\n runtime.innerElement.style.filter = \"none\";\n runtime.innerElement.style.boxShadow = \"0 2px 10px rgba(0, 0, 0, 0.18), inset 0 0 0 2px rgba(255, 255, 255, 0.55)\";\n runtime.innerElement.style.opacity = getBaseOpacity();\n }\n function clearIdleHideTimer() {\n if (idleHideTimer !== null) {\n clearTimeout(idleHideTimer);\n idleHideTimer = null;\n }\n }\n function scheduleIdleHide() {\n clearIdleHideTimer();\n idleHideTimer = setTimeout(() => {\n idleHideTimer = null;\n if (!runtime.enabled || !runtime.innerElement) {\n return;\n }\n runtime.idleHidden = true;\n runtime.innerElement.style.transitionDuration = `${IDLE_FADE_OUT_MS}ms`;\n runtime.innerElement.style.transitionTimingFunction = PRESS_EASING;\n runtime.innerElement.style.opacity = \"0\";\n }, IDLE_HIDE_DELAY_MS);\n }\n function wakeFromIdle(options) {\n runtime.x = options.x;\n runtime.y = options.y;\n runtime.hasPosition = true;\n if (runtime.innerElement) {\n runtime.innerElement.style.transitionDuration = `${PRESS_DURATION_MS}ms`;\n runtime.innerElement.style.transitionTimingFunction = PRESS_EASING;\n runtime.innerElement.style.opacity = getBaseOpacity();\n }\n }\n function moveCursor(options) {\n if (!runtime.enabled) {\n return;\n }\n ensureCursorElement();\n const durationMs = computeDurationMs({ targetX: options.x, targetY: options.y });\n if (runtime.outerElement) {\n runtime.outerElement.style.transitionDuration = `${Math.round(durationMs)}ms`;\n runtime.outerElement.style.transitionTimingFunction = runtime.options.easing;\n }\n runtime.x = options.x;\n runtime.y = options.y;\n runtime.hasPosition = true;\n applyTranslate();\n }\n function setPressed(options) {\n if (!runtime.enabled || !runtime.innerElement) {\n return;\n }\n runtime.scale = options.pressed ? runtime.options.style === \"dot\" ? 0.92 : 0.95 : 1;\n runtime.innerElement.style.transitionDuration = `${PRESS_DURATION_MS}ms`;\n runtime.innerElement.style.transitionTimingFunction = PRESS_EASING;\n runtime.innerElement.style.opacity = options.pressed ? \"1\" : getBaseOpacity();\n applyScale();\n }\n function enable(options) {\n runtime.options = mergeOptions(options);\n runtime.enabled = true;\n ensureCursorElement();\n applyRuntimeVisualOptions();\n if (!runtime.hasPosition) {\n runtime.x = Math.round(window.innerWidth / 2);\n runtime.y = Math.round(window.innerHeight / 2);\n runtime.scale = 1;\n runtime.hasPosition = true;\n }\n runtime.idleHidden = false;\n if (runtime.innerElement) {\n runtime.innerElement.style.opacity = getBaseOpacity();\n }\n applyTranslate();\n applyScale();\n scheduleIdleHide();\n }\n function disable() {\n runtime.enabled = false;\n runtime.scale = 1;\n runtime.hasPosition = false;\n runtime.idleHidden = false;\n clearIdleHideTimer();\n if (runtime.outerElement) {\n runtime.outerElement.remove();\n runtime.outerElement = null;\n runtime.innerElement = null;\n }\n }\n function applyMouseAction(action) {\n if (!runtime.enabled) {\n return;\n }\n if (runtime.idleHidden) {\n runtime.idleHidden = false;\n wakeFromIdle({ x: action.x, y: action.y });\n }\n if (action.type === \"move\" || action.type === \"wheel\") {\n moveCursor({ x: action.x, y: action.y });\n } else if (action.type === \"down\") {\n moveCursor({ x: action.x, y: action.y });\n setPressed({ pressed: true });\n } else if (action.type === \"up\") {\n moveCursor({ x: action.x, y: action.y });\n setPressed({ pressed: false });\n }\n scheduleIdleHide();\n }\n var api = {\n enable,\n disable,\n applyMouseAction,\n isEnabled: () => {\n return runtime.enabled;\n }\n };\n if (isTopFrame) {\n globalThis.__playwriterGhostCursor = api;\n try {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", () => {\n try {\n api.enable();\n } catch {}\n }, { once: true });\n } else {\n api.enable();\n }\n } catch {}\n }\n})();\n";
|
|
408
|
+
//#endregion
|
|
75
409
|
//#region src/recording.ts
|
|
76
410
|
var activeRecordings = /* @__PURE__ */ new Map();
|
|
77
411
|
var offscreenDocumentCreating = null;
|
|
@@ -295,6 +629,13 @@ var RELAY_PORT = 19988;
|
|
|
295
629
|
function sleep(ms) {
|
|
296
630
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
297
631
|
}
|
|
632
|
+
function createInstallId() {
|
|
633
|
+
const values = new Uint32Array(2);
|
|
634
|
+
crypto.getRandomValues(values);
|
|
635
|
+
return Array.from(values).map((value) => {
|
|
636
|
+
return value.toString(36);
|
|
637
|
+
}).join("");
|
|
638
|
+
}
|
|
298
639
|
async function detectBrowserName() {
|
|
299
640
|
if (chrome.ghostPublicAPI) return "Ghost";
|
|
300
641
|
const brands = navigator.userAgentData?.brands;
|
|
@@ -318,6 +659,7 @@ async function detectBrowserName() {
|
|
|
318
659
|
return "Chromium";
|
|
319
660
|
}
|
|
320
661
|
var identityPromise = null;
|
|
662
|
+
var installIdPromise = null;
|
|
321
663
|
var tabSessionScope = (() => {
|
|
322
664
|
const values = new Uint32Array(2);
|
|
323
665
|
crypto.getRandomValues(values);
|
|
@@ -325,22 +667,42 @@ var tabSessionScope = (() => {
|
|
|
325
667
|
return value.toString(36);
|
|
326
668
|
}).join("");
|
|
327
669
|
})();
|
|
670
|
+
async function getInstallId() {
|
|
671
|
+
if (installIdPromise) return installIdPromise;
|
|
672
|
+
installIdPromise = (async () => {
|
|
673
|
+
const existing = await chrome.storage.local.get("playwriterInstallId");
|
|
674
|
+
const storedInstallId = typeof existing.playwriterInstallId === "string" ? existing.playwriterInstallId : "";
|
|
675
|
+
if (storedInstallId) return storedInstallId;
|
|
676
|
+
const installId = createInstallId();
|
|
677
|
+
await chrome.storage.local.set({ playwriterInstallId: installId });
|
|
678
|
+
return installId;
|
|
679
|
+
})().catch((error) => {
|
|
680
|
+
installIdPromise = null;
|
|
681
|
+
throw error;
|
|
682
|
+
});
|
|
683
|
+
return installIdPromise;
|
|
684
|
+
}
|
|
328
685
|
async function getExtensionIdentity() {
|
|
329
686
|
if (identityPromise) return identityPromise;
|
|
330
687
|
identityPromise = (async () => {
|
|
331
688
|
const browser = await detectBrowserName();
|
|
689
|
+
const installId = await getInstallId().catch(() => {
|
|
690
|
+
return tabSessionScope;
|
|
691
|
+
});
|
|
332
692
|
try {
|
|
333
693
|
const info = await chrome.identity.getProfileUserInfo({ accountStatus: "ANY" });
|
|
334
694
|
return {
|
|
335
695
|
browser,
|
|
336
696
|
email: info.email || "",
|
|
337
|
-
id: info.id || ""
|
|
697
|
+
id: info.id || "",
|
|
698
|
+
installId
|
|
338
699
|
};
|
|
339
700
|
} catch {
|
|
340
701
|
return {
|
|
341
702
|
browser,
|
|
342
703
|
email: "",
|
|
343
|
-
id: ""
|
|
704
|
+
id: "",
|
|
705
|
+
installId
|
|
344
706
|
};
|
|
345
707
|
}
|
|
346
708
|
})();
|
|
@@ -415,7 +777,8 @@ var ConnectionManager = class {
|
|
|
415
777
|
if (identity.browser) relayUrl.searchParams.set("browser", identity.browser);
|
|
416
778
|
if (identity.email) relayUrl.searchParams.set("email", identity.email);
|
|
417
779
|
if (identity.id) relayUrl.searchParams.set("id", identity.id);
|
|
418
|
-
relayUrl.searchParams.set("
|
|
780
|
+
if (identity.installId) relayUrl.searchParams.set("installId", identity.installId);
|
|
781
|
+
relayUrl.searchParams.set("v", "0.1.0");
|
|
419
782
|
logger.debug("Creating WebSocket connection to:", relayUrl);
|
|
420
783
|
const socket = new WebSocket(relayUrl.toString());
|
|
421
784
|
await new Promise((resolve, reject) => {
|
|
@@ -677,7 +1040,7 @@ var ConnectionManager = class {
|
|
|
677
1040
|
method: "GET",
|
|
678
1041
|
signal: AbortSignal.timeout(2e3)
|
|
679
1042
|
})).json();
|
|
680
|
-
if (!data.connected
|
|
1043
|
+
if (!data.connected) {
|
|
681
1044
|
store.setState({
|
|
682
1045
|
connectionState: "idle",
|
|
683
1046
|
errorText: void 0
|
|
@@ -1158,6 +1521,12 @@ async function attachTab(tabId, { skipAttachedEvent = false } = {}) {
|
|
|
1158
1521
|
`;
|
|
1159
1522
|
await chrome.debugger.sendCommand(debuggee, "Page.addScriptToEvaluateOnNewDocument", { source: contextMenuScript });
|
|
1160
1523
|
await chrome.debugger.sendCommand(debuggee, "Runtime.evaluate", { expression: contextMenuScript });
|
|
1524
|
+
try {
|
|
1525
|
+
await chrome.debugger.sendCommand(debuggee, "Page.addScriptToEvaluateOnNewDocument", { source: ghost_cursor_client_default });
|
|
1526
|
+
await chrome.debugger.sendCommand(debuggee, "Runtime.evaluate", { expression: ghost_cursor_client_default });
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
logger.debug("Could not inject ghost cursor (restricted page):", err.message);
|
|
1529
|
+
}
|
|
1161
1530
|
const targetInfo = (await chrome.debugger.sendCommand(debuggee, "Target.getTargetInfo")).targetInfo;
|
|
1162
1531
|
if (!targetInfo.url || targetInfo.url === "" || targetInfo.url === ":") logger.error("WARNING: Target.attachedToTarget will be sent with empty URL! tabId:", tabId, "targetInfo:", JSON.stringify(targetInfo));
|
|
1163
1532
|
const attachOrder = nextSessionId;
|
|
@@ -1191,6 +1560,16 @@ async function attachTab(tabId, { skipAttachedEvent = false } = {}) {
|
|
|
1191
1560
|
}
|
|
1192
1561
|
});
|
|
1193
1562
|
logger.debug("Tab attached successfully:", tabId, "sessionId:", sessionId, "targetId:", targetInfo.targetId, "url:", targetInfo.url, "skipAttachedEvent:", skipAttachedEvent);
|
|
1563
|
+
chrome.scripting.executeScript({
|
|
1564
|
+
target: {
|
|
1565
|
+
tabId,
|
|
1566
|
+
allFrames: false
|
|
1567
|
+
},
|
|
1568
|
+
world: "MAIN",
|
|
1569
|
+
func: initPlaywriterToolbar
|
|
1570
|
+
}).catch((err) => {
|
|
1571
|
+
logger.debug("Could not inject toolbar (restricted page):", err.message);
|
|
1572
|
+
});
|
|
1194
1573
|
return {
|
|
1195
1574
|
targetInfo,
|
|
1196
1575
|
sessionId
|
|
@@ -1210,6 +1589,20 @@ function detachTab(tabId, shouldDetachDebugger) {
|
|
|
1210
1589
|
return;
|
|
1211
1590
|
}
|
|
1212
1591
|
cleanupRecordingForTab(tabId);
|
|
1592
|
+
chrome.scripting.executeScript({
|
|
1593
|
+
target: { tabId },
|
|
1594
|
+
world: "MAIN",
|
|
1595
|
+
func: () => {
|
|
1596
|
+
window.__playwriterToolbarDestroy?.();
|
|
1597
|
+
}
|
|
1598
|
+
}).catch(() => {});
|
|
1599
|
+
chrome.scripting.executeScript({
|
|
1600
|
+
target: { tabId },
|
|
1601
|
+
world: "MAIN",
|
|
1602
|
+
func: () => {
|
|
1603
|
+
globalThis.__playwriterGhostCursor?.disable?.();
|
|
1604
|
+
}
|
|
1605
|
+
}).catch(() => {});
|
|
1213
1606
|
logger.warn(`DISCONNECT: detachTab tabId=${tabId} shouldDetach=${shouldDetachDebugger} stack=${getCallStack()}`);
|
|
1214
1607
|
if (tab.sessionId && tab.targetId) sendMessage({
|
|
1215
1608
|
method: "forwardCDPEvent",
|
|
@@ -1254,14 +1647,31 @@ async function connectTab(tabId) {
|
|
|
1254
1647
|
};
|
|
1255
1648
|
});
|
|
1256
1649
|
} else if (isWsError) logger.debug(`WS connection failed, keeping tab ${tabId} in connecting state for retry`);
|
|
1257
|
-
else
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1650
|
+
else {
|
|
1651
|
+
let tabStillExists = true;
|
|
1652
|
+
try {
|
|
1653
|
+
await chrome.tabs.get(tabId);
|
|
1654
|
+
} catch {
|
|
1655
|
+
tabStillExists = false;
|
|
1656
|
+
}
|
|
1657
|
+
if (!tabStillExists) {
|
|
1658
|
+
logger.debug(`Tab ${tabId} was closed during connect, dropping error state`);
|
|
1659
|
+
store.setState((state) => {
|
|
1660
|
+
const newTabs = new Map(state.tabs);
|
|
1661
|
+
newTabs.delete(tabId);
|
|
1662
|
+
return { tabs: newTabs };
|
|
1663
|
+
});
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
store.setState((state) => {
|
|
1667
|
+
const newTabs = new Map(state.tabs);
|
|
1668
|
+
newTabs.set(tabId, {
|
|
1669
|
+
state: "error",
|
|
1670
|
+
errorText: `Error: ${error.message}`
|
|
1671
|
+
});
|
|
1672
|
+
return { tabs: newTabs };
|
|
1262
1673
|
});
|
|
1263
|
-
|
|
1264
|
-
});
|
|
1674
|
+
}
|
|
1265
1675
|
}
|
|
1266
1676
|
}
|
|
1267
1677
|
function setTabConnecting(tabId) {
|
|
@@ -1478,6 +1888,7 @@ async function updateIcons() {
|
|
|
1478
1888
|
}
|
|
1479
1889
|
}
|
|
1480
1890
|
async function onTabRemoved(tabId) {
|
|
1891
|
+
popupSourceTabMap.delete(tabId);
|
|
1481
1892
|
const { tabs } = store.getState();
|
|
1482
1893
|
if (!tabs.has(tabId)) return;
|
|
1483
1894
|
logger.debug(`Connected tab ${tabId} was closed, disconnecting`);
|
|
@@ -1598,6 +2009,75 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
|
1598
2009
|
logger.debug("onTabUpdated handler error:", e);
|
|
1599
2010
|
});
|
|
1600
2011
|
});
|
|
2012
|
+
var popupSourceTabMap = /* @__PURE__ */ new Map();
|
|
2013
|
+
chrome.webNavigation.onCreatedNavigationTarget.addListener((details) => {
|
|
2014
|
+
popupSourceTabMap.set(details.tabId, details.sourceTabId);
|
|
2015
|
+
setTimeout(() => {
|
|
2016
|
+
popupSourceTabMap.delete(details.tabId);
|
|
2017
|
+
}, 1e4);
|
|
2018
|
+
});
|
|
2019
|
+
chrome.windows.onCreated.addListener(async (popupWindow) => {
|
|
2020
|
+
if (popupWindow.type !== "popup" || popupWindow.id === void 0) return;
|
|
2021
|
+
try {
|
|
2022
|
+
let popupTabs = [];
|
|
2023
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
2024
|
+
popupTabs = await chrome.tabs.query({ windowId: popupWindow.id });
|
|
2025
|
+
if (popupTabs.length > 0) break;
|
|
2026
|
+
await sleep(20);
|
|
2027
|
+
}
|
|
2028
|
+
const tabIds = popupTabs.map((t) => t.id).filter((id) => {
|
|
2029
|
+
return id !== void 0;
|
|
2030
|
+
});
|
|
2031
|
+
if (tabIds.length === 0) {
|
|
2032
|
+
logger.debug(`Popup window ${popupWindow.id} has no tabs after retry, skipping`);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const { tabs: connectedTabs } = store.getState();
|
|
2036
|
+
let sourceTabId;
|
|
2037
|
+
for (const tabId of tabIds) {
|
|
2038
|
+
const candidate = popupSourceTabMap.get(tabId);
|
|
2039
|
+
if (candidate !== void 0 && connectedTabs.has(candidate)) {
|
|
2040
|
+
sourceTabId = candidate;
|
|
2041
|
+
break;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
for (const tabId of tabIds) popupSourceTabMap.delete(tabId);
|
|
2045
|
+
if (sourceTabId === void 0) {
|
|
2046
|
+
logger.debug(`Popup window ${popupWindow.id} not opened by a Playwriter-connected tab, leaving alone (tabs=${JSON.stringify(tabIds)})`);
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
let destinationWindowId;
|
|
2050
|
+
try {
|
|
2051
|
+
const sourceTab = await chrome.tabs.get(sourceTabId);
|
|
2052
|
+
if (sourceTab.windowId === void 0) {
|
|
2053
|
+
const focused = await chrome.windows.getLastFocused({ populate: false });
|
|
2054
|
+
if (focused.id === void 0 || focused.id === popupWindow.id) return;
|
|
2055
|
+
destinationWindowId = focused.id;
|
|
2056
|
+
} else destinationWindowId = sourceTab.windowId;
|
|
2057
|
+
} catch (e) {
|
|
2058
|
+
logger.debug(`Source tab ${sourceTabId} no longer exists, skipping relocation:`, e);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
logger.debug(`Relocating ${tabIds.length} popup tab(s) from window ${popupWindow.id} into source window ${destinationWindowId} (sourceTabId=${sourceTabId})`);
|
|
2062
|
+
await chrome.tabs.move(tabIds, {
|
|
2063
|
+
windowId: destinationWindowId,
|
|
2064
|
+
index: -1
|
|
2065
|
+
});
|
|
2066
|
+
try {
|
|
2067
|
+
await chrome.windows.remove(popupWindow.id);
|
|
2068
|
+
} catch {}
|
|
2069
|
+
for (const tabId of tabIds) {
|
|
2070
|
+
if (connectedTabs.has(tabId)) continue;
|
|
2071
|
+
try {
|
|
2072
|
+
await connectTab(tabId);
|
|
2073
|
+
} catch (e) {
|
|
2074
|
+
logger.warn(`Failed to auto-connect relocated popup tab ${tabId}:`, e);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
} catch (e) {
|
|
2078
|
+
logger.warn("Failed to relocate popup window:", e);
|
|
2079
|
+
}
|
|
2080
|
+
});
|
|
1601
2081
|
chrome.contextMenus?.onClicked.addListener(async (info, tab) => {
|
|
1602
2082
|
if (info.menuItemId !== "playwriter-pin-element" || !tab?.id) return;
|
|
1603
2083
|
const tabInfo = store.getState().tabs.get(tab.id);
|
|
@@ -1606,21 +2086,19 @@ chrome.contextMenus?.onClicked.addListener(async (info, tab) => {
|
|
|
1606
2086
|
return;
|
|
1607
2087
|
}
|
|
1608
2088
|
const debuggee = { tabId: tab.id };
|
|
1609
|
-
const count = (tabInfo.pinnedCount || 0) + 1;
|
|
1610
|
-
store.setState((state) => {
|
|
1611
|
-
const newTabs = new Map(state.tabs);
|
|
1612
|
-
const existing = newTabs.get(tab.id);
|
|
1613
|
-
if (existing) newTabs.set(tab.id, {
|
|
1614
|
-
...existing,
|
|
1615
|
-
pinnedCount: count
|
|
1616
|
-
});
|
|
1617
|
-
return { tabs: newTabs };
|
|
1618
|
-
});
|
|
1619
|
-
const name = `playwriterPinnedElem${count}`;
|
|
1620
2089
|
const connectedTabs = Array.from(store.getState().tabs.entries()).filter(([_, t]) => t.state === "connected").sort((a, b) => (a[1].attachOrder ?? 0) - (b[1].attachOrder ?? 0));
|
|
1621
2090
|
const pageIndex = connectedTabs.findIndex(([id]) => id === tab.id);
|
|
1622
2091
|
const hasMultiplePages = connectedTabs.length > 1;
|
|
1623
2092
|
try {
|
|
2093
|
+
const name = `playwriterPinnedElem${(await chrome.debugger.sendCommand(debuggee, "Runtime.evaluate", {
|
|
2094
|
+
expression: `
|
|
2095
|
+
(function() {
|
|
2096
|
+
window.__playwriterPinCount = (window.__playwriterPinCount || 0) + 1;
|
|
2097
|
+
return window.__playwriterPinCount;
|
|
2098
|
+
})()
|
|
2099
|
+
`,
|
|
2100
|
+
returnByValue: true
|
|
2101
|
+
})).result?.value ?? 1}`;
|
|
1624
2102
|
const result = await chrome.debugger.sendCommand(debuggee, "Runtime.evaluate", {
|
|
1625
2103
|
expression: `
|
|
1626
2104
|
if (window.__playwriter_lastRightClicked) {
|
|
@@ -1701,4 +2179,20 @@ chrome.runtime.onMessage.addListener((message, _sender, _sendResponse) => {
|
|
|
1701
2179
|
}
|
|
1702
2180
|
return false;
|
|
1703
2181
|
});
|
|
2182
|
+
chrome.webNavigation.onDOMContentLoaded.addListener((details) => {
|
|
2183
|
+
if (details.frameId !== 0) return;
|
|
2184
|
+
const { tabs } = store.getState();
|
|
2185
|
+
const tabInfo = tabs.get(details.tabId);
|
|
2186
|
+
if (!tabInfo || tabInfo.state !== "connected") return;
|
|
2187
|
+
chrome.scripting.executeScript({
|
|
2188
|
+
target: {
|
|
2189
|
+
tabId: details.tabId,
|
|
2190
|
+
allFrames: false
|
|
2191
|
+
},
|
|
2192
|
+
world: "MAIN",
|
|
2193
|
+
func: initPlaywriterToolbar
|
|
2194
|
+
}).catch((err) => {
|
|
2195
|
+
logger.debug("Could not re-inject toolbar after navigation:", err.message);
|
|
2196
|
+
});
|
|
2197
|
+
});
|
|
1704
2198
|
//#endregion
|