mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Add support for loading cross origin images using proxy
This commit is contained in:
parent
286be1f6f0
commit
5ad8639bc6
6
karma.js
6
karma.js
@ -1,6 +1,7 @@
|
|||||||
const Server = require('karma').Server;
|
const Server = require('karma').Server;
|
||||||
const cfg = require('karma').config;
|
const cfg = require('karma').config;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const proxy = require('html2canvas-proxy');
|
||||||
const karmaConfig = cfg.parseConfig(path.resolve('./karma.conf.js'));
|
const karmaConfig = cfg.parseConfig(path.resolve('./karma.conf.js'));
|
||||||
const server = new Server(karmaConfig, (exitCode) => {
|
const server = new Server(karmaConfig, (exitCode) => {
|
||||||
console.log('Karma has exited with ' + exitCode);
|
console.log('Karma has exited with ' + exitCode);
|
||||||
@ -15,8 +16,9 @@ const filenamifyUrl = require('filenamify-url');
|
|||||||
|
|
||||||
const CORS_PORT = 8081;
|
const CORS_PORT = 8081;
|
||||||
const corsApp = express();
|
const corsApp = express();
|
||||||
corsApp.use(cors());
|
corsApp.use('/proxy', proxy());
|
||||||
corsApp.use('/', express.static(path.resolve(__dirname)));
|
corsApp.use('/cors', cors(), express.static(path.resolve(__dirname)));
|
||||||
|
corsApp.use('/', express.static(path.resolve(__dirname, '/tests')));
|
||||||
corsApp.use((error, req, res, next) => {
|
corsApp.use((error, req, res, next) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
next();
|
next();
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"filenamify-url": "1.0.0",
|
"filenamify-url": "1.0.0",
|
||||||
"flow-bin": "0.50.0",
|
"flow-bin": "0.50.0",
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
|
"html2canvas-proxy": "1.0.0",
|
||||||
"jquery": "3.2.1",
|
"jquery": "3.2.1",
|
||||||
"karma": "1.7.0",
|
"karma": "1.7.0",
|
||||||
"karma-chrome-launcher": "2.2.0",
|
"karma-chrome-launcher": "2.2.0",
|
||||||
|
@ -59,9 +59,9 @@ const testBase64 = (document: Document, src: string): Promise<boolean> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const testCORS = () => {
|
const testCORS = () => typeof new Image().crossOrigin !== 'undefined';
|
||||||
return typeof new Image().crossOrigin !== 'undefined';
|
|
||||||
};
|
const testResponseType = () => typeof new XMLHttpRequest().responseType === 'string';
|
||||||
|
|
||||||
const testSVG = document => {
|
const testSVG = document => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@ -156,6 +156,20 @@ const FEATURES = {
|
|||||||
const value = testCORS();
|
const value = testCORS();
|
||||||
Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', {value});
|
Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', {value});
|
||||||
return value;
|
return value;
|
||||||
|
},
|
||||||
|
// $FlowFixMe - get/set properties not yet supported
|
||||||
|
get SUPPORT_RESPONSE_TYPE() {
|
||||||
|
'use strict';
|
||||||
|
const value = testResponseType();
|
||||||
|
Object.defineProperty(FEATURES, 'SUPPORT_RESPONSE_TYPE', {value});
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
// $FlowFixMe - get/set properties not yet supported
|
||||||
|
get SUPPORT_CORS_XHR() {
|
||||||
|
'use strict';
|
||||||
|
const value = 'withCredentials' in new XMLHttpRequest();
|
||||||
|
Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', {value});
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ export type ImageElement = Image | HTMLCanvasElement;
|
|||||||
type ImageCache<T> = {[string]: Promise<T>};
|
type ImageCache<T> = {[string]: Promise<T>};
|
||||||
|
|
||||||
import FEATURES from './Feature';
|
import FEATURES from './Feature';
|
||||||
|
import {Proxy} from './Proxy';
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
export default class ImageLoader<T> {
|
export default class ImageLoader<T> {
|
||||||
@ -46,7 +47,10 @@ export default class ImageLoader<T> {
|
|||||||
return this.addImage(src, src, false);
|
return this.addImage(src, src, false);
|
||||||
} else if (!this.isSameOrigin(src)) {
|
} else if (!this.isSameOrigin(src)) {
|
||||||
if (typeof this.options.proxy === 'string') {
|
if (typeof this.options.proxy === 'string') {
|
||||||
// TODO proxy
|
this.cache[src] = Proxy(src, this.options).then(src =>
|
||||||
|
loadImage(src, this.options.imageTimeout || 0)
|
||||||
|
);
|
||||||
|
return src;
|
||||||
} else if (this.options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES) {
|
} else if (this.options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES) {
|
||||||
return this.addImage(src, src, true);
|
return this.addImage(src, src, true);
|
||||||
}
|
}
|
||||||
@ -61,7 +65,12 @@ export default class ImageLoader<T> {
|
|||||||
if (this.hasImageInCache(src)) {
|
if (this.hasImageInCache(src)) {
|
||||||
return this.cache[src];
|
return this.cache[src];
|
||||||
}
|
}
|
||||||
// TODO proxy
|
if (!this.isSameOrigin(src) && typeof this.options.proxy === 'string') {
|
||||||
|
return (this.cache[src] = Proxy(src, this.options).then(src =>
|
||||||
|
loadImage(src, this.options.imageTimeout || 0)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return this.xhrImage(src);
|
return this.xhrImage(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +80,12 @@ export default class ImageLoader<T> {
|
|||||||
xhr.onreadystatechange = () => {
|
xhr.onreadystatechange = () => {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status !== 200) {
|
if (xhr.status !== 200) {
|
||||||
reject(`Failed to fetch image ${src} with status code ${xhr.status}`);
|
reject(
|
||||||
|
`Failed to fetch image ${src.substring(
|
||||||
|
0,
|
||||||
|
256
|
||||||
|
)} with status code ${xhr.status}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
@ -87,7 +101,9 @@ export default class ImageLoader<T> {
|
|||||||
const timeout = this.options.imageTimeout;
|
const timeout = this.options.imageTimeout;
|
||||||
xhr.timeout = timeout;
|
xhr.timeout = timeout;
|
||||||
xhr.ontimeout = () =>
|
xhr.ontimeout = () =>
|
||||||
reject(__DEV__ ? `Timed out (${timeout}ms) fetching ${src}` : '');
|
reject(
|
||||||
|
__DEV__ ? `Timed out (${timeout}ms) fetching ${src.substring(0, 256)}` : ''
|
||||||
|
);
|
||||||
}
|
}
|
||||||
xhr.open('GET', src, true);
|
xhr.open('GET', src, true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
|
62
src/Proxy.js
Normal file
62
src/Proxy.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type Options from './index';
|
||||||
|
|
||||||
|
import FEATURES from './Feature';
|
||||||
|
|
||||||
|
export const Proxy = (src: string, options: Options): Promise<string> => {
|
||||||
|
if (!options.proxy) {
|
||||||
|
return Promise.reject(__DEV__ ? 'No proxy defined' : null);
|
||||||
|
}
|
||||||
|
const proxy = options.proxy;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const responseType =
|
||||||
|
FEATURES.SUPPORT_CORS_XHR && FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
|
||||||
|
const xhr = FEATURES.SUPPORT_CORS_XHR ? new XMLHttpRequest() : new XDomainRequest();
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr instanceof XMLHttpRequest) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
if (responseType === 'text') {
|
||||||
|
resolve(xhr.response);
|
||||||
|
} else {
|
||||||
|
const reader = new FileReader();
|
||||||
|
// $FlowFixMe
|
||||||
|
reader.addEventListener('load', () => resolve(reader.result), false);
|
||||||
|
// $FlowFixMe
|
||||||
|
reader.addEventListener('error', e => reject(e), false);
|
||||||
|
reader.readAsDataURL(xhr.response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
__DEV__
|
||||||
|
? `Failed to proxy image ${src.substring(
|
||||||
|
0,
|
||||||
|
256
|
||||||
|
)} with status code ${xhr.status}`
|
||||||
|
: ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve(xhr.responseText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = reject;
|
||||||
|
xhr.open('GET', `${proxy}?url=${encodeURIComponent(src)}&responseType=${responseType}`);
|
||||||
|
|
||||||
|
if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
|
||||||
|
xhr.responseType = responseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.imageTimeout) {
|
||||||
|
const timeout = options.imageTimeout;
|
||||||
|
xhr.timeout = timeout;
|
||||||
|
xhr.ontimeout = () =>
|
||||||
|
reject(__DEV__ ? `Timed out (${timeout}ms) proxying ${src.substring(0, 256)}` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
};
|
@ -9,13 +9,13 @@
|
|||||||
font-family: Arial;
|
font-family: Arial;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<base href="http://www.google.com/" />
|
<base href="http://localhost:8081/" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>External image</h1>
|
<h1>External image</h1>
|
||||||
<img src="http://www.google.com/logos/2011/gregormendel11-hp.jpg" style="border:5px solid black;" />
|
<img src="http://localhost:8081/assets/image2.jpg" style="border:5px solid black;" />
|
||||||
|
|
||||||
<h1>External image (using <base> href)</h1>
|
<h1>External image (using <base> href)</h1>
|
||||||
<img src="/logos/2011/gregormendel11-res.jpg" />
|
<img src="/assets/image_1.jpg" />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -15,6 +15,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>External image (CORS)</h1>
|
<h1>External image (CORS)</h1>
|
||||||
<img src="http://localhost:8081/tests/assets/image2.jpg" />
|
<img src="http://localhost:8081/cors/tests/assets/image2.jpg" />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -5,6 +5,7 @@ const fs = require('fs');
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const config = require('../webpack.config');
|
const config = require('../webpack.config');
|
||||||
const serveIndex = require('serve-index');
|
const serveIndex = require('serve-index');
|
||||||
|
const proxy = require('html2canvas-proxy');
|
||||||
|
|
||||||
const PORT = 8080;
|
const PORT = 8080;
|
||||||
const CORS_PORT = 8081;
|
const CORS_PORT = 8081;
|
||||||
@ -17,8 +18,9 @@ app.listen(PORT, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const corsApp = express();
|
const corsApp = express();
|
||||||
corsApp.use(cors());
|
corsApp.use('/proxy', proxy());
|
||||||
corsApp.use('/', express.static(path.resolve(__dirname, '../')));
|
corsApp.use('/cors', cors(), express.static(path.resolve(__dirname, '../')));
|
||||||
|
corsApp.use('/', express.static(path.resolve(__dirname, '.')));
|
||||||
corsApp.listen(CORS_PORT, () => {
|
corsApp.listen(CORS_PORT, () => {
|
||||||
console.log(`CORS server running on port ${CORS_PORT}`);
|
console.log(`CORS server running on port ${CORS_PORT}`);
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,14 @@ var REFTEST = window.location.search.indexOf('reftest') !== -1;
|
|||||||
(function(document, window) {
|
(function(document, window) {
|
||||||
function appendScript(src) {
|
function appendScript(src) {
|
||||||
document.write(
|
document.write(
|
||||||
'<script type="text/javascript" src="' + src + '.js?' + Math.random() + '"></script>'
|
'<script type="text/javascript" src="' +
|
||||||
|
window.location.protocol +
|
||||||
|
'//' +
|
||||||
|
window.location.host +
|
||||||
|
src +
|
||||||
|
'.js?' +
|
||||||
|
Math.random() +
|
||||||
|
'"></script>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +154,7 @@ var REFTEST = window.location.search.indexOf('reftest') !== -1;
|
|||||||
$.extend(
|
$.extend(
|
||||||
{
|
{
|
||||||
logging: true,
|
logging: true,
|
||||||
proxy: 'http://localhost:8082',
|
proxy: 'http://localhost:8081/proxy',
|
||||||
useCORS: false,
|
useCORS: false,
|
||||||
removeContainer: false,
|
removeContainer: false,
|
||||||
target: targets
|
target: targets
|
||||||
|
@ -118,6 +118,7 @@ const assertPath = (result, expected, desc) => {
|
|||||||
.html2canvas(testContainer.contentWindow.document.documentElement, {
|
.html2canvas(testContainer.contentWindow.document.documentElement, {
|
||||||
removeContainer: true,
|
removeContainer: true,
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
|
proxy: 'http://localhost:8081/proxy',
|
||||||
...(testContainer.contentWindow.h2cOptions || {})
|
...(testContainer.contentWindow.h2cOptions || {})
|
||||||
})
|
})
|
||||||
.then(canvas => {
|
.then(canvas => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user