UNPKG

html2canvas

Version:
582 lines (500 loc) 26.1 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.cloneWindow = exports.DocumentCloner = undefined; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _Bounds = require('./Bounds'); var _Proxy = require('./Proxy'); var _ResourceLoader = require('./ResourceLoader'); var _ResourceLoader2 = _interopRequireDefault(_ResourceLoader); var _Util = require('./Util'); var _background = require('./parsing/background'); var _CanvasRenderer = require('./renderer/CanvasRenderer'); var _CanvasRenderer2 = _interopRequireDefault(_CanvasRenderer); var _PseudoNodeContent = require('./PseudoNodeContent'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var IGNORE_ATTRIBUTE = 'data-html2canvas-ignore'; var DocumentCloner = exports.DocumentCloner = function () { function DocumentCloner(element, options, logger, copyInline, renderer) { _classCallCheck(this, DocumentCloner); this.referenceElement = element; this.scrolledElements = []; this.copyStyles = copyInline; this.inlineImages = copyInline; this.logger = logger; this.options = options; this.renderer = renderer; this.resourceLoader = new _ResourceLoader2.default(options, logger, window); this.pseudoContentData = { counters: {}, quoteDepth: 0 }; // $FlowFixMe this.documentElement = this.cloneNode(element.ownerDocument.documentElement); } _createClass(DocumentCloner, [{ key: 'inlineAllImages', value: function inlineAllImages(node) { var _this = this; if (this.inlineImages && node) { var style = node.style; Promise.all((0, _background.parseBackgroundImage)(style.backgroundImage).map(function (backgroundImage) { if (backgroundImage.method === 'url') { return _this.resourceLoader.inlineImage(backgroundImage.args[0]).then(function (img) { return img && typeof img.src === 'string' ? 'url("' + img.src + '")' : 'none'; }).catch(function (e) { if (process.env.NODE_ENV !== 'production') { _this.logger.log('Unable to load image', e); } }); } return Promise.resolve('' + backgroundImage.prefix + backgroundImage.method + '(' + backgroundImage.args.join(',') + ')'); })).then(function (backgroundImages) { if (backgroundImages.length > 1) { // TODO Multiple backgrounds somehow broken in Chrome style.backgroundColor = ''; } style.backgroundImage = backgroundImages.join(','); }); if (node instanceof HTMLImageElement) { this.resourceLoader.inlineImage(node.src).then(function (img) { if (img && node instanceof HTMLImageElement && node.parentNode) { var parentNode = node.parentNode; var clonedChild = (0, _Util.copyCSSStyles)(node.style, img.cloneNode(false)); parentNode.replaceChild(clonedChild, node); } }).catch(function (e) { if (process.env.NODE_ENV !== 'production') { _this.logger.log('Unable to load image', e); } }); } } } }, { key: 'inlineFonts', value: function inlineFonts(document) { var _this2 = this; return Promise.all(Array.from(document.styleSheets).map(function (sheet) { if (sheet.href) { return fetch(sheet.href).then(function (res) { return res.text(); }).then(function (text) { return createStyleSheetFontsFromText(text, sheet.href); }).catch(function (e) { if (process.env.NODE_ENV !== 'production') { _this2.logger.log('Unable to load stylesheet', e); } return []; }); } return getSheetFonts(sheet, document); })).then(function (fonts) { return fonts.reduce(function (acc, font) { return acc.concat(font); }, []); }).then(function (fonts) { return Promise.all(fonts.map(function (font) { return fetch(font.formats[0].src).then(function (response) { return response.blob(); }).then(function (blob) { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onerror = reject; reader.onload = function () { // $FlowFixMe var result = reader.result; resolve(result); }; reader.readAsDataURL(blob); }); }).then(function (dataUri) { font.fontFace.setProperty('src', 'url("' + dataUri + '")'); return '@font-face {' + font.fontFace.cssText + ' '; }); })); }).then(function (fontCss) { var style = document.createElement('style'); style.textContent = fontCss.join('\n'); _this2.documentElement.appendChild(style); }); } }, { key: 'createElementClone', value: function createElementClone(node) { var _this3 = this; if (this.copyStyles && node instanceof HTMLCanvasElement) { var img = node.ownerDocument.createElement('img'); try { img.src = node.toDataURL(); return img; } catch (e) { if (process.env.NODE_ENV !== 'production') { this.logger.log('Unable to clone canvas contents, canvas is tainted'); } } } if (node instanceof HTMLIFrameElement) { var tempIframe = node.cloneNode(false); var iframeKey = generateIframeKey(); tempIframe.setAttribute('data-html2canvas-internal-iframe-key', iframeKey); var _parseBounds = (0, _Bounds.parseBounds)(node, 0, 0), width = _parseBounds.width, height = _parseBounds.height; this.resourceLoader.cache[iframeKey] = getIframeDocumentElement(node, this.options).then(function (documentElement) { return _this3.renderer(documentElement, { async: _this3.options.async, allowTaint: _this3.options.allowTaint, backgroundColor: '#ffffff', canvas: null, imageTimeout: _this3.options.imageTimeout, logging: _this3.options.logging, proxy: _this3.options.proxy, removeContainer: _this3.options.removeContainer, scale: _this3.options.scale, foreignObjectRendering: _this3.options.foreignObjectRendering, useCORS: _this3.options.useCORS, target: new _CanvasRenderer2.default(), width: width, height: height, x: 0, y: 0, windowWidth: documentElement.ownerDocument.defaultView.innerWidth, windowHeight: documentElement.ownerDocument.defaultView.innerHeight, scrollX: documentElement.ownerDocument.defaultView.pageXOffset, scrollY: documentElement.ownerDocument.defaultView.pageYOffset }, _this3.logger.child(iframeKey)); }).then(function (canvas) { return new Promise(function (resolve, reject) { var iframeCanvas = document.createElement('img'); iframeCanvas.onload = function () { return resolve(canvas); }; iframeCanvas.onerror = reject; iframeCanvas.src = canvas.toDataURL(); if (tempIframe.parentNode) { tempIframe.parentNode.replaceChild((0, _Util.copyCSSStyles)(node.ownerDocument.defaultView.getComputedStyle(node), iframeCanvas), tempIframe); } }); }); return tempIframe; } if (node instanceof HTMLStyleElement && node.sheet && node.sheet.cssRules) { var css = [].slice.call(node.sheet.cssRules, 0).reduce(function (css, rule) { try { if (rule && rule.cssText) { return css + rule.cssText; } return css; } catch (err) { _this3.logger.log('Unable to access cssText property', rule.name); return css; } }, ''); var style = node.cloneNode(false); style.textContent = css; return style; } return node.cloneNode(false); } }, { key: 'cloneNode', value: function cloneNode(node) { var clone = node.nodeType === Node.TEXT_NODE ? document.createTextNode(node.nodeValue) : this.createElementClone(node); var window = node.ownerDocument.defaultView; var style = node instanceof window.HTMLElement ? window.getComputedStyle(node) : null; var styleBefore = node instanceof window.HTMLElement ? window.getComputedStyle(node, ':before') : null; var styleAfter = node instanceof window.HTMLElement ? window.getComputedStyle(node, ':after') : null; if (this.referenceElement === node && clone instanceof window.HTMLElement) { this.clonedReferenceElement = clone; } if (clone instanceof window.HTMLBodyElement) { createPseudoHideStyles(clone); } var counters = (0, _PseudoNodeContent.parseCounterReset)(style, this.pseudoContentData); var contentBefore = (0, _PseudoNodeContent.resolvePseudoContent)(node, styleBefore, this.pseudoContentData); for (var child = node.firstChild; child; child = child.nextSibling) { if (child.nodeType !== Node.ELEMENT_NODE || child.nodeName !== 'SCRIPT' && // $FlowFixMe !child.hasAttribute(IGNORE_ATTRIBUTE) && (typeof this.options.ignoreElements !== 'function' || // $FlowFixMe !this.options.ignoreElements(child))) { if (!this.copyStyles || child.nodeName !== 'STYLE') { clone.appendChild(this.cloneNode(child)); } } } var contentAfter = (0, _PseudoNodeContent.resolvePseudoContent)(node, styleAfter, this.pseudoContentData); (0, _PseudoNodeContent.popCounters)(counters, this.pseudoContentData); if (node instanceof window.HTMLElement && clone instanceof window.HTMLElement) { if (styleBefore) { this.inlineAllImages(inlinePseudoElement(node, clone, styleBefore, contentBefore, PSEUDO_BEFORE)); } if (styleAfter) { this.inlineAllImages(inlinePseudoElement(node, clone, styleAfter, contentAfter, PSEUDO_AFTER)); } if (style && this.copyStyles && !(node instanceof HTMLIFrameElement)) { (0, _Util.copyCSSStyles)(style, clone); } this.inlineAllImages(clone); if (node.scrollTop !== 0 || node.scrollLeft !== 0) { this.scrolledElements.push([clone, node.scrollLeft, node.scrollTop]); } switch (node.nodeName) { case 'CANVAS': if (!this.copyStyles) { cloneCanvasContents(node, clone); } break; case 'TEXTAREA': case 'SELECT': clone.value = node.value; break; } } return clone; } }]); return DocumentCloner; }(); var getSheetFonts = function getSheetFonts(sheet, document) { // $FlowFixMe return (sheet.cssRules ? Array.from(sheet.cssRules) : []).filter(function (rule) { return rule.type === CSSRule.FONT_FACE_RULE; }).map(function (rule) { var src = (0, _background.parseBackgroundImage)(rule.style.getPropertyValue('src')); var formats = []; for (var i = 0; i < src.length; i++) { if (src[i].method === 'url' && src[i + 1] && src[i + 1].method === 'format') { var a = document.createElement('a'); a.href = src[i].args[0]; if (document.body) { document.body.appendChild(a); } var font = { src: a.href, format: src[i + 1].args[0] }; formats.push(font); } } return { // TODO select correct format for browser), formats: formats.filter(function (font) { return (/^woff/i.test(font.format) ); }), fontFace: rule.style }; }).filter(function (font) { return font.formats.length; }); }; var createStyleSheetFontsFromText = function createStyleSheetFontsFromText(text, baseHref) { var doc = document.implementation.createHTMLDocument(''); var base = document.createElement('base'); // $FlowFixMe base.href = baseHref; var style = document.createElement('style'); style.textContent = text; if (doc.head) { doc.head.appendChild(base); } if (doc.body) { doc.body.appendChild(style); } return style.sheet ? getSheetFonts(style.sheet, doc) : []; }; var restoreOwnerScroll = function restoreOwnerScroll(ownerDocument, x, y) { if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) { ownerDocument.defaultView.scrollTo(x, y); } }; var cloneCanvasContents = function cloneCanvasContents(canvas, clonedCanvas) { try { if (clonedCanvas) { clonedCanvas.width = canvas.width; clonedCanvas.height = canvas.height; var ctx = canvas.getContext('2d'); var clonedCtx = clonedCanvas.getContext('2d'); if (ctx) { clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0); } else { clonedCtx.drawImage(canvas, 0, 0); } } } catch (e) {} }; var inlinePseudoElement = function inlinePseudoElement(node, clone, style, contentItems, pseudoElt) { if (!style || !style.content || style.content === 'none' || style.content === '-moz-alt-content' || style.display === 'none') { return; } var anonymousReplacedElement = clone.ownerDocument.createElement('html2canvaspseudoelement'); (0, _Util.copyCSSStyles)(style, anonymousReplacedElement); if (contentItems) { var len = contentItems.length; for (var i = 0; i < len; i++) { var item = contentItems[i]; switch (item.type) { case _PseudoNodeContent.PSEUDO_CONTENT_ITEM_TYPE.IMAGE: var img = clone.ownerDocument.createElement('img'); img.src = (0, _background.parseBackgroundImage)('url(' + item.value + ')')[0].args[0]; img.style.opacity = '1'; anonymousReplacedElement.appendChild(img); break; case _PseudoNodeContent.PSEUDO_CONTENT_ITEM_TYPE.TEXT: anonymousReplacedElement.appendChild(clone.ownerDocument.createTextNode(item.value)); break; } } } anonymousReplacedElement.className = PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ' ' + PSEUDO_HIDE_ELEMENT_CLASS_AFTER; clone.className += pseudoElt === PSEUDO_BEFORE ? ' ' + PSEUDO_HIDE_ELEMENT_CLASS_BEFORE : ' ' + PSEUDO_HIDE_ELEMENT_CLASS_AFTER; if (pseudoElt === PSEUDO_BEFORE) { clone.insertBefore(anonymousReplacedElement, clone.firstChild); } else { clone.appendChild(anonymousReplacedElement); } return anonymousReplacedElement; }; var URL_REGEXP = /^url\((.+)\)$/i; var PSEUDO_BEFORE = ':before'; var PSEUDO_AFTER = ':after'; var PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = '___html2canvas___pseudoelement_before'; var PSEUDO_HIDE_ELEMENT_CLASS_AFTER = '___html2canvas___pseudoelement_after'; var PSEUDO_HIDE_ELEMENT_STYLE = '{\n content: "" !important;\n display: none !important;\n}'; var createPseudoHideStyles = function createPseudoHideStyles(body) { createStyles(body, '.' + PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + PSEUDO_BEFORE + PSEUDO_HIDE_ELEMENT_STYLE + '\n .' + PSEUDO_HIDE_ELEMENT_CLASS_AFTER + PSEUDO_AFTER + PSEUDO_HIDE_ELEMENT_STYLE); }; var createStyles = function createStyles(body, styles) { var style = body.ownerDocument.createElement('style'); style.innerHTML = styles; body.appendChild(style); }; var initNode = function initNode(_ref) { var _ref2 = _slicedToArray(_ref, 3), element = _ref2[0], x = _ref2[1], y = _ref2[2]; element.scrollLeft = x; element.scrollTop = y; }; var generateIframeKey = function generateIframeKey() { return Math.ceil(Date.now() + Math.random() * 10000000).toString(16); }; var DATA_URI_REGEXP = /^data:text\/(.+);(base64)?,(.*)$/i; var getIframeDocumentElement = function getIframeDocumentElement(node, options) { try { return Promise.resolve(node.contentWindow.document.documentElement); } catch (e) { return options.proxy ? (0, _Proxy.Proxy)(node.src, options).then(function (html) { var match = html.match(DATA_URI_REGEXP); if (!match) { return Promise.reject(); } return match[2] === 'base64' ? window.atob(decodeURIComponent(match[3])) : decodeURIComponent(match[3]); }).then(function (html) { return createIframeContainer(node.ownerDocument, (0, _Bounds.parseBounds)(node, 0, 0)).then(function (cloneIframeContainer) { var cloneWindow = cloneIframeContainer.contentWindow; var documentClone = cloneWindow.document; documentClone.open(); documentClone.write(html); var iframeLoad = iframeLoader(cloneIframeContainer).then(function () { return documentClone.documentElement; }); documentClone.close(); return iframeLoad; }); }) : Promise.reject(); } }; var createIframeContainer = function createIframeContainer(ownerDocument, bounds) { var 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 cloneIframeContainer.setAttribute(IGNORE_ATTRIBUTE, 'true'); if (!ownerDocument.body) { return Promise.reject(process.env.NODE_ENV !== 'production' ? 'Body element not found in Document that is getting rendered' : ''); } ownerDocument.body.appendChild(cloneIframeContainer); return Promise.resolve(cloneIframeContainer); }; var iframeLoader = function iframeLoader(cloneIframeContainer) { var cloneWindow = cloneIframeContainer.contentWindow; var documentClone = cloneWindow.document; return new Promise(function (resolve, reject) { cloneWindow.onload = cloneIframeContainer.onload = documentClone.onreadystatechange = function () { var interval = setInterval(function () { if (documentClone.body.childNodes.length > 0 && documentClone.readyState === 'complete') { clearInterval(interval); resolve(cloneIframeContainer); } }, 50); }; }); }; var cloneWindow = exports.cloneWindow = function cloneWindow(ownerDocument, bounds, referenceElement, options, logger, renderer) { var cloner = new DocumentCloner(referenceElement, options, logger, false, renderer); var scrollX = ownerDocument.defaultView.pageXOffset; var scrollY = ownerDocument.defaultView.pageYOffset; return createIframeContainer(ownerDocument, bounds).then(function (cloneIframeContainer) { var cloneWindow = cloneIframeContainer.contentWindow; var 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 */ var iframeLoad = iframeLoader(cloneIframeContainer).then(function () { cloner.scrolledElements.forEach(initNode); 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'; } var result = Promise.resolve([cloneIframeContainer, cloner.clonedReferenceElement, cloner.resourceLoader]); var onclone = options.onclone; return cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement || cloner.clonedReferenceElement instanceof ownerDocument.defaultView.HTMLElement || cloner.clonedReferenceElement instanceof HTMLElement ? typeof onclone === 'function' ? Promise.resolve().then(function () { return onclone(documentClone); }).then(function () { return result; }) : result : Promise.reject(process.env.NODE_ENV !== 'production' ? 'Error finding the ' + referenceElement.nodeName + ' in the cloned document' : ''); }); documentClone.open(); documentClone.write(serializeDoctype(document.doctype) + '<html></html>'); // Chrome scrolls the parent document for some reason after the write to the cloned window??? restoreOwnerScroll(referenceElement.ownerDocument, scrollX, scrollY); documentClone.replaceChild(documentClone.adoptNode(cloner.documentElement), documentClone.documentElement); documentClone.close(); return iframeLoad; }); }; var serializeDoctype = function serializeDoctype(doctype) { var str = ''; if (doctype) { str += '<!DOCTYPE '; if (doctype.name) { str += doctype.name; } if (doctype.internalSubset) { str += doctype.internalSubset; } if (doctype.publicId) { str += '"' + doctype.publicId + '"'; } if (doctype.systemId) { str += '"' + doctype.systemId + '"'; } str += '>'; } return str; };