mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Implement proxied cross-origin iframe rendering
This commit is contained in:
parent
57dc7137b2
commit
929b9de6e0
188
src/Clone.js
188
src/Clone.js
@ -5,6 +5,8 @@ import type {Options} from './index';
|
||||
import type Logger from './Logger';
|
||||
import type {ImageElement} from './ImageLoader';
|
||||
|
||||
import {parseBounds} from './Bounds';
|
||||
import {Proxy} from './Proxy';
|
||||
import ImageLoader from './ImageLoader';
|
||||
import {copyCSSStyles} from './Util';
|
||||
import {parseBackgroundImage} from './parsing/background';
|
||||
@ -130,20 +132,24 @@ export class DocumentCloner {
|
||||
this.logger.child(iframeKey)
|
||||
);
|
||||
})
|
||||
.then(canvas => {
|
||||
const iframeCanvas = document.createElement('img');
|
||||
iframeCanvas.src = canvas.toDataURL();
|
||||
if (tempIframe.parentNode) {
|
||||
tempIframe.parentNode.replaceChild(
|
||||
copyCSSStyles(
|
||||
node.ownerDocument.defaultView.getComputedStyle(node),
|
||||
iframeCanvas
|
||||
),
|
||||
tempIframe
|
||||
);
|
||||
}
|
||||
return canvas;
|
||||
});
|
||||
.then(
|
||||
canvas =>
|
||||
new Promise((resolve, reject) => {
|
||||
const iframeCanvas = document.createElement('img');
|
||||
iframeCanvas.onload = () => resolve(canvas);
|
||||
iframeCanvas.onerror = reject;
|
||||
iframeCanvas.src = canvas.toDataURL();
|
||||
if (tempIframe.parentNode) {
|
||||
tempIframe.parentNode.replaceChild(
|
||||
copyCSSStyles(
|
||||
node.ownerDocument.defaultView.getComputedStyle(node),
|
||||
iframeCanvas
|
||||
),
|
||||
tempIframe
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
return tempIframe;
|
||||
}
|
||||
|
||||
@ -308,6 +314,8 @@ const initNode = ([element, x, y]: [HTMLElement, number, number]) => {
|
||||
const generateIframeKey = (): string =>
|
||||
Math.ceil(Date.now() + Math.random() * 10000000).toString(16);
|
||||
|
||||
const DATA_URI_REGEXP = /^data:text\/(.+);(base64)?,(.*)$/i;
|
||||
|
||||
const getIframeDocumentElement = (
|
||||
node: HTMLIFrameElement,
|
||||
options: Options
|
||||
@ -315,19 +323,44 @@ const getIframeDocumentElement = (
|
||||
try {
|
||||
return Promise.resolve(node.contentWindow.document.documentElement);
|
||||
} catch (e) {
|
||||
return Promise.reject();
|
||||
return options.proxy
|
||||
? Proxy(node.src, options)
|
||||
.then(html => {
|
||||
const match = html.match(DATA_URI_REGEXP);
|
||||
if (!match) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return match[2] === 'base64'
|
||||
? window.atob(decodeURIComponent(match[3]))
|
||||
: decodeURIComponent(match[3]);
|
||||
})
|
||||
.then(html =>
|
||||
createIframeContainer(
|
||||
node.ownerDocument,
|
||||
parseBounds(node)
|
||||
).then(cloneIframeContainer => {
|
||||
const cloneWindow = cloneIframeContainer.contentWindow;
|
||||
const documentClone = cloneWindow.document;
|
||||
|
||||
documentClone.open();
|
||||
documentClone.write(html);
|
||||
const iframeLoad = iframeLoader(cloneIframeContainer).then(
|
||||
() => documentClone.documentElement
|
||||
);
|
||||
|
||||
documentClone.close();
|
||||
return iframeLoad;
|
||||
})
|
||||
)
|
||||
: Promise.reject();
|
||||
}
|
||||
};
|
||||
|
||||
export const cloneWindow = (
|
||||
const createIframeContainer = (
|
||||
ownerDocument: Document,
|
||||
bounds: Bounds,
|
||||
referenceElement: HTMLElement,
|
||||
options: Options,
|
||||
logger: Logger,
|
||||
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>
|
||||
): Promise<[HTMLIFrameElement, HTMLElement, ImageLoader<ImageElement>]> => {
|
||||
const cloner = new DocumentCloner(referenceElement, options, logger, false, renderer);
|
||||
bounds: Bounds
|
||||
): Promise<HTMLIFrameElement> => {
|
||||
const cloneIframeContainer = ownerDocument.createElement('iframe');
|
||||
|
||||
cloneIframeContainer.className = 'html2canvas-container';
|
||||
@ -339,58 +372,81 @@ export const cloneWindow = (
|
||||
cloneIframeContainer.width = bounds.width.toString();
|
||||
cloneIframeContainer.height = bounds.height.toString();
|
||||
cloneIframeContainer.scrolling = 'no'; // ios won't scroll without it
|
||||
if (ownerDocument.body) {
|
||||
ownerDocument.body.appendChild(cloneIframeContainer);
|
||||
} else {
|
||||
if (!ownerDocument.body) {
|
||||
return Promise.reject(
|
||||
__DEV__ ? `Body element not found in Document that is getting rendered` : ''
|
||||
);
|
||||
}
|
||||
|
||||
ownerDocument.body.appendChild(cloneIframeContainer);
|
||||
|
||||
return Promise.resolve(cloneIframeContainer);
|
||||
};
|
||||
|
||||
const iframeLoader = (cloneIframeContainer: HTMLIFrameElement): Promise<HTMLIFrameElement> => {
|
||||
const cloneWindow = cloneIframeContainer.contentWindow;
|
||||
const documentClone = cloneWindow.document;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
cloneWindow.onload = cloneIframeContainer.onload = documentClone.onreadystatechange = () => {
|
||||
const interval = setInterval(() => {
|
||||
if (
|
||||
documentClone.body.childNodes.length > 0 &&
|
||||
documentClone.readyState === 'complete'
|
||||
) {
|
||||
clearInterval(interval);
|
||||
resolve(cloneIframeContainer);
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const cloneWindow = (
|
||||
ownerDocument: Document,
|
||||
bounds: Bounds,
|
||||
referenceElement: HTMLElement,
|
||||
options: Options,
|
||||
logger: Logger,
|
||||
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>
|
||||
): Promise<[HTMLIFrameElement, HTMLElement, ImageLoader<ImageElement>]> => {
|
||||
const cloner = new DocumentCloner(referenceElement, options, logger, false, renderer);
|
||||
|
||||
return createIframeContainer(ownerDocument, bounds).then(cloneIframeContainer => {
|
||||
const cloneWindow = cloneIframeContainer.contentWindow;
|
||||
const documentClone = cloneWindow.document;
|
||||
|
||||
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
|
||||
if window url is about:blank, we can assign the url to current by writing onto the document
|
||||
*/
|
||||
cloneWindow.onload = cloneIframeContainer.onload = () => {
|
||||
const interval = setInterval(() => {
|
||||
if (documentClone.body.childNodes.length > 0) {
|
||||
cloner.scrolledElements.forEach(initNode);
|
||||
clearInterval(interval);
|
||||
if (options.type === 'view') {
|
||||
cloneWindow.scrollTo(bounds.left, bounds.top);
|
||||
if (
|
||||
/(iPad|iPhone|iPod)/g.test(navigator.userAgent) &&
|
||||
(cloneWindow.scrollY !== bounds.top ||
|
||||
cloneWindow.scrollX !== bounds.left)
|
||||
) {
|
||||
documentClone.documentElement.style.top = -bounds.top + 'px';
|
||||
documentClone.documentElement.style.left = -bounds.left + 'px';
|
||||
documentClone.documentElement.style.position = 'absolute';
|
||||
}
|
||||
}
|
||||
if (
|
||||
cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof
|
||||
ownerDocument.defaultView.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof HTMLElement
|
||||
) {
|
||||
resolve([
|
||||
cloneIframeContainer,
|
||||
cloner.clonedReferenceElement,
|
||||
cloner.imageLoader
|
||||
]);
|
||||
} else {
|
||||
reject(
|
||||
__DEV__
|
||||
? `Error finding the ${referenceElement.nodeName} in the cloned document`
|
||||
: ''
|
||||
);
|
||||
}
|
||||
if window url is about:blank, we can assign the url to current by writing onto the document
|
||||
*/
|
||||
|
||||
const iframeLoad = iframeLoader(cloneIframeContainer).then(() => {
|
||||
cloner.scrolledElements.forEach(initNode);
|
||||
if (options.type === 'view') {
|
||||
cloneWindow.scrollTo(bounds.left, bounds.top);
|
||||
if (
|
||||
/(iPad|iPhone|iPod)/g.test(navigator.userAgent) &&
|
||||
(cloneWindow.scrollY !== bounds.top || cloneWindow.scrollX !== bounds.left)
|
||||
) {
|
||||
documentClone.documentElement.style.top = -bounds.top + 'px';
|
||||
documentClone.documentElement.style.left = -bounds.left + 'px';
|
||||
documentClone.documentElement.style.position = 'absolute';
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
}
|
||||
return cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof ownerDocument.defaultView.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof HTMLElement
|
||||
? Promise.resolve([
|
||||
cloneIframeContainer,
|
||||
cloner.clonedReferenceElement,
|
||||
cloner.imageLoader
|
||||
])
|
||||
: Promise.reject(
|
||||
__DEV__
|
||||
? `Error finding the ${referenceElement.nodeName} in the cloned document`
|
||||
: ''
|
||||
);
|
||||
});
|
||||
|
||||
documentClone.open();
|
||||
documentClone.write('<!DOCTYPE html><html></html>');
|
||||
@ -401,5 +457,7 @@ export const cloneWindow = (
|
||||
documentClone.documentElement
|
||||
);
|
||||
documentClone.close();
|
||||
|
||||
return iframeLoad;
|
||||
});
|
||||
};
|
||||
|
@ -239,10 +239,9 @@ const getImage = (
|
||||
case 'CANVAS':
|
||||
// $FlowFixMe
|
||||
return imageLoader.loadCanvas(node);
|
||||
case 'DIV':
|
||||
case 'IFRAME':
|
||||
const iframeKey = node.getAttribute('data-html2canvas-internal-iframe-key');
|
||||
if (iframeKey) {
|
||||
console.log('ok');
|
||||
return iframeKey;
|
||||
}
|
||||
break;
|
||||
|
@ -46,13 +46,18 @@ const parseNodeTree = (
|
||||
for (let childNode = node.firstChild, nextNode; childNode; childNode = nextNode) {
|
||||
nextNode = childNode.nextSibling;
|
||||
const defaultView = childNode.ownerDocument.defaultView;
|
||||
if (childNode instanceof defaultView.Text || childNode instanceof Text) {
|
||||
if (
|
||||
childNode instanceof defaultView.Text ||
|
||||
childNode instanceof Text ||
|
||||
(defaultView.parent && childNode instanceof defaultView.parent.Text)
|
||||
) {
|
||||
if (childNode.data.trim().length > 0) {
|
||||
parent.childNodes.push(TextContainer.fromTextNode(childNode, parent));
|
||||
}
|
||||
} else if (
|
||||
childNode instanceof defaultView.HTMLElement ||
|
||||
childNode instanceof HTMLElement
|
||||
childNode instanceof HTMLElement ||
|
||||
(defaultView.parent && childNode instanceof defaultView.parent.HTMLElement)
|
||||
) {
|
||||
if (IGNORED_NODE_NAMES.indexOf(childNode.nodeName) === -1) {
|
||||
const container = new NodeContainer(childNode, parent, imageLoader, index++);
|
||||
@ -99,7 +104,8 @@ const parseNodeTree = (
|
||||
}
|
||||
} else if (
|
||||
childNode instanceof defaultView.SVGSVGElement ||
|
||||
childNode instanceof SVGSVGElement
|
||||
childNode instanceof SVGSVGElement ||
|
||||
(defaultView.parent && childNode instanceof defaultView.parent.SVGSVGElement)
|
||||
) {
|
||||
const container = new NodeContainer(childNode, parent, imageLoader, index++);
|
||||
const treatAsRealStackingContext = createsRealStackingContext(container, childNode);
|
||||
|
@ -31,7 +31,7 @@ export const Proxy = (src: string, options: Options): Promise<string> => {
|
||||
} else {
|
||||
reject(
|
||||
__DEV__
|
||||
? `Failed to proxy image ${src.substring(
|
||||
? `Failed to proxy resource ${src.substring(
|
||||
0,
|
||||
256
|
||||
)} with status code ${xhr.status}`
|
||||
|
@ -38,7 +38,10 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||
|
||||
this.ctx.scale(this.options.scale, this.options.scale);
|
||||
this.ctx.textBaseline = 'bottom';
|
||||
options.logger.log(`Canvas renderer initialized with scale ${this.options.scale}`);
|
||||
options.logger.log(
|
||||
`Canvas renderer initialized (${options.width}x${options.height}) with scale ${this
|
||||
.options.scale}`
|
||||
);
|
||||
}
|
||||
|
||||
clip(clipPaths: Array<Path>, callback: () => void) {
|
||||
|
@ -2,12 +2,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>cross-origin iframe test</title>
|
||||
<script>
|
||||
var h2cOptions = {proxy: "http://localhost:8082"};
|
||||
</script>
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="https://html2canvas.hertzen.com/" width="800" height="800"></iframe>
|
||||
<iframe src="http://localhost:8081/assets/iframe/frame1.html" width="800" height="800"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user