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 CHANGED
@@ -38,7 +38,7 @@ html2canvas-pro is a fork of [niklasvh/html2canvas](https://github.com/niklasvh/
38
38
  - `conic-gradient()`, `repeating-conic-gradient()` and `repeating-radial-gradient()` — complete gradient family
39
39
  - `text-emphasis` (CJK emphasis marks), `-webkit-box-reflect`, `image-set()`, `border-image-outset` / `border-image-width`
40
40
  - `accent-color`, `outline`, `-webkit-text-fill-color`, `isolation`
41
- - `filter` compositing for eligible layers — `blur()` / `drop-shadow()` rendered on a dedicated surface with correct layer `opacity` (see [filter support notes](docs/filter-support.md))
41
+ - `filter` chain compositing for eligible layers — the full standard filter function set rendered on a dedicated surface with correct layer `opacity` (see [filter support notes](docs/filter-support.md))
42
42
  - Faithful `box-shadow` rendering, including inset shadows, blur scaling, and shadows through transformed ancestors
43
43
  - `image-rendering` CSS property plus `imageSmoothing` / `imageSmoothingQuality` options for pixel-perfect output
44
44
  - Border image, counters & quotes, `direction`, `line-height`, `transform-origin`, and more — see the full [feature list](docs/features.md)
@@ -187,7 +187,7 @@ The project uses [pnpm](https://pnpm.io) (the version is pinned via the `package
187
187
  corepack pnpm install
188
188
  corepack pnpm build # tsc + Rolldown bundles (CJS/ESM/UMD)
189
189
  corepack pnpm unittest # Vitest unit tests
190
- corepack pnpm test # lint + unit tests + browser (Karma) tests
190
+ corepack pnpm test # lint + unit tests + browser reftests (Playwright)
191
191
  corepack pnpm docs:dev # VitePress dev server
192
192
  corepack pnpm docs:api # Generate the TypeDoc API reference
193
193
  ```
@@ -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
  */
@@ -8037,6 +8037,54 @@ const imageReady = (img) => {
8037
8037
  const imagesReady = (document) => {
8038
8038
  return Promise.all([].slice.call(document.images, 0).map(imageReady));
8039
8039
  };
8040
+ /**
8041
+ * Wait until every external stylesheet link of the clone has been applied or
8042
+ * definitively failed, or the readiness budget expires.
8043
+ *
8044
+ * The clone is mounted by adoptNode() into an already-closed document
8045
+ * (close() must precede adoptNode for Chrome uppercase class-name matching,
8046
+ * see the mount sequence below), so the cloned <link rel="stylesheet">
8047
+ * elements are inserted after the document finished loading: they neither
8048
+ * delay the iframe load event nor change readyState, and the iframeLoader
8049
+ * readiness poll cannot observe them. On a cold cache the first capture
8050
+ * could then measure the clone before its external CSS arrived and render
8051
+ * an unstyled layout (issue #245). Inline <style> clones apply
8052
+ * synchronously and document-level adopted stylesheets are serialised at
8053
+ * clone time, so neither needs this wait.
8054
+ * @internal – exported for testing only.
8055
+ */
8056
+ const stylesheetsReady = (documentClone, logger, signal) => {
8057
+ if (signal?.aborted) return Promise.resolve();
8058
+ const pending = [];
8059
+ const links = documentClone.getElementsByTagName("link");
8060
+ for (let i = 0; i < links.length; i++) {
8061
+ const link = links[i];
8062
+ if (!link || link.sheet) continue;
8063
+ const rel = (link.getAttribute("rel") ?? "").trim().toLowerCase().split(/\s+/);
8064
+ if (!rel.includes("stylesheet") || rel.includes("alternate") || link.disabled || !link.getAttribute("href")) continue;
8065
+ pending.push(new Promise((resolve) => {
8066
+ let timer;
8067
+ const settle = () => {
8068
+ link.removeEventListener("load", settle);
8069
+ link.removeEventListener("error", settle);
8070
+ signal?.removeEventListener("abort", onAbort);
8071
+ clearTimeout(timer);
8072
+ resolve();
8073
+ };
8074
+ const onAbort = () => settle();
8075
+ timer = setTimeout(() => {
8076
+ logger?.warn(`Stylesheet ${link.href} did not finish loading within ${CLONE_READY_TIMEOUT_MS / 1e3}s; continuing with a possibly unstyled clone`);
8077
+ settle();
8078
+ }, CLONE_READY_TIMEOUT_MS);
8079
+ link.addEventListener("load", settle);
8080
+ link.addEventListener("error", settle);
8081
+ signal?.addEventListener("abort", onAbort, { once: true });
8082
+ if (link.sheet) settle();
8083
+ }));
8084
+ }
8085
+ if (pending.length === 0) return Promise.resolve();
8086
+ return Promise.all(pending).then(() => void 0);
8087
+ };
8040
8088
  const iframeLoader = (iframe, logger, signal) => {
8041
8089
  return new Promise((resolve, reject) => {
8042
8090
  const cloneWindow = iframe.contentWindow;
@@ -8079,6 +8127,15 @@ const iframeLoader = (iframe, logger, signal) => {
8079
8127
  });
8080
8128
  };
8081
8129
  /**
8130
+ * Reduce a doctype component to characters that are inert inside a
8131
+ * <!DOCTYPE ...> declaration. Doctype content is parsed as a declaration
8132
+ * token, never as HTML markup, but the fields originate from the captured
8133
+ * document — stripping markup delimiters and control characters here makes
8134
+ * that property locally verifiable (and keeps the sanitizer guarantee even
8135
+ * if a future serializer ever embeds the result elsewhere).
8136
+ */
8137
+ const sanitizeDoctypePart = (value) => value.replace(/[<>&"'`\\]|[\u0000-\u001f\u007f]/g, "");
8138
+ /**
8082
8139
  * Serialise a document type declaration to an HTML string.
8083
8140
  * @internal – exported for testing only, not part of the public API.
8084
8141
  */
