html2canvas-pro 2.5.2 → 2.5.3
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/README.md +2 -2
- package/dist/html2canvas-pro.cjs +66 -6
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +66 -6
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +66 -6
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +6 -6
- package/dist/types/dom/iframe-mount.d.ts +19 -0
- package/package.json +18 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.5.
|
|
2
|
+
* html2canvas-pro 2.5.3 <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
|
*/
|
|
@@ -8033,6 +8033,54 @@ const imageReady = (img) => {
|
|
|
8033
8033
|
const imagesReady = (document) => {
|
|
8034
8034
|
return Promise.all([].slice.call(document.images, 0).map(imageReady));
|
|
8035
8035
|
};
|
|
8036
|
+
/**
|
|
8037
|
+
* Wait until every external stylesheet link of the clone has been applied or
|
|
8038
|
+
* definitively failed, or the readiness budget expires.
|
|
8039
|
+
*
|
|
8040
|
+
* The clone is mounted by adoptNode() into an already-closed document
|
|
8041
|
+
* (close() must precede adoptNode for Chrome uppercase class-name matching,
|
|
8042
|
+
* see the mount sequence below), so the cloned <link rel="stylesheet">
|
|
8043
|
+
* elements are inserted after the document finished loading: they neither
|
|
8044
|
+
* delay the iframe load event nor change readyState, and the iframeLoader
|
|
8045
|
+
* readiness poll cannot observe them. On a cold cache the first capture
|
|
8046
|
+
* could then measure the clone before its external CSS arrived and render
|
|
8047
|
+
* an unstyled layout (issue #245). Inline <style> clones apply
|
|
8048
|
+
* synchronously and document-level adopted stylesheets are serialised at
|
|
8049
|
+
* clone time, so neither needs this wait.
|
|
8050
|
+
* @internal – exported for testing only.
|
|
8051
|
+
*/
|
|
8052
|
+
const stylesheetsReady = (documentClone, logger, signal) => {
|
|
8053
|
+
if (signal?.aborted) return Promise.resolve();
|
|
8054
|
+
const pending = [];
|
|
8055
|
+
const links = documentClone.getElementsByTagName("link");
|
|
8056
|
+
for (let i = 0; i < links.length; i++) {
|
|
8057
|
+
const link = links[i];
|
|
8058
|
+
if (!link || link.sheet) continue;
|
|
8059
|
+
const rel = (link.getAttribute("rel") ?? "").trim().toLowerCase().split(/\s+/);
|
|
8060
|
+
if (!rel.includes("stylesheet") || rel.includes("alternate") || link.disabled || !link.getAttribute("href")) continue;
|
|
8061
|
+
pending.push(new Promise((resolve) => {
|
|
8062
|
+
let timer;
|
|
8063
|
+
const settle = () => {
|
|
8064
|
+
link.removeEventListener("load", settle);
|
|
8065
|
+
link.removeEventListener("error", settle);
|
|
8066
|
+
signal?.removeEventListener("abort", onAbort);
|
|
8067
|
+
clearTimeout(timer);
|
|
8068
|
+
resolve();
|
|
8069
|
+
};
|
|
8070
|
+
const onAbort = () => settle();
|
|
8071
|
+
timer = setTimeout(() => {
|
|
8072
|
+
logger?.warn(`Stylesheet ${link.href} did not finish loading within ${CLONE_READY_TIMEOUT_MS / 1e3}s; continuing with a possibly unstyled clone`);
|
|
8073
|
+
settle();
|
|
8074
|
+
}, CLONE_READY_TIMEOUT_MS);
|
|
8075
|
+
link.addEventListener("load", settle);
|
|
8076
|
+
link.addEventListener("error", settle);
|
|
8077
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8078
|
+
if (link.sheet) settle();
|
|
8079
|
+
}));
|
|
8080
|
+
}
|
|
8081
|
+
if (pending.length === 0) return Promise.resolve();
|
|
8082
|
+
return Promise.all(pending).then(() => void 0);
|
|
8083
|
+
};
|
|
8036
8084
|
const iframeLoader = (iframe, logger, signal) => {
|
|
8037
8085
|
return new Promise((resolve, reject) => {
|
|
8038
8086
|
const cloneWindow = iframe.contentWindow;
|
|
@@ -8075,6 +8123,15 @@ const iframeLoader = (iframe, logger, signal) => {
|
|
|
8075
8123
|
});
|
|
8076
8124
|
};
|
|
8077
8125
|
/**
|
|
8126
|
+
* Reduce a doctype component to characters that are inert inside a
|
|
8127
|
+
* <!DOCTYPE ...> declaration. Doctype content is parsed as a declaration
|
|
8128
|
+
* token, never as HTML markup, but the fields originate from the captured
|
|
8129
|
+
* document — stripping markup delimiters and control characters here makes
|
|
8130
|
+
* that property locally verifiable (and keeps the sanitizer guarantee even
|
|
8131
|
+
* if a future serializer ever embeds the result elsewhere).
|
|
8132
|
+
*/
|
|
8133
|
+
const sanitizeDoctypePart = (value) => value.replace(/[<>&"'`\\]|[\u0000-\u001f\u007f]/g, "");
|
|
8134
|
+
/**
|
|
8078
8135
|
* Serialise a document type declaration to an HTML string.
|
|
8079
8136
|
* @internal – exported for testing only, not part of the public API.
|
|
8080
8137
|
*/
|
|
@@ -8082,12 +8139,12 @@ const serializeDoctype = (doctype) => {
|
|
|
8082
8139
|
let str = "";
|
|
8083
8140
|
if (doctype) {
|
|
8084
8141
|
str += "<!DOCTYPE ";
|
|
8085
|
-
if (doctype.name) str += doctype.name;
|
|
8086
|
-
if (doctype.internalSubset) str += " " + doctype.internalSubset
|
|
8142
|
+
if (doctype.name) str += sanitizeDoctypePart(doctype.name);
|
|
8143
|
+
if (doctype.internalSubset) str += " " + sanitizeDoctypePart(doctype.internalSubset);
|
|
8087
8144
|
if (doctype.publicId) {
|
|
8088
|
-
str += " PUBLIC \"" + doctype.publicId
|
|
8089
|
-
if (doctype.systemId) str += " \"" + doctype.systemId
|
|
8090
|
-
} else if (doctype.systemId) str += " SYSTEM \"" + doctype.systemId
|
|
8145
|
+
str += " PUBLIC \"" + sanitizeDoctypePart(doctype.publicId) + "\"";
|
|
8146
|
+
if (doctype.systemId) str += " \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
|
|
8147
|
+
} else if (doctype.systemId) str += " SYSTEM \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
|
|
8091
8148
|
str += ">";
|
|
8092
8149
|
}
|
|
8093
8150
|
return str;
|
|
@@ -8123,6 +8180,8 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
|
|
|
8123
8180
|
const referenceElement = mount.clonedReferenceElement;
|
|
8124
8181
|
if (typeof referenceElement === "undefined") throw new Error(`Error finding the ${mount.referenceElementName} in the cloned document`);
|
|
8125
8182
|
throwIfAborted(mount.signal);
|
|
8183
|
+
await stylesheetWait;
|
|
8184
|
+
throwIfAborted(mount.signal);
|
|
8126
8185
|
if (documentClone.fonts && documentClone.fonts.ready) await documentClone.fonts.ready;
|
|
8127
8186
|
throwIfAborted(mount.signal);
|
|
8128
8187
|
if (/(AppleWebKit)/g.test(navigator.userAgent)) {
|
|
@@ -8173,6 +8232,7 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
|
|
|
8173
8232
|
const adoptedNode = documentClone.adoptNode(mount.documentElement);
|
|
8174
8233
|
addBase(adoptedNode, baseUri);
|
|
8175
8234
|
documentClone.replaceChild(adoptedNode, documentClone.documentElement);
|
|
8235
|
+
const stylesheetWait = stylesheetsReady(documentClone, context.logger, mount.signal);
|
|
8176
8236
|
return iframeLoad;
|
|
8177
8237
|
};
|
|
8178
8238
|
//#endregion
|