html2canvas-pro 2.2.4 → 2.3.0
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/dist/html2canvas-pro.cjs +11218 -0
- package/dist/html2canvas-pro.cjs.map +1 -0
- package/dist/html2canvas-pro.esm.js +2601 -2379
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +2601 -2379
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +4 -5
- package/dist/lib/core/abort-helper.js +20 -0
- package/dist/lib/core/background-parser.js +41 -0
- package/dist/lib/core/cache-storage.js +63 -7
- package/dist/lib/core/config-assembler.js +88 -0
- package/dist/lib/core/lru-map.js +66 -0
- package/dist/lib/core/render-element.js +24 -103
- package/dist/lib/css/property-descriptors/background-position.js +1 -11
- package/dist/lib/css/property-descriptors/background-size.js +3 -1
- package/dist/lib/css/syntax/token-constants.js +214 -0
- package/dist/lib/css/syntax/token-singletons.js +36 -0
- package/dist/lib/css/syntax/token-types.js +10 -0
- package/dist/lib/css/syntax/tokenizer.js +175 -307
- package/dist/lib/css/types/length-percentage.js +63 -3
- package/dist/lib/dom/document-cloner.js +23 -22
- package/dist/lib/dom/node-parser.js +1 -21
- package/dist/lib/dom/slot-cloner.js +8 -8
- package/dist/lib/render/background.js +4 -1
- package/dist/lib/render/canvas/background-renderer.js +27 -39
- package/dist/lib/render/canvas/canvas-renderer.js +2 -2
- package/dist/lib/render/canvas/effects-renderer.js +94 -32
- package/dist/lib/render/canvas/text-renderer.js +68 -12
- package/dist/lib/render/stacking-context.js +12 -6
- package/dist/types/core/abort-helper.d.ts +12 -0
- package/dist/types/core/background-parser.d.ts +16 -0
- package/dist/types/core/cache-storage.d.ts +25 -0
- package/dist/types/core/config-assembler.d.ts +28 -0
- package/dist/types/core/lru-map.d.ts +37 -0
- package/dist/types/css/syntax/token-constants.d.ts +74 -0
- package/dist/types/css/syntax/token-singletons.d.ts +22 -0
- package/dist/types/css/syntax/token-types.d.ts +72 -0
- package/dist/types/css/syntax/tokenizer.d.ts +6 -73
- package/dist/types/css/types/length-percentage.d.ts +42 -1
- package/dist/types/dom/document-cloner.d.ts +5 -0
- package/dist/types/dom/node-parser.d.ts +0 -2
- package/dist/types/render/canvas/background-renderer.d.ts +0 -10
- package/dist/types/render/canvas/effects-renderer.d.ts +42 -17
- package/dist/types/render/canvas/text-renderer.d.ts +12 -0
- package/dist/types/render/stacking-context.d.ts +26 -1
- package/package.json +5 -7
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AbortSignal helper utilities.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.throwIfAborted = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Throw a DOMException if the given AbortSignal is already aborted.
|
|
9
|
+
* Used at checkpoints throughout the rendering pipeline to support
|
|
10
|
+
* cancellable renders.
|
|
11
|
+
*
|
|
12
|
+
* @param signal - The AbortSignal to check. If undefined or not aborted, this is a no-op.
|
|
13
|
+
* @throws {DOMException} With name 'AbortError' if the signal has been aborted.
|
|
14
|
+
*/
|
|
15
|
+
const throwIfAborted = (signal) => {
|
|
16
|
+
if (signal?.aborted) {
|
|
17
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
exports.throwIfAborted = throwIfAborted;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBackgroundColor = void 0;
|
|
4
|
+
const color_1 = require("../css/types/color");
|
|
5
|
+
const color_utilities_1 = require("../css/types/color-utilities");
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the background colour for the rendered canvas, following CSS
|
|
8
|
+
* background propagation rules:
|
|
9
|
+
*
|
|
10
|
+
* 1. If the target element is `<html>`, inherit the first opaque ancestor
|
|
11
|
+
* (doc → body → fallback).
|
|
12
|
+
* 2. Otherwise use the user-supplied backgroundColor, or opaque white.
|
|
13
|
+
*
|
|
14
|
+
* @param context - Current rendering context.
|
|
15
|
+
* @param element - The root element being rendered.
|
|
16
|
+
* @param backgroundColorOverride - User-supplied override (string | null).
|
|
17
|
+
* @returns A resolved colour value suitable for filling the canvas.
|
|
18
|
+
*/
|
|
19
|
+
const parseBackgroundColor = (context, element, backgroundColorOverride) => {
|
|
20
|
+
const ownerDocument = element.ownerDocument;
|
|
21
|
+
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
|
22
|
+
const documentBackgroundColor = ownerDocument.documentElement
|
|
23
|
+
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.documentElement).backgroundColor)
|
|
24
|
+
: color_1.COLORS.TRANSPARENT;
|
|
25
|
+
const bodyBackgroundColor = ownerDocument.body
|
|
26
|
+
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.body).backgroundColor)
|
|
27
|
+
: color_1.COLORS.TRANSPARENT;
|
|
28
|
+
const defaultBackgroundColor = typeof backgroundColorOverride === 'string'
|
|
29
|
+
? (0, color_1.parseColor)(context, backgroundColorOverride)
|
|
30
|
+
: backgroundColorOverride === null
|
|
31
|
+
? color_1.COLORS.TRANSPARENT
|
|
32
|
+
: 0xffffffff;
|
|
33
|
+
return element === ownerDocument.documentElement
|
|
34
|
+
? (0, color_utilities_1.isTransparent)(documentBackgroundColor)
|
|
35
|
+
? (0, color_utilities_1.isTransparent)(bodyBackgroundColor)
|
|
36
|
+
? defaultBackgroundColor
|
|
37
|
+
: bodyBackgroundColor
|
|
38
|
+
: documentBackgroundColor
|
|
39
|
+
: defaultBackgroundColor;
|
|
40
|
+
};
|
|
41
|
+
exports.parseBackgroundColor = parseBackgroundColor;
|
|
@@ -8,6 +8,10 @@ class Cache {
|
|
|
8
8
|
this._options = _options;
|
|
9
9
|
this._cache = new Map();
|
|
10
10
|
this._pendingOperations = new Map();
|
|
11
|
+
/** When true, addImage() collects URLs without starting loads. */
|
|
12
|
+
this._deferMode = false;
|
|
13
|
+
/** URLs collected during defer mode, pending batch preload. */
|
|
14
|
+
this._collectedUrls = new Set();
|
|
11
15
|
// Default cache size: 100 items
|
|
12
16
|
this.maxSize = _options.maxCacheSize ?? 100;
|
|
13
17
|
if (this.maxSize < 1) {
|
|
@@ -18,7 +22,48 @@ class Cache {
|
|
|
18
22
|
`Consider using a smaller value (recommended: 100-1000).`);
|
|
19
23
|
}
|
|
20
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Enter defer mode: subsequent addImage() calls only collect URLs.
|
|
27
|
+
* Call preloadAll() to exit defer mode and batch-load all URLs.
|
|
28
|
+
*/
|
|
29
|
+
startDefer() {
|
|
30
|
+
this._deferMode = true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Exit defer mode and load all collected URLs in parallel, respecting
|
|
34
|
+
* an optional concurrency cap to avoid overwhelming the browser's network
|
|
35
|
+
* stack (default: 10 concurrent loads).
|
|
36
|
+
* After this call returns, all images are either loaded (cache hit) or
|
|
37
|
+
* failed (logged). Subsequent cache.match() calls return immediately.
|
|
38
|
+
*
|
|
39
|
+
* @param concurrency - Max concurrent image loads (1–100, default 10).
|
|
40
|
+
*/
|
|
41
|
+
async preloadAll(concurrency = 10) {
|
|
42
|
+
this._deferMode = false;
|
|
43
|
+
const urls = Array.from(this._collectedUrls);
|
|
44
|
+
this._collectedUrls.clear();
|
|
45
|
+
if (urls.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const limit = Math.max(1, Math.min(concurrency, 100));
|
|
49
|
+
this.context.logger.debug(`Preloading ${urls.length} image(s) with concurrency ${limit}`);
|
|
50
|
+
// Load in batches to respect the concurrency cap while maximising
|
|
51
|
+
// parallelism within each batch.
|
|
52
|
+
for (let i = 0; i < urls.length; i += limit) {
|
|
53
|
+
const batch = urls.slice(i, i + limit);
|
|
54
|
+
const operations = batch.map((url) => this._addImageWithPending(url));
|
|
55
|
+
await Promise.allSettled(operations);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
21
58
|
addImage(src) {
|
|
59
|
+
// ── Defer mode: collect only ──────────────────────────────
|
|
60
|
+
if (this._deferMode) {
|
|
61
|
+
if (!this.has(src) && (isBlobImage(src) || isRenderable(src))) {
|
|
62
|
+
this._collectedUrls.add(src);
|
|
63
|
+
}
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
// ── Normal mode: immediate load ───────────────────────────
|
|
22
67
|
// Wait for any pending operations on this key
|
|
23
68
|
const pending = this._pendingOperations.get(src);
|
|
24
69
|
if (pending) {
|
|
@@ -32,16 +77,27 @@ class Cache {
|
|
|
32
77
|
return Promise.resolve();
|
|
33
78
|
}
|
|
34
79
|
if (isBlobImage(src) || isRenderable(src)) {
|
|
35
|
-
|
|
36
|
-
const operation = this._addImageInternal(src);
|
|
37
|
-
this._pendingOperations.set(src, operation);
|
|
38
|
-
operation.finally(() => {
|
|
39
|
-
this._pendingOperations.delete(src);
|
|
40
|
-
});
|
|
41
|
-
return operation;
|
|
80
|
+
return this._addImageWithPending(src);
|
|
42
81
|
}
|
|
43
82
|
return Promise.resolve();
|
|
44
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Shared helper: enqueues loading for a single URL with pending-operation
|
|
86
|
+
* deduplication and error swallowing. Used by both addImage() (normal mode)
|
|
87
|
+
* and preloadAll() (batch mode).
|
|
88
|
+
*/
|
|
89
|
+
_addImageWithPending(src) {
|
|
90
|
+
const pending = this._pendingOperations.get(src);
|
|
91
|
+
if (pending) {
|
|
92
|
+
return pending;
|
|
93
|
+
}
|
|
94
|
+
const operation = this._addImageInternal(src);
|
|
95
|
+
this._pendingOperations.set(src, operation);
|
|
96
|
+
operation.finally(() => {
|
|
97
|
+
this._pendingOperations.delete(src);
|
|
98
|
+
});
|
|
99
|
+
return operation;
|
|
100
|
+
}
|
|
45
101
|
async _addImageInternal(src) {
|
|
46
102
|
const timeoutMs = this._options.imageTimeout ?? 15000;
|
|
47
103
|
const imageWithTimeout = this.withTimeout(this.loadImage(src), timeoutMs, `Timed out (${timeoutMs}ms) loading image`);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleRenderOptions = exports.assembleCloneOptions = exports.assembleWindowOptions = exports.assembleContextOptions = exports.assembleResourceOptions = exports.coerceNumberOptions = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
/**
|
|
6
|
+
* Coerce known numeric options from string (or other) values to actual numbers.
|
|
7
|
+
* Mutates the opts object in place — this is intentionally a normalisation
|
|
8
|
+
* (not pure) step that runs once at the beginning of renderElement.
|
|
9
|
+
*/
|
|
10
|
+
const coerceNumberOptions = (opts) => {
|
|
11
|
+
const numKeys = [
|
|
12
|
+
'scale',
|
|
13
|
+
'width',
|
|
14
|
+
'height',
|
|
15
|
+
'imageTimeout',
|
|
16
|
+
'x',
|
|
17
|
+
'y',
|
|
18
|
+
'windowWidth',
|
|
19
|
+
'windowHeight',
|
|
20
|
+
'scrollX',
|
|
21
|
+
'scrollY'
|
|
22
|
+
];
|
|
23
|
+
numKeys.forEach((key) => {
|
|
24
|
+
const v = opts[key];
|
|
25
|
+
if (v !== undefined && v !== null && typeof v !== 'number') {
|
|
26
|
+
const n = Number(v);
|
|
27
|
+
if (!Number.isNaN(n)) {
|
|
28
|
+
opts[key] = n;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
exports.coerceNumberOptions = coerceNumberOptions;
|
|
34
|
+
/** Assemble resource loading options from user-provided Options. */
|
|
35
|
+
const assembleResourceOptions = (opts) => ({
|
|
36
|
+
allowTaint: opts.allowTaint ?? false,
|
|
37
|
+
imageTimeout: opts.imageTimeout ?? constants_1.DEFAULT_IMAGE_TIMEOUT_MS,
|
|
38
|
+
proxy: opts.proxy,
|
|
39
|
+
useCORS: opts.useCORS ?? false,
|
|
40
|
+
customIsSameOrigin: opts.customIsSameOrigin,
|
|
41
|
+
maxCacheSize: opts.maxCacheSize
|
|
42
|
+
});
|
|
43
|
+
exports.assembleResourceOptions = assembleResourceOptions;
|
|
44
|
+
/** Assemble context (logging + cache) options, extending resource options. */
|
|
45
|
+
const assembleContextOptions = (opts, config, resourceOptions) => ({
|
|
46
|
+
logging: opts.logging ?? true,
|
|
47
|
+
cache: opts.cache ?? config.cache,
|
|
48
|
+
...resourceOptions
|
|
49
|
+
});
|
|
50
|
+
exports.assembleContextOptions = assembleContextOptions;
|
|
51
|
+
const DEFAULT_WINDOW_WIDTH = 800;
|
|
52
|
+
const DEFAULT_WINDOW_HEIGHT = 600;
|
|
53
|
+
const DEFAULT_SCROLL = 0;
|
|
54
|
+
const assembleWindowOptions = (opts, defaultView) => {
|
|
55
|
+
const win = defaultView;
|
|
56
|
+
return {
|
|
57
|
+
windowWidth: opts.windowWidth ?? win.innerWidth ?? DEFAULT_WINDOW_WIDTH,
|
|
58
|
+
windowHeight: opts.windowHeight ?? win.innerHeight ?? DEFAULT_WINDOW_HEIGHT,
|
|
59
|
+
scrollX: opts.scrollX ?? win.pageXOffset ?? DEFAULT_SCROLL,
|
|
60
|
+
scrollY: opts.scrollY ?? win.pageYOffset ?? DEFAULT_SCROLL
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
exports.assembleWindowOptions = assembleWindowOptions;
|
|
64
|
+
/** Assemble DOM cloning options. */
|
|
65
|
+
const assembleCloneOptions = (opts, config, foreignObjectRendering) => ({
|
|
66
|
+
allowTaint: opts.allowTaint ?? false,
|
|
67
|
+
onclone: opts.onclone,
|
|
68
|
+
ignoreElements: opts.ignoreElements,
|
|
69
|
+
iframeContainer: opts.iframeContainer,
|
|
70
|
+
inlineImages: foreignObjectRendering,
|
|
71
|
+
copyStyles: foreignObjectRendering,
|
|
72
|
+
cspNonce: opts.cspNonce ?? config.cspNonce
|
|
73
|
+
});
|
|
74
|
+
exports.assembleCloneOptions = assembleCloneOptions;
|
|
75
|
+
/** Assemble canvas rendering options. */
|
|
76
|
+
const assembleRenderOptions = (opts, backgroundColor, left, top, width, height, devicePixelRatio) => ({
|
|
77
|
+
canvas: opts.canvas,
|
|
78
|
+
backgroundColor,
|
|
79
|
+
signal: opts.signal,
|
|
80
|
+
scale: opts.scale ?? devicePixelRatio ?? 1,
|
|
81
|
+
x: (opts.x ?? 0) + left,
|
|
82
|
+
y: (opts.y ?? 0) + top,
|
|
83
|
+
width: opts.width ?? Math.ceil(width),
|
|
84
|
+
height: opts.height ?? Math.ceil(height),
|
|
85
|
+
imageSmoothing: opts.imageSmoothing,
|
|
86
|
+
imageSmoothingQuality: opts.imageSmoothingQuality
|
|
87
|
+
});
|
|
88
|
+
exports.assembleRenderOptions = assembleRenderOptions;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LRUMap = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Generic LRU (Least-Recently-Used) wrapper over a native `Map`.
|
|
6
|
+
*
|
|
7
|
+
* Both the CSS parse cache and the background pattern cache implement the
|
|
8
|
+
* same LRU eviction pattern using Map's insertion-order guarantee. This
|
|
9
|
+
* tiny utility centralises that logic so it can be reused without
|
|
10
|
+
* duplication.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const cache = new LRUMap<string, CanvasPattern>(50);
|
|
15
|
+
* const entry = cache.get(key); // promotes if found, returns undefined otherwise
|
|
16
|
+
* cache.set(key, value); // evicts oldest entry when at capacity
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
class LRUMap {
|
|
20
|
+
constructor(maxSize) {
|
|
21
|
+
this.maxSize = maxSize;
|
|
22
|
+
this._map = new Map();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get a value by key.
|
|
26
|
+
* On cache hit the entry is promoted to the end of the Map (most-recently-used).
|
|
27
|
+
* Returns `undefined` on miss.
|
|
28
|
+
*/
|
|
29
|
+
get(key) {
|
|
30
|
+
if (!this._map.has(key)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
// Delete-then-set promotes the entry to MRU position.
|
|
34
|
+
const value = this._map.get(key);
|
|
35
|
+
this._map.delete(key);
|
|
36
|
+
this._map.set(key, value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Insert or update a key-value pair.
|
|
41
|
+
* If the key already exists it is moved to MRU position. If the map is
|
|
42
|
+
* at capacity the least-recently-used entry (oldest insertion order) is
|
|
43
|
+
* evicted before inserting.
|
|
44
|
+
*/
|
|
45
|
+
set(key, value) {
|
|
46
|
+
if (this._map.has(key)) {
|
|
47
|
+
this._map.delete(key);
|
|
48
|
+
}
|
|
49
|
+
else if (this._map.size >= this.maxSize) {
|
|
50
|
+
const oldestKey = this._map.keys().next().value;
|
|
51
|
+
if (oldestKey !== undefined) {
|
|
52
|
+
this._map.delete(oldestKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this._map.set(key, value);
|
|
56
|
+
}
|
|
57
|
+
/** Current number of entries. */
|
|
58
|
+
get size() {
|
|
59
|
+
return this._map.size;
|
|
60
|
+
}
|
|
61
|
+
/** Remove all entries. */
|
|
62
|
+
clear() {
|
|
63
|
+
this._map.clear();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.LRUMap = LRUMap;
|
|
@@ -3,39 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.renderElement = void 0;
|
|
4
4
|
const bounds_1 = require("../css/layout/bounds");
|
|
5
5
|
const color_1 = require("../css/types/color");
|
|
6
|
-
const color_utilities_1 = require("../css/types/color-utilities");
|
|
7
6
|
const document_cloner_1 = require("../dom/document-cloner");
|
|
7
|
+
const node_type_guards_1 = require("../dom/node-type-guards");
|
|
8
8
|
const node_parser_1 = require("../dom/node-parser");
|
|
9
9
|
const canvas_renderer_1 = require("../render/canvas/canvas-renderer");
|
|
10
10
|
const foreignobject_renderer_1 = require("../render/canvas/foreignobject-renderer");
|
|
11
11
|
const context_1 = require("./context");
|
|
12
12
|
const validator_1 = require("./validator");
|
|
13
13
|
const performance_monitor_1 = require("./performance-monitor");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'width',
|
|
18
|
-
'height',
|
|
19
|
-
'imageTimeout',
|
|
20
|
-
'x',
|
|
21
|
-
'y',
|
|
22
|
-
'windowWidth',
|
|
23
|
-
'windowHeight',
|
|
24
|
-
'scrollX',
|
|
25
|
-
'scrollY'
|
|
26
|
-
];
|
|
27
|
-
numKeys.forEach((key) => {
|
|
28
|
-
const v = opts[key];
|
|
29
|
-
if (v !== undefined && v !== null && typeof v !== 'number') {
|
|
30
|
-
const n = Number(v);
|
|
31
|
-
if (!Number.isNaN(n)) {
|
|
32
|
-
opts[key] = n;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
};
|
|
14
|
+
const abort_helper_1 = require("./abort-helper");
|
|
15
|
+
const config_assembler_1 = require("./config-assembler");
|
|
16
|
+
const background_parser_1 = require("./background-parser");
|
|
37
17
|
const renderElement = async (element, opts, config) => {
|
|
38
|
-
coerceNumberOptions(opts);
|
|
18
|
+
(0, config_assembler_1.coerceNumberOptions)(opts);
|
|
39
19
|
// Input validation (unless explicitly skipped)
|
|
40
20
|
if (!opts.skipValidation) {
|
|
41
21
|
const validator = opts.validator || (0, validator_1.createDefaultValidator)();
|
|
@@ -59,29 +39,9 @@ const renderElement = async (element, opts, config) => {
|
|
|
59
39
|
if (!defaultView) {
|
|
60
40
|
throw new Error(`Document is not attached to a Window`);
|
|
61
41
|
}
|
|
62
|
-
const resourceOptions =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
proxy: opts.proxy,
|
|
66
|
-
useCORS: opts.useCORS ?? false,
|
|
67
|
-
customIsSameOrigin: opts.customIsSameOrigin,
|
|
68
|
-
maxCacheSize: opts.maxCacheSize
|
|
69
|
-
};
|
|
70
|
-
const contextOptions = {
|
|
71
|
-
logging: opts.logging ?? true,
|
|
72
|
-
cache: opts.cache ?? config.cache,
|
|
73
|
-
...resourceOptions
|
|
74
|
-
};
|
|
75
|
-
const DEFAULT_WINDOW_WIDTH = 800;
|
|
76
|
-
const DEFAULT_WINDOW_HEIGHT = 600;
|
|
77
|
-
const DEFAULT_SCROLL = 0;
|
|
78
|
-
const win = defaultView;
|
|
79
|
-
const windowOptions = {
|
|
80
|
-
windowWidth: opts.windowWidth ?? win.innerWidth ?? DEFAULT_WINDOW_WIDTH,
|
|
81
|
-
windowHeight: opts.windowHeight ?? win.innerHeight ?? DEFAULT_WINDOW_HEIGHT,
|
|
82
|
-
scrollX: opts.scrollX ?? win.pageXOffset ?? DEFAULT_SCROLL,
|
|
83
|
-
scrollY: opts.scrollY ?? win.pageYOffset ?? DEFAULT_SCROLL
|
|
84
|
-
};
|
|
42
|
+
const resourceOptions = (0, config_assembler_1.assembleResourceOptions)(opts);
|
|
43
|
+
const contextOptions = (0, config_assembler_1.assembleContextOptions)(opts, config, resourceOptions);
|
|
44
|
+
const windowOptions = (0, config_assembler_1.assembleWindowOptions)(opts, defaultView);
|
|
85
45
|
const windowBounds = new bounds_1.Bounds(windowOptions.scrollX, windowOptions.scrollY, windowOptions.windowWidth, windowOptions.windowHeight);
|
|
86
46
|
const context = new context_1.Context(contextOptions, windowBounds, config);
|
|
87
47
|
const performanceMonitoring = opts.enablePerformanceMonitoring ?? opts.logging ?? false;
|
|
@@ -91,19 +51,9 @@ const renderElement = async (element, opts, config) => {
|
|
|
91
51
|
height: windowOptions.windowHeight
|
|
92
52
|
});
|
|
93
53
|
const signal = opts.signal;
|
|
94
|
-
|
|
95
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
96
|
-
}
|
|
54
|
+
(0, abort_helper_1.throwIfAborted)(signal);
|
|
97
55
|
const foreignObjectRendering = opts.foreignObjectRendering ?? false;
|
|
98
|
-
const cloneOptions =
|
|
99
|
-
allowTaint: opts.allowTaint ?? false,
|
|
100
|
-
onclone: opts.onclone,
|
|
101
|
-
ignoreElements: opts.ignoreElements,
|
|
102
|
-
iframeContainer: opts.iframeContainer,
|
|
103
|
-
inlineImages: foreignObjectRendering,
|
|
104
|
-
copyStyles: foreignObjectRendering,
|
|
105
|
-
cspNonce: opts.cspNonce ?? config.cspNonce
|
|
106
|
-
};
|
|
56
|
+
const cloneOptions = (0, config_assembler_1.assembleCloneOptions)(opts, config, foreignObjectRendering);
|
|
107
57
|
context.logger.debug(`Starting document clone with size ${windowBounds.width}x${windowBounds.height} scrolled to ${-windowBounds.left},${-windowBounds.top}`);
|
|
108
58
|
perfMonitor.start('clone');
|
|
109
59
|
const documentCloner = new document_cloner_1.DocumentCloner(context, element, cloneOptions);
|
|
@@ -117,24 +67,13 @@ const renderElement = async (element, opts, config) => {
|
|
|
117
67
|
if (opts.removeContainer ?? true) {
|
|
118
68
|
document_cloner_1.DocumentCloner.destroy(container);
|
|
119
69
|
}
|
|
120
|
-
|
|
70
|
+
(0, abort_helper_1.throwIfAborted)(signal);
|
|
121
71
|
}
|
|
122
|
-
const { width, height, left, top } = (0,
|
|
72
|
+
const { width, height, left, top } = (0, node_type_guards_1.isBodyElement)(clonedElement) || (0, node_type_guards_1.isHTMLElement)(clonedElement)
|
|
123
73
|
? (0, bounds_1.parseDocumentSize)(clonedElement.ownerDocument)
|
|
124
74
|
: (0, bounds_1.parseBounds)(context, clonedElement);
|
|
125
|
-
const backgroundColor = parseBackgroundColor(context, clonedElement, opts.backgroundColor);
|
|
126
|
-
const renderOptions =
|
|
127
|
-
canvas: opts.canvas,
|
|
128
|
-
backgroundColor,
|
|
129
|
-
signal,
|
|
130
|
-
scale: opts.scale ?? defaultView.devicePixelRatio ?? 1,
|
|
131
|
-
x: (opts.x ?? 0) + left,
|
|
132
|
-
y: (opts.y ?? 0) + top,
|
|
133
|
-
width: opts.width ?? Math.ceil(width),
|
|
134
|
-
height: opts.height ?? Math.ceil(height),
|
|
135
|
-
imageSmoothing: opts.imageSmoothing,
|
|
136
|
-
imageSmoothingQuality: opts.imageSmoothingQuality
|
|
137
|
-
};
|
|
75
|
+
const backgroundColor = (0, background_parser_1.parseBackgroundColor)(context, clonedElement, opts.backgroundColor);
|
|
76
|
+
const renderOptions = (0, config_assembler_1.assembleRenderOptions)(opts, backgroundColor, left, top, width, height, defaultView.devicePixelRatio ?? 1);
|
|
138
77
|
let canvas;
|
|
139
78
|
let root;
|
|
140
79
|
try {
|
|
@@ -149,19 +88,23 @@ const renderElement = async (element, opts, config) => {
|
|
|
149
88
|
context.logger.debug(`Document cloned, element located at ${left},${top} with size ${width}x${height} using computed rendering`);
|
|
150
89
|
context.logger.debug(`Starting DOM parsing`);
|
|
151
90
|
perfMonitor.start('parse');
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
91
|
+
(0, abort_helper_1.throwIfAborted)(signal);
|
|
92
|
+
// Enter defer mode: collect all image URLs during parse without
|
|
93
|
+
// starting individual loads. This avoids serialised loading in
|
|
94
|
+
// DOM-traversal order.
|
|
95
|
+
context.cache.startDefer();
|
|
155
96
|
root = (0, node_parser_1.parseTree)(context, clonedElement);
|
|
156
97
|
perfMonitor.end('parse');
|
|
98
|
+
// Batch-preload all collected images in parallel before rendering.
|
|
99
|
+
perfMonitor.start('preload');
|
|
100
|
+
await context.cache.preloadAll();
|
|
101
|
+
perfMonitor.end('preload');
|
|
157
102
|
if (backgroundColor === root.styles.backgroundColor) {
|
|
158
103
|
root.styles.backgroundColor = color_1.COLORS.TRANSPARENT;
|
|
159
104
|
}
|
|
160
105
|
context.logger.debug(`Starting renderer for element at ${renderOptions.x},${renderOptions.y} with size ${renderOptions.width}x${renderOptions.height}`);
|
|
161
106
|
perfMonitor.start('render');
|
|
162
|
-
|
|
163
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
164
|
-
}
|
|
107
|
+
(0, abort_helper_1.throwIfAborted)(signal);
|
|
165
108
|
const renderer = new canvas_renderer_1.CanvasRenderer(context, renderOptions);
|
|
166
109
|
canvas = await renderer.render(root);
|
|
167
110
|
perfMonitor.end('render');
|
|
@@ -187,25 +130,3 @@ const renderElement = async (element, opts, config) => {
|
|
|
187
130
|
}
|
|
188
131
|
};
|
|
189
132
|
exports.renderElement = renderElement;
|
|
190
|
-
const parseBackgroundColor = (context, element, backgroundColorOverride) => {
|
|
191
|
-
const ownerDocument = element.ownerDocument;
|
|
192
|
-
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
|
193
|
-
const documentBackgroundColor = ownerDocument.documentElement
|
|
194
|
-
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.documentElement).backgroundColor)
|
|
195
|
-
: color_1.COLORS.TRANSPARENT;
|
|
196
|
-
const bodyBackgroundColor = ownerDocument.body
|
|
197
|
-
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.body).backgroundColor)
|
|
198
|
-
: color_1.COLORS.TRANSPARENT;
|
|
199
|
-
const defaultBackgroundColor = typeof backgroundColorOverride === 'string'
|
|
200
|
-
? (0, color_1.parseColor)(context, backgroundColorOverride)
|
|
201
|
-
: backgroundColorOverride === null
|
|
202
|
-
? color_1.COLORS.TRANSPARENT
|
|
203
|
-
: 0xffffffff;
|
|
204
|
-
return element === ownerDocument.documentElement
|
|
205
|
-
? (0, color_utilities_1.isTransparent)(documentBackgroundColor)
|
|
206
|
-
? (0, color_utilities_1.isTransparent)(bodyBackgroundColor)
|
|
207
|
-
? defaultBackgroundColor
|
|
208
|
-
: bodyBackgroundColor
|
|
209
|
-
: documentBackgroundColor
|
|
210
|
-
: defaultBackgroundColor;
|
|
211
|
-
};
|
|
@@ -12,17 +12,7 @@ exports.backgroundPosition = {
|
|
|
12
12
|
return (0, parser_1.parseFunctionArgs)(tokens)
|
|
13
13
|
.map((values) => {
|
|
14
14
|
// Convert calc() to length-percentage tokens, keep other length-percentage as is
|
|
15
|
-
return values
|
|
16
|
-
.map((value) => {
|
|
17
|
-
if ((0, length_percentage_1.isCalcFunction)(value)) {
|
|
18
|
-
// For calc() at parse time, we can't know the container size
|
|
19
|
-
// So we evaluate with 0 context which will work for px-only calc()
|
|
20
|
-
// Percentage-based calc() will need special handling
|
|
21
|
-
return (0, length_percentage_1.evaluateCalcToLengthPercentage)(value, 0);
|
|
22
|
-
}
|
|
23
|
-
return (0, length_percentage_1.isLengthPercentage)(value) ? value : null;
|
|
24
|
-
})
|
|
25
|
-
.filter((v) => v !== null);
|
|
15
|
+
return values.map(length_percentage_1.parseOptionalCalcOrLength).filter((v) => v !== null);
|
|
26
16
|
})
|
|
27
17
|
.map(length_percentage_1.parseLengthPercentageTuple);
|
|
28
18
|
}
|
|
@@ -15,7 +15,9 @@ exports.backgroundSize = {
|
|
|
15
15
|
prefix: false,
|
|
16
16
|
type: 1 /* PropertyDescriptorParsingType.LIST */,
|
|
17
17
|
parse: (_context, tokens) => {
|
|
18
|
-
return (0, parser_1.parseFunctionArgs)(tokens).map((values) => values
|
|
18
|
+
return (0, parser_1.parseFunctionArgs)(tokens).map((values) => values
|
|
19
|
+
.map((value) => (isBackgroundSizeInfoToken(value) ? value : (0, length_percentage_1.parseOptionalCalcOrLength)(value)))
|
|
20
|
+
.filter((v) => v !== null));
|
|
19
21
|
}
|
|
20
22
|
};
|
|
21
23
|
const isBackgroundSizeInfoToken = (value) => (0, parser_1.isIdentToken)(value) || (0, length_percentage_1.isLengthPercentage)(value);
|