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