Skip to content

Commit

Permalink
fix: using existing canvas option (#2017)
Browse files Browse the repository at this point in the history
* refactor: document cleanup to DocumentCloner

* fix: using existing canvas option

* fix: lint errors

* fix: preview transform origin
  • Loading branch information
niklasvh committed Sep 26, 2019
1 parent 34b06d6 commit 0764920
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 14 deletions.
2 changes: 1 addition & 1 deletion examples/existing_canvas.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h1>Existing canvas:</h1>
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);
Expand Down
91 changes: 91 additions & 0 deletions src/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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', () => {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
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 () => {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
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();
});
});
17 changes: 17 additions & 0 deletions src/core/__mocks__/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export class Logger {
debug() {}

static create() {}

static destroy() {}

static getInstance(): Logger {
return logger;
}

info() {}

error() {}
}

const logger = new Logger();
4 changes: 4 additions & 0 deletions src/css/layout/__mocks__/bounds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const {Bounds} = jest.requireActual('../bounds');
export const parseBounds = () => {
return new Bounds(0, 0, 200, 50);
};
16 changes: 16 additions & 0 deletions src/dom/__mocks__/document-cloner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class DocumentCloner {
clonedReferenceElement?: HTMLElement;

constructor() {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
this.clonedReferenceElement = {} as HTMLElement;
}

toIFrame() {
return Promise.resolve({});
}

static destroy() {
return true;
}
}
8 changes: 8 additions & 0 deletions src/dom/document-cloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 2 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
const renderOptions = {
id: instanceName,
cache: options.cache,
canvas: options.canvas,
backgroundColor,
scale: options.scale,
x: options.x,
Expand Down Expand Up @@ -153,7 +154,7 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): 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`);
}
}
Expand All @@ -163,11 +164,3 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
CacheStorage.destroy(instanceName);
return canvas;
};

const cleanContainer = (container: HTMLIFrameElement): boolean => {
if (container.parentNode) {
container.parentNode.removeChild(container);
return true;
}
return false;
};
10 changes: 6 additions & 4 deletions src/render/canvas/canvas-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions www/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
}
}
}
Expand Down

0 comments on commit 0764920

Please sign in to comment.