@browserless/screenshot 13.9.16 → 13.9.17
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 -3
- package/src/evaluate-isolated.js +9 -0
- package/src/index.js +27 -21
- package/src/prepare-full-document.js +4 -3
- package/src/wait-for-ready.js +2 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@browserless/screenshot",
|
|
3
3
|
"description": "Capture high-quality screenshots of websites with overlay support, device emulation, and automated image optimization.",
|
|
4
4
|
"homepage": "https://browserless.js.org/#/?id=screenshoturl-options",
|
|
5
|
-
"version": "13.9.
|
|
5
|
+
"version": "13.9.17",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "hello@microlink.io",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@browserless/errors": "13.9.11",
|
|
36
|
-
"@browserless/goto": "13.9.
|
|
36
|
+
"@browserless/goto": "13.9.17",
|
|
37
37
|
"@kikobeats/content-type": "~1.0.4",
|
|
38
38
|
"@kikobeats/time-span": "~1.0.13",
|
|
39
39
|
"automad-prism-themes": "~0.3.8",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"timeout": "2m",
|
|
71
71
|
"workerThreads": false
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "23a3064ebf0ebc903178c705e223cc70907e256b"
|
|
74
74
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const isolatedRealm = page =>
|
|
4
|
+
typeof page.mainFrame === 'function' ? page.mainFrame().isolatedRealm() : page
|
|
5
|
+
|
|
6
|
+
const evaluateIsolated = (page, pageFunction, ...args) =>
|
|
7
|
+
isolatedRealm(page).evaluate(pageFunction, ...args)
|
|
8
|
+
|
|
9
|
+
module.exports = { evaluateIsolated }
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const pReflect = require('p-reflect')
|
|
|
7
7
|
|
|
8
8
|
const isTransientContextLoss = require('./is-transient-context-loss')
|
|
9
9
|
const isWhiteScreenshot = require('./is-white-screenshot')
|
|
10
|
+
const { evaluateIsolated } = require('./evaluate-isolated')
|
|
10
11
|
const waitForPrism = require('./pretty')
|
|
11
12
|
const prettyTimeSpan = require('./time-span')
|
|
12
13
|
const overlay = require('./overlay')
|
|
@@ -22,6 +23,8 @@ const {
|
|
|
22
23
|
|
|
23
24
|
const timeSpan = require('@kikobeats/time-span')()
|
|
24
25
|
|
|
26
|
+
const ELEMENT_CLIP_ATTEMPTS = 2
|
|
27
|
+
|
|
25
28
|
// No pacing here on purpose: `waitUntilAuto` is a network-idle wait, which on a
|
|
26
29
|
// live page costs at least its idle window (~500ms) and at most what is left of
|
|
27
30
|
// the budget. The runaway case was never a fast loop — it was retrying a page
|
|
@@ -43,7 +46,7 @@ const captureWithNavigationRetry = async (capture, { page, goto, timeout }) => {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
const getPageMeta = page =>
|
|
46
|
-
page
|
|
49
|
+
evaluateIsolated(page, () => ({
|
|
47
50
|
title: document.title || '',
|
|
48
51
|
bodyText: document.body ? document.body.innerText || '' : '',
|
|
49
52
|
url: window.location.href || ''
|
|
@@ -63,15 +66,10 @@ const checkPageReady = async (page, { isPageReady, response, screenshot, isWhite
|
|
|
63
66
|
return !pageReadyResult.isRejected && !!pageReadyResult.value
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
const getBoundingClientRect = element => {
|
|
67
|
-
const { top, left, height, width, x, y } = element.getBoundingClientRect()
|
|
68
|
-
return { top, left, height, width, x, y }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
69
|
const waitForImagesOnViewport = page =>
|
|
72
|
-
page
|
|
70
|
+
evaluateIsolated(page, () =>
|
|
73
71
|
Promise.all(
|
|
74
|
-
|
|
72
|
+
Array.from(document.querySelectorAll('img[src]:not([aria-hidden="true"])'))
|
|
75
73
|
.filter(el => {
|
|
76
74
|
if (el.naturalHeight === 0 || el.naturalWidth === 0) return false
|
|
77
75
|
const { top, left, bottom, right } = el.getBoundingClientRect()
|
|
@@ -86,15 +84,21 @@ const waitForImagesOnViewport = page =>
|
|
|
86
84
|
)
|
|
87
85
|
)
|
|
88
86
|
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
await
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return screenshotOpts
|
|
87
|
+
const readElementClip = async (page, element) => {
|
|
88
|
+
const handle = await page.waitForSelector(element, { visible: true })
|
|
89
|
+
try {
|
|
90
|
+
return await handle.boundingBox()
|
|
91
|
+
} finally {
|
|
92
|
+
await handle.dispose()
|
|
96
93
|
}
|
|
97
|
-
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const waitForElement = async (page, element, screenshotOpts) => {
|
|
97
|
+
for (let attempt = 0; attempt < ELEMENT_CLIP_ATTEMPTS; attempt++) {
|
|
98
|
+
screenshotOpts.clip = await readElementClip(page, element)
|
|
99
|
+
if (screenshotOpts.clip !== null) break
|
|
100
|
+
}
|
|
101
|
+
screenshotOpts.fullPage = false
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
const SCREENSHOT_DEFAULT_OPTS = {
|
|
@@ -143,10 +147,10 @@ module.exports = ({ goto, ...gotoOpts }) => {
|
|
|
143
147
|
const beforeScreenshot = async (page, response, { element, fullPage = false } = {}) => {
|
|
144
148
|
const timeout = goto.timeouts.action(opts.timeout)
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
const screenshotOpts = {}
|
|
147
151
|
const tasks = [
|
|
148
152
|
{
|
|
149
|
-
fn: () => page
|
|
153
|
+
fn: () => evaluateIsolated(page, 'document.fonts.ready'),
|
|
150
154
|
debug: 'beforeScreenshot:fontsReady'
|
|
151
155
|
},
|
|
152
156
|
{
|
|
@@ -164,9 +168,7 @@ module.exports = ({ goto, ...gotoOpts }) => {
|
|
|
164
168
|
|
|
165
169
|
if (element && !fullPage) {
|
|
166
170
|
tasks.push({
|
|
167
|
-
fn:
|
|
168
|
-
screenshotOpts = await waitForElement(page, element)
|
|
169
|
-
},
|
|
171
|
+
fn: () => waitForElement(page, element, screenshotOpts),
|
|
170
172
|
debug: 'beforeScreenshot:waitForElement'
|
|
171
173
|
})
|
|
172
174
|
}
|
|
@@ -181,6 +183,10 @@ module.exports = ({ goto, ...gotoOpts }) => {
|
|
|
181
183
|
)
|
|
182
184
|
)
|
|
183
185
|
|
|
186
|
+
if (screenshotOpts.clip === null) {
|
|
187
|
+
throw new Error(`Element \`${element}\` detached before its clip could be read`)
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
return screenshotOpts
|
|
185
191
|
}
|
|
186
192
|
|
|
@@ -4,6 +4,7 @@ const debug = require('debug-logfmt')('browserless:prepare')
|
|
|
4
4
|
const pReflect = require('p-reflect')
|
|
5
5
|
|
|
6
6
|
const { waitForDomStability } = require('./wait-for-dom')
|
|
7
|
+
const { evaluateIsolated } = require('./evaluate-isolated')
|
|
7
8
|
|
|
8
9
|
const SCROLL_STEP_MS = 50
|
|
9
10
|
const OVERFLOW_MIN_PX = 200
|
|
@@ -25,14 +26,14 @@ function findTallestOverflowScroller (minPx) {
|
|
|
25
26
|
return best
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
+
// An evaluate only serializes the function it is given, so an in-page helper
|
|
29
30
|
// can't be shared across evaluates by reference. Compose the scanner source in
|
|
30
31
|
// front of `fn` on the Node side (no in-page eval, so page CSP is untouched) to
|
|
31
32
|
// give every caller the same scanner.
|
|
32
33
|
const evaluateInPage = (page, fn, ...args) => {
|
|
33
34
|
const source = `${findTallestOverflowScroller}\nreturn (${fn}).apply(null, arguments)`
|
|
34
35
|
// eslint-disable-next-line no-new-func
|
|
35
|
-
return page
|
|
36
|
+
return evaluateIsolated(page, new Function(source), ...args)
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const waitForOverflowHeight = (page, timeout = OVERFLOW_WAIT_MS) =>
|
|
@@ -91,7 +92,7 @@ const expandOverflow = (page, minPx = OVERFLOW_MIN_PX) =>
|
|
|
91
92
|
|
|
92
93
|
const settleDom = async (page, { idle, timeout }, label) => {
|
|
93
94
|
const started = Date.now()
|
|
94
|
-
const result = await page
|
|
95
|
+
const result = await evaluateIsolated(page, waitForDomStability, { idle, timeout })
|
|
95
96
|
debug(label, { ...result, duration: Date.now() - started })
|
|
96
97
|
}
|
|
97
98
|
|
package/src/wait-for-ready.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { setTimeout: sleep } = require('node:timers/promises')
|
|
4
4
|
const isTransientContextLoss = require('./is-transient-context-loss')
|
|
5
|
+
const { evaluateIsolated } = require('./evaluate-isolated')
|
|
5
6
|
|
|
6
7
|
const DEFAULT_QUIET_MS = 300
|
|
7
8
|
const DEFAULT_POLL_MS = 150
|
|
@@ -191,7 +192,7 @@ const waitForReady = async (
|
|
|
191
192
|
while (Date.now() < deadline) {
|
|
192
193
|
let signals
|
|
193
194
|
try {
|
|
194
|
-
signals = await page
|
|
195
|
+
signals = await evaluateIsolated(page, paintSignals)
|
|
195
196
|
} catch (error) {
|
|
196
197
|
if (!isTransientContextLoss(page, error)) throw error
|
|
197
198
|
resets++
|