@browserless/pdf 13.6.2 → 13.6.4
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 +60 -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.4",
|
|
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.4",
|
|
34
|
+
"@browserless/screenshot": "13.6.4",
|
|
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": "c9d9abe1af82ce0ddf4ce1b6e8fe500c1b58532a"
|
|
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,48 @@ 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) — and that content is not `covered` by an
|
|
122
|
+
// opaque viewport-filling layer (a fixed white loading overlay passes
|
|
123
|
+
// every other DOM signal while a capture stays white): skip the
|
|
124
|
+
// screenshot poll. Height and viewport come from the same in-page
|
|
125
|
+
// snapshot (`page.viewport()` is null under `defaultViewport: null`),
|
|
126
|
+
// and an unknown viewport skips the fast path rather than dropping the
|
|
127
|
+
// taller-than-viewport guard. A gate that timed out never settled, so
|
|
128
|
+
// don't trust its partial snapshot: fall through to the blank check.
|
|
129
|
+
const painted = ready.painted > 0 || (ready.text >= TEXT_PAINTED_MIN && ready.fonts)
|
|
130
|
+
if (
|
|
131
|
+
!ready.timedOut &&
|
|
132
|
+
painted &&
|
|
133
|
+
!ready.covered &&
|
|
134
|
+
ready.viewport > 0 &&
|
|
135
|
+
ready.height > ready.viewport
|
|
136
|
+
) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Otherwise fall back to the screenshot poll — re-wait while the first
|
|
141
|
+
// paint is still blank — to keep the blank-SPA protection. The page has
|
|
142
|
+
// already settled, so the first capture won't race a navigation, and a
|
|
143
|
+
// non-blank page exits after that single shot without an extra re-wait.
|
|
92
144
|
let isWhite = false
|
|
93
145
|
let retry = -1
|
|
94
|
-
|
|
95
|
-
const timePdf = timeSpan()
|
|
96
|
-
|
|
97
146
|
do {
|
|
98
147
|
++retry
|
|
99
148
|
const screenshotTime = timeSpan()
|
|
@@ -105,14 +154,17 @@ module.exports = ({ goto, ...gotoOpts } = {}) => {
|
|
|
105
154
|
type: 'jpeg',
|
|
106
155
|
quality: 30
|
|
107
156
|
}),
|
|
108
|
-
|
|
157
|
+
// The retry loop keeps its own clock, so hand it only what is left
|
|
158
|
+
// of the shared budget — a fresh full `timeout` here would let one
|
|
159
|
+
// navigation-racing capture double the worst-case prepare time.
|
|
160
|
+
{ page, goto, timeout: Math.max(0, timeout - elapsed()) }
|
|
109
161
|
)
|
|
110
162
|
isWhite = await isWhiteScreenshot(screenshot)
|
|
111
163
|
if (isWhite) await goto.waitUntilAuto(page, { timeout: rest.timeout })
|
|
112
164
|
debug('retry', { waitUntil, isWhite, retry, duration: screenshotTime() })
|
|
113
|
-
} while (isWhite &&
|
|
165
|
+
} while (isWhite && elapsed() < timeout)
|
|
114
166
|
|
|
115
|
-
debug({ waitUntil, isWhite, timeout, duration: require('pretty-ms')(
|
|
167
|
+
debug({ waitUntil, isWhite, timeout, duration: require('pretty-ms')(elapsed()) })
|
|
116
168
|
}
|
|
117
169
|
}
|
|
118
170
|
|