@browserless/pdf 13.6.2 → 13.6.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 +4 -4
- package/src/index.js +50 -8
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@browserless/pdf",
|
|
3
3
|
"description": "Convert websites to high-quality PDFs with customizable margins, background printing, and optimized scaling.",
|
|
4
4
|
"homepage": "https://browserless.js.org/#/?id=pdfurl-options",
|
|
5
|
-
"version": "13.6.
|
|
5
|
+
"version": "13.6.3",
|
|
6
6
|
"main": "src",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "hello@microlink.io",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"website-to-pdf"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@browserless/goto": "13.6.
|
|
34
|
-
"@browserless/screenshot": "13.6.
|
|
33
|
+
"@browserless/goto": "13.6.3",
|
|
34
|
+
"@browserless/screenshot": "13.6.3",
|
|
35
35
|
"@kikobeats/time-span": "~1.0.12",
|
|
36
36
|
"debug-logfmt": "~1.4.10",
|
|
37
37
|
"p-reflect": "~2.1.0",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"test": "exit 0"
|
|
48
48
|
},
|
|
49
49
|
"license": "MIT",
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "7bc1c12120186d999a5ddedbcad4e3b5b52fb8fb"
|
|
51
51
|
}
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
captureWithNavigationRetry,
|
|
10
10
|
isWhiteScreenshot,
|
|
11
11
|
waitForDomStability,
|
|
12
|
+
waitForReady,
|
|
12
13
|
resolveWaitForDom,
|
|
13
14
|
SCREENSHOT_DEFAULT_OPTS
|
|
14
15
|
} = require('@browserless/screenshot')
|
|
@@ -21,6 +22,16 @@ const PDF_DEFAULT_OPTS = {
|
|
|
21
22
|
waitUntil: 'auto'
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// Share of the action budget the readiness gate may consume in `auto` mode. The
|
|
26
|
+
// remainder is reserved for the blank-SPA screenshot poll to re-wait, so the
|
|
27
|
+
// gate can't starve the fallback while total prepare stays within one `timeout`.
|
|
28
|
+
const READY_BUDGET_RATIO = 0.5
|
|
29
|
+
|
|
30
|
+
// Minimum visible characters for the text fast path. Matches the counting cap
|
|
31
|
+
// in `waitForReady`'s snapshot, which stops walking text nodes once reached —
|
|
32
|
+
// raising this above the cap would make the text fast path unreachable.
|
|
33
|
+
const TEXT_PAINTED_MIN = 200
|
|
34
|
+
|
|
24
35
|
const getMargin = unit => {
|
|
25
36
|
if (!unit) return unit
|
|
26
37
|
if (typeof unit === 'object') return unit
|
|
@@ -54,8 +65,9 @@ module.exports = ({ goto, ...gotoOpts } = {}) => {
|
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
// Navigate `page` to `url` and wait until it is ready to print: DOM stability
|
|
57
|
-
// plus, in `auto` mode, a
|
|
58
|
-
//
|
|
68
|
+
// plus, in `auto` mode, a navigation-tolerant readiness gate. Only a page that
|
|
69
|
+
// settles still-blank falls back to the (expensive, navigation-fragile)
|
|
70
|
+
// screenshot poll.
|
|
59
71
|
const prepare = async (page, url, opts = {}) => {
|
|
60
72
|
const {
|
|
61
73
|
margin,
|
|
@@ -89,11 +101,38 @@ module.exports = ({ goto, ...gotoOpts } = {}) => {
|
|
|
89
101
|
async function waitUntilAuto (page) {
|
|
90
102
|
await waitForDomStabilityResult(page)
|
|
91
103
|
const timeout = goto.timeouts.action(rest.timeout)
|
|
104
|
+
// One action budget shared by the readiness gate and the screenshot poll,
|
|
105
|
+
// so worst-case prepare stays within a single `timeout` instead of one
|
|
106
|
+
// per stage.
|
|
107
|
+
const elapsed = timeSpan()
|
|
108
|
+
|
|
109
|
+
// Cheap, navigation-tolerant readiness — no screenshots. Resolves once the
|
|
110
|
+
// page is visually quiet (height stable, images decoded, load complete),
|
|
111
|
+
// absorbing the client-side re-navigation that makes a screenshot poll
|
|
112
|
+
// throw `Execution context was destroyed`. Capped at a share of the budget
|
|
113
|
+
// so a slow gate still leaves the blank-SPA poll room to re-wait.
|
|
114
|
+
const ready = await waitForReady(page, { timeout: Math.round(timeout * READY_BUDGET_RATIO) })
|
|
115
|
+
debug('ready', { ...ready, duration: elapsed() })
|
|
116
|
+
|
|
117
|
+
// Fast path: the page settled with real painted content in a document
|
|
118
|
+
// taller than the viewport — a visibly rendered image (not a tracking
|
|
119
|
+
// pixel), or enough visible text with webfonts loaded (a pending
|
|
120
|
+
// `font-display: block` font renders text invisible, exactly when a
|
|
121
|
+
// capture would be white) — so it can't be a blank shell: skip the
|
|
122
|
+
// screenshot poll. Height and viewport come from the same in-page
|
|
123
|
+
// snapshot (`page.viewport()` is null under `defaultViewport: null`),
|
|
124
|
+
// and an unknown viewport skips the fast path rather than dropping the
|
|
125
|
+
// taller-than-viewport guard. A gate that timed out never settled, so
|
|
126
|
+
// don't trust its partial snapshot: fall through to the blank check.
|
|
127
|
+
const painted = ready.painted > 0 || (ready.text >= TEXT_PAINTED_MIN && ready.fonts)
|
|
128
|
+
if (!ready.timedOut && painted && ready.viewport > 0 && ready.height > ready.viewport) return
|
|
129
|
+
|
|
130
|
+
// Otherwise fall back to the screenshot poll — re-wait while the first
|
|
131
|
+
// paint is still blank — to keep the blank-SPA protection. The page has
|
|
132
|
+
// already settled, so the first capture won't race a navigation, and a
|
|
133
|
+
// non-blank page exits after that single shot without an extra re-wait.
|
|
92
134
|
let isWhite = false
|
|
93
135
|
let retry = -1
|
|
94
|
-
|
|
95
|
-
const timePdf = timeSpan()
|
|
96
|
-
|
|
97
136
|
do {
|
|
98
137
|
++retry
|
|
99
138
|
const screenshotTime = timeSpan()
|
|
@@ -105,14 +144,17 @@ module.exports = ({ goto, ...gotoOpts } = {}) => {
|
|
|
105
144
|
type: 'jpeg',
|
|
106
145
|
quality: 30
|
|
107
146
|
}),
|
|
108
|
-
|
|
147
|
+
// The retry loop keeps its own clock, so hand it only what is left
|
|
148
|
+
// of the shared budget — a fresh full `timeout` here would let one
|
|
149
|
+
// navigation-racing capture double the worst-case prepare time.
|
|
150
|
+
{ page, goto, timeout: Math.max(0, timeout - elapsed()) }
|
|
109
151
|
)
|
|
110
152
|
isWhite = await isWhiteScreenshot(screenshot)
|
|
111
153
|
if (isWhite) await goto.waitUntilAuto(page, { timeout: rest.timeout })
|
|
112
154
|
debug('retry', { waitUntil, isWhite, retry, duration: screenshotTime() })
|
|
113
|
-
} while (isWhite &&
|
|
155
|
+
} while (isWhite && elapsed() < timeout)
|
|
114
156
|
|
|
115
|
-
debug({ waitUntil, isWhite, timeout, duration: require('pretty-ms')(
|
|
157
|
+
debug({ waitUntil, isWhite, timeout, duration: require('pretty-ms')(elapsed()) })
|
|
116
158
|
}
|
|
117
159
|
}
|
|
118
160
|
|