From 73763c8114b61ec2f94a7808741259a9f85b26e6 Mon Sep 17 00:00:00 2001 From: Obexer Christoph Date: Mon, 21 Nov 2011 12:11:48 +0100 Subject: [PATCH] rewrote image handling from using an array to an object The image loading done in Preload.js used an array to store image src and image object as 2 consecutive entries in that array. Using the src as an index in a hash allows direct instead of a linear search and also allows to store more data. * improved cleanup of images loaded with the proxy * this also adds a timeout for the image loading phase, after that running image requests are aborted and the rendering is started --- src/Parse.js | 38 +++------- src/Preload.js | 188 +++++++++++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 95 deletions(-) diff --git a/src/Parse.js b/src/Parse.js index 63c6831..4a48f9d 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -48,7 +48,7 @@ html2canvas.Parse = function (element, images, opts) { childrenLen; - images = images || []; + images = images || {}; // Test whether we can use ranges to measure bounding boxes // Opera doesn't provide valid bounds.height/bottom even though it supports the method. @@ -514,28 +514,13 @@ html2canvas.Parse = function (element, images, opts) { } - function loadImage (src){ - - var imgIndex = -1, - i, - imgLen; - if (images.indexOf){ - imgIndex = images.indexOf(src); - }else{ - for(i = 0, imgLen = images.length; i < imgLen.length; i+=1){ - if(images[i] === src) { - imgIndex = i; - break; - } - } - } - - if (imgIndex > -1){ - return images[imgIndex+1]; - }else{ - return false; - } - + function loadImage (src){ + var img = images[src]; + if (img && img.succeeded === true) { + return img.img; + } else { + return false; + } } @@ -1004,8 +989,7 @@ html2canvas.Parse = function (element, images, opts) { } }else{ - - html2canvas.log("Error loading background:" + background_image); + html2canvas.log("html2canvas: Error loading background:" + background_image); //console.log(images); } @@ -1164,8 +1148,8 @@ html2canvas.Parse = function (element, images, opts) { bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh ); - }else { - html2canvas.log("Error loading :" + imgSrc); + }else{ + html2canvas.log("html2canvas: Error loading :" + imgSrc); } break; case "INPUT": diff --git a/src/Preload.js b/src/Preload.js index 878cdf1..d91d093 100644 --- a/src/Preload.js +++ b/src/Preload.js @@ -9,11 +9,16 @@ html2canvas.Preload = function(element, opts){ var options = { - "proxy": "http://html2canvas.appspot.com/" + proxy: "http://html2canvas.appspot.com/", + timeout: 0 // no timeout + }, + images = { + numLoaded: 0, // also failed are counted here + numFailed: 0, + numTotal: 0, + cleanupDone: false }, - images = [], pageOrigin = window.location.protocol + window.location.host, - imagesLoaded = 0, methods, i, count = 0, @@ -36,24 +41,9 @@ html2canvas.Preload = function(element, opts){ } - function getIndex(array,src){ - var i, arrLen; - if (array.indexOf){ - return array.indexOf(src); - }else{ - for(i = 0, arrLen = array.length; i < arrLen; i+=1){ - if(this[i] === src) { - return i; - } - } - return -1; - } - - } - function start(){ - if (images.length === 0 || imagesLoaded === images.length/2){ - + html2canvas.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")"); + if (!images.firstRun && images.numLoaded >= images.numTotal){ /* this.log('Finished loading '+this.imagesLoaded+' images, Started parsing'); @@ -63,20 +53,22 @@ html2canvas.Preload = function(element, opts){ if (typeof options.complete === "function"){ options.complete(images); } + + html2canvas.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")"); } } function proxyGetImage(url, img){ - - link.href = url; - url = link.href; // work around for pages with base href="" set var callback_name, - scriptUrl = options.proxy, - script; - + scriptUrl = options.proxy, + script, + imgObj = images[url]; + + link.href = url; + url = link.href; // work around for pages with base href="" set - WARNING: this may change the url -> so access imgObj from images map before changing that url! + callback_name = 'html2canvas_' + count; - - + imgObj.callbackname = callback_name; if (scriptUrl.indexOf("?") > -1) { scriptUrl += "&"; @@ -87,26 +79,35 @@ html2canvas.Preload = function(element, opts){ window[callback_name] = function(a){ if (a.substring(0,6) === "error:"){ - images.splice(getIndex(images, url), 2); + imgObj.succeeded = false; + images.numLoaded++; + images.numFailed++; start(); - }else{ + } else { img.onload = function(){ - imagesLoaded+=1; - start(); + imgObj.succeeded = true; + images.numLoaded++; + start(); }; - img.src = a; } - delete window[callback_name]; + window[callback_name] = 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[callback_name]; // for all browser that support this + } catch(ex) {} + script.parentNode.removeChild(script); + script = null; + imgObj.callbackname = undefined; }; count += 1; script = doc.createElement("script"); script.setAttribute("src", scriptUrl); - script.setAttribute("type", "text/javascript"); + script.setAttribute("type", "text/javascript"); + imgObj.script = script; window.document.body.appendChild(script); - + /* // enable xhr2 requests where available (no need for base64 / json) @@ -179,10 +180,10 @@ html2canvas.Preload = function(element, opts){ img = html2canvas.Generate.Gradient( background_image, html2canvas.Util.Bounds( el ) ); - if ( img !== undefined ){ - images.push(background_image); - images.push(img); - imagesLoaded++; + if ( img !== undefined ){ + images[background_image] = { img: img, succeeded: true }; + images.numTotal++; + images.numLoaded++; start(); } @@ -204,58 +205,110 @@ html2canvas.Preload = function(element, opts){ methods = { loadImage: function( src ) { var img; - if ( getIndex(images, src) === -1 ) { + if ( images[src] === undefined ) { if ( src.match(/data:image\/.*;base64,/i) ) { //Base64 src img = new Image(); img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''); - - images.push( src ); - images.push( img ); - - imagesLoaded+=1; + images[src] = { img: img, succeeded: true }; + images.numTotal++; + images.numLoaded++; start(); }else if ( isSameOrigin( src ) ) { - images.push( src ); - img = new Image(); + img = new Image(); + images[src] = { img: img }; + images.numTotal++; img.onload = function() { - imagesLoaded+=1; - start(); - + images.numLoaded++; + images[src].succeeded = true; + start(); }; img.onerror = function() { - images.splice( getIndex( images, img.src ), 2 ); - start(); + images.numLoaded++; + images.numFailed++; + images[src].succeeded = false; + start(); }; - img.src = src; - images.push(img); + img.src = src; }else if ( options.proxy ){ // console.log('b'+src); - images.push( src ); - img = new Image(); + img = new Image(); + images[src] = { img: img }; + images.numTotal++; proxyGetImage( src, img ); - images.push( img ); } } + }, + cleanupDOM: function(cause) { + var img; + if (!images.cleanupDone) { + if (cause && typeof cause === "string") { + html2canvas.log("html2canvas: Cleanup because: " + cause); + } else { + html2canvas.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms."); + } + + for (src in images) { + if (images.hasOwnProperty(src)) { + img = images[src]; + if (typeof img === "object" && img.callbackname && img.succeeded === undefined) { + // cancel proxy image request + window[img.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[img.callbackname]; // for all browser that support this + } catch(ex) {} + if (img.script && img.script.parentNode) { + img.script.setAttribute("src", "about:blank"); // try to cancel running request + img.script.parentNode.removeChild(img.script); + } + images.numLoaded++; + images.numFailed++; + html2canvas.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal); + } + } + } + + // cancel any pending requests + if(window.stop !== undefined) { + window.stop(); + } else if(document.execCommand !== undefined) { + document.execCommand("Stop", false); + } + if (document.close !== undefined) { + document.close(); + } + images.cleanupDone = true; + if (!(cause && typeof cause === "string")) { + start(); + } + } + }, + renderingDone: function() { + if (timeoutTimer) { + window.clearTimeout(timeoutTimer); + } } - }; - - // add something to array - images.push('start'); - + + if (options.timeout > 0) { + timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout); + } + var startTime = (new Date()).getTime(); + this.log('html2canvas: Preload starts: finding background-images'); + images.firstRun = true; + getImages( element ); - + this.log('html2canvas: Preload: Finding images'); // load images for (i = 0; i < imgLen; i+=1){ var imgSrc = domImages[i].getAttribute( "src" ); @@ -264,10 +317,9 @@ html2canvas.Preload = function(element, opts){ } } - // remove 'start' - images.splice(0, 1); - - if ( images.length === 0 ) { + images.firstRun = false; + this.log('html2canvas: Preload: Done.'); + if ( images.numTotal === images.numLoaded ) { start(); }