Skip to content

Commit 8a3d1d7

Browse files
committed
Fix firefox cross-origin iframe rendering
1 parent e6d31ad commit 8a3d1d7

File tree

7 files changed

+86
-58
lines changed

7 files changed

+86
-58
lines changed

dist/html2canvas.js

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ window.html2canvas = function(nodeList, options) {
581581
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
582582
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
583583
options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
584+
options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
584585

585586
if (typeof(nodeList) === "string") {
586587
if (typeof(options.proxy) !== "string") {
@@ -627,7 +628,7 @@ function renderWindow(node, container, options, windowWidth, windowHeight) {
627628
var parser = new NodeParser(node, renderer, support, imageLoader, options);
628629
return parser.ready.then(function() {
629630
log("Finished rendering");
630-
var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, bounds);
631+
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});
631632
cleanupContainer(container, options);
632633
return canvas;
633634
});
@@ -693,10 +694,13 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti
693694
if window url is about:blank, we can assign the url to current by writing onto the document
694695
*/
695696
container.contentWindow.onload = container.onload = function() {
696-
setTimeout(function() {
697-
cloneCanvasContents(ownerDocument, documentClone);
698-
resolve(container);
699-
}, 0);
697+
var interval = setInterval(function() {
698+
if (documentClone.body.childNodes.length > 0) {
699+
cloneCanvasContents(ownerDocument, documentClone);
700+
clearInterval(interval);
701+
resolve(container);
702+
}
703+
}, 50);
700704
};
701705

702706
documentClone.open();
@@ -718,11 +722,8 @@ function loadUrlDocument(src, proxy, document, width, height, options) {
718722

719723
function documentFromHTML(src) {
720724
return function(html) {
721-
var doc = document.implementation.createHTMLDocument("");
722-
doc.open();
723-
doc.write(html);
724-
doc.close();
725-
725+
var parser = new DOMParser();
726+
var doc = parser.parseFromString(html, "text/html");
726727
var b = doc.querySelector("base");
727728
if (!b || !b.href.host) {
728729
var base = doc.createElement("base");
@@ -871,7 +872,7 @@ function FrameContainer(container, sameOrigin, options) {
871872
resolve(container);
872873
}
873874
})).then(function(container) {
874-
return html2canvas(container.contentWindow.document.documentElement, {type: 'view', proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer});
875+
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});
875876
}).then(function(canvas) {
876877
return self.image = canvas;
877878
});
@@ -927,17 +928,17 @@ ImageLoader.prototype.findImages = function(nodes) {
927928
var images = [];
928929
nodes.reduce(function(imageNodes, container) {
929930
switch(container.node.nodeName) {
930-
case "IMG":
931-
return imageNodes.concat([{
932-
args: [container.node.src],
933-
method: "url"
934-
}]);
935-
case "svg":
936-
case "IFRAME":
937-
return imageNodes.concat([{
938-
args: [container.node],
939-
method: container.node.nodeName
940-
}]);
931+
case "IMG":
932+
return imageNodes.concat([{
933+
args: [container.node.src],
934+
method: "url"
935+
}]);
936+
case "svg":
937+
case "IFRAME":
938+
return imageNodes.concat([{
939+
args: [container.node],
940+
method: container.node.nodeName
941+
}]);
941942
}
942943
return imageNodes;
943944
}, []).forEach(this.addImage(images, this.loadImage), this);
@@ -1012,7 +1013,7 @@ ImageLoader.prototype.isSameOrigin = function(url) {
10121013
};
10131014

10141015
ImageLoader.prototype.getPromise = function(container) {
1015-
return container.promise['catch'](function() {
1016+
return this.timeout(container, this.options.imageTimeout)['catch'](function() {
10161017
var dummy = new DummyImageContainer(container.src);
10171018
return dummy.promise.then(function(image) {
10181019
container.image = image;
@@ -1031,16 +1032,29 @@ ImageLoader.prototype.fetch = function(nodes) {
10311032
this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
10321033
this.images.forEach(function(image, index) {
10331034
image.promise.then(function() {
1034-
log("Succesfully loaded image #"+ (index+1));
1035+
log("Succesfully loaded image #"+ (index+1), image);
10351036
}, function(e) {
1036-
log("Failed loading image #"+ (index+1), e);
1037+
log("Failed loading image #"+ (index+1), image, e);
10371038
});
10381039
});
1039-
this.ready = Promise.all(this.images.map(this.getPromise));
1040+
this.ready = Promise.all(this.images.map(this.getPromise, this));
10401041
log("Finished searching images");
10411042
return this;
10421043
};
10431044

1045+
ImageLoader.prototype.timeout = function(container, timeout) {
1046+
var timer;
1047+
return Promise.race([container.promise, new Promise(function(res, reject) {
1048+
timer = setTimeout(function() {
1049+
log("Timed out loading image", container);
1050+
reject(container);
1051+
}, timeout);
1052+
})]).then(function(container) {
1053+
clearTimeout(timer);
1054+
return container;
1055+
});
1056+
};
1057+
10441058
function LinearGradientContainer(imageData) {
10451059
GradientContainer.apply(this, arguments);
10461060
this.type = this.TYPES.LINEAR;
@@ -1509,7 +1523,7 @@ function NodeParser(element, renderer, support, imageLoader, options) {
15091523
return container.visible = container.isElementVisible();
15101524
}).map(this.getPseudoElements, this));
15111525
this.fontMetrics = new FontMetrics();
1512-
log("Fetched nodes");
1526+
log("Fetched nodes, total:", this.nodes.length);
15131527
this.images = imageLoader.fetch(this.nodes.filter(isElement));
15141528
this.ready = this.images.ready.then(bind(function() {
15151529
log("Images loaded, starting parsing");
@@ -2658,7 +2672,7 @@ function CanvasRenderer(width, height) {
26582672
this.taintCtx = this.document.createElement("canvas").getContext("2d");
26592673
this.ctx.textBaseline = "bottom";
26602674
this.variables = {};
2661-
log("Initialized CanvasRenderer");
2675+
log("Initialized CanvasRenderer with size", width, "x", height);
26622676
}
26632677

26642678
CanvasRenderer.prototype = Object.create(Renderer.prototype);

dist/html2canvas.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ window.html2canvas = function(nodeList, options) {
1313
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
1414
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
1515
options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
16+
options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
1617

1718
if (typeof(nodeList) === "string") {
1819
if (typeof(options.proxy) !== "string") {
@@ -59,7 +60,7 @@ function renderWindow(node, container, options, windowWidth, windowHeight) {
5960
var parser = new NodeParser(node, renderer, support, imageLoader, options);
6061
return parser.ready.then(function() {
6162
log("Finished rendering");
62-
var canvas = (options.type !== "view" && (node === clonedWindow.document.body || node === clonedWindow.document.documentElement)) ? renderer.canvas : crop(renderer.canvas, bounds);
63+
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});
6364
cleanupContainer(container, options);
6465
return canvas;
6566
});
@@ -125,10 +126,13 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti
125126
if window url is about:blank, we can assign the url to current by writing onto the document
126127
*/
127128
container.contentWindow.onload = container.onload = function() {
128-
setTimeout(function() {
129-
cloneCanvasContents(ownerDocument, documentClone);
130-
resolve(container);
131-
}, 0);
129+
var interval = setInterval(function() {
130+
if (documentClone.body.childNodes.length > 0) {
131+
cloneCanvasContents(ownerDocument, documentClone);
132+
clearInterval(interval);
133+
resolve(container);
134+
}
135+
}, 50);
132136
};
133137

134138
documentClone.open();
@@ -150,11 +154,8 @@ function loadUrlDocument(src, proxy, document, width, height, options) {
150154

151155
function documentFromHTML(src) {
152156
return function(html) {
153-
var doc = document.implementation.createHTMLDocument("");
154-
doc.open();
155-
doc.write(html);
156-
doc.close();
157-
157+
var parser = new DOMParser();
158+
var doc = parser.parseFromString(html, "text/html");
158159
var b = doc.querySelector("base");
159160
if (!b || !b.href.host) {
160161
var base = doc.createElement("base");

src/framecontainer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function FrameContainer(container, sameOrigin, options) {
1212
resolve(container);
1313
}
1414
})).then(function(container) {
15-
return html2canvas(container.contentWindow.document.documentElement, {type: 'view', proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer});
15+
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});
1616
}).then(function(canvas) {
1717
return self.image = canvas;
1818
});

src/imageloader.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ ImageLoader.prototype.findImages = function(nodes) {
99
var images = [];
1010
nodes.reduce(function(imageNodes, container) {
1111
switch(container.node.nodeName) {
12-
case "IMG":
13-
return imageNodes.concat([{
14-
args: [container.node.src],
15-
method: "url"
16-
}]);
17-
case "svg":
18-
case "IFRAME":
19-
return imageNodes.concat([{
20-
args: [container.node],
21-
method: container.node.nodeName
22-
}]);
12+
case "IMG":
13+
return imageNodes.concat([{
14+
args: [container.node.src],
15+
method: "url"
16+
}]);
17+
case "svg":
18+
case "IFRAME":
19+
return imageNodes.concat([{
20+
args: [container.node],
21+
method: container.node.nodeName
22+
}]);
2323
}
2424
return imageNodes;
2525
}, []).forEach(this.addImage(images, this.loadImage), this);
@@ -94,7 +94,7 @@ ImageLoader.prototype.isSameOrigin = function(url) {
9494
};
9595

9696
ImageLoader.prototype.getPromise = function(container) {
97-
return container.promise['catch'](function() {
97+
return this.timeout(container, this.options.imageTimeout)['catch'](function() {
9898
var dummy = new DummyImageContainer(container.src);
9999
return dummy.promise.then(function(image) {
100100
container.image = image;
@@ -113,12 +113,25 @@ ImageLoader.prototype.fetch = function(nodes) {
113113
this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
114114
this.images.forEach(function(image, index) {
115115
image.promise.then(function() {
116-
log("Succesfully loaded image #"+ (index+1));
116+
log("Succesfully loaded image #"+ (index+1), image);
117117
}, function(e) {
118-
log("Failed loading image #"+ (index+1), e);
118+
log("Failed loading image #"+ (index+1), image, e);
119119
});
120120
});
121-
this.ready = Promise.all(this.images.map(this.getPromise));
121+
this.ready = Promise.all(this.images.map(this.getPromise, this));
122122
log("Finished searching images");
123123
return this;
124124
};
125+
126+
ImageLoader.prototype.timeout = function(container, timeout) {
127+
var timer;
128+
return Promise.race([container.promise, new Promise(function(res, reject) {
129+
timer = setTimeout(function() {
130+
log("Timed out loading image", container);
131+
reject(container);
132+
}, timeout);
133+
})]).then(function(container) {
134+
clearTimeout(timer);
135+
return container;
136+
});
137+
};

src/nodeparser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function NodeParser(element, renderer, support, imageLoader, options) {
1818
return container.visible = container.isElementVisible();
1919
}).map(this.getPseudoElements, this));
2020
this.fontMetrics = new FontMetrics();
21-
log("Fetched nodes");
21+
log("Fetched nodes, total:", this.nodes.length);
2222
this.images = imageLoader.fetch(this.nodes.filter(isElement));
2323
this.ready = this.images.ready.then(bind(function() {
2424
log("Images loaded, starting parsing");

src/renderers/canvas.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function CanvasRenderer(width, height) {
77
this.taintCtx = this.document.createElement("canvas").getContext("2d");
88
this.ctx.textBaseline = "bottom";
99
this.variables = {};
10-
log("Initialized CanvasRenderer");
10+
log("Initialized CanvasRenderer with size", width, "x", height);
1111
}
1212

1313
CanvasRenderer.prototype = Object.create(Renderer.prototype);

0 commit comments

Comments
 (0)