@@ -479,13 +479,18 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
479
479
function transformClone ( cacheElement ) {
480
480
var clone = cacheElement . clone ( ) ;
481
481
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 ;
483
489
484
490
// Verify that the newUid only contains a number and not some XSS content.
485
491
if ( ! isFinite ( Number ( newUid ) ) ) {
486
492
throw new Error ( 'Unsafe and unexpected non-number result from $mdUtil.nextUid().' ) ;
487
493
}
488
-
489
494
cacheSuffix = '_cache' + newUid ;
490
495
491
496
// 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) {
495
500
clone . id += cacheSuffix ;
496
501
}
497
502
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.
501
505
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
+
502
534
descendantElem . id += cacheSuffix ;
503
535
} ) ;
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 ;
510
561
} 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
513
563
// 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 + ")" ) ;
516
567
}
517
-
518
- return clone ;
519
568
}
520
569
521
570
/**
0 commit comments