@browserless/agent 14.0.1 → 14.0.3
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/package.json +3 -2
- package/src/snapshot.js +96 -34
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@browserless/agent",
|
|
3
3
|
"description": "Drive a Puppeteer page toward a natural-language goal using finite decisions.",
|
|
4
4
|
"homepage": "https://browserless.js.org",
|
|
5
|
-
"version": "14.0.
|
|
5
|
+
"version": "14.0.3",
|
|
6
6
|
"main": "src",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "hello@microlink.io",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"test": "ava",
|
|
48
48
|
"start": "browserless exec examples/wallapop.js --no-headless",
|
|
49
49
|
"benchmark": "browserless exec scripts/benchmark.js",
|
|
50
|
+
"perf": "node scripts/perf-baseline.js",
|
|
50
51
|
"compare": "browserless exec scripts/compare.js"
|
|
51
52
|
},
|
|
52
53
|
"license": "MIT",
|
|
@@ -58,5 +59,5 @@
|
|
|
58
59
|
"access": "public"
|
|
59
60
|
},
|
|
60
61
|
"types": "src/index.d.ts",
|
|
61
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "f26f8ff96f57c2f2a848837097b560bd25ddd78a"
|
|
62
63
|
}
|
package/src/snapshot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global location, scrollX, scrollY, innerWidth, innerHeight
|
|
1
|
+
/* global HTMLElement, location, scrollX, scrollY, innerWidth, innerHeight */
|
|
2
2
|
// Adapted from browser-use/jev-ultrafast snapshot.js (MIT). See README.md.
|
|
3
3
|
module.exports = function snapshot () {
|
|
4
4
|
return (() => {
|
|
@@ -54,9 +54,39 @@ module.exports = function snapshot () {
|
|
|
54
54
|
return active
|
|
55
55
|
}
|
|
56
56
|
const safe = e => !['password', 'file', 'hidden'].includes(e.type)
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const hiddenMemo = new WeakMap()
|
|
58
|
+
const visibleMemo = new WeakMap()
|
|
59
|
+
const hiddenAncestor = element => {
|
|
60
|
+
const seen = []
|
|
61
|
+
for (let current = element; current; current = flatParent(current)) {
|
|
62
|
+
if (hiddenMemo.has(current)) {
|
|
63
|
+
const found = hiddenMemo.get(current)
|
|
64
|
+
for (const item of seen) hiddenMemo.set(item, found)
|
|
65
|
+
return found
|
|
66
|
+
}
|
|
67
|
+
seen.push(current)
|
|
68
|
+
if (current.matches('[aria-hidden="true"],[inert]')) {
|
|
69
|
+
for (const item of seen) hiddenMemo.set(item, current)
|
|
70
|
+
return current
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const item of seen) hiddenMemo.set(item, null)
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
const visible = e => {
|
|
77
|
+
if (visibleMemo.has(e)) return visibleMemo.get(e)
|
|
78
|
+
const shown =
|
|
79
|
+
!hiddenAncestor(e) && e.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
|
|
80
|
+
visibleMemo.set(e, shown)
|
|
81
|
+
return shown
|
|
82
|
+
}
|
|
83
|
+
// cache.guard runs again at input time, so it must not reuse this snapshot's memos.
|
|
84
|
+
const visibleNow = e => {
|
|
85
|
+
for (let current = e; current; current = flatParent(current)) {
|
|
86
|
+
if (current.matches('[aria-hidden="true"],[inert]')) return false
|
|
87
|
+
}
|
|
88
|
+
return e.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
|
|
89
|
+
}
|
|
60
90
|
const name = (e, seen = new Set()) => {
|
|
61
91
|
if (!e || seen.has(e)) return ''
|
|
62
92
|
seen.add(e)
|
|
@@ -139,8 +169,10 @@ module.exports = function snapshot () {
|
|
|
139
169
|
.filter(safe)
|
|
140
170
|
.map(e => [identity(e), e.value, e.checked, e.selectedIndex, e.disabled, e.readOnly])
|
|
141
171
|
]
|
|
172
|
+
// A named form control shadows the form's own innerText property.
|
|
173
|
+
const readInnerText = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText').get
|
|
142
174
|
cache.guard = e => {
|
|
143
|
-
if (!e?.isConnected || !
|
|
175
|
+
if (!e?.isConnected || !visibleNow(e)) return null
|
|
144
176
|
const scope =
|
|
145
177
|
cache.closest(e, 'form,dialog,[role="dialog"],article,li,tr,[role="row"]') || flatParent(e)
|
|
146
178
|
return [
|
|
@@ -165,7 +197,9 @@ module.exports = function snapshot () {
|
|
|
165
197
|
])
|
|
166
198
|
: null,
|
|
167
199
|
e.getAttribute('href'),
|
|
168
|
-
scope
|
|
200
|
+
(scope instanceof HTMLElement
|
|
201
|
+
? readInnerText.call(scope).slice(0, 6000)
|
|
202
|
+
: scope?.textContent?.slice(0, 6000)) || '',
|
|
169
203
|
[...(cache.closest(e, 'form')?.querySelectorAll('input,textarea,select') || [])]
|
|
170
204
|
.filter(safe)
|
|
171
205
|
.map(field => [
|
|
@@ -252,37 +286,65 @@ module.exports = function snapshot () {
|
|
|
252
286
|
const words = []
|
|
253
287
|
const range = document.createRange()
|
|
254
288
|
let length = 0
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
const TEXT_SKIP = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE'])
|
|
290
|
+
const intersectsViewport = rect =>
|
|
291
|
+
rect.width > 0 &&
|
|
292
|
+
rect.height > 0 &&
|
|
293
|
+
rect.bottom > 0 &&
|
|
294
|
+
rect.top < innerHeight &&
|
|
295
|
+
rect.right > 0 &&
|
|
296
|
+
rect.left < innerWidth
|
|
297
|
+
const outsideViewport = rect =>
|
|
298
|
+
rect.width > 0 &&
|
|
299
|
+
rect.height > 0 &&
|
|
300
|
+
(rect.bottom <= 0 || rect.top >= innerHeight || rect.right <= 0 || rect.left >= innerWidth)
|
|
301
|
+
const fullyInside = rect =>
|
|
302
|
+
rect.top >= 0 &&
|
|
303
|
+
rect.left >= 0 &&
|
|
304
|
+
rect.bottom <= innerHeight &&
|
|
305
|
+
rect.right <= innerWidth &&
|
|
306
|
+
rect.width > 0 &&
|
|
307
|
+
rect.height > 0
|
|
308
|
+
const pushText = (node, skipRect) => {
|
|
309
|
+
if (length >= 6000 || node.nodeType !== 3) return
|
|
310
|
+
const value = node.textContent.trim()
|
|
311
|
+
if (!value) return
|
|
312
|
+
if (!skipRect) {
|
|
272
313
|
range.selectNodeContents(node)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
314
|
+
if (!intersectsViewport(range.getBoundingClientRect())) return
|
|
315
|
+
}
|
|
316
|
+
words.push(value)
|
|
317
|
+
length += value.length
|
|
318
|
+
}
|
|
319
|
+
// Off-screen subtrees are skipped. A fixed node inside one of those subtrees is not read.
|
|
320
|
+
const collectText = element => {
|
|
321
|
+
if (length >= 6000 || TEXT_SKIP.has(element.tagName)) return
|
|
322
|
+
const rect = element.getBoundingClientRect()
|
|
323
|
+
if (outsideViewport(rect)) return
|
|
324
|
+
const inside = fullyInside(rect)
|
|
325
|
+
if (!intersectsViewport(rect) || !visible(element)) {
|
|
326
|
+
for (const node of element.childNodes) {
|
|
327
|
+
if (node.nodeType === 1) collectText(node)
|
|
284
328
|
}
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
for (const node of element.childNodes) {
|
|
332
|
+
if (length >= 6000) return
|
|
333
|
+
if (node.nodeType === 3) pushText(node, inside)
|
|
334
|
+
else if (node.nodeType === 1) collectText(node)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (const root of roots) {
|
|
338
|
+
if (length >= 6000) break
|
|
339
|
+
if (root === document) {
|
|
340
|
+
if (document.body) collectText(document.body)
|
|
341
|
+
continue
|
|
342
|
+
}
|
|
343
|
+
const host = root.host
|
|
344
|
+
if (host && visible(host)) {
|
|
345
|
+
for (const node of root.childNodes) pushText(node)
|
|
285
346
|
}
|
|
347
|
+
for (const child of root.children) collectText(child)
|
|
286
348
|
}
|
|
287
349
|
const unsupported = []
|
|
288
350
|
for (const e of queryAll('iframe,canvas,input[type="password"],input[type="file"]')) {
|