Fix firefox cross-origin iframe rendering

This commit is contained in:
Niklas von Hertzen 2014-09-27 23:03:57 +03:00
parent e6d31ada4a
commit 8a3d1d7f22
7 changed files with 86 additions and 58 deletions

70
dist/html2canvas.js vendored
View File

@ -581,6 +581,7 @@ window.html2canvas = function(nodeList, options) {
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint; options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled; options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
if (typeof(nodeList) === "string") { if (typeof(nodeList) === "string") {
if (typeof(options.proxy) !== "string") { if (typeof(options.proxy) !== "string") {
@ -627,7 +628,7 @@ function renderWindow(node, container, options, windowWidth, windowHeight) {
var parser = new NodeParser(node, renderer, support, imageLoader, options); var parser = new NodeParser(node, renderer, support, imageLoader, options);
return parser.ready.then(function() { return parser.ready.then(function() {
log("Finished rendering"); log("Finished rendering");
var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, bounds); var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, {width: width, height: height, top: bounds.top, left: bounds.left});
cleanupContainer(container, options); cleanupContainer(container, options);
return canvas; return canvas;
}); });
@ -693,10 +694,13 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti
if window url is about:blank, we can assign the url to current by writing onto the document if window url is about:blank, we can assign the url to current by writing onto the document
*/ */
container.contentWindow.onload = container.onload = function() { container.contentWindow.onload = container.onload = function() {
setTimeout(function() { var interval = setInterval(function() {
cloneCanvasContents(ownerDocument, documentClone); if (documentClone.body.childNodes.length > 0) {
resolve(container); cloneCanvasContents(ownerDocument, documentClone);
}, 0); clearInterval(interval);
resolve(container);
}
}, 50);
}; };
documentClone.open(); documentClone.open();
@ -718,11 +722,8 @@ function loadUrlDocument(src, proxy, document, width, height, options) {
function documentFromHTML(src) { function documentFromHTML(src) {
return function(html) { return function(html) {
var doc = document.implementation.createHTMLDocument(""); var parser = new DOMParser();
doc.open(); var doc = parser.parseFromString(html, "text/html");
doc.write(html);
doc.close();
var b = doc.querySelector("base"); var b = doc.querySelector("base");
if (!b || !b.href.host) { if (!b || !b.href.host) {
var base = doc.createElement("base"); var base = doc.createElement("base");
@ -871,7 +872,7 @@ function FrameContainer(container, sameOrigin, options) {
resolve(container); resolve(container);
} }
})).then(function(container) { })).then(function(container) {
return html2canvas(container.contentWindow.document.documentElement, {type: 'view', proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer}); return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});
}).then(function(canvas) { }).then(function(canvas) {
return self.image = canvas; return self.image = canvas;
}); });
@ -927,17 +928,17 @@ ImageLoader.prototype.findImages = function(nodes) {
var images = []; var images = [];
nodes.reduce(function(imageNodes, container) { nodes.reduce(function(imageNodes, container) {
switch(container.node.nodeName) { switch(container.node.nodeName) {
case "IMG": case "IMG":
return imageNodes.concat([{ return imageNodes.concat([{
args: [container.node.src], args: [container.node.src],
method: "url" method: "url"
}]); }]);
case "svg": case "svg":
case "IFRAME": case "IFRAME":
return imageNodes.concat([{ return imageNodes.concat([{
args: [container.node], args: [container.node],
method: container.node.nodeName method: container.node.nodeName
}]); }]);
} }
return imageNodes; return imageNodes;
}, []).forEach(this.addImage(images, this.loadImage), this); }, []).forEach(this.addImage(images, this.loadImage), this);
@ -1012,7 +1013,7 @@ ImageLoader.prototype.isSameOrigin = function(url) {
}; };
ImageLoader.prototype.getPromise = function(container) { ImageLoader.prototype.getPromise = function(container) {
return container.promise['catch'](function() { return this.timeout(container, this.options.imageTimeout)['catch'](function() {
var dummy = new DummyImageContainer(container.src); var dummy = new DummyImageContainer(container.src);
return dummy.promise.then(function(image) { return dummy.promise.then(function(image) {
container.image = image; container.image = image;
@ -1031,16 +1032,29 @@ ImageLoader.prototype.fetch = function(nodes) {
this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes)); this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
this.images.forEach(function(image, index) { this.images.forEach(function(image, index) {
image.promise.then(function() { image.promise.then(function() {
log("Succesfully loaded image #"+ (index+1)); log("Succesfully loaded image #"+ (index+1), image);
}, function(e) { }, function(e) {
log("Failed loading image #"+ (index+1), e); log("Failed loading image #"+ (index+1), image, e);
}); });
}); });
this.ready = Promise.all(this.images.map(this.getPromise)); this.ready = Promise.all(this.images.map(this.getPromise, this));
log("Finished searching images"); log("Finished searching images");
return this; return this;
}; };
ImageLoader.prototype.timeout = function(container, timeout) {
var timer;
return Promise.race([container.promise, new Promise(function(res, reject) {
timer = setTimeout(function() {
log("Timed out loading image", container);
reject(container);
}, timeout);
})]).then(function(container) {
clearTimeout(timer);
return container;
});
};
function LinearGradientContainer(imageData) { function LinearGradientContainer(imageData) {
GradientContainer.apply(this, arguments); GradientContainer.apply(this, arguments);
this.type = this.TYPES.LINEAR; this.type = this.TYPES.LINEAR;
@ -1509,7 +1523,7 @@ function NodeParser(element, renderer, support, imageLoader, options) {
return container.visible = container.isElementVisible(); return container.visible = container.isElementVisible();
}).map(this.getPseudoElements, this)); }).map(this.getPseudoElements, this));
this.fontMetrics = new FontMetrics(); this.fontMetrics = new FontMetrics();
log("Fetched nodes"); log("Fetched nodes, total:", this.nodes.length);
this.images = imageLoader.fetch(this.nodes.filter(isElement)); this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.ready = this.images.ready.then(bind(function() { this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing"); log("Images loaded, starting parsing");
@ -2658,7 +2672,7 @@ function CanvasRenderer(width, height) {
this.taintCtx = this.document.createElement("canvas").getContext("2d"); this.taintCtx = this.document.createElement("canvas").getContext("2d");
this.ctx.textBaseline = "bottom"; this.ctx.textBaseline = "bottom";
this.variables = {}; this.variables = {};
log("Initialized CanvasRenderer"); log("Initialized CanvasRenderer with size", width, "x", height);
} }
CanvasRenderer.prototype = Object.create(Renderer.prototype); CanvasRenderer.prototype = Object.create(Renderer.prototype);

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@ window.html2canvas = function(nodeList, options) {
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint; options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled; options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
if (typeof(nodeList) === "string") { if (typeof(nodeList) === "string") {
if (typeof(options.proxy) !== "string") { if (typeof(options.proxy) !== "string") {
@ -59,7 +60,7 @@ function renderWindow(node, container, options, windowWidth, windowHeight) {
var parser = new NodeParser(node, renderer, support, imageLoader, options); var parser = new NodeParser(node, renderer, support, imageLoader, options);
return parser.ready.then(function() { return parser.ready.then(function() {
log("Finished rendering"); log("Finished rendering");
var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, bounds); var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, {width: width, height: height, top: bounds.top, left: bounds.left});
cleanupContainer(container, options); cleanupContainer(container, options);
return canvas; return canvas;
}); });
@ -125,10 +126,13 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti
if window url is about:blank, we can assign the url to current by writing onto the document if window url is about:blank, we can assign the url to current by writing onto the document
*/ */
container.contentWindow.onload = container.onload = function() { container.contentWindow.onload = container.onload = function() {
setTimeout(function() { var interval = setInterval(function() {
cloneCanvasContents(ownerDocument, documentClone); if (documentClone.body.childNodes.length > 0) {
resolve(container); cloneCanvasContents(ownerDocument, documentClone);
}, 0); clearInterval(interval);
resolve(container);
}
}, 50);
}; };
documentClone.open(); documentClone.open();
@ -150,11 +154,8 @@ function loadUrlDocument(src, proxy, document, width, height, options) {
function documentFromHTML(src) { function documentFromHTML(src) {
return function(html) { return function(html) {
var doc = document.implementation.createHTMLDocument(""); var parser = new DOMParser();
doc.open(); var doc = parser.parseFromString(html, "text/html");
doc.write(html);
doc.close();
var b = doc.querySelector("base"); var b = doc.querySelector("base");
if (!b || !b.href.host) { if (!b || !b.href.host) {
var base = doc.createElement("base"); var base = doc.createElement("base");

View File

@ -12,7 +12,7 @@ function FrameContainer(container, sameOrigin, options) {
resolve(container); resolve(container);
} }
})).then(function(container) { })).then(function(container) {
return html2canvas(container.contentWindow.document.documentElement, {type: 'view', proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer}); return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});
}).then(function(canvas) { }).then(function(canvas) {
return self.image = canvas; return self.image = canvas;
}); });

View File

@ -9,17 +9,17 @@ ImageLoader.prototype.findImages = function(nodes) {
var images = []; var images = [];
nodes.reduce(function(imageNodes, container) { nodes.reduce(function(imageNodes, container) {
switch(container.node.nodeName) { switch(container.node.nodeName) {
case "IMG": case "IMG":
return imageNodes.concat([{ return imageNodes.concat([{
args: [container.node.src], args: [container.node.src],
method: "url" method: "url"
}]); }]);
case "svg": case "svg":
case "IFRAME": case "IFRAME":
return imageNodes.concat([{ return imageNodes.concat([{
args: [container.node], args: [container.node],
method: container.node.nodeName method: container.node.nodeName
}]); }]);
} }
return imageNodes; return imageNodes;
}, []).forEach(this.addImage(images, this.loadImage), this); }, []).forEach(this.addImage(images, this.loadImage), this);
@ -94,7 +94,7 @@ ImageLoader.prototype.isSameOrigin = function(url) {
}; };
ImageLoader.prototype.getPromise = function(container) { ImageLoader.prototype.getPromise = function(container) {
return container.promise['catch'](function() { return this.timeout(container, this.options.imageTimeout)['catch'](function() {
var dummy = new DummyImageContainer(container.src); var dummy = new DummyImageContainer(container.src);
return dummy.promise.then(function(image) { return dummy.promise.then(function(image) {
container.image = image; container.image = image;
@ -113,12 +113,25 @@ ImageLoader.prototype.fetch = function(nodes) {
this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes)); this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
this.images.forEach(function(image, index) { this.images.forEach(function(image, index) {
image.promise.then(function() { image.promise.then(function() {
log("Succesfully loaded image #"+ (index+1)); log("Succesfully loaded image #"+ (index+1), image);
}, function(e) { }, function(e) {
log("Failed loading image #"+ (index+1), e); log("Failed loading image #"+ (index+1), image, e);
}); });
}); });
this.ready = Promise.all(this.images.map(this.getPromise)); this.ready = Promise.all(this.images.map(this.getPromise, this));
log("Finished searching images"); log("Finished searching images");
return this; return this;
}; };
ImageLoader.prototype.timeout = function(container, timeout) {
var timer;
return Promise.race([container.promise, new Promise(function(res, reject) {
timer = setTimeout(function() {
log("Timed out loading image", container);
reject(container);
}, timeout);
})]).then(function(container) {
clearTimeout(timer);
return container;
});
};

View File

@ -18,7 +18,7 @@ function NodeParser(element, renderer, support, imageLoader, options) {
return container.visible = container.isElementVisible(); return container.visible = container.isElementVisible();
}).map(this.getPseudoElements, this)); }).map(this.getPseudoElements, this));
this.fontMetrics = new FontMetrics(); this.fontMetrics = new FontMetrics();
log("Fetched nodes"); log("Fetched nodes, total:", this.nodes.length);
this.images = imageLoader.fetch(this.nodes.filter(isElement)); this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.ready = this.images.ready.then(bind(function() { this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing"); log("Images loaded, starting parsing");

View File

@ -7,7 +7,7 @@ function CanvasRenderer(width, height) {
this.taintCtx = this.document.createElement("canvas").getContext("2d"); this.taintCtx = this.document.createElement("canvas").getContext("2d");
this.ctx.textBaseline = "bottom"; this.ctx.textBaseline = "bottom";
this.variables = {}; this.variables = {};
log("Initialized CanvasRenderer"); log("Initialized CanvasRenderer with size", width, "x", height);
} }
CanvasRenderer.prototype = Object.create(Renderer.prototype); CanvasRenderer.prototype = Object.create(Renderer.prototype);