@browserless/agent 14.0.1 → 14.0.2
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 +91 -33
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.2",
|
|
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": "2f8b09a1509de4868c5ca2981ed147af8638968d"
|
|
62
63
|
}
|
package/src/snapshot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global location, scrollX, scrollY, innerWidth, innerHeight
|
|
1
|
+
/* global 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)
|
|
@@ -140,7 +170,7 @@ module.exports = function snapshot () {
|
|
|
140
170
|
.map(e => [identity(e), e.value, e.checked, e.selectedIndex, e.disabled, e.readOnly])
|
|
141
171
|
]
|
|
142
172
|
cache.guard = e => {
|
|
143
|
-
if (!e?.isConnected || !
|
|
173
|
+
if (!e?.isConnected || !visibleNow(e)) return null
|
|
144
174
|
const scope =
|
|
145
175
|
cache.closest(e, 'form,dialog,[role="dialog"],article,li,tr,[role="row"]') || flatParent(e)
|
|
146
176
|
return [
|
|
@@ -252,37 +282,65 @@ module.exports = function snapshot () {
|
|
|
252
282
|
const words = []
|
|
253
283
|
const range = document.createRange()
|
|
254
284
|
let length = 0
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
const TEXT_SKIP = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE'])
|
|
286
|
+
const intersectsViewport = rect =>
|
|
287
|
+
rect.width > 0 &&
|
|
288
|
+
rect.height > 0 &&
|
|
289
|
+
rect.bottom > 0 &&
|
|
290
|
+
rect.top < innerHeight &&
|
|
291
|
+
rect.right > 0 &&
|
|
292
|
+
rect.left < innerWidth
|
|
293
|
+
const outsideViewport = rect =>
|
|
294
|
+
rect.width > 0 &&
|
|
295
|
+
rect.height > 0 &&
|
|
296
|
+
(rect.bottom <= 0 || rect.top >= innerHeight || rect.right <= 0 || rect.left >= innerWidth)
|
|
297
|
+
const fullyInside = rect =>
|
|
298
|
+
rect.top >= 0 &&
|
|
299
|
+
rect.left >= 0 &&
|
|
300
|
+
rect.bottom <= innerHeight &&
|
|
301
|
+
rect.right <= innerWidth &&
|
|
302
|
+
rect.width > 0 &&
|
|
303
|
+
rect.height > 0
|
|
304
|
+
const pushText = (node, skipRect) => {
|
|
305
|
+
if (length >= 6000 || node.nodeType !== 3) return
|
|
306
|
+
const value = node.textContent.trim()
|
|
307
|
+
if (!value) return
|
|
308
|
+
if (!skipRect) {
|
|
272
309
|
range.selectNodeContents(node)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
310
|
+
if (!intersectsViewport(range.getBoundingClientRect())) return
|
|
311
|
+
}
|
|
312
|
+
words.push(value)
|
|
313
|
+
length += value.length
|
|
314
|
+
}
|
|
315
|
+
// Off-screen subtrees are skipped. A fixed node inside one of those subtrees is not read.
|
|
316
|
+
const collectText = element => {
|
|
317
|
+
if (length >= 6000 || TEXT_SKIP.has(element.tagName)) return
|
|
318
|
+
const rect = element.getBoundingClientRect()
|
|
319
|
+
if (outsideViewport(rect)) return
|
|
320
|
+
const inside = fullyInside(rect)
|
|
321
|
+
if (!intersectsViewport(rect) || !visible(element)) {
|
|
322
|
+
for (const node of element.childNodes) {
|
|
323
|
+
if (node.nodeType === 1) collectText(node)
|
|
284
324
|
}
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
for (const node of element.childNodes) {
|
|
328
|
+
if (length >= 6000) return
|
|
329
|
+
if (node.nodeType === 3) pushText(node, inside)
|
|
330
|
+
else if (node.nodeType === 1) collectText(node)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const root of roots) {
|
|
334
|
+
if (length >= 6000) break
|
|
335
|
+
if (root === document) {
|
|
336
|
+
if (document.body) collectText(document.body)
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
const host = root.host
|
|
340
|
+
if (host && visible(host)) {
|
|
341
|
+
for (const node of root.childNodes) pushText(node)
|
|
285
342
|
}
|
|
343
|
+
for (const child of root.children) collectText(child)
|
|
286
344
|
}
|
|
287
345
|
const unsupported = []
|
|
288
346
|
for (const e of queryAll('iframe,canvas,input[type="password"],input[type="file"]')) {
|