Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 6a68c96

Browse files
Splaktarjosephperrott
authored andcommitted
fix(icon): large SVG files can cause icon caching to hang (#11653)
1 parent 686b365 commit 6a68c96

File tree

1 file changed

+66
-17
lines changed

1 file changed

+66
-17
lines changed

src/components/icon/js/iconService.js

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId);
508-
svgElement = svgElement.replace(/(.*xlink:href="#)(\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(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId);
515-
clone.innerHTML = clone.innerHTML.replace(/(.*xlink:href="#)(\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

Comments
 (0)