mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
feat: add support for <video> elements (#2788)
This commit is contained in:
parent
46db86755f
commit
181d1b1103
@ -13,7 +13,8 @@ import {
|
|||||||
isStyleElement,
|
isStyleElement,
|
||||||
isSVGElementNode,
|
isSVGElementNode,
|
||||||
isTextareaElement,
|
isTextareaElement,
|
||||||
isTextNode
|
isTextNode,
|
||||||
|
isVideoElement
|
||||||
} from './node-parser';
|
} from './node-parser';
|
||||||
import {isIdentToken, nonFunctionArgSeparator} from '../css/syntax/parser';
|
import {isIdentToken, nonFunctionArgSeparator} from '../css/syntax/parser';
|
||||||
import {TokenType} from '../css/syntax/tokenizer';
|
import {TokenType} from '../css/syntax/tokenizer';
|
||||||
@ -145,7 +146,9 @@ export class DocumentCloner {
|
|||||||
if (isCanvasElement(node)) {
|
if (isCanvasElement(node)) {
|
||||||
return this.createCanvasClone(node);
|
return this.createCanvasClone(node);
|
||||||
}
|
}
|
||||||
|
if (isVideoElement(node)) {
|
||||||
|
return this.createVideoClone(node);
|
||||||
|
}
|
||||||
if (isStyleElement(node)) {
|
if (isStyleElement(node)) {
|
||||||
return this.createStyleClone(node);
|
return this.createStyleClone(node);
|
||||||
}
|
}
|
||||||
@ -244,6 +247,32 @@ export class DocumentCloner {
|
|||||||
return clonedCanvas;
|
return clonedCanvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createVideoClone(video: HTMLVideoElement): HTMLCanvasElement {
|
||||||
|
const canvas = video.ownerDocument.createElement('canvas');
|
||||||
|
|
||||||
|
canvas.width = video.offsetWidth;
|
||||||
|
canvas.height = video.offsetHeight;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ctx) {
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
if (!this.options.allowTaint) {
|
||||||
|
ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canvas;
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.info(`Unable to clone video as it is tainted`, video);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blankCanvas = video.ownerDocument.createElement('canvas');
|
||||||
|
|
||||||
|
blankCanvas.width = video.offsetWidth;
|
||||||
|
blankCanvas.height = video.offsetHeight;
|
||||||
|
return blankCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
appendChildNode(clone: HTMLElement | SVGElement, child: Node, copyStyles: boolean): void {
|
appendChildNode(clone: HTMLElement | SVGElement, child: Node, copyStyles: boolean): void {
|
||||||
if (
|
if (
|
||||||
!isElementNode(child) ||
|
!isElementNode(child) ||
|
||||||
@ -257,6 +286,23 @@ export class DocumentCloner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneChildNodes(node: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
|
||||||
|
for (
|
||||||
|
let child = node.shadowRoot ? node.shadowRoot.firstChild : node.firstChild;
|
||||||
|
child;
|
||||||
|
child = child.nextSibling
|
||||||
|
) {
|
||||||
|
if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === 'function') {
|
||||||
|
const assignedNodes = child.assignedNodes() as ChildNode[];
|
||||||
|
if (assignedNodes.length) {
|
||||||
|
assignedNodes.forEach((assignedNode) => this.appendChildNode(clone, assignedNode, copyStyles));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.appendChildNode(clone, child, copyStyles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cloneNode(node: Node, copyStyles: boolean): Node {
|
cloneNode(node: Node, copyStyles: boolean): Node {
|
||||||
if (isTextNode(node)) {
|
if (isTextNode(node)) {
|
||||||
return document.createTextNode(node.data);
|
return document.createTextNode(node.data);
|
||||||
@ -290,19 +336,8 @@ export class DocumentCloner {
|
|||||||
copyStyles = true;
|
copyStyles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (
|
if (!isVideoElement(node)) {
|
||||||
let child = node.shadowRoot ? node.shadowRoot.firstChild : node.firstChild;
|
this.cloneChildNodes(node, clone, copyStyles);
|
||||||
child;
|
|
||||||
child = child.nextSibling
|
|
||||||
) {
|
|
||||||
if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === 'function') {
|
|
||||||
const assignedNodes = child.assignedNodes() as ChildNode[];
|
|
||||||
if (assignedNodes.length) {
|
|
||||||
assignedNodes.forEach((assignedNode) => this.appendChildNode(clone, assignedNode, copyStyles));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.appendChildNode(clone, child, copyStyles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
|
@ -124,6 +124,7 @@ export const isHTMLElement = (node: Element): node is HTMLHtmlElement => node.ta
|
|||||||
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
|
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
|
||||||
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
|
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
|
||||||
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
|
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
|
||||||
|
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';
|
||||||
export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG';
|
export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG';
|
||||||
export const isIFrameElement = (node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME';
|
export const isIFrameElement = (node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME';
|
||||||
export const isStyleElement = (node: Element): node is HTMLStyleElement => node.tagName === 'STYLE';
|
export const isStyleElement = (node: Element): node is HTMLStyleElement => node.tagName === 'STYLE';
|
||||||
|
BIN
tests/assets/cc0-video.mp4
Normal file
BIN
tests/assets/cc0-video.mp4
Normal file
Binary file not shown.
20
tests/reftests/images/video.html
Normal file
20
tests/reftests/images/video.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Video tests</title>
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Same origin</h2>
|
||||||
|
<video controls width="250">
|
||||||
|
<source src="../../assets/cc0-video.mp4" type="video/mp4">
|
||||||
|
Sorry, your browser doesn't support embedded videos.
|
||||||
|
</video>
|
||||||
|
<h2>Cross-origin (doesn't taint)</h2>
|
||||||
|
<video controls width="250">
|
||||||
|
<source src="http://localhost:8081/cors/tests/assets/cc0-video.mp4" type="video/mp4">
|
||||||
|
Sorry, your browser doesn't support embedded videos.
|
||||||
|
</video>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user