@@ -479,13 +479,18 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
479479 function transformClone ( cacheElement ) {
480480 var clone = cacheElement . clone ( ) ;
481481 var newUid = $mdUtil . nextUid ( ) ;
482- var cacheSuffix , svgElement ;
482+ var cacheSuffix , svgUrlQuerySelector , i , xlinkHrefValue ;
483+ // These are SVG attributes that can reference element ids.
484+ var svgUrlAttributes = [
485+ 'clip-path' , 'color-profile' , 'cursor' , 'fill' , 'filter' , 'href' , 'marker-start' ,
486+ 'marker-mid' , 'marker-end' , 'mask' , 'stroke' , 'style' , 'vector-effect'
487+ ] ;
488+ var isIeSvg = clone . innerHTML === undefined ;
483489
484490 // Verify that the newUid only contains a number and not some XSS content.
485491 if ( ! isFinite ( Number ( newUid ) ) ) {
486492 throw new Error ( 'Unsafe and unexpected non-number result from $mdUtil.nextUid().' ) ;
487493 }
488-
489494 cacheSuffix = '_cache' + newUid ;
490495
491496 // For each cached icon, we need to modify the id attributes and references.
@@ -495,27 +500,71 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
495500 clone . id += cacheSuffix ;
496501 }
497502
498- var addCacheSuffixToId = function ( match , p1 , p2 , p3 ) {
499- return [ p1 , p2 , cacheSuffix , p3 ] . join ( '' ) ;
500- } ;
503+ // Do as much as possible with querySelectorAll as it provides much greater performance
504+ // than RegEx against serialized DOM.
501505 angular . forEach ( clone . querySelectorAll ( '[id]' ) , function ( descendantElem ) {
506+ svgUrlQuerySelector = '' ;
507+ for ( i = 0 ; i < svgUrlAttributes . length ; i ++ ) {
508+ svgUrlQuerySelector += '[' + svgUrlAttributes [ i ] + '="url(#' + descendantElem . id + ')"]' ;
509+ if ( i + 1 < svgUrlAttributes . length ) {
510+ svgUrlQuerySelector += ', ' ;
511+ }
512+ }
513+ // Append the cacheSuffix to references of the element's id within url(#id) calls.
514+ angular . forEach ( clone . querySelectorAll ( svgUrlQuerySelector ) , function ( refItem ) {
515+ updateSvgIdReferences ( descendantElem , refItem , isIeSvg , newUid ) ;
516+ } ) ;
517+ // Handle usages of url(#id) in the SVG's stylesheets
518+ angular . forEach ( clone . querySelectorAll ( 'style' ) , function ( refItem ) {
519+ updateSvgIdReferences ( descendantElem , refItem , isIeSvg , newUid ) ;
520+ } ) ;
521+
522+ // Update ids referenced by the deprecated (in SVG v2) xlink:href XML attribute. The now
523+ // preferred href attribute is handled above. However, this non-standard XML namespaced
524+ // attribute cannot be handled in the same way. Explanation of this query selector here:
525+ // https://stackoverflow.com/q/23034283/633107.
526+ angular . forEach ( clone . querySelectorAll ( '[*|href]:not([href])' ) , function ( refItem ) {
527+ xlinkHrefValue = refItem . getAttribute ( 'xlink:href' ) ;
528+ if ( xlinkHrefValue ) {
529+ xlinkHrefValue = xlinkHrefValue . replace ( "#" + descendantElem . id , "#" + descendantElem . id + cacheSuffix ) ;
530+ refItem . setAttribute ( 'xlink:href' , xlinkHrefValue ) ;
531+ }
532+ } ) ;
533+
502534 descendantElem . id += cacheSuffix ;
503535 } ) ;
504- // innerHTML of SVG elements is not supported by IE11
505- if ( clone . innerHTML === undefined ) {
506- svgElement = $mdUtil . getOuterHTML ( clone ) ;
507- svgElement = svgElement . replace ( / ( .* u r l \( # ) ( \w * ) ( \) .* ) / g, addCacheSuffixToId ) ;
508- svgElement = svgElement . replace ( / ( .* x l i n k : h r e f = " # ) ( \w * ) ( " .* ) / g, addCacheSuffixToId ) ;
509- clone = angular . element ( svgElement ) [ 0 ] ;
536+
537+ return clone ;
538+ }
539+
540+ /**
541+ * @param {Element } referencedElement element w/ id that needs to be updated
542+ * @param {Element } referencingElement element that references the original id
543+ * @param {boolean } isIeSvg true if we're dealing with an SVG in IE11, false otherwise
544+ * @param {string } newUid the cache id to add as part of the cache suffix
545+ */
546+ function updateSvgIdReferences ( referencedElement , referencingElement , isIeSvg , newUid ) {
547+ var svgElement , cacheSuffix ;
548+
549+ // Verify that the newUid only contains a number and not some XSS content.
550+ if ( ! isFinite ( Number ( newUid ) ) ) {
551+ throw new Error ( 'Unsafe and unexpected non-number result for newUid.' ) ;
552+ }
553+ cacheSuffix = '_cache' + newUid ;
554+
555+ // outerHTML of SVG elements is not supported by IE11
556+ if ( isIeSvg ) {
557+ svgElement = $mdUtil . getOuterHTML ( referencingElement ) ;
558+ svgElement = svgElement . replace ( "url(#" + referencedElement . id + ")" ,
559+ "url(#" + referencedElement . id + cacheSuffix + ")" ) ;
560+ referencingElement . textContent = angular . element ( svgElement ) [ 0 ] . innerHTML ;
510561 } else {
511- // Inject the cacheSuffix into all instances of url(id) and xlink:href="#id".
512- // This use of innerHTML should be safe from XSS attack since we are only injecting the
562+ // This use of outerHTML should be safe from XSS attack since we are only injecting the
513563 // cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above.
514- clone . innerHTML = clone . innerHTML . replace ( / ( .* u r l \( # ) ( \w * ) ( \) .* ) / g, addCacheSuffixToId ) ;
515- clone . innerHTML = clone . innerHTML . replace ( / ( .* x l i n k : h r e f = " # ) ( \w * ) ( " .* ) / g, addCacheSuffixToId ) ;
564+ referencingElement . outerHTML = referencingElement . outerHTML . replace (
565+ "url(#" + referencedElement . id + ")" ,
566+ "url(#" + referencedElement . id + cacheSuffix + ")" ) ;
516567 }
517-
518- return clone ;
519568 }
520569
521570 /**
0 commit comments