html2canvas-pro 2.1.0 → 2.2.0

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.
Files changed (266) hide show
  1. package/dist/html2canvas-pro.esm.js +10226 -10526
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +10869 -11171
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +8 -8
  6. package/dist/lib/config.js +0 -22
  7. package/dist/lib/core/cache-storage.js +3 -40
  8. package/dist/lib/core/constants.js +25 -0
  9. package/dist/lib/core/context.js +1 -0
  10. package/dist/lib/core/features.js +3 -2
  11. package/dist/lib/core/validator.js +3 -3
  12. package/dist/lib/css/grouped/background-styles.js +36 -0
  13. package/dist/lib/css/grouped/border-styles.js +75 -0
  14. package/dist/lib/css/grouped/font-styles.js +93 -0
  15. package/dist/lib/css/grouped/layout-styles.js +127 -0
  16. package/dist/lib/css/index.js +74 -46
  17. package/dist/lib/css/layout/text.js +7 -6
  18. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  19. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  20. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  21. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  22. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  23. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  24. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  25. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  26. package/dist/lib/css/property-descriptors/filter.js +76 -0
  27. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  28. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  29. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  30. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  31. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  32. package/dist/lib/css/syntax/parser.js +0 -1
  33. package/dist/lib/css/types/color.js +5 -1
  34. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  35. package/dist/lib/css/types/image.js +12 -2
  36. package/dist/lib/css/types/length-percentage.js +6 -2
  37. package/dist/lib/css/types/safe-eval.js +80 -0
  38. package/dist/lib/dom/document-cloner.js +23 -163
  39. package/dist/lib/dom/slot-cloner.js +176 -0
  40. package/dist/lib/index.js +1 -17
  41. package/dist/lib/render/canvas/background-renderer.js +169 -30
  42. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  43. package/dist/lib/render/canvas/canvas-renderer.js +39 -190
  44. package/dist/lib/render/canvas/content-renderer.js +202 -0
  45. package/dist/lib/render/canvas/effects-renderer.js +3 -0
  46. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  47. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  48. package/dist/lib/render/canvas/text-renderer.js +100 -224
  49. package/dist/lib/render/effects.js +38 -3
  50. package/dist/lib/render/object-fit.js +19 -15
  51. package/dist/lib/render/stacking-context.js +11 -0
  52. package/dist/types/config.d.ts +0 -10
  53. package/dist/types/core/cache-storage.d.ts +0 -24
  54. package/dist/types/core/constants.d.ts +22 -0
  55. package/dist/types/core/context.d.ts +3 -0
  56. package/dist/types/core/performance-monitor.d.ts +4 -4
  57. package/dist/types/core/validator.d.ts +6 -8
  58. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  59. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  60. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  61. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  62. package/dist/types/css/index.d.ts +30 -0
  63. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  64. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  65. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  66. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  67. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  68. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  69. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  71. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  72. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  73. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  74. package/dist/types/css/types/image.d.ts +4 -2
  75. package/dist/types/css/types/safe-eval.d.ts +8 -0
  76. package/dist/types/dom/document-cloner.d.ts +3 -44
  77. package/dist/types/dom/slot-cloner.d.ts +66 -0
  78. package/dist/types/index.d.ts +3 -7
  79. package/dist/types/options.d.ts +11 -0
  80. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  81. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  82. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  83. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  84. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  85. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  86. package/dist/types/render/effects.d.ts +12 -2
  87. package/dist/types/render/object-fit.d.ts +2 -1
  88. package/dist/types/render/renderer-interface.d.ts +11 -9
  89. package/package.json +7 -20
  90. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  91. package/dist/lib/invariant.js +0 -9
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
  93. package/dist/types/invariant.d.ts +0 -1
  94. package/src/__tests__/index.ts +0 -99
  95. package/src/config.ts +0 -107
  96. package/src/core/__mocks__/cache-storage.ts +0 -1
  97. package/src/core/__mocks__/context.ts +0 -19
  98. package/src/core/__mocks__/features.ts +0 -8
  99. package/src/core/__mocks__/logger.ts +0 -17
  100. package/src/core/__tests__/cache-storage.test.ts +0 -205
  101. package/src/core/__tests__/cache-storage.ts +0 -278
  102. package/src/core/__tests__/logger.ts +0 -29
  103. package/src/core/__tests__/validator.ts +0 -359
  104. package/src/core/bitwise.ts +0 -1
  105. package/src/core/cache-storage.ts +0 -315
  106. package/src/core/context.ts +0 -31
  107. package/src/core/debugger.ts +0 -32
  108. package/src/core/features.ts +0 -222
  109. package/src/core/logger.ts +0 -64
  110. package/src/core/origin-checker.ts +0 -57
  111. package/src/core/performance-monitor.ts +0 -241
  112. package/src/core/render-element.ts +0 -272
  113. package/src/core/util.ts +0 -1
  114. package/src/core/validator.ts +0 -593
  115. package/src/css/index.ts +0 -427
  116. package/src/css/layout/__mocks__/bounds.ts +0 -6
  117. package/src/css/layout/bounds.ts +0 -79
  118. package/src/css/layout/text.ts +0 -161
  119. package/src/css/property-descriptor.ts +0 -49
  120. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  121. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  122. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  123. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  124. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  125. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  126. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  127. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  128. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  129. package/src/css/property-descriptors/background-clip.ts +0 -30
  130. package/src/css/property-descriptors/background-color.ts +0 -9
  131. package/src/css/property-descriptors/background-image.ts +0 -27
  132. package/src/css/property-descriptors/background-origin.ts +0 -31
  133. package/src/css/property-descriptors/background-position.ts +0 -38
  134. package/src/css/property-descriptors/background-repeat.ts +0 -44
  135. package/src/css/property-descriptors/background-size.ts +0 -27
  136. package/src/css/property-descriptors/border-color.ts +0 -13
  137. package/src/css/property-descriptors/border-radius.ts +0 -19
  138. package/src/css/property-descriptors/border-style.ts +0 -34
  139. package/src/css/property-descriptors/border-width.ts +0 -20
  140. package/src/css/property-descriptors/box-shadow.ts +0 -60
  141. package/src/css/property-descriptors/clip-path.ts +0 -271
  142. package/src/css/property-descriptors/color.ts +0 -9
  143. package/src/css/property-descriptors/content.ts +0 -26
  144. package/src/css/property-descriptors/counter-increment.ts +0 -43
  145. package/src/css/property-descriptors/counter-reset.ts +0 -36
  146. package/src/css/property-descriptors/direction.ts +0 -23
  147. package/src/css/property-descriptors/display.ts +0 -117
  148. package/src/css/property-descriptors/duration.ts +0 -14
  149. package/src/css/property-descriptors/float.ts +0 -29
  150. package/src/css/property-descriptors/font-family.ts +0 -38
  151. package/src/css/property-descriptors/font-size.ts +0 -9
  152. package/src/css/property-descriptors/font-style.ts +0 -25
  153. package/src/css/property-descriptors/font-variant.ts +0 -12
  154. package/src/css/property-descriptors/font-weight.ts +0 -26
  155. package/src/css/property-descriptors/image-rendering.ts +0 -33
  156. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  157. package/src/css/property-descriptors/line-break.ts +0 -22
  158. package/src/css/property-descriptors/line-height.ts +0 -22
  159. package/src/css/property-descriptors/list-style-image.ts +0 -19
  160. package/src/css/property-descriptors/list-style-position.ts +0 -22
  161. package/src/css/property-descriptors/list-style-type.ts +0 -179
  162. package/src/css/property-descriptors/margin.ts +0 -13
  163. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  164. package/src/css/property-descriptors/object-fit.ts +0 -39
  165. package/src/css/property-descriptors/opacity.ts +0 -15
  166. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  167. package/src/css/property-descriptors/overflow.ts +0 -34
  168. package/src/css/property-descriptors/padding.ts +0 -14
  169. package/src/css/property-descriptors/paint-order.ts +0 -42
  170. package/src/css/property-descriptors/position.ts +0 -30
  171. package/src/css/property-descriptors/quotes.ts +0 -57
  172. package/src/css/property-descriptors/rotate.ts +0 -34
  173. package/src/css/property-descriptors/text-align.ts +0 -26
  174. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  175. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  176. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  177. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  178. package/src/css/property-descriptors/text-overflow.ts +0 -23
  179. package/src/css/property-descriptors/text-shadow.ts +0 -52
  180. package/src/css/property-descriptors/text-transform.ts +0 -27
  181. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  182. package/src/css/property-descriptors/transform-origin.ts +0 -29
  183. package/src/css/property-descriptors/transform.ts +0 -74
  184. package/src/css/property-descriptors/visibility.ts +0 -25
  185. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  186. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  187. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  188. package/src/css/property-descriptors/word-break.ts +0 -25
  189. package/src/css/property-descriptors/writing-mode.ts +0 -37
  190. package/src/css/property-descriptors/z-index.ts +0 -27
  191. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  192. package/src/css/syntax/parser.ts +0 -188
  193. package/src/css/syntax/tokenizer.ts +0 -822
  194. package/src/css/type-descriptor.ts +0 -7
  195. package/src/css/types/__tests__/color-tests.ts +0 -147
  196. package/src/css/types/__tests__/image-tests.ts +0 -239
  197. package/src/css/types/angle.ts +0 -86
  198. package/src/css/types/color-math.ts +0 -22
  199. package/src/css/types/color-spaces/a98.ts +0 -86
  200. package/src/css/types/color-spaces/p3.ts +0 -92
  201. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  202. package/src/css/types/color-spaces/rec2020.ts +0 -90
  203. package/src/css/types/color-spaces/srgb.ts +0 -87
  204. package/src/css/types/color-utilities.ts +0 -452
  205. package/src/css/types/color.ts +0 -485
  206. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  207. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  208. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  209. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  210. package/src/css/types/functions/counter.ts +0 -511
  211. package/src/css/types/functions/gradient.ts +0 -206
  212. package/src/css/types/functions/linear-gradient.ts +0 -28
  213. package/src/css/types/functions/radial-gradient.ts +0 -101
  214. package/src/css/types/image.ts +0 -120
  215. package/src/css/types/index.ts +0 -1
  216. package/src/css/types/length-percentage.ts +0 -137
  217. package/src/css/types/length.ts +0 -7
  218. package/src/css/types/time.ts +0 -20
  219. package/src/dom/__mocks__/document-cloner.ts +0 -22
  220. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  221. package/src/dom/__tests__/element-container.test.ts +0 -129
  222. package/src/dom/document-cloner.ts +0 -929
  223. package/src/dom/dom-normalizer.ts +0 -133
  224. package/src/dom/element-container.ts +0 -75
  225. package/src/dom/elements/li-element-container.ts +0 -10
  226. package/src/dom/elements/ol-element-container.ts +0 -12
  227. package/src/dom/elements/select-element-container.ts +0 -10
  228. package/src/dom/elements/textarea-element-container.ts +0 -9
  229. package/src/dom/node-parser.ts +0 -177
  230. package/src/dom/node-type-guards.ts +0 -70
  231. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  232. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  233. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  234. package/src/dom/replaced-elements/index.ts +0 -5
  235. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  236. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  237. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  238. package/src/dom/text-container.ts +0 -42
  239. package/src/global.d.ts +0 -19
  240. package/src/index.ts +0 -82
  241. package/src/invariant.ts +0 -5
  242. package/src/options.ts +0 -55
  243. package/src/render/__tests__/object-fit.test.ts +0 -85
  244. package/src/render/background.ts +0 -298
  245. package/src/render/bezier-curve.ts +0 -47
  246. package/src/render/border.ts +0 -165
  247. package/src/render/bound-curves.ts +0 -388
  248. package/src/render/box-sizing.ts +0 -31
  249. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  250. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  251. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  252. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  253. package/src/render/canvas/background-renderer.ts +0 -271
  254. package/src/render/canvas/border-renderer.ts +0 -224
  255. package/src/render/canvas/canvas-path.ts +0 -31
  256. package/src/render/canvas/canvas-renderer.ts +0 -641
  257. package/src/render/canvas/effects-renderer.ts +0 -130
  258. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  259. package/src/render/canvas/text-renderer.ts +0 -700
  260. package/src/render/effects.ts +0 -75
  261. package/src/render/font-metrics.ts +0 -72
  262. package/src/render/object-fit.ts +0 -100
  263. package/src/render/path.ts +0 -37
  264. package/src/render/renderer-interface.ts +0 -28
  265. package/src/render/stacking-context.ts +0 -386
  266. package/src/render/vector.ts +0 -19
