diff --git a/src/Bounds.js b/src/Bounds.js index 5bab812..25f81e2 100644 --- a/src/Bounds.js +++ b/src/Bounds.js @@ -49,10 +49,8 @@ export const parseBounds = (node: HTMLElement, isTransformed: boolean): Bounds = }; const offsetBounds = (node: HTMLElement): Bounds => { - const parent = - node.offsetParent instanceof HTMLElement - ? offsetBounds(node.offsetParent) - : {top: 0, left: 0}; + // //$FlowFixMe + const parent = node.offsetParent ? offsetBounds(node.offsetParent) : {top: 0, left: 0}; return new Bounds( node.offsetLeft + parent.left, diff --git a/src/Clone.js b/src/Clone.js new file mode 100644 index 0000000..1d0d3d8 --- /dev/null +++ b/src/Clone.js @@ -0,0 +1,160 @@ +/* @flow */ +'use strict'; +import type {Bounds} from './Bounds'; +import type {Options} from './index'; + +const restoreOwnerScroll = (ownerDocument: Document, x: number, y: number) => { + if ( + ownerDocument.defaultView && + (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset) + ) { + ownerDocument.defaultView.scrollTo(x, y); + } +}; + +const cloneCanvasContents = (canvas: HTMLCanvasElement, clonedCanvas: HTMLCanvasElement) => { + try { + if (clonedCanvas) { + clonedCanvas.width = canvas.width; + clonedCanvas.height = canvas.height; + clonedCanvas + .getContext('2d') + .putImageData( + canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height), + 0, + 0 + ); + } + } catch (e) {} +}; + +const cloneNode = ( + node: Node, + referenceElement: [HTMLElement, ?HTMLElement], + scrolledElements: Array<[HTMLElement, number, number]> +) => { + const clone = + node.nodeType === Node.TEXT_NODE + ? document.createTextNode(node.nodeValue) + : node.cloneNode(false); + + if (referenceElement[0] === node && clone instanceof HTMLElement) { + referenceElement[1] = clone; + } + + for (let child = node.firstChild; child; child = child.nextSibling) { + if (child.nodeType !== Node.ELEMENT_NODE || child.nodeName !== 'SCRIPT') { + clone.appendChild(cloneNode(child, referenceElement, scrolledElements)); + } + } + + if (node instanceof HTMLElement) { + if (node.scrollTop !== 0 || node.scrollLeft !== 0) { + scrolledElements.push([node, node.scrollLeft, node.scrollTop]); + } + switch (node.nodeName) { + case 'CANVAS': + // $FlowFixMe + cloneCanvasContents(node, clone); + break; + case 'TEXTAREA': + case 'SELECT': + // $FlowFixMe + clone.value = node.value; + break; + } + } + + return clone; +}; + +const initNode = ([element, x, y]: [HTMLElement, number, number]) => { + element.scrollLeft = x; + element.scrollTop = y; +}; + +export const cloneWindow = ( + documentToBeCloned: Document, + ownerDocument: Document, + bounds: Bounds, + referenceElement: HTMLElement, + options: Options +): Promise<[HTMLIFrameElement, HTMLElement]> => { + const scrolledElements = []; + const referenceElementSearch = [referenceElement, null]; + if (!(documentToBeCloned.documentElement instanceof HTMLElement)) { + return Promise.reject(__DEV__ ? `Invalid document provided for cloning` : ''); + } + + const documentElement = cloneNode( + documentToBeCloned.documentElement, + referenceElementSearch, + scrolledElements + ); + + const cloneIframeContainer = ownerDocument.createElement('iframe'); + + cloneIframeContainer.className = 'html2canvas-container'; + cloneIframeContainer.style.visibility = 'hidden'; + cloneIframeContainer.style.position = 'fixed'; + cloneIframeContainer.style.left = '-10000px'; + cloneIframeContainer.style.top = '0px'; + cloneIframeContainer.style.border = '0'; + 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 { + return Promise.reject( + __DEV__ ? `Body element not found in Document that is getting rendered` : '' + ); + } + return new Promise((resolve, reject) => { + const documentClone = cloneIframeContainer.contentWindow.document; + + /* Chrome doesn't detect relative background-images assigned in inline