Skip to content

Commit 181d1b1

Browse files
authored
feat: add support for <video> elements (#2788)
1 parent 46db867 commit 181d1b1

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-15
lines changed

src/dom/document-cloner.ts

+50-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
isStyleElement,
1414
isSVGElementNode,
1515
isTextareaElement,
16-
isTextNode
16+
isTextNode,
17+
isVideoElement
1718
} from './node-parser';
1819
import {isIdentToken, nonFunctionArgSeparator} from '../css/syntax/parser';
1920
import {TokenType} from '../css/syntax/tokenizer';
@@ -145,7 +146,9 @@ export class DocumentCloner {
145146
if (isCanvasElement(node)) {
146147
return this.createCanvasClone(node);
147148
}
148-
149+
if (isVideoElement(node)) {
150+
return this.createVideoClone(node);
151+
}
149152
if (isStyleElement(node)) {
150153
return this.createStyleClone(node);
151154
}
@@ -244,6 +247,32 @@ export class DocumentCloner {
244247
return clonedCanvas;
245248
}
246249

250+
createVideoClone(video: HTMLVideoElement): HTMLCanvasElement {
251+
const canvas = video.ownerDocument.createElement('canvas');
252+
253+
canvas.width = video.offsetWidth;
254+
canvas.height = video.offsetHeight;
255+
const ctx = canvas.getContext('2d');
256+
257+
try {
258+
if (ctx) {
259+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
260+
if (!this.options.allowTaint) {
261+
ctx.getImageData(0, 0, canvas.width, canvas.height);
262+
}
263+
}
264+
return canvas;
265+
} catch (e) {
266+
this.context.logger.info(`Unable to clone video as it is tainted`, video);
267+
}
268+
269+
const blankCanvas = video.ownerDocument.createElement('canvas');
270+
271+
blankCanvas.width = video.offsetWidth;
272+
blankCanvas.height = video.offsetHeight;
273+
return blankCanvas;
274+
}
275+
247276
appendChildNode(clone: HTMLElement | SVGElement, child: Node, copyStyles: boolean): void {
248277
if (
249278
!isElementNode(child) ||
@@ -257,6 +286,23 @@ export class DocumentCloner {
257286
}
258287
}
259288

289+
cloneChildNodes(node: Element, clone: HTMLElement | SVGElement, copyStyles: boolean): void {
290+
for (
291+
let child = node.shadowRoot ? node.shadowRoot.firstChild : node.firstChild;
292+
child;
293+
child = child.nextSibling
294+
) {
295+
if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === 'function') {
296+
const assignedNodes = child.assignedNodes() as ChildNode[];
297+
if (assignedNodes.length) {
298+
assignedNodes.forEach((assignedNode) => this.appendChildNode(clone, assignedNode, copyStyles));
299+
}
300+
} else {
301+
this.appendChildNode(clone, child, copyStyles);
302+
}
303+
}
304+
}
305+
260306
cloneNode(node: Node, copyStyles: boolean): Node {
261307
if (isTextNode(node)) {
262308
return document.createTextNode(node.data);
@@ -290,19 +336,8 @@ export class DocumentCloner {
290336
copyStyles = true;
291337
}
292338

293-
for (
294-
let child = node.shadowRoot ? node.shadowRoot.firstChild : node.firstChild;
295-
child;
296-
child = child.nextSibling
297-
) {
298-
if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === 'function') {
299-
const assignedNodes = child.assignedNodes() as ChildNode[];
300-
if (assignedNodes.length) {
301-
assignedNodes.forEach((assignedNode) => this.appendChildNode(clone, assignedNode, copyStyles));
302-
}
303-
} else {
304-
this.appendChildNode(clone, child, copyStyles);
305-
}
339+
if (!isVideoElement(node)) {
340+
this.cloneChildNodes(node, clone, copyStyles);
306341
}
307342

308343
if (before) {

src/dom/node-parser.ts

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export const isHTMLElement = (node: Element): node is HTMLHtmlElement => node.ta
124124
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
125125
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
126126
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
127+
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';
127128
export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG';
128129
export const isIFrameElement = (node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME';
129130
export const isStyleElement = (node: Element): node is HTMLStyleElement => node.tagName === 'STYLE';

tests/assets/cc0-video.mp4

379 KB
Binary file not shown.

tests/reftests/images/video.html

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Video tests</title>
6+
<script type="text/javascript" src="../../test.js"></script>
7+
</head>
8+
<body>
9+
<h2>Same origin</h2>
10+
<video controls width="250">
11+
<source src="../../assets/cc0-video.mp4" type="video/mp4">
12+
Sorry, your browser doesn't support embedded videos.
13+
</video>
14+
<h2>Cross-origin (doesn't taint)</h2>
15+
<video controls width="250">
16+
<source src="http://localhost:8081/cors/tests/assets/cc0-video.mp4" type="video/mp4">
17+
Sorry, your browser doesn't support embedded videos.
18+
</video>
19+
</body>
20+
</html>

0 commit comments

Comments
 (0)