feat: add support for <video> elements (#2788)

This commit is contained in:
Niklas von Hertzen 2022-01-02 16:14:27 +08:00 committed by GitHub
parent 46db86755f
commit 181d1b1103
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 15 deletions

View File

@ -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) {

View File

@ -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

Binary file not shown.

View 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>