2012-03-02 21:07:15 +04:00
_html2canvas . Preload = function ( options ) {
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
var images = {
numLoaded : 0 , // also failed are counted here
numFailed : 0 ,
numTotal : 0 ,
cleanupDone : false
} ,
pageOrigin ,
methods ,
i ,
count = 0 ,
element = options . elements [ 0 ] || document . body ,
doc = element . ownerDocument ,
domImages = doc . images , // TODO probably should limit it to images present in the element only
imgLen = domImages . length ,
link = doc . createElement ( "a" ) ,
supportCORS = ( function ( img ) {
return ( img . crossOrigin !== undefined ) ;
} ) ( new Image ( ) ) ,
timeoutTimer ;
link . href = window . location . href ;
pageOrigin = link . protocol + link . host ;
function isSameOrigin ( url ) {
link . href = url ;
link . href = link . href ; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
var origin = link . protocol + link . host ;
return ( origin === pageOrigin ) ;
}
function start ( ) {
h2clog ( "html2canvas: start: images: " + images . numLoaded + " / " + images . numTotal + " (failed: " + images . numFailed + ")" ) ;
if ( ! images . firstRun && images . numLoaded >= images . numTotal ) {
h2clog ( "Finished loading images: # " + images . numTotal + " (failed: " + images . numFailed + ")" ) ;
if ( typeof options . complete === "function" ) {
options . complete ( images ) ;
}
2012-03-03 23:03:59 +04:00
2011-11-27 06:33:41 +04:00
}
2012-11-25 22:59:31 +04:00
}
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
// TODO modify proxy to serve images with CORS enabled, where available
function proxyGetImage ( url , img , imageObj ) {
var callback _name ,
scriptUrl = options . proxy ,
script ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
link . href = url ;
url = link . href ; // work around for pages with base href="" set - WARNING: this may change the url
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
callback _name = 'html2canvas_' + ( count ++ ) ;
imageObj . callbackname = callback _name ;
if ( scriptUrl . indexOf ( "?" ) > - 1 ) {
scriptUrl += "&" ;
} else {
scriptUrl += "?" ;
2011-11-27 06:33:41 +04:00
}
2012-11-25 22:59:31 +04:00
scriptUrl += 'url=' + encodeURIComponent ( url ) + '&callback=' + callback _name ;
script = doc . createElement ( "script" ) ;
window [ callback _name ] = function ( a ) {
if ( a . substring ( 0 , 6 ) === "error:" ) {
imageObj . succeeded = false ;
images . numLoaded ++ ;
images . numFailed ++ ;
start ( ) ;
} else {
setImageLoadHandlers ( img , imageObj ) ;
img . src = a ;
}
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 ;
delete imageObj . script ;
delete imageObj . callbackname ;
} ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
script . setAttribute ( "type" , "text/javascript" ) ;
script . setAttribute ( "src" , scriptUrl ) ;
imageObj . script = script ;
window . document . body . appendChild ( script ) ;
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
}
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
function getImages ( el ) {
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
var contents = _html2canvas . Util . Children ( el ) ,
i ,
background _image ,
2012-12-28 21:53:15 +04:00
background _images ,
2012-11-25 22:59:31 +04:00
src ,
img ,
elNodeType = false ;
// Firefox fails with permission denied on pages with iframes
try {
var contentsLen = contents . length ;
for ( i = 0 ; i < contentsLen ; i += 1 ) {
getImages ( contents [ i ] ) ;
}
2011-11-27 06:33:41 +04:00
}
2012-11-25 22:59:31 +04:00
catch ( e ) { }
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
try {
elNodeType = el . nodeType ;
} catch ( ex ) {
elNodeType = false ;
h2clog ( "html2canvas: failed to access some element's nodeType - Exception: " + ex . message ) ;
}
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
if ( elNodeType === 1 || elNodeType === undefined ) {
2012-06-26 02:30:45 +04:00
2012-11-25 22:59:31 +04:00
// opera throws exception on external-content.html
try {
background _image = _html2canvas . Util . getCSS ( el , 'backgroundImage' ) ;
} catch ( e ) {
h2clog ( "html2canvas: failed to get background-image - Exception: " + e . message ) ;
}
2012-03-03 23:03:59 +04:00
2012-12-28 22:33:57 +04:00
background _images = _html2canvas . Util . parseBackgroundImage ( background _image ) ;
2012-12-28 21:53:15 +04:00
while ( ! ! ( background _image = background _images . shift ( ) ) ) {
2012-12-30 22:20:35 +04:00
if ( ! background _image ||
! background _image . method ||
! background _image . args ||
background _image . args . length === 0 ) {
continue ;
}
2011-11-27 06:33:41 +04:00
2012-12-30 22:20:35 +04:00
if ( background _image . method === 'url' ) {
src = background _image . args [ 0 ] ;
methods . loadImage ( src ) ;
2012-03-03 23:03:59 +04:00
2012-12-30 22:20:35 +04:00
} else if ( background _image . method . match ( /\-gradient$/ ) ) {
img = _html2canvas . Generate . Gradient ( background _image . value , _html2canvas . Util . Bounds ( el ) ) ;
2012-03-03 23:03:59 +04:00
2012-12-30 22:20:35 +04:00
if ( img !== undefined ) {
images [ background _image . value ] = {
img : img ,
succeeded : true
} ;
images . numTotal ++ ;
images . numLoaded ++ ;
start ( ) ;
2012-12-28 21:53:15 +04:00
2012-11-25 22:59:31 +04:00
}
}
}
}
}
function setImageLoadHandlers ( img , imageObj ) {
img . onload = function ( ) {
if ( imageObj . timer !== undefined ) {
// CORS succeeded
window . clearTimeout ( imageObj . timer ) ;
}
images . numLoaded ++ ;
imageObj . succeeded = true ;
img . onerror = img . onload = null ;
start ( ) ;
} ;
img . onerror = function ( ) {
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
if ( img . crossOrigin === "anonymous" ) {
// CORS failed
window . clearTimeout ( imageObj . timer ) ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
// let's try with proxy instead
if ( options . proxy ) {
var src = img . src ;
img = new Image ( ) ;
imageObj . img = img ;
img . src = src ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
proxyGetImage ( img . src , img , imageObj ) ;
return ;
2011-11-27 06:33:41 +04:00
}
2012-11-25 22:59:31 +04:00
}
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
images . numLoaded ++ ;
images . numFailed ++ ;
imageObj . succeeded = false ;
img . onerror = img . onload = null ;
start ( ) ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
} ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
// TODO Opera has no load/error event for SVG images
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
// Opera ninja onload's cached images
/ *
2012-03-03 21:18:39 +04:00
window . setTimeout ( function ( ) {
if ( img . width !== 0 && imageObj . succeeded === undefined ) {
img . onload ( ) ;
2012-03-03 23:03:59 +04:00
}
2012-03-03 21:18:39 +04:00
} , 100 ) ; // needs a reflow for base64 encoded images? interestingly timeout of 0 doesn't work but 1 does.
2012-06-26 02:30:45 +04:00
* /
2012-11-25 22:59:31 +04:00
}
2013-01-02 22:58:48 +04:00
var uid = 0 , injectStyle ;
function injectPseudoElements ( el ) {
var before = getPseudoElement ( el , ':before' ) ,
after = getPseudoElement ( el , ':after' ) ;
if ( ! before && ! after ) {
return ;
}
if ( ! el . id ) {
el . id = '__html2cavas__' + ( uid ++ ) ;
}
if ( ! injectStyle ) {
injectStyle = document . createElement ( 'style' ) ;
}
if ( before ) {
el . _ _html2canvas _before = before ;
injectStyle . innerHTML += '#' + el . id + ':before { content: "" !important; display: none !important; }\n' ;
if ( el . childNodes . length > 0 ) {
el . insertBefore ( before , el . childNodes [ 0 ] ) ;
} else {
el . appendChild ( before ) ;
}
}
if ( after ) {
el . _ _html2canvas _after = after ;
injectStyle . innerHTML += '#' + el . id + ':after { content: "" !important; display: none !important; }\n' ;
el . appendChild ( after ) ;
}
}
function removePseudoElements ( el ) {
var before = el . _ _html2canvas _before ,
after = el . _ _html2canvas _after ;
if ( before ) {
el . _ _html2canvas _before = undefined ;
el . removeChild ( before ) ;
}
if ( after ) {
el . _ _html2canvas _after = undefined ;
el . removeChild ( after ) ;
}
}
function getPseudoElement ( el , which ) {
var elStyle = window . getComputedStyle ( el , which ) ;
if ( ! elStyle || ! elStyle . content ) { return ; }
var content = elStyle . content + '' ,
first = content . substr ( 0 , 1 ) ;
//strips quotes
if ( first === content . substr ( content . length - 1 ) && first . match ( /'|"/ ) ) {
content = content . substr ( 1 , content . length - 2 ) ;
}
var isImage = content . substr ( 0 , 3 ) === 'url' ,
elps = document . createElement ( isImage ? 'img' : 'span' ) ;
elps . className = '__html2canvas__' + which . substr ( 1 ) ;
Object . keys ( elStyle ) . forEach ( function ( prop ) {
//skip indexed properties
if ( ! isNaN ( parseInt ( prop , 10 ) ) ) { return ; }
elps . style [ prop ] = elStyle [ prop ] ;
} ) ;
if ( isImage ) {
elps . src = _html2canvas . Util . parseBackgroundImage ( content ) [ 0 ] . args [ 0 ] ;
} else {
elps . innerHTML = content ;
}
return elps ;
}
2012-11-25 22:59:31 +04:00
methods = {
loadImage : function ( src ) {
var img , imageObj ;
if ( src && images [ src ] === undefined ) {
img = new Image ( ) ;
if ( src . match ( /data:image\/.*;base64,/i ) ) {
img . src = src . replace ( /url\(['"]{0,}|['"]{0,}\)$/ig , '' ) ;
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
} else if ( isSameOrigin ( src ) || options . allowTaint === true ) {
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
img . src = src ;
} else if ( supportCORS && ! options . allowTaint && options . useCORS ) {
// attempt to load with CORS
img . crossOrigin = "anonymous" ;
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
img . src = src ;
// work around for https://bugs.webkit.org/show_bug.cgi?id=80028
img . customComplete = function ( ) {
if ( ! this . img . complete ) {
this . timer = window . setTimeout ( this . img . customComplete , 100 ) ;
} else {
this . img . onerror ( ) ;
2012-03-03 23:03:59 +04:00
}
2012-11-25 22:59:31 +04:00
} . bind ( imageObj ) ;
img . customComplete ( ) ;
} else if ( options . proxy ) {
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
proxyGetImage ( src , img , imageObj ) ;
}
}
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
} ,
cleanupDOM : function ( cause ) {
var img , src ;
if ( ! images . cleanupDone ) {
if ( cause && typeof cause === "string" ) {
h2clog ( "html2canvas: Cleanup because: " + cause ) ;
} else {
h2clog ( "html2canvas: Cleanup after timeout: " + options . timeout + " ms." ) ;
2011-11-27 06:33:41 +04:00
}
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
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 ++ ;
h2clog ( "html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images . numLoaded + " / " + images . numTotal ) ;
}
}
}
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
// 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 ( ) ;
}
}
2013-01-02 22:58:48 +04:00
if ( injectStyle ) {
injectStyle . parentNode . removeChild ( injectStyle ) ;
injectStyle = undefined ;
[ ] . slice . apply ( element . all || element . getElementsByTagName ( '*' ) )
. forEach ( removePseudoElements ) ;
}
2012-11-25 22:59:31 +04:00
} ,
2013-01-02 22:58:48 +04:00
2012-11-25 22:59:31 +04:00
renderingDone : function ( ) {
if ( timeoutTimer ) {
window . clearTimeout ( timeoutTimer ) ;
}
2011-11-27 06:33:41 +04:00
}
2012-11-25 22:59:31 +04:00
} ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
if ( options . timeout > 0 ) {
timeoutTimer = window . setTimeout ( methods . cleanupDOM , options . timeout ) ;
}
2013-01-02 22:58:48 +04:00
[ ] . slice . apply ( element . all || element . getElementsByTagName ( '*' ) )
. forEach ( injectPseudoElements ) ;
if ( injectStyle ) {
element . appendChild ( injectStyle ) ;
}
2012-11-25 22:59:31 +04:00
h2clog ( 'html2canvas: Preload starts: finding background-images' ) ;
images . firstRun = true ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
getImages ( element ) ;
2012-03-03 23:03:59 +04:00
2012-11-25 22:59:31 +04:00
h2clog ( 'html2canvas: Preload: Finding images' ) ;
// load <img> images
for ( i = 0 ; i < imgLen ; i += 1 ) {
methods . loadImage ( domImages [ i ] . getAttribute ( "src" ) ) ;
}
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
images . firstRun = false ;
h2clog ( 'html2canvas: Preload: Done.' ) ;
if ( images . numTotal === images . numLoaded ) {
start ( ) ;
}
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
return methods ;
2011-11-27 06:33:41 +04:00
2012-11-25 22:59:31 +04:00
} ;