Fix render ordering of nodes that form fake stacking contexts

This commit is contained in:
MoyuScript
2014-02-15 00:33:09 +02:00
parent db437f8950
commit ddc81fe697
5 changed files with 128 additions and 88 deletions

View File

@ -4,6 +4,10 @@ window.html2canvas = function(nodeList, options) {
window.html2canvas.logging = true;
window.html2canvas.start = Date.now();
}
options.async = typeof(options.async) === "undefined" ? true : options.async;
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
return renderDocument(document, options, window.innerWidth, window.innerHeight).then(function(canvas) {
if (typeof(options.onrendered) === "function") {
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
@ -14,7 +18,7 @@ window.html2canvas = function(nodeList, options) {
};
function renderDocument(document, options, windowWidth, windowHeight) {
return createWindowClone(document, windowWidth, windowHeight).then(function(container) {
return createWindowClone(document, windowWidth, windowHeight, options).then(function(container) {
log("Document cloned");
var clonedWindow = container.contentWindow;
//var element = (nodeList === undefined) ? document.body : nodeList[0];
@ -27,7 +31,10 @@ function renderDocument(document, options, windowWidth, windowHeight) {
var renderer = new CanvasRenderer(width, height, imageLoader);
var parser = new NodeParser(node, renderer, support, imageLoader, options);
return parser.ready.then(function() {
container.parentNode.removeChild(container);
log("Finished rendering");
if (options.removeContainer) {
container.parentNode.removeChild(container);
}
return renderer.canvas;
});
});
@ -53,7 +60,7 @@ function smallImage() {
return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
}
function createWindowClone(ownerDocument, width, height) {
function createWindowClone(ownerDocument, width, height, options) {
var documentElement = ownerDocument.documentElement.cloneNode(true),
container = ownerDocument.createElement("iframe");
@ -65,18 +72,6 @@ function createWindowClone(ownerDocument, width, height) {
ownerDocument.body.appendChild(container);
return new Promise(function(resolve) {
var loadedTimer = function() {
/* Chrome doesn't detect relative background-images assigned in style sheets when fetched through getComputedStyle,
before a certain time has passed
*/
if (container.contentWindow.getComputedStyle(div, null)['backgroundImage'] !== "none") {
documentClone.body.removeChild(div);
documentClone.body.removeChild(style);
resolve(container);
} else {
window.setTimeout(loadedTimer, 10);
}
};
var documentClone = container.contentWindow.document;
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
if window url is about:blank, we can assign the url to current by writing onto the document
@ -86,13 +81,9 @@ function createWindowClone(ownerDocument, width, height) {
documentClone.close();
documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
container.contentWindow.scrollTo(window.scrollX, window.scrollY);
var div = documentClone.createElement("div");
div.className = "html2canvas-ready-test";
documentClone.body.appendChild(div);
var style = documentClone.createElement("style");
style.innerHTML = "body div.html2canvas-ready-test { background-image:url(" + smallImage() + "); }";
documentClone.body.appendChild(style);
window.setTimeout(loadedTimer, 1000);
if (options.type === "view") {
container.contentWindow.scrollTo(window.scrollX, window.scrollY);
}
resolve(container);
});
}

View File

@ -4,6 +4,7 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.options = options;
this.range = null;
this.support = support;
this.renderQueue = [];
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null);
parent.visibile = parent.isElementVisible();
@ -21,10 +22,35 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing");
this.parse(this.stack);
log("Finished rendering");
log("Render queue created with " + this.renderQueue.length + " items");
return new Promise(bind(function(resolve) {
if (!options.async) {
this.renderQueue.forEach(this.paint, this);
resolve();
} else if (typeof(options.async) === "function") {
options.async.call(this, this.renderQueue, resolve);
} else {
this.renderIndex = 0;
this.asyncRenderer(this.renderQueue, resolve);
}
}, this));
}, this));
}
NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
asyncTimer = asyncTimer || Date.now();
this.paint(queue[this.renderIndex++]);
if (queue.length === this.renderIndex) {
resolve();
} else if (asyncTimer + 20 > Date.now()) {
this.asyncRenderer(queue, resolve, asyncTimer);
} else {
setTimeout(bind(function() {
this.asyncRenderer(queue, resolve);
}, this), 0);
}
};
NodeParser.prototype.createPseudoHideStyles = function(document) {
var hidePseudoElements = document.createElement('style');
hidePseudoElements.innerHTML = '.' + this.pseudoHideClass + ':before { content: "" !important; display: none !important; }' +
@ -101,7 +127,7 @@ NodeParser.prototype.getChildren = function(parentContainer) {
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
stack.visible = container.visible;
var parentStack = stack.getParentStack(this);
var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack);
container.stack = stack;
};
@ -110,7 +136,7 @@ NodeParser.prototype.createStackingContexts = function() {
this.nodes.forEach(function(container) {
if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container))) {
this.newStackingContext(container, true);
} else if (isElement(container) && (isPositioned(container))) {
} else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
this.newStackingContext(container, false);
} else {
container.assignStack(container.parent.stack);
@ -202,16 +228,9 @@ NodeParser.prototype.parse = function(stack) {
var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
var text = stack.children.filter(isTextNode).filter(hasText);
var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
var rendered = [];
negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
.concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
this.paint(container);
if (rendered.indexOf(container.node) !== -1) {
log(container, container.node);
throw new Error("rendering twice");
}
rendered.push(container.node);
this.renderQueue.push(container);
if (isStackingContext(container)) {
this.parse(container);
}
@ -238,7 +257,7 @@ NodeParser.prototype.paintNode = function(container) {
var bounds = this.parseBounds(container);
var borderData = this.parseBorders(container);
this.renderer.clip(borderData.clip, function() {
this.renderer.renderBackground(container, bounds);
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
}, this);
this.renderer.renderBorders(borderData.borders);
@ -575,6 +594,10 @@ function isFloating(container) {
return container.css("float") !== "none";
}
function isInlineBlock(container) {
return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
}
function not(callback) {
var context = this;
return function() {
@ -608,6 +631,10 @@ function asInt(value) {
return parseInt(value, 10);
}
function getWidth(border) {
return border.width;
}
function nonIgnoredElement(nodeContainer) {
return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR"].indexOf(nodeContainer.node.nodeName) === -1);
}