New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. #1614
Comments
I have also the same issues. |
i'm doing the same thing in Firefox 61.0.2 and i'm getting a SecurityError despite setting allowTaint to true |
No solution as far as I know, but I have a workaround: change every image to base64. That way, you could render it in canvas even though it's originally from different domain. |
Have you saw this: CORS_enabled_image If the source of the foreign content is an HTML element, attempting to retrieve the contents of the canvas isn't allowed. As soon as you draw into a canvas any data that was loaded from another origin without CORS approval, the canvas becomes tainted. I use the configuration options like this:
|
Hi, we faced the same problem. We followed @dorklord23 suggestion because we already had a proxy url that did the conversion. If someone found it helpful the solution was: html2canvas(document.body, {
proxy: this._proxyURL,
allowTaint: true,
onclone: (cloned) => convertAllImagesToBase64(this._proxyURL, cloned),
}).then((canvas) => {
this._postmessageChannel.send(`get.screenshot:${canvas.toDataURL('image/png')}`);
}); Where the helper function convertAllImagesToBase64 is: const convertAllImagesToBase64 = (proxyURL, cloned) => {
const pendingImagesPromises = [];
const pendingPromisesData = [];
const images = cloned.getElementsByTagName('img');
for (let i = 0; i < images.length; i += 1) {
// First we create an empty promise for each image
const promise = new Promise((resolve, reject) => {
pendingPromisesData.push({
index: i, resolve, reject,
});
});
// We save the promise for later resolve them
pendingImagesPromises.push(promise);
}
for (let i = 0; i < images.length; i += 1) {
// We fetch the current image
fetch(`${proxyURL}?url=${images[i].src}`)
.then((response) => response.json())
.then((data) => {
const pending = pendingPromisesData.find((p) => p.index === i);
images[i].src = data;
pending.resolve(data);
})
.catch((e) => {
const pending = pendingPromisesData.find((p) => p.index === i);
pending.reject(e);
});
}
// This will resolve only when all the promises resolve
return Promise.all(pendingImagesPromises);
};
export { convertAllImagesToBase64 }; By the way this are the tests for that helper function (we are using jest for wrting test and mockFetch packages): import { convertAllImagesToBase64 } from '../images';
fetch.resetMocks();
// Mock fetch to respond different for each image so we can assert that the image return the correct response
// Also make one of the response be delayed (2 seconds) to simulate the response is not in the same order we do the call (network latency, image size, etc)
fetch.mockImplementation((url) => {
if (url.includes('imagesrc1')) {
return Promise.resolve(new Response(JSON.stringify('data:image/png;base64,1')));
} else if (url.includes('imagesrc2')) {
return new Promise((resolve) => setTimeout(resolve(new Response(JSON.stringify('data:image/png;base64,2'))), 2000));
} else if (url.includes('imagesrc3')) {
return Promise.resolve(new Response(JSON.stringify('data:image/png;base64,3')));
}
return Promise.resolve(new Response(JSON.stringify('')));
});
const mocksImages = [
{ id: 1, src: 'imagesrc1' },
{ id: 2, src: 'imagesrc2' },
{ id: 3, src: 'imagesrc3' },
];
const mockClone = {
getElementsByTagName: jest.fn(() => mocksImages),
};
describe('utils/images', () => {
it('convertAllImagesToBase64. Expect to call 3 times to the correct enpoint using the image source', async () => {
const allPromises = convertAllImagesToBase64('http://localhost/fake_proxy', mockClone);
// Expect the clone elements gets all the image tags
expect(mockClone.getElementsByTagName).toBeCalledWith('img');
allPromises.then(() => {
// Expect to have done the 3 fetch calls and with the correct params
expect(fetch).toBeCalledTimes(3);
expect(fetch).toHaveBeenNthCalledWith(1, 'http://localhost/fake_proxy?url=imagesrc1');
expect(fetch).toHaveBeenNthCalledWith(2, 'http://localhost/fake_proxy?url=imagesrc2');
expect(fetch).toHaveBeenNthCalledWith(3, 'http://localhost/fake_proxy?url=imagesrc3');
// Expect that our images where updated properly
expect(mocksImages).toContainEqual({
id: 1, src: 'data:image/png;base64,1',
});
expect(mocksImages).toContainEqual({
id: 2, src: 'data:image/png;base64,2',
});
expect(mocksImages).toContainEqual({
id: 3, src: 'data:image/png;base64,3',
});
});
});
}); Ruby backend enpdoint: require 'base64'
require 'net/http'
module Api
module V1
class ImageProxyController < ApiController
def index
url = URI.parse(params[:url])
image = Net::HTTP.get_response(url)
render json: data_url(image).to_json, callback: params[:callback]
end
private
def data_url(image)
"data:#{image.content_type};base64,#{Base64.encode64(image.body)}"
end
end
end
end I hope someone find this helpful. I hope it helps someone not to invest as much time as we did to fix this properly. If you can see any improvment please suggest. |
If you do like below, what will happen? const TempImage = window.Image
const Image = function() {
const img = new TempImage()
img.crossOrigin = 'anonymous'
return img
} |
Found a solution and it is working
|
You will need to use just the property 'useCORS: true', if you use the property 'allowTaint: true' you give permssion to turn your canvas into a tainted canvas USE THIS:
INSTEAD OF THIS:
|
Hello, nice work for html2canvas. Sadly I'm facing the same issue, has anyone solved this?
Thanks in advance |
I have also the same issues. |
:( same issue. we have html with nested svg and it will not render |
I have this issue when I don't use SSL, With SSL works perfect |
I have used your approach and get image from my backend as base64 and it works |
I had same problem. |
Setting foreignObjectRendering to true worked for me. |
Oh, this worked for me. Thank you! |
this worked for my next.js app.
|
Bug reports:
Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
The text was updated successfully, but these errors were encountered: