html2canvas-pro 2.5.3 → 2.5.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/dist/html2canvas-pro.cjs +122 -32
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +122 -32
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +122 -32
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +5 -5
- package/dist/types/core/constants.d.ts +11 -0
- package/dist/types/dom/iframe-mount.d.ts +7 -1
- package/package.json +1 -1
package/dist/html2canvas-pro.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.5.
|
|
2
|
+
* html2canvas-pro 2.5.4 <https://yorickshan.github.io/html2canvas-pro/>
|
|
3
3
|
* Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
|
|
4
4
|
* Released under MIT License
|
|
5
5
|
*/
|
|
@@ -6525,6 +6525,15 @@ const SHADOW_MASK_OFFSET = 1e4;
|
|
|
6525
6525
|
*/
|
|
6526
6526
|
const CLONE_READY_TIMEOUT_MS = 3e4;
|
|
6527
6527
|
/**
|
|
6528
|
+
* Total time (ms) to wait for the clone's external stylesheets (link and
|
|
6529
|
+
* @import chains) to apply. Shorter than the document readiness window: a
|
|
6530
|
+
* stylesheet that needs longer usually means a hung CDN, and rendering with
|
|
6531
|
+
* a 10s-old layout beats blocking the capture for 30s. A missed deadline
|
|
6532
|
+
* resolves with a warning (the capture continues with a possibly unstyled
|
|
6533
|
+
* clone).
|
|
6534
|
+
*/
|
|
6535
|
+
const STYLESHEET_READY_TIMEOUT_MS = 1e4;
|
|
6536
|
+
/**
|
|
6528
6537
|
* Hard ceiling (ms) for the cloned iframe's load event itself. If the event
|
|
6529
6538
|
* never fires, the mount is rejected instead of hanging forever.
|
|
6530
6539
|
*/
|
|
@@ -8038,7 +8047,49 @@ const imagesReady = (document) => {
|
|
|
8038
8047
|
return Promise.all([].slice.call(document.images, 0).map(imageReady));
|
|
8039
8048
|
};
|
|
8040
8049
|
/**
|
|
8041
|
-
*
|
|
8050
|
+
* Whether every CSS rule of the sheet (and of the @import chains it pulls
|
|
8051
|
+
* in) has been applied. A sheet is "attached" as soon as its rules are
|
|
8052
|
+
* readable; an external @import inside it may still be loading, which shows
|
|
8053
|
+
* up as an inaccessible CSSImportRule.sheet. `cssRules` can throw for
|
|
8054
|
+
* cross-origin sheets without a CORS-approved response — those rules cannot
|
|
8055
|
+
* be inspected, so the sheet counts as ready rather than blocking the render
|
|
8056
|
+
* forever.
|
|
8057
|
+
*/
|
|
8058
|
+
const isSheetFullyApplied = (sheet, depth = 0) => {
|
|
8059
|
+
if (!sheet || depth > MAX_IMPORT_DEPTH) return true;
|
|
8060
|
+
let rules;
|
|
8061
|
+
try {
|
|
8062
|
+
rules = sheet.cssRules;
|
|
8063
|
+
} catch (_e) {
|
|
8064
|
+
return true;
|
|
8065
|
+
}
|
|
8066
|
+
if (!rules) return true;
|
|
8067
|
+
for (let i = 0; i < rules.length; i++) {
|
|
8068
|
+
const rule = rules[i];
|
|
8069
|
+
if (rule && typeof CSSImportRule !== "undefined" && rule instanceof CSSImportRule) {
|
|
8070
|
+
if (!rule.styleSheet || !isSheetFullyApplied(rule.styleSheet, depth + 1)) return false;
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
return true;
|
|
8074
|
+
};
|
|
8075
|
+
const MAX_IMPORT_DEPTH = 32;
|
|
8076
|
+
/** Collect every <link> in the document, including links inside shadow roots. */
|
|
8077
|
+
const collectLinks = (root, out) => {
|
|
8078
|
+
const links = root.querySelectorAll("link");
|
|
8079
|
+
for (let i = 0; i < links.length; i++) {
|
|
8080
|
+
const link = links[i];
|
|
8081
|
+
if (link) out.push(link);
|
|
8082
|
+
}
|
|
8083
|
+
const walker = (root.ownerDocument ?? root).createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
8084
|
+
let current = walker.nextNode();
|
|
8085
|
+
while (current) {
|
|
8086
|
+
const shadowRoot = current.shadowRoot;
|
|
8087
|
+
if (shadowRoot) collectLinks(shadowRoot, out);
|
|
8088
|
+
current = walker.nextNode();
|
|
8089
|
+
}
|
|
8090
|
+
};
|
|
8091
|
+
/**
|
|
8092
|
+
* Wait until every external stylesheet of the clone has been applied or
|
|
8042
8093
|
* definitively failed, or the readiness budget expires.
|
|
8043
8094
|
*
|
|
8044
8095
|
* The clone is mounted by adoptNode() into an already-closed document
|
|
@@ -8051,35 +8102,68 @@ const imagesReady = (document) => {
|
|
|
8051
8102
|
* an unstyled layout (issue #245). Inline <style> clones apply
|
|
8052
8103
|
* synchronously and document-level adopted stylesheets are serialised at
|
|
8053
8104
|
* clone time, so neither needs this wait.
|
|
8105
|
+
*
|
|
8106
|
+
* Readiness is polled rather than purely event-driven because a <style>
|
|
8107
|
+
* clone serialised from `createStyleClone` re-fetches its external @import
|
|
8108
|
+
* rules without firing link load/error events, and shadow-root links are
|
|
8109
|
+
* equally event-hostile in some engines. The poll covers both; link events
|
|
8110
|
+
* remain as the settle path for plain <link> fetches.
|
|
8054
8111
|
* @internal – exported for testing only.
|
|
8055
8112
|
*/
|
|
8056
8113
|
const stylesheetsReady = (documentClone, logger, signal) => {
|
|
8057
8114
|
if (signal?.aborted) return Promise.resolve();
|
|
8058
8115
|
const pending = [];
|
|
8059
|
-
const links =
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
if (
|
|
8116
|
+
const links = [];
|
|
8117
|
+
collectLinks(documentClone, links);
|
|
8118
|
+
for (const link of links) {
|
|
8119
|
+
if (link.sheet) continue;
|
|
8063
8120
|
const rel = (link.getAttribute("rel") ?? "").trim().toLowerCase().split(/\s+/);
|
|
8064
8121
|
if (!rel.includes("stylesheet") || rel.includes("alternate") || link.disabled || !link.getAttribute("href")) continue;
|
|
8065
8122
|
pending.push(new Promise((resolve) => {
|
|
8066
|
-
|
|
8123
|
+
const href = link.href;
|
|
8067
8124
|
const settle = () => {
|
|
8068
8125
|
link.removeEventListener("load", settle);
|
|
8069
8126
|
link.removeEventListener("error", settle);
|
|
8070
8127
|
signal?.removeEventListener("abort", onAbort);
|
|
8071
|
-
|
|
8128
|
+
clearInterval(timer);
|
|
8072
8129
|
resolve();
|
|
8073
8130
|
};
|
|
8074
8131
|
const onAbort = () => settle();
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
}
|
|
8132
|
+
if (link.sheet) {
|
|
8133
|
+
resolve();
|
|
8134
|
+
return;
|
|
8135
|
+
}
|
|
8079
8136
|
link.addEventListener("load", settle);
|
|
8080
8137
|
link.addEventListener("error", settle);
|
|
8081
8138
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8082
|
-
|
|
8139
|
+
let elapsed = 0;
|
|
8140
|
+
const timer = setInterval(() => {
|
|
8141
|
+
elapsed += 50;
|
|
8142
|
+
if (link.sheet) settle();
|
|
8143
|
+
else if (elapsed >= 1e4) {
|
|
8144
|
+
logger?.warn(`Stylesheet ${href} did not finish loading within ${STYLESHEET_READY_TIMEOUT_MS / 1e3}s; continuing with a possibly unstyled clone`);
|
|
8145
|
+
settle();
|
|
8146
|
+
}
|
|
8147
|
+
}, 50);
|
|
8148
|
+
}));
|
|
8149
|
+
}
|
|
8150
|
+
const styleElements = documentClone.getElementsByTagName("style");
|
|
8151
|
+
for (let i = 0; i < styleElements.length; i++) {
|
|
8152
|
+
const style = styleElements[i];
|
|
8153
|
+
const sheet = style?.sheet;
|
|
8154
|
+
if (!sheet || !style?.textContent?.includes("@import")) continue;
|
|
8155
|
+
if (isSheetFullyApplied(sheet)) continue;
|
|
8156
|
+
pending.push(new Promise((resolve) => {
|
|
8157
|
+
let elapsed = 0;
|
|
8158
|
+
const timer = setInterval(() => {
|
|
8159
|
+
elapsed += 50;
|
|
8160
|
+
const currentSheet = style.sheet;
|
|
8161
|
+
if (currentSheet && isSheetFullyApplied(currentSheet) || elapsed >= 1e4) {
|
|
8162
|
+
if (elapsed >= 1e4 && !isSheetFullyApplied(currentSheet ?? null)) logger?.warn(`@import chain in an inline <style> did not finish loading within ${STYLESHEET_READY_TIMEOUT_MS / 1e3}s; continuing with a possibly unstyled clone`);
|
|
8163
|
+
clearInterval(timer);
|
|
8164
|
+
resolve();
|
|
8165
|
+
}
|
|
8166
|
+
}, 50);
|
|
8083
8167
|
}));
|
|
8084
8168
|
}
|
|
8085
8169
|
if (pending.length === 0) return Promise.resolve();
|
|
@@ -8237,7 +8321,10 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
|
|
|
8237
8321
|
addBase(adoptedNode, baseUri);
|
|
8238
8322
|
documentClone.replaceChild(adoptedNode, documentClone.documentElement);
|
|
8239
8323
|
const stylesheetWait = stylesheetsReady(documentClone, context.logger, mount.signal);
|
|
8240
|
-
return iframeLoad
|
|
8324
|
+
return iframeLoad.catch((error) => {
|
|
8325
|
+
iframe.parentNode?.removeChild(iframe);
|
|
8326
|
+
throw error;
|
|
8327
|
+
});
|
|
8241
8328
|
};
|
|
8242
8329
|
//#endregion
|
|
8243
8330
|
//#region src/dom/document-cloner.ts
|
|
@@ -13545,14 +13632,19 @@ var ForeignObjectRenderer = class {
|
|
|
13545
13632
|
const svg = createForeignObjectSVG(this.options.width, this.options.height, 0, 0, element);
|
|
13546
13633
|
const imagePromise = loadSerializedSVG(svg);
|
|
13547
13634
|
restoreRootOffsets();
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13635
|
+
try {
|
|
13636
|
+
const img = await imagePromise;
|
|
13637
|
+
if (this.options.signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
13638
|
+
if (this.options.backgroundColor) {
|
|
13639
|
+
this.ctx.fillStyle = asString(this.options.backgroundColor);
|
|
13640
|
+
this.ctx.fillRect(this.options.x, this.options.y, this.options.width, this.options.height);
|
|
13641
|
+
}
|
|
13642
|
+
this.ctx.drawImage(img, this.options.x, this.options.y, this.options.width, this.options.height);
|
|
13643
|
+
return this.canvas;
|
|
13644
|
+
} catch (error) {
|
|
13645
|
+
if (!this.options.canvas) releaseSurface(this.canvas);
|
|
13646
|
+
throw error;
|
|
13553
13647
|
}
|
|
13554
|
-
this.ctx.drawImage(img, this.options.x, this.options.y, this.options.width, this.options.height);
|
|
13555
|
-
return this.canvas;
|
|
13556
13648
|
}
|
|
13557
13649
|
};
|
|
13558
13650
|
/**
|
|
@@ -14108,19 +14200,17 @@ const renderElement = async (element, opts, config) => {
|
|
|
14108
14200
|
const documentCloner = new DocumentCloner(context, element, cloneOptions);
|
|
14109
14201
|
const clonedElement = documentCloner.clonedReferenceElement;
|
|
14110
14202
|
if (!clonedElement) throw new Error("Unable to find element in cloned iframe");
|
|
14111
|
-
|
|
14112
|
-
perfMonitor.end("clone");
|
|
14113
|
-
emitProgress(opts.onProgress, context, "clone", 30);
|
|
14114
|
-
if (signal?.aborted) {
|
|
14115
|
-
if (opts.removeContainer ?? true) DocumentCloner.destroy(container);
|
|
14116
|
-
throwIfAborted(signal);
|
|
14117
|
-
}
|
|
14118
|
-
const { width, height, left, top } = isBodyElement(clonedElement) || isHTMLElement(clonedElement) ? parseDocumentSize(clonedElement.ownerDocument) : parseBounds(context, clonedElement);
|
|
14119
|
-
const backgroundColor = parseBackgroundColor(context, clonedElement, opts.backgroundColor);
|
|
14120
|
-
const renderOptions = assembleRenderOptions(opts, backgroundColor, left, top, width, height, defaultView.devicePixelRatio ?? 1);
|
|
14203
|
+
let container;
|
|
14121
14204
|
let canvas;
|
|
14122
14205
|
let root;
|
|
14123
14206
|
try {
|
|
14207
|
+
container = await documentCloner.toIFrame(ownerDocument, windowBounds);
|
|
14208
|
+
perfMonitor.end("clone");
|
|
14209
|
+
emitProgress(opts.onProgress, context, "clone", 30);
|
|
14210
|
+
throwIfAborted(signal);
|
|
14211
|
+
const { width, height, left, top } = isBodyElement(clonedElement) || isHTMLElement(clonedElement) ? parseDocumentSize(clonedElement.ownerDocument) : parseBounds(context, clonedElement);
|
|
14212
|
+
const backgroundColor = parseBackgroundColor(context, clonedElement, opts.backgroundColor);
|
|
14213
|
+
const renderOptions = assembleRenderOptions(opts, backgroundColor, left, top, width, height, defaultView.devicePixelRatio ?? 1);
|
|
14124
14214
|
if (foreignObjectRendering) {
|
|
14125
14215
|
context.logger.debug(`Document cloned, using foreign object rendering`);
|
|
14126
14216
|
perfMonitor.start("render-foreignobject");
|
|
@@ -14154,7 +14244,7 @@ const renderElement = async (element, opts, config) => {
|
|
|
14154
14244
|
try {
|
|
14155
14245
|
if (root) root.restoreTree();
|
|
14156
14246
|
} finally {
|
|
14157
|
-
if (opts.removeContainer ?? true) DocumentCloner.destroy(container);
|
|
14247
|
+
if (container && (opts.removeContainer ?? true)) DocumentCloner.destroy(container);
|
|
14158
14248
|
perfMonitor.end("cleanup");
|
|
14159
14249
|
}
|
|
14160
14250
|
}
|