@@ -1041,36 +1041,162 @@ _html2canvas.Parse = function (images, options, cb) {
1041
1041
body = doc . body ,
1042
1042
getCSS = Util . getCSS ,
1043
1043
pseudoHide = "___html2canvas___pseudoelement" ,
1044
- hidePseudoElements = doc . createElement ( 'style' ) ;
1044
+ hidePseudoElementsStyles = doc . createElement ( 'style' ) ;
1045
1045
1046
- hidePseudoElements . innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
1047
- '.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }' ;
1046
+ hidePseudoElementsStyles . innerHTML = '.' + pseudoHide +
1047
+ '-parent:before { content: "" !important; display: none !important; }' +
1048
+ '.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }' ;
1048
1049
1049
- body . appendChild ( hidePseudoElements ) ;
1050
+ body . appendChild ( hidePseudoElementsStyles ) ;
1050
1051
1051
1052
images = images || { } ;
1052
1053
1054
+ init ( ) ;
1055
+
1053
1056
function init ( ) {
1054
1057
var background = getCSS ( document . documentElement , "backgroundColor" ) ,
1055
1058
transparentBackground = ( Util . isTransparent ( background ) && element === document . body ) ,
1056
1059
stack = renderElement ( element , null , false , transparentBackground ) ;
1060
+
1061
+ // create pseudo elements in a single pass to prevent synchronous layouts
1062
+ addPseudoElements ( element ) ;
1057
1063
1058
- parseChildren ( element , stack , null , function ( ) {
1064
+ parseChildren ( element , stack , function ( ) {
1059
1065
if ( transparentBackground ) {
1060
1066
background = stack . backgroundColor ;
1061
1067
}
1062
1068
1069
+ removePseudoElements ( ) ;
1070
+
1063
1071
Util . log ( 'Done parsing, moving to Render.' ) ;
1064
1072
1065
- body . removeChild ( hidePseudoElements ) ;
1066
1073
cb ( {
1067
1074
backgroundColor : background ,
1068
1075
stack : stack
1069
1076
} ) ;
1070
1077
} ) ;
1071
1078
}
1072
1079
1073
- init ( ) ;
1080
+ // Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
1081
+ // so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
1082
+ // with layout.
1083
+ function addPseudoElements ( el ) {
1084
+ // These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
1085
+ // layouts & getPseudoElement calling getComputedStyle.
1086
+ var jobs = [ ] , classes = [ ] ;
1087
+ getPseudoElementClasses ( ) ;
1088
+ findPseudoElements ( el ) ;
1089
+ runJobs ( ) ;
1090
+
1091
+ function getPseudoElementClasses ( ) {
1092
+ var findPsuedoEls = / : b e f o r e | : a f t e r / ;
1093
+ var sheets = document . styleSheets ;
1094
+ for ( var i = 0 , j = sheets . length ; i < j ; i ++ ) {
1095
+ try {
1096
+ var rules = sheets [ i ] . cssRules ;
1097
+ for ( var k = 0 , l = rules . length ; k < l ; k ++ ) {
1098
+ if ( findPsuedoEls . test ( rules [ k ] . selectorText ) ) {
1099
+ classes . push ( rules [ k ] . selectorText ) ;
1100
+ }
1101
+ }
1102
+ }
1103
+ catch ( e ) { // will throw security exception for style sheets loaded from external domains
1104
+ }
1105
+ }
1106
+
1107
+ // Trim off the :after and :before (or ::after and ::before)
1108
+ for ( i = 0 , j = classes . length ; i < j ; i ++ ) {
1109
+ classes [ i ] = classes [ i ] . match ( / ( ^ [ ^ : ] * ) / ) [ 1 ] ;
1110
+ }
1111
+ }
1112
+
1113
+ // Using the list of elements we know how pseudo el styles, create fake pseudo elements.
1114
+ function findPseudoElements ( el ) {
1115
+ var els = document . querySelectorAll ( classes . join ( ',' ) ) ;
1116
+ for ( var i = 0 , j = els . length ; i < j ; i ++ ) {
1117
+ createPseudoElements ( els [ i ] ) ;
1118
+ }
1119
+ }
1120
+
1121
+ // Create pseudo elements & add them to a job queue.
1122
+ function createPseudoElements ( el ) {
1123
+ var before = getPseudoElement ( el , ':before' ) ,
1124
+ after = getPseudoElement ( el , ':after' ) ;
1125
+
1126
+ if ( before ) {
1127
+ jobs . push ( { type : 'before' , pseudo : before , el : el } ) ;
1128
+ }
1129
+
1130
+ if ( after ) {
1131
+ jobs . push ( { type : 'after' , pseudo : after , el : el } ) ;
1132
+ }
1133
+ }
1134
+
1135
+ // Adds a class to the pseudo's parent to prevent the original before/after from messing
1136
+ // with layouts.
1137
+ // Execute the inserts & addClass() calls in a batch to prevent relayouts.
1138
+ function runJobs ( ) {
1139
+ // Add Class
1140
+ jobs . forEach ( function ( job ) {
1141
+ addClass ( job . el , pseudoHide + "-parent" ) ;
1142
+ } ) ;
1143
+
1144
+ // Insert el
1145
+ jobs . forEach ( function ( job ) {
1146
+ if ( job . type === 'before' ) {
1147
+ job . el . insertBefore ( job . pseudo , job . el . firstChild ) ;
1148
+ } else {
1149
+ job . el . appendChild ( job . pseudo ) ;
1150
+ }
1151
+ } ) ;
1152
+ }
1153
+ }
1154
+
1155
+
1156
+
1157
+ // Delete our fake pseudo elements from the DOM. This will remove those actual elements
1158
+ // and the classes on their parents that hide the actual pseudo elements.
1159
+ // Note that NodeLists are 'live' collections so you can't use a for loop here. They are
1160
+ // actually deleted from the NodeList after each iteration.
1161
+ function removePseudoElements ( ) {
1162
+ // delete pseudo elements
1163
+ body . removeChild ( hidePseudoElementsStyles ) ;
1164
+ var pseudos = document . getElementsByClassName ( pseudoHide + "-element" ) ;
1165
+ while ( pseudos . length ) {
1166
+ pseudos [ 0 ] . parentNode . removeChild ( pseudos [ 0 ] ) ;
1167
+ }
1168
+
1169
+ // Remove pseudo hiding classes
1170
+ var parents = document . getElementsByClassName ( pseudoHide + "-parent" ) ;
1171
+ while ( parents . length ) {
1172
+ removeClass ( parents [ 0 ] , pseudoHide + "-parent" ) ;
1173
+ }
1174
+ }
1175
+
1176
+ function addClass ( el , className ) {
1177
+ if ( el . classList ) {
1178
+ el . classList . add ( className ) ;
1179
+ } else {
1180
+ el . className = el . className + " " + className ;
1181
+ }
1182
+ }
1183
+
1184
+ function removeClass ( el , className ) {
1185
+ if ( el . classList ) {
1186
+ el . classList . remove ( className ) ;
1187
+ } else {
1188
+ el . className = el . className . replace ( className , "" ) . trim ( ) ;
1189
+ }
1190
+ }
1191
+
1192
+ function hasClass ( el , className ) {
1193
+ return el . className . indexOf ( className ) > - 1 ;
1194
+ }
1195
+
1196
+ // Note that this doesn't work in < IE8, but we don't support that anyhow
1197
+ function nodeListToArray ( nodeList ) {
1198
+ return Array . prototype . slice . call ( nodeList ) ;
1199
+ }
1074
1200
1075
1201
function documentWidth ( ) {
1076
1202
return Math . max (
@@ -1831,20 +1957,25 @@ _html2canvas.Parse = function (images, options, cb) {
1831
1957
1832
1958
function getPseudoElement ( el , which ) {
1833
1959
var elStyle = window . getComputedStyle ( el , which ) ;
1834
- if ( ! elStyle || ! elStyle . content || elStyle . content === "none" || elStyle . content === "-moz-alt-content" || elStyle . display === "none" ) {
1960
+ var parentStyle = window . getComputedStyle ( el ) ;
1961
+ // If no content attribute is present, the pseudo element is hidden,
1962
+ // or the parent has a content property equal to the content on the pseudo element,
1963
+ // move along.
1964
+ if ( ! elStyle || ! elStyle . content || elStyle . content === "none" || elStyle . content === "-moz-alt-content" ||
1965
+ elStyle . display === "none" || parentStyle . content === elStyle . content ) {
1835
1966
return ;
1836
1967
}
1837
- var content = elStyle . content + '' ,
1838
- first = content . substr ( 0 , 1 ) ;
1839
- //strips quotes
1840
- if ( first === content . substr ( content . length - 1 ) && first . match ( / ' | " / ) ) {
1841
- content = content . substr ( 1 , content . length - 2 ) ;
1968
+ var content = elStyle . content + '' ;
1969
+
1970
+ // Strip inner quotes
1971
+ if ( content [ 0 ] === "'" || content [ 0 ] === "\"" ) {
1972
+ content = content . replace ( / ( ^ [ ' " ] ) | ( [ ' " ] $ ) / g , '' ) ;
1842
1973
}
1843
1974
1844
1975
var isImage = content . substr ( 0 , 3 ) === 'url' ,
1845
1976
elps = document . createElement ( isImage ? 'img' : 'span' ) ;
1846
1977
1847
- elps . className = pseudoHide + "-before " + pseudoHide + "-after ";
1978
+ elps . className = pseudoHide + "-element " ;
1848
1979
1849
1980
Object . keys ( elStyle ) . filter ( indexedProperty ) . forEach ( function ( prop ) {
1850
1981
// Prevent assigning of read only CSS Rules, ex. length, parentRule
@@ -1867,31 +1998,6 @@ _html2canvas.Parse = function (images, options, cb) {
1867
1998
return ( isNaN ( window . parseInt ( property , 10 ) ) ) ;
1868
1999
}
1869
2000
1870
- function injectPseudoElements ( el , stack ) {
1871
- var before = getPseudoElement ( el , ':before' ) ,
1872
- after = getPseudoElement ( el , ':after' ) ;
1873
- if ( ! before && ! after ) {
1874
- return ;
1875
- }
1876
-
1877
- if ( before ) {
1878
- el . className += " " + pseudoHide + "-before" ;
1879
- el . parentNode . insertBefore ( before , el ) ;
1880
- parseElement ( before , stack , true ) ;
1881
- el . parentNode . removeChild ( before ) ;
1882
- el . className = el . className . replace ( pseudoHide + "-before" , "" ) . trim ( ) ;
1883
- }
1884
-
1885
- if ( after ) {
1886
- el . className += " " + pseudoHide + "-after" ;
1887
- el . appendChild ( after ) ;
1888
- parseElement ( after , stack , true ) ;
1889
- el . removeChild ( after ) ;
1890
- el . className = el . className . replace ( pseudoHide + "-after" , "" ) . trim ( ) ;
1891
- }
1892
-
1893
- }
1894
-
1895
2001
function renderBackgroundRepeat ( ctx , image , backgroundPosition , bounds ) {
1896
2002
var offsetX = Math . round ( bounds . left + backgroundPosition . left ) ,
1897
2003
offsetY = Math . round ( bounds . top + backgroundPosition . top ) ;
@@ -2079,7 +2185,7 @@ _html2canvas.Parse = function (images, options, cb) {
2079
2185
return bounds ;
2080
2186
}
2081
2187
2082
- function renderElement ( element , parentStack , pseudoElement , ignoreBackground ) {
2188
+ function renderElement ( element , parentStack , ignoreBackground ) {
2083
2189
var transform = getTransform ( element , parentStack ) ,
2084
2190
bounds = getBounds ( element , transform ) ,
2085
2191
image ,
@@ -2109,10 +2215,6 @@ _html2canvas.Parse = function (images, options, cb) {
2109
2215
renderBorders ( ctx , border . args , border . color ) ;
2110
2216
} ) ;
2111
2217
2112
- if ( ! pseudoElement ) {
2113
- injectPseudoElements ( element , stack ) ;
2114
- }
2115
-
2116
2218
switch ( element . nodeName ) {
2117
2219
case "IMG" :
2118
2220
if ( ( image = loadImage ( element . getAttribute ( 'src' ) ) ) ) {
@@ -2153,20 +2255,20 @@ _html2canvas.Parse = function (images, options, cb) {
2153
2255
return ( getCSS ( element , 'display' ) !== "none" && getCSS ( element , 'visibility' ) !== "hidden" && ! element . hasAttribute ( "data-html2canvas-ignore" ) ) ;
2154
2256
}
2155
2257
2156
- function parseElement ( element , stack , pseudoElement , cb ) {
2258
+ function parseElement ( element , stack , cb ) {
2157
2259
if ( ! cb ) {
2158
2260
cb = function ( ) { } ;
2159
2261
}
2160
2262
if ( isElementVisible ( element ) ) {
2161
- stack = renderElement ( element , stack , pseudoElement , false ) || stack ;
2263
+ stack = renderElement ( element , stack , false ) || stack ;
2162
2264
if ( ! ignoreElementsRegExp . test ( element . nodeName ) ) {
2163
- return parseChildren ( element , stack , pseudoElement , cb ) ;
2265
+ return parseChildren ( element , stack , cb ) ;
2164
2266
}
2165
2267
}
2166
2268
cb ( ) ;
2167
2269
}
2168
2270
2169
- function parseChildren ( element , stack , pseudoElement , cb ) {
2271
+ function parseChildren ( element , stack , cb ) {
2170
2272
var children = Util . Children ( element ) ;
2171
2273
// After all nodes have processed, finished() will call the cb.
2172
2274
// We add one and kick it off so this will still work when children.length === 0.
@@ -2185,7 +2287,7 @@ _html2canvas.Parse = function (images, options, cb) {
2185
2287
2186
2288
function parseNode ( node ) {
2187
2289
if ( node . nodeType === node . ELEMENT_NODE ) {
2188
- parseElement ( node , stack , pseudoElement , finished ) ;
2290
+ parseElement ( node , stack , finished ) ;
2189
2291
} else if ( node . nodeType === node . TEXT_NODE ) {
2190
2292
renderText ( element , node , stack ) ;
2191
2293
finished ( ) ;
@@ -2706,9 +2808,9 @@ window.html2canvas = function(elements, opts) {
2706
2808
useOverflow : true ,
2707
2809
letterRendering : false ,
2708
2810
chinese : false ,
2811
+ async : false , // If true, parsing will not block, but if the user scrolls during parse the image can get weird
2709
2812
2710
2813
// render options
2711
-
2712
2814
width : null ,
2713
2815
height : null ,
2714
2816
taintTest : true , // do a taint test with all images before applying to canvas
0 commit comments