@@ -6,6 +6,9 @@ import uniqueId from './unique-id'
66
77type ElementType = Element | HTMLElement | null
88
9+ // Tracks elements currently being injected. Prevents duplicate injection if
10+ // SVGInjector is called with the same element twice before the first injection
11+ // completes.
912const injectedElements : ElementType [ ] = [ ]
1013const ranScripts : Record < string , boolean > = { }
1114const svgNamespace = 'http://www.w3.org/2000/svg'
@@ -27,22 +30,17 @@ const injectElement = (
2730 return
2831 }
2932
30- // Make sure we aren't already in the process of injecting this element to
31- // avoid a race condition if multiple injections for the same element are run.
32- // :NOTE: Using indexOf() only _after_ we check for SVG support and bail, so
33- // no need for IE8 indexOf() polyfill.
3433 if ( injectedElements . indexOf ( el ) !== - 1 ) {
35- // TODO: Extract.
3634 injectedElements . splice ( injectedElements . indexOf ( el ) , 1 )
35+ // Release the DOM reference to allow GC. The cast is needed because the
36+ // parameter is typed as NonNullable.
3737 ; ( el as ElementType ) = null
3838 return
3939 }
4040
41- // Remember the request to inject this element, in case other injection calls
42- // are also trying to replace this element before we finish.
4341 injectedElements . push ( el )
44-
45- // Try to avoid loading the orginal image src if possible .
42+ // Clear src to prevent the browser from fetching the original image URL while
43+ // the SVG load is in progress .
4644 el . setAttribute ( 'src' , '' )
4745
4846 // Strip fragment identifier for sprite support. The base URL is used for
@@ -55,7 +53,6 @@ const injectElement = (
5553
5654 loadSvg ( baseUrl , httpRequestWithCredentials , ( error , loadedSvg ) => {
5755 if ( ! loadedSvg ) {
58- // TODO: Extract.
5956 injectedElements . splice ( injectedElements . indexOf ( el ) , 1 )
6057 ; ( el as ElementType ) = null
6158 callback ( error )
@@ -115,7 +112,6 @@ const injectElement = (
115112
116113 svg . setAttribute ( 'data-src' , elUrl )
117114
118- // Copy all the data elements to the svg.
119115 const elData : Attr [ ] = [ ] . filter . call ( el . attributes , ( at : Attr ) => {
120116 return / ^ d a t a - \w [ \w - ] * $ / . test ( at . name )
121117 } )
@@ -128,20 +124,13 @@ const injectElement = (
128124 } )
129125
130126 if ( renumerateIRIElements ) {
131- // Make sure any internally referenced clipPath ids and their clip-path
132- // references are unique.
133- //
134- // This addresses the issue of having multiple instances of the same SVG
135- // on a page and only the first clipPath id is referenced.
127+ // Rewrite IRI element ids to be unique across injection instances.
128+ // Browsers skip clipPaths in hidden parent elements, so duplicate ids
129+ // cause all but the first instance to lose clipping. Reference:
130+ // https://bugzilla.mozilla.org/show_bug.cgi?id=376027.
136131 //
137- // Browsers often shortcut the SVG Spec and don't use clipPaths contained
138- // in parent elements that are hidden, so if you hide the first SVG
139- // instance on the page, then all other instances lose their clipping.
140- // Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=376027
141-
142- // Handle all defs elements that have iri capable attributes as defined by
143- // w3c: http://www.w3.org/TR/SVG/linking.html#processingIRI. Mapping IRI
144- // addressable elements to the properties that can reference them.
132+ // IRI-addressable elements mapped to referencing properties per the SVG
133+ // spec: http://www.w3.org/TR/SVG/linking.html#processingIRI.
145134 const iriElementsAndProperties : Record < string , string [ ] > = {
146135 clipPath : [ 'clip-path' ] ,
147136 'color-profile' : [ 'color-profile' ] ,
@@ -214,7 +203,6 @@ const injectElement = (
214203 Object . keys ( iriElementsAndProperties ) . forEach ( ( key ) => {
215204 properties = iriElementsAndProperties [ key ] !
216205
217- // All of the properties that can reference this element type.
218206 let referencingElements : NodeListOf < Element >
219207 Array . prototype . forEach . call ( properties , ( property : string ) => {
220208 referencingElements = svg . querySelectorAll ( '[' + property + ']' )
@@ -300,14 +288,12 @@ const injectElement = (
300288 }
301289 }
302290
303- // Remove any unwanted/invalid namespaces that might have been added by SVG
304- // editing tools.
291+ // Remove invalid namespaces that SVG editing tools may have added.
305292 svg . removeAttribute ( 'xmlns:a' )
306293
307- // Post page load injected SVGs don't automatically have their script
308- // elements run, so we'll need to make that happen, if requested.
294+ // Injected SVGs don't automatically run their script elements, so extract
295+ // and evaluate them manually if requested.
309296
310- // Find then prune the scripts.
311297 const scripts = svg . querySelectorAll ( 'script' )
312298 const scriptsToEval : string [ ] = [ ]
313299 let script : string | null
@@ -317,7 +303,7 @@ const injectElement = (
317303 const scriptElement = scripts [ i ] !
318304 scriptType = scriptElement . getAttribute ( 'type' )
319305
320- // Only process javascript types. SVG defaults to 'application/ecmascript'
306+ // Only process JavaScript types. SVG defaults to 'application/ecmascript'
321307 // for unset types.
322308 /* istanbul ignore else */
323309 if (
@@ -326,21 +312,17 @@ const injectElement = (
326312 scriptType === 'application/javascript' ||
327313 scriptType === 'text/javascript'
328314 ) {
329- // innerText for IE, textContent for other browsers.
330315 script = scriptElement . innerText || scriptElement . textContent
331316
332- // Stash.
333317 /* istanbul ignore else */
334318 if ( script ) {
335319 scriptsToEval . push ( script )
336320 }
337321
338- // Tidy up and remove the script element since we don't need it anymore.
339322 svg . removeChild ( scriptElement )
340323 }
341324 }
342325
343- // Run/Eval the scripts if needed.
344326 if (
345327 scriptsToEval . length > 0 &&
346328 ( evalScripts === 'always' ||
@@ -351,24 +333,18 @@ const injectElement = (
351333 l < scriptsToEvalLen ;
352334 l ++
353335 ) {
354- // :NOTE: Yup, this is a form of eval, but it is being used to eval code
355- // the caller has explictely asked to be loaded, and the code is in a
356- // caller defined SVG file... not raw user input.
357- //
358- // Also, the code is evaluated in a closure and not in the global scope.
359- // If you need to put something in global scope, use 'window'.
336+ // This is a form of eval, but only for code the caller has explicitly
337+ // asked to load from their own SVG files. The code runs in a closure,
338+ // not the global scope.
360339 new Function ( scriptsToEval [ l ] ! ) ( window )
361340 }
362341
363- // Remember we already ran scripts for this svg.
364342 ranScripts [ elUrl ] = true
365343 }
366344
367- // :WORKAROUND: IE doesn't evaluate <style> tags in SVGs that are
368- // dynamically added to the page. This trick will trigger IE to read and use
369- // any existing SVG <style> tags.
370- //
371- // Reference: https://github.com/iconic/SVGInjector/issues/23.
345+ // Some browsers don't evaluate <style> tags in SVGs that are dynamically
346+ // added to the page. This triggers a re-read. Reference:
347+ // https://github.com/iconic/SVGInjector/issues/23.
372348 const styleTags = svg . querySelectorAll ( 'style' )
373349 Array . prototype . forEach . call ( styleTags , ( styleTag : HTMLStyleElement ) => {
374350 styleTag . textContent += ''
@@ -386,12 +362,7 @@ const injectElement = (
386362 return
387363 }
388364
389- // Replace the image with the svg.
390365 el . parentNode . replaceChild ( svg , el )
391-
392- // Now that we no longer need it, drop references to the original element so
393- // it can be GC'd.
394- // TODO: Extract
395366 injectedElements . splice ( injectedElements . indexOf ( el ) , 1 )
396367 ; ( el as ElementType ) = null
397368
0 commit comments