@empiricalrun/test-gen 0.42.4 → 0.42.9
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/CHANGELOG.md +39 -0
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +1 -0
- package/dist/agent/codegen/create-test-block.d.ts.map +1 -1
- package/dist/agent/codegen/create-test-block.js +4 -2
- package/dist/agent/codegen/lexical-scoped-vars.d.ts.map +1 -1
- package/dist/agent/codegen/lexical-scoped-vars.js +4 -6
- package/dist/agent/codegen/promptBuilder.d.ts +3 -0
- package/dist/agent/codegen/promptBuilder.d.ts.map +1 -0
- package/dist/agent/codegen/promptBuilder.js +44 -0
- package/dist/agent/codegen/repo-edit.js +1 -1
- package/dist/agent/master/action-tool-calls.d.ts +40 -0
- package/dist/agent/master/action-tool-calls.d.ts.map +1 -0
- package/dist/agent/master/action-tool-calls.js +83 -0
- package/dist/agent/master/element-annotation.d.ts +7 -2
- package/dist/agent/master/element-annotation.d.ts.map +1 -1
- package/dist/agent/master/element-annotation.js +13 -3
- package/dist/agent/master/next-action.d.ts +12 -14
- package/dist/agent/master/next-action.d.ts.map +1 -1
- package/dist/agent/master/next-action.js +62 -63
- package/dist/agent/master/run.d.ts.map +1 -1
- package/dist/agent/master/run.js +68 -51
- package/dist/agent/master/scroller.d.ts +15 -0
- package/dist/agent/master/scroller.d.ts.map +1 -0
- package/dist/agent/master/scroller.js +371 -0
- package/dist/agent/master/with-hints.d.ts.map +1 -1
- package/dist/agent/master/with-hints.js +4 -1
- package/dist/agent/utils.d.ts +2 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +12 -0
- package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
- package/dist/bin/utils/platform/web/index.js +2 -0
- package/dist/browser-injected-scripts/annotate-elements.js +122 -74
- package/dist/browser-injected-scripts/annotate-elements.spec.d.ts +2 -0
- package/dist/browser-injected-scripts/annotate-elements.spec.d.ts.map +1 -0
- package/dist/browser-injected-scripts/annotate-elements.spec.js +186 -0
- package/dist/browser-injected-scripts/annotate-elements.spec.ts +52 -26
- package/dist/evals/master-agent.evals.d.ts.map +1 -1
- package/dist/evals/master-agent.evals.js +5 -4
- package/dist/prompts/lib/ts-transformer.d.ts +4 -0
- package/dist/prompts/lib/ts-transformer.d.ts.map +1 -0
- package/dist/prompts/lib/ts-transformer.js +90 -0
- package/dist/prompts/lib/vitest-plugin.d.ts +8 -0
- package/dist/prompts/lib/vitest-plugin.d.ts.map +1 -0
- package/dist/prompts/lib/vitest-plugin.js +20 -0
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +4 -0
- package/package.json +10 -8
- package/playwright.config.ts +1 -1
- package/vitest.config.ts +5 -0
- package/browser-injected-scripts/annotate-elements.js +0 -491
- package/browser-injected-scripts/annotate-elements.spec.ts +0 -277
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-undef */
|
|
2
|
-
/**
|
|
3
|
-
* Annotates all clickable elements on the page with unique hint markers.
|
|
4
|
-
* Returns an object containing annotations and methods to enable/disable them.
|
|
5
|
-
*
|
|
6
|
-
* @param {Object} options - Configuration options for hint markers.
|
|
7
|
-
* @param {string} options.hintCharacterSet - Characters to use for generating hint identifiers.
|
|
8
|
-
* @param {number} options.maxHints - Maximum number of hints to generate.
|
|
9
|
-
* @param {string} options.markerClass - CSS class to apply to hint markers.
|
|
10
|
-
* @returns {Object} An object containing annotations map and enable/disable methods.
|
|
11
|
-
*/
|
|
12
|
-
function annotateClickableElements({ options = {}, preference } = {}) {
|
|
13
|
-
const {
|
|
14
|
-
hintCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", // Default set of characters for hints
|
|
15
|
-
maxHints = 1000, // Maximum number of hints to generate
|
|
16
|
-
markerClass = "hint-marker", // CSS class for markers
|
|
17
|
-
} = options;
|
|
18
|
-
|
|
19
|
-
const document = window.document;
|
|
20
|
-
const annotationsMap = {};
|
|
21
|
-
const usedHints = new Set();
|
|
22
|
-
let annotationsContainer = null;
|
|
23
|
-
|
|
24
|
-
// Check if the element is not blocked and visible for clicking
|
|
25
|
-
function isElementClickNotBlocked(element, windowToAnnotate) {
|
|
26
|
-
const rect = element.getBoundingClientRect();
|
|
27
|
-
const originalScrollX = windowToAnnotate.scrollX;
|
|
28
|
-
const originalScrollY = windowToAnnotate.scrollY;
|
|
29
|
-
|
|
30
|
-
// Calculate the center point of the element
|
|
31
|
-
const centerX = rect.left + rect.width / 2;
|
|
32
|
-
const centerY = rect.top + rect.height / 2;
|
|
33
|
-
|
|
34
|
-
// check if element is within the viewport
|
|
35
|
-
if (
|
|
36
|
-
centerX < 0 ||
|
|
37
|
-
centerY < 0 ||
|
|
38
|
-
centerX > (windowToAnnotate.innerWidth || document.documentElement.clientWidth) ||
|
|
39
|
-
centerY > (windowToAnnotate.innerHeight || document.documentElement.clientHeight)
|
|
40
|
-
) {
|
|
41
|
-
// Determine the viewport dimensions
|
|
42
|
-
const viewportWidth =
|
|
43
|
-
windowToAnnotate.innerWidth || document.documentElement.clientWidth;
|
|
44
|
-
const viewportHeight =
|
|
45
|
-
windowToAnnotate.innerHeight || document.documentElement.clientHeight;
|
|
46
|
-
|
|
47
|
-
// Calculate the new scroll positions to bring the element into the center of the viewport
|
|
48
|
-
const newScrollX = centerX - viewportWidth / 2;
|
|
49
|
-
const newScrollY = centerY - viewportHeight / 2;
|
|
50
|
-
|
|
51
|
-
// Scroll the window to the new positions
|
|
52
|
-
windowToAnnotate.scrollTo({
|
|
53
|
-
top: newScrollY,
|
|
54
|
-
left: newScrollX,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const newRect = element.getBoundingClientRect();
|
|
58
|
-
const newCenterX = newRect.left + newRect.width / 2;
|
|
59
|
-
const newCenterY = newRect.top + newRect.height / 2;
|
|
60
|
-
const topElement = document.elementFromPoint(newCenterX, newCenterY);
|
|
61
|
-
const doesElementContainTopElement = element.contains(topElement);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Restore the original scroll positions
|
|
65
|
-
windowToAnnotate.scrollTo(originalScrollX, originalScrollY);
|
|
66
|
-
return doesElementContainTopElement;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const topElement = windowToAnnotate.document.elementFromPoint(centerX, centerY);
|
|
70
|
-
|
|
71
|
-
// Check if the topmost element is the target element or one of its descendants
|
|
72
|
-
return element.contains(topElement);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function generateHintStrings(charset, max) {
|
|
76
|
-
const hints = [];
|
|
77
|
-
let length = 1;
|
|
78
|
-
|
|
79
|
-
while (hints.length < max) {
|
|
80
|
-
const combos = cartesianProduct(Array(length).fill(charset.split("")));
|
|
81
|
-
for (const combo of combos) {
|
|
82
|
-
const hint = combo.join("");
|
|
83
|
-
if (!usedHints.has(hint)) {
|
|
84
|
-
hints.push(hint);
|
|
85
|
-
usedHints.add(hint);
|
|
86
|
-
}
|
|
87
|
-
if (hints.length >= max) break;
|
|
88
|
-
}
|
|
89
|
-
length++;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return hints;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function cartesianProduct(arrays) {
|
|
96
|
-
return arrays.reduce(
|
|
97
|
-
(acc, curr) =>
|
|
98
|
-
acc
|
|
99
|
-
.map((a) => curr.map((b) => a.concat([b])))
|
|
100
|
-
.reduce((a, b) => a.concat(b), []),
|
|
101
|
-
[[]]
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check if an element is clickable
|
|
106
|
-
function isElementClickable(element, windowToAnnotate) {
|
|
107
|
-
if (!(element instanceof windowToAnnotate.Element)) return false;
|
|
108
|
-
|
|
109
|
-
const tagName = element.tagName.toLowerCase();
|
|
110
|
-
let isClickable = false;
|
|
111
|
-
|
|
112
|
-
// Check for aria-disabled
|
|
113
|
-
const ariaDisabled = element.getAttribute("aria-disabled");
|
|
114
|
-
if (ariaDisabled && ["", "true"].includes(ariaDisabled.toLowerCase())) {
|
|
115
|
-
return false; // Element should not be clickable if aria-disabled is true
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Check for visibility
|
|
119
|
-
const style = window.getComputedStyle(element);
|
|
120
|
-
if (
|
|
121
|
-
style.display === "none" ||
|
|
122
|
-
style.visibility === "hidden" ||
|
|
123
|
-
// This is done for cases where opacity is undefined
|
|
124
|
-
// parseFloat(style.opacity) === 0
|
|
125
|
-
style.pointerEvents === "none"
|
|
126
|
-
) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Check if element is disabled (for applicable elements)
|
|
131
|
-
if (element.disabled) return false;
|
|
132
|
-
|
|
133
|
-
// Check for AngularJS click handlers
|
|
134
|
-
if (!isElementClickable._checkForAngularJs) {
|
|
135
|
-
isElementClickable._checkForAngularJs = (function () {
|
|
136
|
-
const angularElements = document.getElementsByClassName("ng-scope");
|
|
137
|
-
if (angularElements.length === 0) {
|
|
138
|
-
return () => false;
|
|
139
|
-
} else {
|
|
140
|
-
const ngAttributes = [];
|
|
141
|
-
for (const prefix of ["", "data-", "x-"]) {
|
|
142
|
-
for (const separator of ["-", ":", "_"]) {
|
|
143
|
-
ngAttributes.push(`${prefix}ng${separator}click`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return function (el) {
|
|
147
|
-
for (const attribute of ngAttributes) {
|
|
148
|
-
if (el.hasAttribute(attribute)) return true;
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
})();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!isClickable && isElementClickable._checkForAngularJs(element)) {
|
|
157
|
-
isClickable = true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Tag-specific clickability
|
|
161
|
-
const focusableTags = [
|
|
162
|
-
"a",
|
|
163
|
-
"button",
|
|
164
|
-
"input",
|
|
165
|
-
"select",
|
|
166
|
-
"textarea",
|
|
167
|
-
"object",
|
|
168
|
-
"embed",
|
|
169
|
-
"label",
|
|
170
|
-
"details",
|
|
171
|
-
];
|
|
172
|
-
|
|
173
|
-
// Check for onclick attribute or listener
|
|
174
|
-
if (
|
|
175
|
-
(element.hasAttribute("onclick") ||
|
|
176
|
-
typeof element.onclick === "function")
|
|
177
|
-
// check for parent element having same on click
|
|
178
|
-
&& element.parentNode.onclick !== element.onclick
|
|
179
|
-
// parent element should not be a focusable tag like button
|
|
180
|
-
&& focusableTags.indexOf(element.parentNode.tagName.toLowerCase()) === -1
|
|
181
|
-
) {
|
|
182
|
-
isClickable = true;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Check for jsaction attribute (commonly used in frameworks like Google's)
|
|
186
|
-
if (!isClickable && element.hasAttribute("jsaction")) {
|
|
187
|
-
const jsactionRules = element.getAttribute("jsaction").split(";");
|
|
188
|
-
for (const jsactionRule of jsactionRules) {
|
|
189
|
-
const ruleSplit = jsactionRule.trim().split(":");
|
|
190
|
-
if (ruleSplit.length >= 1 && ruleSplit.length <= 2) {
|
|
191
|
-
const [eventType] = ruleSplit[0].trim().split(".");
|
|
192
|
-
if (eventType === "click") {
|
|
193
|
-
isClickable = true;
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Check for role attributes that imply clickability
|
|
201
|
-
if (!isClickable) {
|
|
202
|
-
const role = element.getAttribute("role");
|
|
203
|
-
const clickableRoles = [
|
|
204
|
-
"button",
|
|
205
|
-
"tab",
|
|
206
|
-
"link",
|
|
207
|
-
"checkbox",
|
|
208
|
-
"menuitem",
|
|
209
|
-
"menuitemcheckbox",
|
|
210
|
-
"menuitemradio",
|
|
211
|
-
"radio",
|
|
212
|
-
"switch",
|
|
213
|
-
];
|
|
214
|
-
if (role && clickableRoles.includes(role.toLowerCase())) {
|
|
215
|
-
isClickable = true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Check for contentEditable
|
|
220
|
-
if (!isClickable) {
|
|
221
|
-
const contentEditable = element.getAttribute("contentEditable");
|
|
222
|
-
if (
|
|
223
|
-
contentEditable != null &&
|
|
224
|
-
["", "contenteditable", "true"].includes(contentEditable.toLowerCase())
|
|
225
|
-
) {
|
|
226
|
-
isClickable = true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (focusableTags.includes(tagName)) {
|
|
232
|
-
switch (tagName) {
|
|
233
|
-
case "a":
|
|
234
|
-
// Ensure it's not just a named anchor without href
|
|
235
|
-
if (element.hasAttribute("href")) {
|
|
236
|
-
isClickable = true;
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case "input": {
|
|
240
|
-
const type = (element.getAttribute("type") || "").toLowerCase();
|
|
241
|
-
if (
|
|
242
|
-
type !== "hidden" &&
|
|
243
|
-
!element.disabled &&
|
|
244
|
-
!(element.readOnly && isInputSelectable(element))
|
|
245
|
-
) {
|
|
246
|
-
isClickable = true;
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
case "textarea":
|
|
251
|
-
if (!element.disabled && !element.readOnly) {
|
|
252
|
-
isClickable = true;
|
|
253
|
-
}
|
|
254
|
-
break;
|
|
255
|
-
case "button":
|
|
256
|
-
case "select":
|
|
257
|
-
if (!element.disabled) {
|
|
258
|
-
isClickable = true;
|
|
259
|
-
}
|
|
260
|
-
break;
|
|
261
|
-
case "object":
|
|
262
|
-
case "embed":
|
|
263
|
-
isClickable = true;
|
|
264
|
-
break;
|
|
265
|
-
case "label":
|
|
266
|
-
if (element.control && !element.control.disabled) {
|
|
267
|
-
isClickable = true;
|
|
268
|
-
}
|
|
269
|
-
break;
|
|
270
|
-
case "details":
|
|
271
|
-
isClickable = true;
|
|
272
|
-
break;
|
|
273
|
-
default:
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Check for class names containing 'button' as a possible click target
|
|
279
|
-
if (!isClickable) {
|
|
280
|
-
const className = element.getAttribute("class");
|
|
281
|
-
if (className && className.toLowerCase().includes("button")) {
|
|
282
|
-
isClickable = true;
|
|
283
|
-
} else if (element.classList.contains("cursor-pointer")) {
|
|
284
|
-
isClickable = true;
|
|
285
|
-
} else if (element.classList.contains("v-list-item--link")) {
|
|
286
|
-
// vue specific click handling
|
|
287
|
-
isClickable = true;
|
|
288
|
-
} else if (element.style.cursor === "pointer") {
|
|
289
|
-
isClickable = true;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Check for tabindex
|
|
294
|
-
if (!isClickable) {
|
|
295
|
-
const tabIndexValue = element.getAttribute("tabindex");
|
|
296
|
-
const tabIndex = tabIndexValue ? parseInt(tabIndexValue) : -1;
|
|
297
|
-
if (tabIndex >= 0 && !isNaN(tabIndex)) {
|
|
298
|
-
isClickable = true;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return isClickable;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function isInputSelectable(input) {
|
|
306
|
-
const selectableTypes = [
|
|
307
|
-
"text",
|
|
308
|
-
"search",
|
|
309
|
-
"password",
|
|
310
|
-
"url",
|
|
311
|
-
"email",
|
|
312
|
-
"number",
|
|
313
|
-
"tel",
|
|
314
|
-
];
|
|
315
|
-
const type = (input.getAttribute("type") || "").toLowerCase();
|
|
316
|
-
return selectableTypes.includes(type);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
var parentElements = [];
|
|
320
|
-
|
|
321
|
-
// Create a hint marker
|
|
322
|
-
function createHintMarker(el, hint, parentElement, windowToAnnotate) {
|
|
323
|
-
const rect = el.getBoundingClientRect();
|
|
324
|
-
|
|
325
|
-
// Create the marker element
|
|
326
|
-
const marker = document.createElement("div");
|
|
327
|
-
marker.textContent = hint;
|
|
328
|
-
marker.className = markerClass;
|
|
329
|
-
// Style the marker
|
|
330
|
-
Object.assign(marker.style, {
|
|
331
|
-
position: "absolute",
|
|
332
|
-
top: `${rect.top + windowToAnnotate.scrollY}px`,
|
|
333
|
-
left: `${rect.left + windowToAnnotate.scrollX}px`,
|
|
334
|
-
background:
|
|
335
|
-
"-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 247, 133)), to(rgb(255, 197, 66)))",
|
|
336
|
-
padding: "1px 3px 0px",
|
|
337
|
-
borderRadius: "3px",
|
|
338
|
-
border: "1px solid rgb(227, 190, 35)",
|
|
339
|
-
fontSize: "11px",
|
|
340
|
-
pointerEvents: "none",
|
|
341
|
-
zIndex: "10000",
|
|
342
|
-
whiteSpace: "nowrap",
|
|
343
|
-
overflow: "hidden",
|
|
344
|
-
boxShadow: "rgba(0, 0, 0, 0.3) 0px 3px 7px 0px",
|
|
345
|
-
letterSpacing: 0,
|
|
346
|
-
minHeight: 0,
|
|
347
|
-
lineHeight: "100%",
|
|
348
|
-
color: "rgb(48, 37, 5)",
|
|
349
|
-
fontFamily: "Helvetica, Arial, sans-serif",
|
|
350
|
-
fontWeight: "bold",
|
|
351
|
-
textShadow: "rgba(255, 255, 255, 0.6) 0px 1px 0px",
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// Attach the marker to the specified parent element
|
|
355
|
-
parentElement.appendChild(marker);
|
|
356
|
-
parentElements.push(parentElement);
|
|
357
|
-
return marker;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// Clear existing annotations
|
|
362
|
-
//TODO: Handle clearing annotations
|
|
363
|
-
function clearAnnotations() {
|
|
364
|
-
parentElements.forEach((parentElement) => {
|
|
365
|
-
const markers = parentElement.querySelectorAll(`.${markerClass}`);
|
|
366
|
-
markers.forEach((marker) => marker.remove());
|
|
367
|
-
});
|
|
368
|
-
parentElements = [];
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function isInputElement(el) {
|
|
372
|
-
// Check if it's an input with a text-like type
|
|
373
|
-
if (el instanceof HTMLInputElement) {
|
|
374
|
-
const textTypes = ['text', 'email', 'number', 'password', 'search', 'tel', 'url', 'checkbox'];
|
|
375
|
-
return textTypes.includes(el.type.toLowerCase());
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check if it's a textarea
|
|
379
|
-
if (el instanceof HTMLTextAreaElement) {
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Check if it's contentEditable
|
|
384
|
-
return el.isContentEditable;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Initialize annotations for a given window (including iframes)
|
|
388
|
-
function initializeAnnotations(windowToAnnotate, parentHints, depth) {
|
|
389
|
-
const container =
|
|
390
|
-
parentHints?.nodeName === "IFRAME"
|
|
391
|
-
? parentHints.contentWindow.document.body
|
|
392
|
-
: annotationsContainer;
|
|
393
|
-
|
|
394
|
-
// Ensure the container exists
|
|
395
|
-
if (!container) return;
|
|
396
|
-
|
|
397
|
-
// Filter for clickable elements
|
|
398
|
-
const clickableElements = Array.from(windowToAnnotate.document.querySelectorAll("*")).filter((el) => {
|
|
399
|
-
|
|
400
|
-
//If preference is fill then it should only annotate input elements
|
|
401
|
-
if(preference === "fill"){
|
|
402
|
-
if (!isInputElement(el)) {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const isClickable = isElementClickable(el, windowToAnnotate);
|
|
408
|
-
const isClickNotBlocked = isElementClickNotBlocked(el, windowToAnnotate);
|
|
409
|
-
return isClickable && isClickNotBlocked;
|
|
410
|
-
});
|
|
411
|
-
// Generate hint strings for the clickable elements
|
|
412
|
-
const hints = generateHintStrings(hintCharacterSet, Math.min(maxHints, clickableElements.length));
|
|
413
|
-
|
|
414
|
-
// Create markers for the elements
|
|
415
|
-
clickableElements.slice(0, maxHints).forEach((el, index) => {
|
|
416
|
-
const hint = hints[index];
|
|
417
|
-
const rect = el.getBoundingClientRect();
|
|
418
|
-
|
|
419
|
-
// Use createHintMarker with the specified container
|
|
420
|
-
createHintMarker(el, hint, container, windowToAnnotate);
|
|
421
|
-
el.style.boxShadow = `inset 0 0 0px 2px red`;
|
|
422
|
-
|
|
423
|
-
// Add element details to the annotations map
|
|
424
|
-
annotationsMap[hint] = {
|
|
425
|
-
node: el,
|
|
426
|
-
rect: {
|
|
427
|
-
top: rect.top + windowToAnnotate.scrollY,
|
|
428
|
-
left: rect.left + windowToAnnotate.scrollX,
|
|
429
|
-
width: rect.width,
|
|
430
|
-
height: rect.height,
|
|
431
|
-
},
|
|
432
|
-
depth: [...depth ]
|
|
433
|
-
};
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Process iframes recursively
|
|
437
|
-
Array.from(windowToAnnotate.document.querySelectorAll("iframe")).forEach((iframe) => {
|
|
438
|
-
try {
|
|
439
|
-
const frameWindow = iframe.contentWindow;
|
|
440
|
-
if (frameWindow && iframe.offsetWidth > 0 && iframe.offsetHeight > 0) {
|
|
441
|
-
initializeAnnotations(frameWindow, iframe, [...depth, iframe]);
|
|
442
|
-
}
|
|
443
|
-
} catch (e) {
|
|
444
|
-
console.warn("Cannot access iframe:", e);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// Initialize and enable annotations
|
|
451
|
-
function enable() {
|
|
452
|
-
clearAnnotations();
|
|
453
|
-
if (!annotationsContainer) {
|
|
454
|
-
annotationsContainer = document.createElement("div");
|
|
455
|
-
annotationsContainer.className = "annotations";
|
|
456
|
-
Object.assign(annotationsContainer.style, {
|
|
457
|
-
position: "absolute",
|
|
458
|
-
top: "0",
|
|
459
|
-
left: "0",
|
|
460
|
-
width: "100%",
|
|
461
|
-
height: "100%",
|
|
462
|
-
pointerEvents: "none",
|
|
463
|
-
zIndex: "9999",
|
|
464
|
-
});
|
|
465
|
-
document.body.appendChild(annotationsContainer);
|
|
466
|
-
initializeAnnotations(window, null, []);
|
|
467
|
-
} else {
|
|
468
|
-
annotationsContainer.style.display = "block";
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Destroy annotations
|
|
473
|
-
function destroy() {
|
|
474
|
-
if (annotationsContainer) {
|
|
475
|
-
clearAnnotations();
|
|
476
|
-
Object.values(annotationsMap).forEach((annotation) => {
|
|
477
|
-
annotation.node.style.boxShadow = "none";
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
annotationsContainer.parentNode.removeChild(annotationsContainer);
|
|
481
|
-
annotationsContainer = null;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
enable();
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
annotations: annotationsMap,
|
|
489
|
-
destroy,
|
|
490
|
-
};
|
|
491
|
-
}
|