@browserless/pdf 13.6.3 → 13.6.5

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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/src/index.js +43 -18
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.3",
5
+ "version": "13.6.5",
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.3",
34
- "@browserless/screenshot": "13.6.3",
33
+ "@browserless/goto": "13.6.5",
34
+ "@browserless/screenshot": "13.6.5",
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": "7bc1c12120186d999a5ddedbcad4e3b5b52fb8fb"
50
+ "gitHead": "679c9a8f0db8a6a98bb54fd3d19532ea8a64d76b"
51
51
  }
package/src/index.js CHANGED
@@ -22,10 +22,13 @@ const PDF_DEFAULT_OPTS = {
22
22
  waitUntil: 'auto'
23
23
  }
24
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
25
+ // Share of the phase's load allowance the readiness gate may consume in `auto`
26
+ // mode. Pages observed settling in 0.6-3.3s (a hydrating document is the slow
27
+ // end), so a quarter of the allowance ~3.9s at the default request budget —
28
+ // covers them with margin while keeping the cap well short of the render's
29
+ // share. The gate returns as soon as the page is quiet, so this bounds only a
30
+ // page that never settles.
31
+ const READY_BUDGET_RATIO = 0.25
29
32
 
30
33
  // Minimum visible characters for the text fast path. Matches the counting cap
31
34
  // in `waitForReady`'s snapshot, which stops walking text nodes once reached —
@@ -97,35 +100,57 @@ module.exports = ({ goto, ...gotoOpts } = {}) => {
97
100
  return
98
101
  }
99
102
 
103
+ // Surfaced to the caller: a page whose readiness could not be confirmed
104
+ // (`timedOut`) is a poor one to keep rendering on, so a caller reusing this
105
+ // load across page-ranges can choose a fresh context instead.
106
+ let readiness
107
+
100
108
  await goto(page, { ...rest, url, waitUntil, waitUntilAuto })
101
- async function waitUntilAuto (page) {
109
+ return readiness
110
+
111
+ async function waitUntilAuto (page, { timeout: autoTimeout } = {}) {
102
112
  await waitForDomStabilityResult(page)
103
113
  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
114
 
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() })
115
+ // The readiness gate waits for a page to settle page-load work, not a
116
+ // small action. Budgeting it from `timeouts.action` (timeout/11) gave it
117
+ // ~1.2s while a hydrating document needs 2-3s, so it timed out on every
118
+ // tall page and the zero-capture fast path never fired. Budget it from
119
+ // the load allowance goto actually assigned to this phase; the gate
120
+ // returns as soon as the page is quiet, so this is a cap, not a cost.
121
+ const readyTime = timeSpan()
122
+ const ready = await waitForReady(page, {
123
+ timeout: Math.round((autoTimeout ?? timeout) * READY_BUDGET_RATIO)
124
+ })
125
+ readiness = ready
126
+ debug('ready', { ...ready, duration: readyTime() })
127
+
128
+ // The blank-page poll keeps its own action budget, measured from here so
129
+ // a slow gate cannot starve it.
130
+ const elapsed = timeSpan()
116
131
 
117
132
  // Fast path: the page settled with real painted content in a document
118
133
  // taller than the viewport — a visibly rendered image (not a tracking
119
134
  // pixel), or enough visible text with webfonts loaded (a pending
120
135
  // `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
136
+ // capture would be white) — and that content is not `covered` by an
137
+ // opaque viewport-filling layer (a fixed white loading overlay passes
138
+ // every other DOM signal while a capture stays white): skip the
122
139
  // screenshot poll. Height and viewport come from the same in-page
123
140
  // snapshot (`page.viewport()` is null under `defaultViewport: null`),
124
141
  // and an unknown viewport skips the fast path rather than dropping the
125
142
  // taller-than-viewport guard. A gate that timed out never settled, so
126
143
  // don't trust its partial snapshot: fall through to the blank check.
127
144
  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
145
+ if (
146
+ !ready.timedOut &&
147
+ painted &&
148
+ !ready.covered &&
149
+ ready.viewport > 0 &&
150
+ ready.height > ready.viewport
151
+ ) {
152
+ return
153
+ }
129
154
 
130
155
  // Otherwise fall back to the screenshot poll — re-wait while the first
131
156
  // paint is still blank — to keep the blank-SPA protection. The page has