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