Fallback to jsonp proxy if browser doesn't support cors xhr/image

This commit is contained in:
MoyuScript 2014-09-20 19:53:22 +03:00
parent df0a696b6a
commit bc08edc340
10 changed files with 116 additions and 57 deletions

View File

@ -15,5 +15,5 @@
"jQuery": true "jQuery": true
}, },
"predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise", "getBounds", "offsetBounds", "XHR", "predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise", "getBounds", "offsetBounds", "XHR",
"ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "SVGContainer", "SVGNodeContainer", "FrameContainer", "html2canvas", "log", "smallImage", "parseBackgrounds", "createWindowClone"] "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "SVGContainer", "SVGNodeContainer", "FrameContainer", "html2canvas", "log", "smallImage", "parseBackgrounds", "createWindowClone", "decode64", "Proxy", "ProxyURL"]
} }

80
dist/html2canvas.js vendored
View File

@ -591,6 +591,7 @@ window.html2canvas = function(nodeList, options) {
}; };
window.html2canvas.punycode = this.punycode; window.html2canvas.punycode = this.punycode;
window.html2canvas.proxy = {};
function renderDocument(document, options, windowWidth, windowHeight) { function renderDocument(document, options, windowWidth, windowHeight) {
return createWindowClone(document, document, windowWidth, windowHeight, options).then(function(container) { return createWindowClone(document, document, windowWidth, windowHeight, options).then(function(container) {
@ -799,7 +800,7 @@ function FrameContainer(container, sameOrigin, proxy) {
FrameContainer.prototype.proxyLoad = function(proxy, bounds) { FrameContainer.prototype.proxyLoad = function(proxy, bounds) {
var container = this.src; var container = this.src;
return XHR(proxy + "?url=" + container.src).then(documentFromHTML(container)).then(function(doc) { return new Proxy(container.src, proxy, window.document).then(documentFromHTML(container)).then(function(doc) {
return createWindowClone(doc, container.ownerDocument, bounds.width, bounds.height, {}); return createWindowClone(doc, container.ownerDocument, bounds.width, bounds.height, {});
}); });
}; };
@ -2172,45 +2173,74 @@ function hasUnicode(string) {
return /[^\u0000-\u00ff]/.test(string); return /[^\u0000-\u00ff]/.test(string);
} }
function Proxy(src, proxyUrl, document) {
var callback = createCallback(supportsCORS);
var url = createProxyUrl(proxyUrl, src, callback);
return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
return decode64(response.content);
}));
}
var proxyCount = 0;
var supportsCORS = ('withCredentials' in new XMLHttpRequest());
var supportsCORSImage = ('crossOrigin' in new Image());
function ProxyURL(src, proxyUrl, document) {
var callback = createCallback(supportsCORSImage);
var url = createProxyUrl(proxyUrl, src, callback);
return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
return "data:" + response.type + ";base64," + response.content;
}));
}
function jsonp(document, url, callback) {
return new Promise(function(resolve, reject) {
var s = document.createElement("script");
var cleanup = function() {
delete window.html2canvas.proxy[callback];
document.body.removeChild(s);
};
window.html2canvas.proxy[callback] = function(response) {
cleanup();
resolve(response);
};
s.src = url;
s.onerror = function(e) {
cleanup();
reject(e);
};
document.body.appendChild(s);
});
}
function createCallback(useCORS) {
return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
}
function createProxyUrl(proxyUrl, src, callback) {
return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
}
function ProxyImageContainer(src, proxy) { function ProxyImageContainer(src, proxy) {
var callbackName = "html2canvas_" + proxyImageCount++;
var script = document.createElement("script"); var script = document.createElement("script");
var link = document.createElement("a"); var link = document.createElement("a");
link.href = src; link.href = src;
src = link.href; src = link.href;
var requestUrl = proxy + ((proxy.indexOf("?") > -1) ? "&" : "?" ) + 'url=' + encodeURIComponent(src) + '&callback=' + callbackName;
this.src = src; this.src = src;
this.image = new Image(); this.image = new Image();
var self = this; var self = this;
this.promise = new Promise(function(resolve, reject) { this.promise = new Promise(function(resolve, reject) {
self.image.crossOrigin = "Anonymous";
self.image.onload = resolve; self.image.onload = resolve;
self.image.onerror = reject; self.image.onerror = reject;
window[callbackName] = function(a){ new ProxyURL(src, proxy, document).then(function(url) {
if (a.substring(0,6) === "error:"){ self.image.src = url;
reject(); })['catch'](reject);
} else {
self.image.src = a;
}
window[callbackName] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
try {
delete window[callbackName]; // for all browser that support this
} catch(ex) {}
script.parentNode.removeChild(script);
};
script.setAttribute("type", "text/javascript");
script.setAttribute("src", requestUrl);
document.body.appendChild(script);
})['catch'](function() {
var dummy = new DummyImageContainer(src);
return dummy.promise.then(function(image) {
self.image = image;
});
}); });
} }
var proxyImageCount = 0;
function Renderer(width, height, images, options, document) { function Renderer(width, height, images, options, document) {
this.width = width; this.width = width;
this.height = height; this.height = height;

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@
"url": "http://hertzen.com" "url": "http://hertzen.com"
}, },
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.10.0"
}, },
"dependencies": {}, "dependencies": {},
"repository": { "repository": {
@ -30,7 +30,7 @@
"grunt-contrib-uglify": "^0.6.0", "grunt-contrib-uglify": "^0.6.0",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"grunt-execute": "^0.2.2", "grunt-execute": "^0.2.2",
"html2canvas-proxy": "0.0.2", "html2canvas-proxy": "0.0.5",
"humanize-duration": "^2.0.1", "humanize-duration": "^2.0.1",
"lodash": "^2.4.1", "lodash": "^2.4.1",
"png-js": ">= 0.1.1", "png-js": ">= 0.1.1",

View File

@ -23,6 +23,7 @@ window.html2canvas = function(nodeList, options) {
}; };
window.html2canvas.punycode = this.punycode; window.html2canvas.punycode = this.punycode;
window.html2canvas.proxy = {};
function renderDocument(document, options, windowWidth, windowHeight) { function renderDocument(document, options, windowWidth, windowHeight) {
return createWindowClone(document, document, windowWidth, windowHeight, options).then(function(container) { return createWindowClone(document, document, windowWidth, windowHeight, options).then(function(container) {

View File

@ -20,7 +20,7 @@ function FrameContainer(container, sameOrigin, proxy) {
FrameContainer.prototype.proxyLoad = function(proxy, bounds) { FrameContainer.prototype.proxyLoad = function(proxy, bounds) {
var container = this.src; var container = this.src;
return XHR(proxy + "?url=" + container.src).then(documentFromHTML(container)).then(function(doc) { return new Proxy(container.src, proxy, window.document).then(documentFromHTML(container)).then(function(doc) {
return createWindowClone(doc, container.ownerDocument, bounds.width, bounds.height, {}); return createWindowClone(doc, container.ownerDocument, bounds.width, bounds.height, {});
}); });
}; };

48
src/proxy.js Normal file
View File

@ -0,0 +1,48 @@
function Proxy(src, proxyUrl, document) {
var callback = createCallback(supportsCORS);
var url = createProxyUrl(proxyUrl, src, callback);
return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
return decode64(response.content);
}));
}
var proxyCount = 0;
var supportsCORS = ('withCredentials' in new XMLHttpRequest());
var supportsCORSImage = ('crossOrigin' in new Image());
function ProxyURL(src, proxyUrl, document) {
var callback = createCallback(supportsCORSImage);
var url = createProxyUrl(proxyUrl, src, callback);
return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
return "data:" + response.type + ";base64," + response.content;
}));
}
function jsonp(document, url, callback) {
return new Promise(function(resolve, reject) {
var s = document.createElement("script");
var cleanup = function() {
delete window.html2canvas.proxy[callback];
document.body.removeChild(s);
};
window.html2canvas.proxy[callback] = function(response) {
cleanup();
resolve(response);
};
s.src = url;
s.onerror = function(e) {
cleanup();
reject(e);
};
document.body.appendChild(s);
});
}
function createCallback(useCORS) {
return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
}
function createProxyUrl(proxyUrl, src, callback) {
return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
}

View File

@ -1,38 +1,18 @@
function ProxyImageContainer(src, proxy) { function ProxyImageContainer(src, proxy) {
var callbackName = "html2canvas_" + proxyImageCount++;
var script = document.createElement("script"); var script = document.createElement("script");
var link = document.createElement("a"); var link = document.createElement("a");
link.href = src; link.href = src;
src = link.href; src = link.href;
var requestUrl = proxy + ((proxy.indexOf("?") > -1) ? "&" : "?" ) + 'url=' + encodeURIComponent(src) + '&callback=' + callbackName;
this.src = src; this.src = src;
this.image = new Image(); this.image = new Image();
var self = this; var self = this;
this.promise = new Promise(function(resolve, reject) { this.promise = new Promise(function(resolve, reject) {
self.image.crossOrigin = "Anonymous";
self.image.onload = resolve; self.image.onload = resolve;
self.image.onerror = reject; self.image.onerror = reject;
window[callbackName] = function(a){ new ProxyURL(src, proxy, document).then(function(url) {
if (a.substring(0,6) === "error:"){ self.image.src = url;
reject(); })['catch'](reject);
} else {
self.image.src = a;
}
window[callbackName] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
try {
delete window[callbackName]; // for all browser that support this
} catch(ex) {}
script.parentNode.removeChild(script);
};
script.setAttribute("type", "text/javascript");
script.setAttribute("src", requestUrl);
document.body.appendChild(script);
})['catch'](function() {
var dummy = new DummyImageContainer(src);
return dummy.promise.then(function(image) {
self.image = image;
});
}); });
} }
var proxyImageCount = 0;

View File

@ -8,6 +8,6 @@
<script type="text/javascript" src="../test.js"></script> <script type="text/javascript" src="../test.js"></script>
</head> </head>
<body> <body>
<iframe src="http://hertzen.com/" width="500" height="500"></iframe> <iframe src="http://hertzen.com/" width="800" height="800"></iframe>
</body> </body>
</html> </html>

View File

@ -12,7 +12,7 @@ var h2cSelector, h2cOptions;
} }
var sources = ['log', 'punycode/punycode', 'core', 'nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'imagecontainer', 'dummyimagecontainer', 'proxyimagecontainer', 'gradientcontainer', var sources = ['log', 'punycode/punycode', 'core', 'nodecontainer', 'stackingcontext', 'textcontainer', 'support', 'imagecontainer', 'dummyimagecontainer', 'proxyimagecontainer', 'gradientcontainer',
'lineargradientcontainer', 'webkitgradientcontainer', 'svgcontainer', 'svgnodecontainer', 'imageloader', 'nodeparser', 'font', 'fontmetrics', 'renderer', 'promise', 'xhr', 'framecontainer', 'renderers/canvas']; 'lineargradientcontainer', 'webkitgradientcontainer', 'svgcontainer', 'svgnodecontainer', 'imageloader', 'nodeparser', 'font', 'fontmetrics', 'renderer', 'promise', 'xhr', 'framecontainer', 'proxy', 'renderers/canvas'];
['/tests/assets/jquery-1.6.2'].concat(window.location.search === "?selenium" ? ['/dist/html2canvas'] : sources.map(function(src) { return '/src/' + src; })).forEach(appendScript); ['/tests/assets/jquery-1.6.2'].concat(window.location.search === "?selenium" ? ['/dist/html2canvas'] : sources.map(function(src) { return '/src/' + src; })).forEach(appendScript);