@@ -8086,12 +8143,12 @@ const serializeDoctype = (doctype) => {
8086
8143
  let str = "";
8087
8144
  if (doctype) {
8088
8145
  str += "<!DOCTYPE ";
8089
- if (doctype.name) str += doctype.name;
8090
- if (doctype.internalSubset) str += " " + doctype.internalSubset.replace(/"/g, "&quot;").replace(/>/g, "&gt;");
8146
+ if (doctype.name) str += sanitizeDoctypePart(doctype.name);
8147
+ if (doctype.internalSubset) str += " " + sanitizeDoctypePart(doctype.internalSubset);
8091
8148
  if (doctype.publicId) {
8092
- str += " PUBLIC \"" + doctype.publicId.replace(/"/g, "&quot;") + "\"";
8093
- if (doctype.systemId) str += " \"" + doctype.systemId.replace(/"/g, "&quot;") + "\"";
8094
- } else if (doctype.systemId) str += " SYSTEM \"" + doctype.systemId.replace(/"/g, "&quot;") + "\"";
8149
+ str += " PUBLIC \"" + sanitizeDoctypePart(doctype.publicId) + "\"";
8150
+ if (doctype.systemId) str += " \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
8151
+ } else if (doctype.systemId) str += " SYSTEM \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
8095
8152
  str += ">";
8096
8153
  }
8097
8154
  return str;
@@ -8127,6 +8184,8 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
8127
8184
  const referenceElement = mount.clonedReferenceElement;
8128
8185
  if (typeof referenceElement === "undefined") throw new Error(`Error finding the ${mount.referenceElementName} in the cloned document`);
8129
8186
  throwIfAborted(mount.signal);
8187
+ await stylesheetWait;
8188
+ throwIfAborted(mount.signal);
8130
8189
  if (documentClone.fonts && documentClone.fonts.ready) await documentClone.fonts.ready;
8131
8190
  throwIfAborted(mount.signal);
8132
8191
  if (/(AppleWebKit)/g.test(navigator.userAgent)) {
@@ -8177,6 +8236,7 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
8177
8236
  const adoptedNode = documentClone.adoptNode(mount.documentElement);
8178
8237
  addBase(adoptedNode, baseUri);
8179
8238
  documentClone.replaceChild(adoptedNode, documentClone.documentElement);
8239
+ const stylesheetWait = stylesheetsReady(documentClone, context.logger, mount.signal);
8180
8240
  return iframeLoad;
8181
8241
  };
8182
8242
  //#endregion