From 06e88c6418d5cfc05de11b29e7dd37744a27903b Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Wed, 25 Sep 2019 03:44:56 -0700 Subject: [PATCH 1/4] refactor: document cleanup to DocumentCloner --- src/dom/document-cloner.ts | 8 ++++++++ src/index.ts | 10 +--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/dom/document-cloner.ts b/src/dom/document-cloner.ts index 360084aee..54e7ba45b 100644 --- a/src/dom/document-cloner.ts +++ b/src/dom/document-cloner.ts @@ -410,6 +410,14 @@ export class DocumentCloner { : ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`; return anonymousReplacedElement; } + + static destroy(container: HTMLIFrameElement): boolean { + if (container.parentNode) { + container.parentNode.removeChild(container); + return true; + } + return false; + } } enum PseudoElementType { diff --git a/src/index.ts b/src/index.ts index 7e4707309..b36834594 100644 --- a/src/index.ts +++ b/src/index.ts @@ -153,7 +153,7 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom } if (options.removeContainer === true) { - if (!cleanContainer(container)) { + if (!DocumentCloner.destroy(container)) { Logger.getInstance(instanceName).error(`Cannot detach cloned iframe as it is not in the DOM anymore`); } } @@ -163,11 +163,3 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom CacheStorage.destroy(instanceName); return canvas; }; - -const cleanContainer = (container: HTMLIFrameElement): boolean => { - if (container.parentNode) { - container.parentNode.removeChild(container); - return true; - } - return false; -}; From 38f803fe9800933dbdf0b77e0616d7a31d69ed3b Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Wed, 25 Sep 2019 23:12:30 -0700 Subject: [PATCH 2/4] fix: using existing canvas option --- examples/existing_canvas.html | 2 +- src/__tests__/index.ts | 89 ++++++++++++++++++++++++++++ src/core/__mocks__/logger.ts | 17 ++++++ src/css/layout/__mocks__/bounds.ts | 4 ++ src/dom/__mocks__/document-cloner.ts | 15 +++++ src/index.ts | 1 + src/render/canvas/canvas-renderer.ts | 10 ++-- 7 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/index.ts create mode 100644 src/core/__mocks__/logger.ts create mode 100644 src/css/layout/__mocks__/bounds.ts create mode 100644 src/dom/__mocks__/document-cloner.ts diff --git a/examples/existing_canvas.html b/examples/existing_canvas.html index ac7be534e..baa743eed 100644 --- a/examples/existing_canvas.html +++ b/examples/existing_canvas.html @@ -42,7 +42,7 @@

Existing canvas:

ctx.stroke(); document.querySelector("button").addEventListener("click", function() { - html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) { + html2canvas(document.querySelector("#content"), {canvas: canvas, scale: 1}).then(function(canvas) { console.log('Drew on the existing canvas'); }); }, false); diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts new file mode 100644 index 000000000..be3a11d9c --- /dev/null +++ b/src/__tests__/index.ts @@ -0,0 +1,89 @@ +import html2canvas from '../index'; + +import {CanvasRenderer} from '../render/canvas/canvas-renderer'; +import {DocumentCloner} from '../dom/document-cloner'; +import {COLORS} from '../css/types/color'; + +jest.mock('../core/logger'); +jest.mock('../css/layout/bounds'); +jest.mock('../dom/document-cloner'); +jest.mock('../dom/node-parser', () => { + return { + isBodyElement: () => false, + isHTMLElement: () => false, + parseTree: jest.fn().mockImplementation(() => { + return {styles: {}}; + }) + }; +}); + +jest.mock('../render/stacking-context'); +jest.mock('../render/canvas/canvas-renderer'); + +describe('html2canvas', () => { + const element = { + ownerDocument: { + defaultView: { + pageXOffset: 12, + pageYOffset: 34 + } + } + } as HTMLElement; + + it('should render with an element', async () => { + DocumentCloner.destroy = jest.fn().mockReturnValue(true); + await html2canvas(element); + expect(CanvasRenderer).toHaveBeenLastCalledWith( + expect.objectContaining({ + backgroundColor: 0xffffffff, + scale: 1, + height: 50, + width: 200, + x: 0, + y: 0, + scrollX: 12, + scrollY: 34, + canvas: undefined + }) + ); + expect(DocumentCloner.destroy).toBeCalled(); + }); + + it('should have transparent background with backgroundColor: null', async () => { + await html2canvas(element, {backgroundColor: null}); + expect(CanvasRenderer).toHaveBeenLastCalledWith( + expect.objectContaining({ + backgroundColor: COLORS.TRANSPARENT + }) + ); + }); + + it('should use existing canvas when given as option', async () => { + const canvas = {} as HTMLCanvasElement; + await html2canvas(element, {canvas}); + expect(CanvasRenderer).toHaveBeenLastCalledWith( + expect.objectContaining({ + canvas + }) + ); + }); + + it('should not remove cloned window when removeContainer: false', async () => { + DocumentCloner.destroy = jest.fn(); + await html2canvas(element, {removeContainer: false}); + expect(CanvasRenderer).toHaveBeenLastCalledWith( + expect.objectContaining({ + backgroundColor: 0xffffffff, + scale: 1, + height: 50, + width: 200, + x: 0, + y: 0, + scrollX: 12, + scrollY: 34, + canvas: undefined + }) + ); + expect(DocumentCloner.destroy).not.toBeCalled(); + }); +}); diff --git a/src/core/__mocks__/logger.ts b/src/core/__mocks__/logger.ts new file mode 100644 index 000000000..8819bcdc8 --- /dev/null +++ b/src/core/__mocks__/logger.ts @@ -0,0 +1,17 @@ +export class Logger { + debug() {} + + static create() {} + + static destroy() {} + + static getInstance(): Logger { + return logger; + } + + info() {} + + error() {} +} + +const logger = new Logger(); diff --git a/src/css/layout/__mocks__/bounds.ts b/src/css/layout/__mocks__/bounds.ts new file mode 100644 index 000000000..34daef70f --- /dev/null +++ b/src/css/layout/__mocks__/bounds.ts @@ -0,0 +1,4 @@ +export const {Bounds} = jest.requireActual('../bounds'); +export const parseBounds = () => { + return new Bounds(0, 0, 200, 50); +}; diff --git a/src/dom/__mocks__/document-cloner.ts b/src/dom/__mocks__/document-cloner.ts new file mode 100644 index 000000000..8d7724966 --- /dev/null +++ b/src/dom/__mocks__/document-cloner.ts @@ -0,0 +1,15 @@ +export class DocumentCloner { + clonedReferenceElement?: HTMLElement; + + constructor() { + this.clonedReferenceElement = {} as HTMLElement; + } + + toIFrame() { + return Promise.resolve({}); + } + + static destroy() { + return true; + } +} diff --git a/src/index.ts b/src/index.ts index b36834594..ee5e9a165 100644 --- a/src/index.ts +++ b/src/index.ts @@ -116,6 +116,7 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom const renderOptions = { id: instanceName, cache: options.cache, + canvas: options.canvas, backgroundColor, scale: options.scale, x: options.x, diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts index 87303c191..93ca92340 100644 --- a/src/render/canvas/canvas-renderer.ts +++ b/src/render/canvas/canvas-renderer.ts @@ -71,10 +71,12 @@ export class CanvasRenderer { this.canvas = options.canvas ? options.canvas : document.createElement('canvas'); this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D; this.options = options; - this.canvas.width = Math.floor(options.width * options.scale); - this.canvas.height = Math.floor(options.height * options.scale); - this.canvas.style.width = `${options.width}px`; - this.canvas.style.height = `${options.height}px`; + if (!options.canvas) { + this.canvas.width = Math.floor(options.width * options.scale); + this.canvas.height = Math.floor(options.height * options.scale); + this.canvas.style.width = `${options.width}px`; + this.canvas.style.height = `${options.height}px`; + } this.fontMetrics = new FontMetrics(document); this.ctx.scale(this.options.scale, this.options.scale); this.ctx.translate(-options.x + options.scrollX, -options.y + options.scrollY); From 3e8d5319bfb7b5afe4326b10def2fe2541df1929 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Wed, 25 Sep 2019 23:19:35 -0700 Subject: [PATCH 3/4] fix: lint errors --- src/__tests__/index.ts | 2 ++ src/dom/__mocks__/document-cloner.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index be3a11d9c..1cc77085e 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -21,6 +21,7 @@ jest.mock('../render/stacking-context'); jest.mock('../render/canvas/canvas-renderer'); describe('html2canvas', () => { + // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion const element = { ownerDocument: { defaultView: { @@ -59,6 +60,7 @@ describe('html2canvas', () => { }); it('should use existing canvas when given as option', async () => { + // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion const canvas = {} as HTMLCanvasElement; await html2canvas(element, {canvas}); expect(CanvasRenderer).toHaveBeenLastCalledWith( diff --git a/src/dom/__mocks__/document-cloner.ts b/src/dom/__mocks__/document-cloner.ts index 8d7724966..366ceeccf 100644 --- a/src/dom/__mocks__/document-cloner.ts +++ b/src/dom/__mocks__/document-cloner.ts @@ -2,6 +2,7 @@ export class DocumentCloner { clonedReferenceElement?: HTMLElement; constructor() { + // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion this.clonedReferenceElement = {} as HTMLElement; } From 7179a43a83001876a46c21106635ccd2a18ed475 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Wed, 25 Sep 2019 23:33:46 -0700 Subject: [PATCH 4/4] fix: preview transform origin --- www/src/preview.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/src/preview.ts b/www/src/preview.ts index 6d21d3b80..d780a1944 100644 --- a/www/src/preview.ts +++ b/www/src/preview.ts @@ -42,8 +42,10 @@ function onBrowserChange(browserTest: Test) { previewImage.src = `/results/${browserTest.screenshot}.png`; if (browserTest.devicePixelRatio > 1) { previewImage.style.transform = `scale(${1 / browserTest.devicePixelRatio})`; + previewImage.style.transformOrigin = 'top left'; } else { previewImage.style.transform = ''; + previewImage.style.transformOrigin = ''; } } }