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