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.
- package/dist/html2canvas-pro.esm.js +10226 -10526
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +10869 -11171
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +8 -8
- package/dist/lib/config.js +0 -22
- package/dist/lib/core/cache-storage.js +3 -40
- package/dist/lib/core/constants.js +25 -0
- package/dist/lib/core/context.js +1 -0
- package/dist/lib/core/features.js +3 -2
- package/dist/lib/core/validator.js +3 -3
- package/dist/lib/css/grouped/background-styles.js +36 -0
- package/dist/lib/css/grouped/border-styles.js +75 -0
- package/dist/lib/css/grouped/font-styles.js +93 -0
- package/dist/lib/css/grouped/layout-styles.js +127 -0
- package/dist/lib/css/index.js +74 -46
- package/dist/lib/css/layout/text.js +7 -6
- package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
- package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
- package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
- package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
- package/dist/lib/css/property-descriptors/border-radius.js +1 -1
- package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
- package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
- package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
- package/dist/lib/css/property-descriptors/filter.js +76 -0
- package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
- package/dist/lib/css/property-descriptors/object-fit.js +1 -1
- package/dist/lib/css/property-descriptors/object-position.js +42 -0
- package/dist/lib/css/property-descriptors/visibility.js +1 -1
- package/dist/lib/css/property-descriptors/zoom.js +18 -0
- package/dist/lib/css/syntax/parser.js +0 -1
- package/dist/lib/css/types/color.js +5 -1
- package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
- package/dist/lib/css/types/image.js +12 -2
- package/dist/lib/css/types/length-percentage.js +6 -2
- package/dist/lib/css/types/safe-eval.js +80 -0
- package/dist/lib/dom/document-cloner.js +23 -163
- package/dist/lib/dom/slot-cloner.js +176 -0
- package/dist/lib/index.js +1 -17
- package/dist/lib/render/canvas/background-renderer.js +169 -30
- package/dist/lib/render/canvas/border-image-renderer.js +153 -0
- package/dist/lib/render/canvas/canvas-renderer.js +39 -190
- package/dist/lib/render/canvas/content-renderer.js +202 -0
- package/dist/lib/render/canvas/effects-renderer.js +3 -0
- package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
- package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
- package/dist/lib/render/canvas/text-renderer.js +100 -224
- package/dist/lib/render/effects.js +38 -3
- package/dist/lib/render/object-fit.js +19 -15
- package/dist/lib/render/stacking-context.js +11 -0
- package/dist/types/config.d.ts +0 -10
- package/dist/types/core/cache-storage.d.ts +0 -24
- package/dist/types/core/constants.d.ts +22 -0
- package/dist/types/core/context.d.ts +3 -0
- package/dist/types/core/performance-monitor.d.ts +4 -4
- package/dist/types/core/validator.d.ts +6 -8
- package/dist/types/css/grouped/background-styles.d.ts +16 -0
- package/dist/types/css/grouped/border-styles.d.ts +31 -0
- package/dist/types/css/grouped/font-styles.d.ts +35 -0
- package/dist/types/css/grouped/layout-styles.d.ts +46 -0
- package/dist/types/css/index.d.ts +30 -0
- package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
- package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
- package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
- package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
- package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
- package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
- package/dist/types/css/property-descriptors/filter.d.ts +3 -0
- package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
- package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
- package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
- package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
- package/dist/types/css/types/image.d.ts +4 -2
- package/dist/types/css/types/safe-eval.d.ts +8 -0
- package/dist/types/dom/document-cloner.d.ts +3 -44
- package/dist/types/dom/slot-cloner.d.ts +66 -0
- package/dist/types/index.d.ts +3 -7
- package/dist/types/options.d.ts +11 -0
- package/dist/types/render/canvas/background-renderer.d.ts +23 -0
- package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
- package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
- package/dist/types/render/canvas/content-renderer.d.ts +44 -0
- package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
- package/dist/types/render/canvas/text-renderer.d.ts +12 -1
- package/dist/types/render/effects.d.ts +12 -2
- package/dist/types/render/object-fit.d.ts +2 -1
- package/dist/types/render/renderer-interface.d.ts +11 -9
- package/package.json +7 -20
- package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
- package/dist/lib/invariant.js +0 -9
- package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
- package/dist/types/invariant.d.ts +0 -1
- package/src/__tests__/index.ts +0 -99
- package/src/config.ts +0 -107
- package/src/core/__mocks__/cache-storage.ts +0 -1
- package/src/core/__mocks__/context.ts +0 -19
- package/src/core/__mocks__/features.ts +0 -8
- package/src/core/__mocks__/logger.ts +0 -17
- package/src/core/__tests__/cache-storage.test.ts +0 -205
- package/src/core/__tests__/cache-storage.ts +0 -278
- package/src/core/__tests__/logger.ts +0 -29
- package/src/core/__tests__/validator.ts +0 -359
- package/src/core/bitwise.ts +0 -1
- package/src/core/cache-storage.ts +0 -315
- package/src/core/context.ts +0 -31
- package/src/core/debugger.ts +0 -32
- package/src/core/features.ts +0 -222
- package/src/core/logger.ts +0 -64
- package/src/core/origin-checker.ts +0 -57
- package/src/core/performance-monitor.ts +0 -241
- package/src/core/render-element.ts +0 -272
- package/src/core/util.ts +0 -1
- package/src/core/validator.ts +0 -593
- package/src/css/index.ts +0 -427
- package/src/css/layout/__mocks__/bounds.ts +0 -6
- package/src/css/layout/bounds.ts +0 -79
- package/src/css/layout/text.ts +0 -161
- package/src/css/property-descriptor.ts +0 -49
- package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
- package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
- package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
- package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
- package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
- package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
- package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
- package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
- package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
- package/src/css/property-descriptors/background-clip.ts +0 -30
- package/src/css/property-descriptors/background-color.ts +0 -9
- package/src/css/property-descriptors/background-image.ts +0 -27
- package/src/css/property-descriptors/background-origin.ts +0 -31
- package/src/css/property-descriptors/background-position.ts +0 -38
- package/src/css/property-descriptors/background-repeat.ts +0 -44
- package/src/css/property-descriptors/background-size.ts +0 -27
- package/src/css/property-descriptors/border-color.ts +0 -13
- package/src/css/property-descriptors/border-radius.ts +0 -19
- package/src/css/property-descriptors/border-style.ts +0 -34
- package/src/css/property-descriptors/border-width.ts +0 -20
- package/src/css/property-descriptors/box-shadow.ts +0 -60
- package/src/css/property-descriptors/clip-path.ts +0 -271
- package/src/css/property-descriptors/color.ts +0 -9
- package/src/css/property-descriptors/content.ts +0 -26
- package/src/css/property-descriptors/counter-increment.ts +0 -43
- package/src/css/property-descriptors/counter-reset.ts +0 -36
- package/src/css/property-descriptors/direction.ts +0 -23
- package/src/css/property-descriptors/display.ts +0 -117
- package/src/css/property-descriptors/duration.ts +0 -14
- package/src/css/property-descriptors/float.ts +0 -29
- package/src/css/property-descriptors/font-family.ts +0 -38
- package/src/css/property-descriptors/font-size.ts +0 -9
- package/src/css/property-descriptors/font-style.ts +0 -25
- package/src/css/property-descriptors/font-variant.ts +0 -12
- package/src/css/property-descriptors/font-weight.ts +0 -26
- package/src/css/property-descriptors/image-rendering.ts +0 -33
- package/src/css/property-descriptors/letter-spacing.ts +0 -25
- package/src/css/property-descriptors/line-break.ts +0 -22
- package/src/css/property-descriptors/line-height.ts +0 -22
- package/src/css/property-descriptors/list-style-image.ts +0 -19
- package/src/css/property-descriptors/list-style-position.ts +0 -22
- package/src/css/property-descriptors/list-style-type.ts +0 -179
- package/src/css/property-descriptors/margin.ts +0 -13
- package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
- package/src/css/property-descriptors/object-fit.ts +0 -39
- package/src/css/property-descriptors/opacity.ts +0 -15
- package/src/css/property-descriptors/overflow-wrap.ts +0 -22
- package/src/css/property-descriptors/overflow.ts +0 -34
- package/src/css/property-descriptors/padding.ts +0 -14
- package/src/css/property-descriptors/paint-order.ts +0 -42
- package/src/css/property-descriptors/position.ts +0 -30
- package/src/css/property-descriptors/quotes.ts +0 -57
- package/src/css/property-descriptors/rotate.ts +0 -34
- package/src/css/property-descriptors/text-align.ts +0 -26
- package/src/css/property-descriptors/text-decoration-color.ts +0 -9
- package/src/css/property-descriptors/text-decoration-line.ts +0 -38
- package/src/css/property-descriptors/text-decoration-style.ts +0 -32
- package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
- package/src/css/property-descriptors/text-overflow.ts +0 -23
- package/src/css/property-descriptors/text-shadow.ts +0 -52
- package/src/css/property-descriptors/text-transform.ts +0 -27
- package/src/css/property-descriptors/text-underline-offset.ts +0 -27
- package/src/css/property-descriptors/transform-origin.ts +0 -29
- package/src/css/property-descriptors/transform.ts +0 -74
- package/src/css/property-descriptors/visibility.ts +0 -25
- package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
- package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
- package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
- package/src/css/property-descriptors/word-break.ts +0 -25
- package/src/css/property-descriptors/writing-mode.ts +0 -37
- package/src/css/property-descriptors/z-index.ts +0 -27
- package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
- package/src/css/syntax/parser.ts +0 -188
- package/src/css/syntax/tokenizer.ts +0 -822
- package/src/css/type-descriptor.ts +0 -7
- package/src/css/types/__tests__/color-tests.ts +0 -147
- package/src/css/types/__tests__/image-tests.ts +0 -239
- package/src/css/types/angle.ts +0 -86
- package/src/css/types/color-math.ts +0 -22
- package/src/css/types/color-spaces/a98.ts +0 -86
- package/src/css/types/color-spaces/p3.ts +0 -92
- package/src/css/types/color-spaces/pro-photo.ts +0 -87
- package/src/css/types/color-spaces/rec2020.ts +0 -90
- package/src/css/types/color-spaces/srgb.ts +0 -87
- package/src/css/types/color-utilities.ts +0 -452
- package/src/css/types/color.ts +0 -485
- package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
- package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
- package/src/css/types/functions/-webkit-gradient.ts +0 -69
- package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
- package/src/css/types/functions/counter.ts +0 -511
- package/src/css/types/functions/gradient.ts +0 -206
- package/src/css/types/functions/linear-gradient.ts +0 -28
- package/src/css/types/functions/radial-gradient.ts +0 -101
- package/src/css/types/image.ts +0 -120
- package/src/css/types/index.ts +0 -1
- package/src/css/types/length-percentage.ts +0 -137
- package/src/css/types/length.ts +0 -7
- package/src/css/types/time.ts +0 -20
- package/src/dom/__mocks__/document-cloner.ts +0 -22
- package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
- package/src/dom/__tests__/element-container.test.ts +0 -129
- package/src/dom/document-cloner.ts +0 -929
- package/src/dom/dom-normalizer.ts +0 -133
- package/src/dom/element-container.ts +0 -75
- package/src/dom/elements/li-element-container.ts +0 -10
- package/src/dom/elements/ol-element-container.ts +0 -12
- package/src/dom/elements/select-element-container.ts +0 -10
- package/src/dom/elements/textarea-element-container.ts +0 -9
- package/src/dom/node-parser.ts +0 -177
- package/src/dom/node-type-guards.ts +0 -70
- package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
- package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
- package/src/dom/replaced-elements/image-element-container.ts +0 -16
- package/src/dom/replaced-elements/index.ts +0 -5
- package/src/dom/replaced-elements/input-element-container.ts +0 -105
- package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
- package/src/dom/replaced-elements/svg-element-container.ts +0 -23
- package/src/dom/text-container.ts +0 -42
- package/src/global.d.ts +0 -19
- package/src/index.ts +0 -82
- package/src/invariant.ts +0 -5
- package/src/options.ts +0 -55
- package/src/render/__tests__/object-fit.test.ts +0 -85
- package/src/render/background.ts +0 -298
- package/src/render/bezier-curve.ts +0 -47
- package/src/render/border.ts +0 -165
- package/src/render/bound-curves.ts +0 -388
- package/src/render/box-sizing.ts +0 -31
- package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
- package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
- package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
- package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
- package/src/render/canvas/background-renderer.ts +0 -271
- package/src/render/canvas/border-renderer.ts +0 -224
- package/src/render/canvas/canvas-path.ts +0 -31
- package/src/render/canvas/canvas-renderer.ts +0 -641
- package/src/render/canvas/effects-renderer.ts +0 -130
- package/src/render/canvas/foreignobject-renderer.ts +0 -53
- package/src/render/canvas/text-renderer.ts +0 -700
- package/src/render/effects.ts +0 -75
- package/src/render/font-metrics.ts +0 -72
- package/src/render/object-fit.ts +0 -100
- package/src/render/path.ts +0 -37
- package/src/render/renderer-interface.ts +0 -28
- package/src/render/stacking-context.ts +0 -386
- 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, '"').replace(/>/g, '>');
|
|
864
|
-
}
|
|
865
|
-
if (doctype.publicId) {
|
|
866
|
-
str += ' PUBLIC "' + doctype.publicId.replace(/"/g, '"') + '"';
|
|
867
|
-
if (doctype.systemId) {
|
|
868
|
-
str += ' "' + doctype.systemId.replace(/"/g, '"') + '"';
|
|
869
|
-
}
|
|
870
|
-
} else if (doctype.systemId) {
|
|
871
|
-
str += ' SYSTEM "' + doctype.systemId.replace(/"/g, '"') + '"';
|
|
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
|
-
};
|