@@ -1,929 +0,0 @@
1
- import { Bounds } from '../css/layout/bounds';
2
- import {
3
- isBodyElement,
4
- isCanvasElement,
5
- isCustomElement,
6
- isElementNode,
7
- isHTMLElementNode,
8
- isIFrameElement,
9
- isImageElement,
10
- isScriptElement,
11
- isSelectElement,
12
- isSlotElement,
13
- isStyleElement,
14
- isSVGElementNode,
15
- isTextareaElement,
16
- isTextNode,
17
- isVideoElement
18
- } from './node-parser';
19
- import { canHavePseudoElements } from './node-type-guards';
20
- import { isIdentToken, nonFunctionArgSeparator } from '../css/syntax/parser';
21
- import { TokenType } from '../css/syntax/tokenizer';
22
- import { CounterState, createCounterText } from '../css/types/functions/counter';
23
- import { LIST_STYLE_TYPE, listStyleType } from '../css/property-descriptors/list-style-type';
24
- import { CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration } from '../css/index';
25
- import { getQuote } from '../css/property-descriptors/quotes';
26
- import { Context } from '../core/context';
27
- import { DebuggerType, isDebugging } from '../core/debugger';
28
-
29
- export interface CloneOptions {
30
- ignoreElements?: (element: Element) => boolean;
31
- onclone?: (document: Document, element: HTMLElement) => void;
32
- allowTaint?: boolean;
33
- iframeContainer?: HTMLElement | ShadowRoot;
34
- }
35
-
36
- export interface WindowOptions {
37
- scrollX: number;
38
- scrollY: number;
39
- windowWidth: number;
40
- windowHeight: number;
41
- }
42
-
43
- export type CloneConfigurations = CloneOptions & {
44
- inlineImages: boolean;
45
- copyStyles: boolean;
46
- cspNonce?: string;
47
- };
48
-
49
- const IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';
50
-
51
- /**
52
- * Trusted Types factory (getPolicy / createPolicy) for document.write in strict CSP environments.
53
- * Used in toIFrame() when writing initial HTML to the cloned document.
54
- */
55
- type TrustedTypesFactory = {
56
- getPolicy?: (name: string) => unknown;
57
- createPolicy: (name: string, config: object) => unknown;
58
- };
59
-
60
- /**
61
- * Find the parent ShadowRoot of an element, if any
62
- * @param element - The element to check
63
- * @returns The parent ShadowRoot or null
64
- */
65
- const findParentShadowRoot = (element: Element): ShadowRoot | null => {
66
- let current: Node | null = element;
67
- while (current) {
68
- // Check if we've reached a shadow root boundary
69
- if (current.parentNode && (current.parentNode as ShadowRoot).host) {
70
- return current.parentNode as ShadowRoot;
71
- }
72
- // Use getRootNode to check if we're in a shadow root
73
- const root = current.getRootNode();
74
- if (root && root !== current.ownerDocument && (root as ShadowRoot).host) {
75
- return root as ShadowRoot;
76
- }
77
- current = current.parentNode;
78
- }
79
- return null;
80
- };
81
-
82
- export class DocumentCloner {
83
- private readonly scrolledElements: [Element, number, number][];
84
- private readonly referenceElement: HTMLElement;
85
- clonedReferenceElement?: HTMLElement;
86
- private readonly documentElement: HTMLElement;
87
- private readonly counters: CounterState;
88
- private quoteDepth: number;
89
-
90
- constructor(
91
- private readonly context: Context,
92
- element: HTMLElement,
93
- private readonly options: CloneConfigurations
94
- ) {
95
- this.scrolledElements = [];
96
- this.referenceElement = element;
97
- this.counters = new CounterState();
98
- this.quoteDepth = 0;
99
- if (!element.ownerDocument) {
100
- throw new Error('Cloned element does not have an owner document');
101
- }
102
-
103
- // Auto-detect Shadow Root if not explicitly provided
104
- if (!this.options.iframeContainer) {
105
- const shadowRoot = findParentShadowRoot(element);
106
- if (shadowRoot) {
107
- this.options.iframeContainer = shadowRoot;
108
- }
109
- }
110
-
111
- this.documentElement = this.cloneNode(element.ownerDocument.documentElement, false) as HTMLElement;
112
- }
113
-
114
- toIFrame(ownerDocument: Document, windowSize: Bounds): Promise<HTMLIFrameElement> {
115
- const iframe: HTMLIFrameElement = createIFrameContainer(
116
- ownerDocument,
117
- windowSize,
118
- this.options.iframeContainer
119
- );
120
-
121
- if (!iframe.contentWindow) {
122
- throw new Error('Unable to find iframe window');
123
- }
124
-
125
- const scrollX = (ownerDocument.defaultView as Window).pageXOffset;
126
- const scrollY = (ownerDocument.defaultView as Window).pageYOffset;
127
-
128
- const cloneWindow = iframe.contentWindow;
129
- const documentClone: Document = cloneWindow.document;
130
-
131
- /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
132
- if window url is about:blank, we can assign the url to current by writing onto the document
133
- */
134
-
135
- const iframeLoad = iframeLoader(iframe).then(async () => {
136
- this.scrolledElements.forEach(restoreNodeScroll);
137
- if (cloneWindow) {
138
- cloneWindow.scrollTo(windowSize.left, windowSize.top);
139
- if (
140
- /AppleWebKit/g.test(navigator.userAgent) &&
141
- (cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)
142
- ) {
143
- this.context.logger.warn('Unable to restore scroll position for cloned document');
144
- this.context.windowBounds = this.context.windowBounds.add(
145
- cloneWindow.scrollX - windowSize.left,
146
- cloneWindow.scrollY - windowSize.top,
147
- 0,
148
- 0
149
- );
150
- }
151
- }
152
-
153
- const onclone = this.options.onclone;
154
-
155
- const referenceElement = this.clonedReferenceElement;
156
-
157
- if (typeof referenceElement === 'undefined') {
158
- throw new Error(`Error finding the ${this.referenceElement.nodeName} in the cloned document`);
159
- }
160
-
161
- if (documentClone.fonts && documentClone.fonts.ready) {
162
- await documentClone.fonts.ready;
163
- }
164
-
165
- if (/(AppleWebKit)/g.test(navigator.userAgent)) {
166
- await imagesReady(documentClone);
167
- }
168
-
169
- if (typeof onclone === 'function') {
170
- return Promise.resolve()
171
- .then(() => onclone(documentClone, referenceElement))
172
- .then(() => iframe);
173
- }
174
-
175
- return iframe;
176
- });
177
- /**
178
- * The base URI used for resolving relative URLs (e.g. background-image) in the clone.
179
- * Must come from the source document: the iframe document is about:blank, so
180
- * documentClone.baseURI would break getComputedStyle() for relative background URLs.
181
- */
182
- const baseUri = ownerDocument.baseURI;
183
- documentClone.open();
184
- const rawHTML = serializeDoctype(document.doctype) + '<html></html>';
185
- try {
186
- // Fixing "This document requires 'TrustedHTML' assignment. The action has been blocked." error.
187
- // Reuse existing policy when present (e.g. second html2canvas call) to avoid createPolicy duplicate-name throw.
188
- const ownerWindow = this.referenceElement.ownerDocument?.defaultView;
189
- const trustedTypesFactory =
190
- ownerWindow && (ownerWindow as Window & { trustedTypes?: TrustedTypesFactory }).trustedTypes;
191
- let policy = trustedTypesFactory?.getPolicy?.('html2canvas-pro');
192
- if (!policy && trustedTypesFactory) {
193
- policy = trustedTypesFactory.createPolicy('html2canvas-pro', {
194
- createHTML: (string: string) => string
195
- });
196
- }
197
- if (policy) {
198
- documentClone.write((policy as { createHTML: (s: string) => string }).createHTML(rawHTML) as string);
199
- } else {
200
- documentClone.write(rawHTML);
201
- }
202
- } catch (_e) {
203
- documentClone.write(rawHTML);
204
- }
205
- // Chrome scrolls the parent document for some reason after the write to the cloned window???
206
- restoreOwnerScroll(this.referenceElement.ownerDocument, scrollX, scrollY);
207
- /**
208
- * IMPORTANT: documentClone.close() MUST be called BEFORE adoptNode().
209
- *
210
- * In Chrome, calling adoptNode() while the document is still "open"
211
- * (between document.open() and document.close()) causes CSS rules with
212
- * uppercase characters in class names (e.g. ".MyClass") to not match
213
- * correctly. Chrome's CSS engine only enters a fully-resolved matching
214
- * mode once the document is closed.
215
- *
216
- * Correct order: open() → write() → close() → adoptNode() → replaceChild()
217
- *
218
- * Timing: close() queues the iframe 'load' event; because JS is single-threaded,
219
- * the synchronous adoptNode() and replaceChild() below complete before that
220
- * event is dispatched. iframeLoader's setInterval will therefore see the body
221
- * already populated on its first tick.
222
- */
223
- documentClone.close();
224
- const adoptedNode = documentClone.adoptNode(this.documentElement);
225
- addBase(adoptedNode, baseUri);
226
- documentClone.replaceChild(adoptedNode, documentClone.documentElement);
227
-
228
- return iframeLoad;
229
- }
230
-
231
- createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement {
232
- if (isDebugging(node, DebuggerType.CLONE)) {
233
- debugger;
234
- }
235
- if (isCanvasElement(node)) {
236
- return this.createCanvasClone(node);
237
- }
238
- if (isVideoElement(node)) {
239
- return this.createVideoClone(node);
240
- }
241
- if (isStyleElement(node)) {
242
- return this.createStyleClone(node);
243
- }
244
-
245
- const clone = node.cloneNode(false) as T;
246
- if (isImageElement(clone)) {
247
- if (isImageElement(node) && node.currentSrc && node.currentSrc !== node.src) {
248
- clone.src = node.currentSrc;
249
- clone.srcset = '';
250
- }
251
-
252
- if (clone.loading === 'lazy') {
253
- clone.loading = 'eager';
254
- }
255
- }
256
-
257
- if (isCustomElement(clone) && !isSVGElementNode(clone)) {
258
- return this.createCustomElementClone(clone as HTMLElement);
259
- }
260
-
261
- return clone;
262
- }
263
-
264
- createCustomElementClone(node: HTMLElement): HTMLElement {
265
- const clone = document.createElement('div');
266
- clone.className = node.className;
267
- copyCSSStyles(node.style, clone);
268
-
269
- // Clone shadow DOM if it exists
270
- // Fix for Issue #108: This is critical for Web Components with slots to work correctly
271
- if (node.shadowRoot) {
272
- try {
273
- clone.attachShadow({ mode: 'open' });
274
- // The actual shadow DOM content will be cloned in cloneChildNodes
275
- } catch (e) {
276
- // Some elements cannot have shadow roots attached
277
- // This can happen if the element doesn't support shadow DOM
278
- this.context.logger.error('Failed to attach shadow root to custom element clone:', e);
279
- }
280
- }
281
-
282
- return clone;
283
- }
284
-
285
- createStyleClone(node: HTMLStyleElement): HTMLStyleElement {
286
- try {
287
- const sheet = node.sheet as CSSStyleSheet | undefined;
288
- if (sheet && sheet.cssRules) {
289
- const css: string = [].slice.call(sheet.cssRules, 0).reduce((css: string, rule: CSSRule) => {
290
- if (rule && typeof rule.cssText === 'string') {
291
- return css + rule.cssText;
292
- }
293
- return css;
294
- }, '');
295
- const style = node.cloneNode(false) as HTMLStyleElement;
296
- style.textContent = css;
297
- if (this.options.cspNonce) {
298
- style.nonce = this.options.cspNonce;
299
- }
300
- return style;
301
- }
302
- } catch (e) {
303
- // accessing node.sheet.cssRules throws a DOMException
304
- this.context.logger.error('Unable to access cssRules property', e);
305
- if (e.name !== 'SecurityError') {
306
- throw e;
307
- }
308
- }
309
- const cloned = node.cloneNode(false) as HTMLStyleElement;
310
- if (this.options.cspNonce) {
311
- cloned.nonce = this.options.cspNonce;
312
- }
313
- return cloned;
314
- }
315
-
316
- createCanvasClone(canvas: HTMLCanvasElement): HTMLImageElement | HTMLCanvasElement {
317
- if (this.options.inlineImages && canvas.ownerDocument) {
318
- const img = canvas.ownerDocument.createElement('img');
319
- try {
320
- img.src = canvas.toDataURL();
321
- return img;
322
- } catch (e) {
323
- this.context.logger.info(`Unable to inline canvas contents, canvas is tainted`, canvas);
324
- }
325
- }
326
-
327
- const clonedCanvas = canvas.cloneNode(false) as HTMLCanvasElement;
328
-
329
- try {
330
- clonedCanvas.width = canvas.width;
331
- clonedCanvas.height = canvas.height;
332
- const ctx = canvas.getContext('2d');
333
- const clonedCtx = clonedCanvas.getContext('2d', { willReadFrequently: true }) as CanvasRenderingContext2D;
334
- if (clonedCtx) {
335
- if (!this.options.allowTaint && ctx) {
336
- clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
337
- } else {
338
- const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
339
- if (gl) {
340
- const attribs = gl.getContextAttributes();
341
- if (attribs?.preserveDrawingBuffer === false) {
342
- this.context.logger.warn(
343
- 'Unable to clone WebGL context as it has preserveDrawingBuffer=false',
344
- canvas
345
- );
346
- }
347
- }
348
-
349
- clonedCtx.drawImage(canvas, 0, 0);
350
- }
351
- }
352
- return clonedCanvas;
353
- } catch (e) {
354
- this.context.logger.info(`Unable to clone canvas as it is tainted`, canvas);
355
- }
356
-
357
- return clonedCanvas;
358
- }
359
-
360
- createVideoClone(video: HTMLVideoElement): HTMLCanvasElement {
361
- const canvas = video.ownerDocument.createElement('canvas');
362
-
363
- canvas.width = video.offsetWidth;
364
- canvas.height = video.offsetHeight;
365
- const ctx = canvas.getContext('2d');
366
-
367
- try {
368
- if (ctx) {
369
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
370
- if (!this.options.allowTaint) {
371
- ctx.getImageData(0, 0, canvas.width, canvas.height);
372
- }
373
- }
374
- return canvas;
375
- } catch (e) {
376
- this.context.logger.info(`Unable to clone video as it is tainted`, video);
377
- }
378
-
379
- const blankCanvas = video.ownerDocument.createElement('canvas');
380
-
381
- blankCanvas.width = video.offsetWidth;
382
- blankCanvas.height = video.offsetHeight;
383
- return blankCanvas;
384
- }
385
-
386
- appendChildNode(clone: HTMLElement | SVGElement, child: Node, copyStyles: boolean): void {
387
- this.safeAppendClonedChild(clone, child, copyStyles);
388
- }
389
-
390
- /**
391
- * Check if a child node should be cloned based on filtering rules
392
- * Filters out: scripts, ignored elements, and optionally styles
393
- */
394
- private shouldCloneChild(child: Node): boolean {
395
- return (
396
- !isElementNode(child) ||
397
- (!isScriptElement(child) &&
398
- !child.hasAttribute(IGNORE_ATTRIBUTE) &&
399
- (typeof this.options.ignoreElements !== 'function' || !this.options.ignoreElements(child)))
400
- );
401
- }
402
-
403
- /**
404
- * Check if a style element should be cloned based on copyStyles option
405
- */
406
- private shouldCloneStyleElement(child: Node): boolean {
407
- return !this.options.copyStyles || !isElementNode(child) || !isStyleElement(child);
408
- }
409
-
410
- /**
411
- * Safely append a cloned child to a target, applying all filtering rules
412
- */
413
- private safeAppendClonedChild(
414
- target: ShadowRoot | HTMLElement | SVGElement,
415
- child: Node,
416
- copyStyles: boolean
417
- ): void {
418
- if (this.shouldCloneChild(child) && this.shouldCloneStyleElement(child)) {
419
- target.appendChild(this.cloneNode(child, copyStyles));
420
- }
421
- }
422
-
423
- /**
424
- * Clone assigned nodes from a slot element to the target
425
- */
426
- private cloneAssignedNodes(assignedNodes: Node[], target: ShadowRoot, copyStyles: boolean): void {
427
- assignedNodes.forEach((node) => {
428
- this.safeAppendClonedChild(target, node, copyStyles);
429
- });
430
- }
431
-
432
- /**
433
- * Clone fallback content from a slot element when no nodes are assigned
434
- */
435
- private cloneSlotFallbackContent(slot: Element, target: ShadowRoot, copyStyles: boolean): void {
436
- for (let child = slot.firstChild; child; child = child.nextSibling) {
437
- this.safeAppendClonedChild(target, child, copyStyles);
438
- }
439
- }
440
-
441
- /**
442
- * Handle cloning of a slot element, including assigned nodes or fallback content
443
- */
444
- private cloneSlotElement(slot: Element, targetShadowRoot: ShadowRoot, copyStyles: boolean): void {
445
- if (!isSlotElement(slot)) {
446
- return;
447
- }
448
-
449
- const slotElement = slot as HTMLSlotElement;
450
-
451
- // Defensive check: ensure assignedNodes method exists
452
- if (typeof slotElement.assignedNodes !== 'function') {
453
- this.context.logger.warn('HTMLSlotElement.assignedNodes is not available', slot);
454
- this.cloneSlotFallbackContent(slot, targetShadowRoot, copyStyles);
455
- return;
456
- }
457
-
458
- const assignedNodes = slotElement.assignedNodes();
459
-
460
- // Defensive check: ensure assignedNodes returns an array
461
- if (!assignedNodes || !Array.isArray(assignedNodes)) {
462
- this.context.logger.warn('assignedNodes() did not return a valid array', slot);
463
- this.cloneSlotFallbackContent(slot, targetShadowRoot, copyStyles);
464
- return;
465
- }
466
-
467
- if (assignedNodes.length > 0) {
468
- // Clone assigned nodes
469
- this.cloneAssignedNodes(assignedNodes, targetShadowRoot, copyStyles);
470
- } else {
471
- // Clone fallback content
472
- this.cloneSlotFallbackContent(slot, targetShadowRoot, copyStyles);
473
- }
474
- }
475
-
476
- /**
477
- * Clone shadow DOM children to the target shadow root
478
- */
479
- private cloneShadowDOMChildren(shadowRoot: ShadowRoot, targetShadowRoot: ShadowRoot, copyStyles: boolean): void {
480
- for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
481
- if (isElementNode(child) && isSlotElement(child)) {
482
- // Handle slot elements specially
483
- this.cloneSlotElement(child, targetShadowRoot, copyStyles);
484
- } else {
485
- // Clone regular elements
486
- this.safeAppendClonedChild(targetShadowRoot, child, copyStyles);
487
- }
488
- }
489
- }
490
-
491
- /**
492
- * Clone light DOM children to the target element
493
- */
494
- private cloneLightDOMChildren(node: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
495
- for (let child = node.firstChild; child; child = child.nextSibling) {
496
- this.appendChildNode(clone, child, copyStyles);
497
- }
498
- }
499
-
500
- /**
501
- * Clone slot element as light DOM when shadow root creation failed
502
- */
503
- private cloneSlotElementAsLightDOM(slot: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
504
- if (!isSlotElement(slot)) {
505
- return;
506
- }
507
-
508
- const slotElement = slot as HTMLSlotElement;
509
-
510
- if (typeof slotElement.assignedNodes !== 'function') {
511
- // Fallback: clone slot's children
512
- for (let child = slot.firstChild; child; child = child.nextSibling) {
513
- this.appendChildNode(clone, child, copyStyles);
514
- }
515
- return;
516
- }
517
-
518
- const assignedNodes = slotElement.assignedNodes();
519
-
520
- if (assignedNodes && Array.isArray(assignedNodes) && assignedNodes.length > 0) {
521
- // Clone assigned nodes as light DOM
522
- assignedNodes.forEach((node) => this.appendChildNode(clone, node, copyStyles));
523
- } else {
524
- // Clone fallback content as light DOM
525
- for (let child = slot.firstChild; child; child = child.nextSibling) {
526
- this.appendChildNode(clone, child, copyStyles);
527
- }
528
- }
529
- }
530
-
531
- /**
532
- * Clone shadow DOM content as light DOM when shadow root creation failed
533
- * This is a fallback mechanism to ensure content is not lost
534
- */
535
- private cloneShadowDOMAsLightDOM(
536
- shadowRoot: ShadowRoot,
537
- clone: HTMLElement | SVGElement,
538
- copyStyles: boolean
539
- ): void {
540
- for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
541
- if (isElementNode(child) && isSlotElement(child)) {
542
- this.cloneSlotElementAsLightDOM(child, clone, copyStyles);
543
- } else {
544
- this.appendChildNode(clone, child, copyStyles);
545
- }
546
- }
547
- }
548
-
549
- /**
550
- * Clone child nodes from source element to clone element
551
- * Handles shadow DOM, slots, and light DOM appropriately
552
- */
553
- cloneChildNodes(node: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
554
- if (node.shadowRoot && clone.shadowRoot) {
555
- // Both original and clone have shadow roots - clone shadow DOM content
556
- this.cloneShadowDOMChildren(node.shadowRoot, clone.shadowRoot, copyStyles);
557
- // Also clone light DOM (slot content sources)
558
- this.cloneLightDOMChildren(node, clone, copyStyles);
559
- } else if (node.shadowRoot && !clone.shadowRoot) {
560
- // Original has shadow root but clone doesn't (creation failed)
561
- // Fallback: clone shadow DOM content as light DOM to preserve content
562
- this.cloneShadowDOMAsLightDOM(node.shadowRoot, clone, copyStyles);
563
- } else {
564
- // No shadow DOM - just clone light DOM children
565
- this.cloneLightDOMChildren(node, clone, copyStyles);
566
- }
567
- }
568
-
569
- cloneNode(node: Node, copyStyles: boolean): Node {
570
- if (isTextNode(node)) {
571
- return document.createTextNode(node.data);
572
- }
573
-
574
- if (!node.ownerDocument) {
575
- return node.cloneNode(false);
576
- }
577
-
578
- const window = node.ownerDocument.defaultView;
579
-
580
- if (window && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) {
581
- const clone = this.createElementClone(node);
582
- clone.style.transitionProperty = 'none';
583
-
584
- const style = window.getComputedStyle(node);
585
-
586
- // Per CSS spec, replaced elements, void elements, and SVG elements
587
- // cannot have ::before / ::after pseudo-elements — skip these queries.
588
- const checkPseudoElements = canHavePseudoElements(node);
589
-
590
- if (this.referenceElement === node && isHTMLElementNode(clone)) {
591
- this.clonedReferenceElement = clone;
592
- }
593
- if (isBodyElement(clone)) {
594
- createPseudoHideStyles(clone, this.options.cspNonce);
595
- }
596
-
597
- const counters = this.counters.parse(new CSSParsedCounterDeclaration(this.context, style));
598
-
599
- if (checkPseudoElements) {
600
- const styleBefore = window.getComputedStyle(node, ':before');
601
- const before = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE);
602
- if (before) {
603
- clone.insertBefore(before, clone.firstChild);
604
- }
605
-
606
- const styleAfter = window.getComputedStyle(node, ':after');
607
- const after = this.resolvePseudoContent(node, clone, styleAfter, PseudoElementType.AFTER);
608
- if (after) {
609
- clone.appendChild(after);
610
- }
611
- }
612
-
613
- if (isCustomElement(node)) {
614
- copyStyles = true;
615
- }
616
-
617
- if (!isVideoElement(node)) {
618
- this.cloneChildNodes(node, clone, copyStyles);
619
- }
620
-
621
- this.counters.pop(counters);
622
-
623
- if (
624
- (style && (this.options.copyStyles || isSVGElementNode(node)) && !isIFrameElement(node)) ||
625
- copyStyles
626
- ) {
627
- copyCSSStyles(style, clone);
628
- }
629
-
630
- if (node.scrollTop !== 0 || node.scrollLeft !== 0) {
631
- this.scrolledElements.push([clone, node.scrollLeft, node.scrollTop]);
632
- }
633
-
634
- if (
635
- (isTextareaElement(node) || isSelectElement(node)) &&
636
- (isTextareaElement(clone) || isSelectElement(clone))
637
- ) {
638
- clone.value = node.value;
639
- }
640
-
641
- return clone;
642
- }
643
-
644
- return node.cloneNode(false);
645
- }
646
-
647
- resolvePseudoContent(
648
- node: Element,
649
- clone: Element,
650
- style: CSSStyleDeclaration,
651
- pseudoElt: PseudoElementType
652
- ): HTMLElement | void {
653
- if (!style) {
654
- return;
655
- }
656
-
657
- const value = style.content;
658
- const document = clone.ownerDocument;
659
- if (!document || !value || value === 'none' || value === '-moz-alt-content' || style.display === 'none') {
660
- return;
661
- }
662
-
663
- this.counters.parse(new CSSParsedCounterDeclaration(this.context, style));
664
- const declaration = new CSSParsedPseudoDeclaration(this.context, style);
665
-
666
- const anonymousReplacedElement = document.createElement('html2canvaspseudoelement');
667
- copyCSSStyles(style, anonymousReplacedElement);
668
-
669
- declaration.content.forEach((token) => {
670
- if (token.type === TokenType.STRING_TOKEN) {
671
- anonymousReplacedElement.appendChild(document.createTextNode(token.value));
672
- } else if (token.type === TokenType.URL_TOKEN) {
673
- const img = document.createElement('img');
674
- img.src = token.value;
675
- img.style.opacity = '1';
676
- anonymousReplacedElement.appendChild(img);
677
- } else if (token.type === TokenType.FUNCTION) {
678
- if (token.name === 'attr') {
679
- const attr = token.values.filter(isIdentToken);
680
- if (attr.length) {
681
- anonymousReplacedElement.appendChild(
682
- document.createTextNode(node.getAttribute(attr[0].value) || '')
683
- );
684
- }
685
- } else if (token.name === 'counter') {
686
- const [counter, counterStyle] = token.values.filter(nonFunctionArgSeparator);
687
- if (counter && isIdentToken(counter)) {
688
- const counterState = this.counters.getCounterValue(counter.value);
689
- const counterType =
690
- counterStyle && isIdentToken(counterStyle)
691
- ? listStyleType.parse(this.context, counterStyle.value)
692
- : LIST_STYLE_TYPE.DECIMAL;
693
-
694
- anonymousReplacedElement.appendChild(
695
- document.createTextNode(createCounterText(counterState, counterType, false))
696
- );
697
- }
698
- } else if (token.name === 'counters') {
699
- const [counter, delim, counterStyle] = token.values.filter(nonFunctionArgSeparator);
700
- if (counter && isIdentToken(counter)) {
701
- const counterStates = this.counters.getCounterValues(counter.value);
702
- const counterType =
703
- counterStyle && isIdentToken(counterStyle)
704
- ? listStyleType.parse(this.context, counterStyle.value)
705
- : LIST_STYLE_TYPE.DECIMAL;
706
- const separator = delim && delim.type === TokenType.STRING_TOKEN ? delim.value : '';
707
- const text = counterStates
708
- .map((value) => createCounterText(value, counterType, false))
709
- .join(separator);
710
-
711
- anonymousReplacedElement.appendChild(document.createTextNode(text));
712
- }
713
- } else {
714
- // console.log('FUNCTION_TOKEN', token);
715
- }
716
- } else if (token.type === TokenType.IDENT_TOKEN) {
717
- switch (token.value) {
718
- case 'open-quote':
719
- anonymousReplacedElement.appendChild(
720
- document.createTextNode(getQuote(declaration.quotes, this.quoteDepth++, true))
721
- );
722
- break;
723
- case 'close-quote':
724
- anonymousReplacedElement.appendChild(
725
- document.createTextNode(getQuote(declaration.quotes, --this.quoteDepth, false))
726
- );
727
- break;
728
- default:
729
- // safari doesn't parse string tokens correctly because of lack of quotes
730
- anonymousReplacedElement.appendChild(document.createTextNode(token.value));
731
- }
732
- }
733
- });
734
-
735
- anonymousReplacedElement.className = `${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE} ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
736
- const newClassName =
737
- pseudoElt === PseudoElementType.BEFORE
738
- ? ` ${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE}`
739
- : ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
740
-
741
- if (isSVGElementNode(clone)) {
742
- clone.className.baseValue += newClassName;
743
- } else {
744
- clone.className += newClassName;
745
- }
746
-
747
- return anonymousReplacedElement;
748
- }
749
-
750
- static destroy(container: HTMLIFrameElement): boolean {
751
- if (container.parentNode) {
752
- container.parentNode.removeChild(container);
753
- return true;
754
- }
755
- return false;
756
- }
757
- }
758
-
759
- enum PseudoElementType {
760
- BEFORE,
761
- AFTER
762
- }
763
-
764
- const createIFrameContainer = (
765
- ownerDocument: Document,
766
- bounds: Bounds,
767
- customContainer?: HTMLElement | ShadowRoot
768
- ): HTMLIFrameElement => {
769
- const cloneIframeContainer = ownerDocument.createElement('iframe');
770
-
771
- cloneIframeContainer.className = 'html2canvas-container';
772
- cloneIframeContainer.style.visibility = 'hidden';
773
- cloneIframeContainer.style.position = 'fixed';
774
- cloneIframeContainer.style.left = '-10000px';
775
- cloneIframeContainer.style.top = '0px';
776
- cloneIframeContainer.style.border = '0';
777
- cloneIframeContainer.width = bounds.width.toString();
778
- cloneIframeContainer.height = bounds.height.toString();
779
- cloneIframeContainer.scrolling = 'no'; // ios won't scroll without it
780
- cloneIframeContainer.setAttribute(IGNORE_ATTRIBUTE, 'true');
781
-
782
- // Use custom container if provided, otherwise use body
783
- const container = customContainer || ownerDocument.body;
784
- container.appendChild(cloneIframeContainer);
785
-
786
- return cloneIframeContainer;
787
- };
788
-
789
- const imageReady = (img: HTMLImageElement): Promise<Event | void | string> => {
790
- return new Promise((resolve) => {
791
- if (img.complete) {
792
- resolve();
793
- return;
794
- }
795
- if (!img.src) {
796
- resolve();
797
- return;
798
- }
799
- img.onload = resolve;
800
- img.onerror = resolve;
801
- });
802
- };
803
-
804
- const imagesReady = (document: HTMLDocument): Promise<unknown[]> => {
805
- return Promise.all([].slice.call(document.images, 0).map(imageReady));
806
- };
807
-
808
- const iframeLoader = (iframe: HTMLIFrameElement): Promise<HTMLIFrameElement> => {
809
- return new Promise((resolve, reject) => {
810
- const cloneWindow = iframe.contentWindow;
811
-
812
- if (!cloneWindow) {
813
- return reject(`No window assigned for iframe`);
814
- }
815
-
816
- const documentClone = cloneWindow.document;
817
-
818
- cloneWindow.onload = iframe.onload = () => {
819
- cloneWindow.onload = iframe.onload = null;
820
- const interval = setInterval(() => {
821
- if (documentClone.body.childNodes.length > 0 && documentClone.readyState === 'complete') {
822
- clearInterval(interval);
823
- resolve(iframe);
824
- }
825
- }, 50);
826
- };
827
- });
828
- };
829
-
830
- const ignoredStyleProperties = [
831
- 'all', // #2476
832
- 'd', // #2483
833
- 'content' // Safari shows pseudoelements if content is set
834
- ];
835
-
836
- export const copyCSSStyles = <T extends HTMLElement | SVGElement>(style: CSSStyleDeclaration, target: T): T => {
837
- const parts: string[] = [];
838
- for (let i = style.length - 1; i >= 0; i--) {
839
- const property = style.item(i);
840
- // fix: Chrome_138 ignore custom properties
841
- if (ignoredStyleProperties.indexOf(property) === -1 && !property.startsWith('--')) {
842
- const value = style.getPropertyValue(property);
843
- if (value) {
844
- const priority = style.getPropertyPriority(property);
845
- parts.push(priority ? `${property}:${value} !${priority}` : `${property}:${value}`);
846
- }
847
- }
848
- }
849
- if (parts.length > 0) {
850
- target.style.cssText = parts.join(';') + ';';
851
- }
852
- return target;
853
- };
854
-
855
- const serializeDoctype = (doctype?: DocumentType | null): string => {
856
- let str = '';
857
- if (doctype) {
858
- str += '<!DOCTYPE ';
859
- if (doctype.name) {
860
- str += doctype.name;
861
- }
862
- if (doctype.internalSubset) {
863
- str += ' ' + doctype.internalSubset.replace(/"/g, '&quot;').replace(/>/g, '&gt;');
864
- }
865
- if (doctype.publicId) {
866
- str += ' PUBLIC "' + doctype.publicId.replace(/"/g, '&quot;') + '"';
867
- if (doctype.systemId) {
868
- str += ' "' + doctype.systemId.replace(/"/g, '&quot;') + '"';
869
- }
870
- } else if (doctype.systemId) {
871
- str += ' SYSTEM "' + doctype.systemId.replace(/"/g, '&quot;') + '"';
872
- }
873
- str += '>';
874
- }
875
- return str;
876
- };
877
-
878
- const restoreOwnerScroll = (ownerDocument: Document | null, x: number, y: number) => {
879
- if (
880
- ownerDocument &&
881
- ownerDocument.defaultView &&
882
- (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)
883
- ) {
884
- ownerDocument.defaultView.scrollTo(x, y);
885
- }
886
- };
887
-
888
- const restoreNodeScroll = ([element, x, y]: [HTMLElement, number, number]) => {
889
- element.scrollLeft = x;
890
- element.scrollTop = y;
891
- };
892
-
893
- const PSEUDO_BEFORE = ':before';
894
- const PSEUDO_AFTER = ':after';
895
- const PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = '___html2canvas___pseudoelement_before';
896
- const PSEUDO_HIDE_ELEMENT_CLASS_AFTER = '___html2canvas___pseudoelement_after';
897
-
898
- const PSEUDO_HIDE_ELEMENT_STYLE = `{
899
- content: "" !important;
900
- display: none !important;
901
- }`;
902
-
903
- const createPseudoHideStyles = (body: HTMLElement, cspNonce?: string) => {
904
- createStyles(
905
- body,
906
- `.${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE}${PSEUDO_BEFORE}${PSEUDO_HIDE_ELEMENT_STYLE}
907
- .${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}${PSEUDO_AFTER}${PSEUDO_HIDE_ELEMENT_STYLE}`,
908
- cspNonce
909
- );
910
- };
911
-
912
- const createStyles = (body: HTMLElement, styles: string, cspNonce?: string) => {
913
- const document = body.ownerDocument;
914
- if (document) {
915
- const style = document.createElement('style');
916
- style.textContent = styles;
917
- if (cspNonce) {
918
- style.nonce = cspNonce;
919
- }
920
- body.appendChild(style);
921
- }
922
- };
923
-
924
- const addBase = (targetELement: HTMLElement, baseUri: string) => {
925
- const baseNode = targetELement.ownerDocument.createElement('base');
926
- baseNode.href = baseUri;
927
- const headEle = targetELement.getElementsByTagName('head').item(0);
928
- headEle?.insertBefore(baseNode, headEle?.firstChild ?? null);
929
- };