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

Commit 47527f2

Browse files
Splaktarjelbourn
authored andcommitted
fix(icon): exceptions thrown by IE11 (#11545)
innerHTML is not supported on svg or symbol elements in IE11 use XMLSerializer instead of innerHTML when innerHTML is not defined add a test for empty SVGs to match a g3 test Fixes #11543. Relates to #11342. Relates to #11162.
1 parent 44a6946 commit 47527f2

File tree

3 files changed

+78
-28
lines changed

3 files changed

+78
-28
lines changed

src/components/icon/icon.spec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,22 @@ describe('MdIcon service', function() {
593593
$scope.$digest();
594594
});
595595

596+
// This covers a case where we saw a g3 test using an empty <svg></svg> and it could
597+
// throw an exception "Looking up elements via selectors is not supported by jqLite!"
598+
// if proper checks weren't in place in the transformClone() code.
599+
it('should handle empty SVGs', function() {
600+
// Just request the icon to be stored in the cache.
601+
$mdIcon('emptyGroup.svg');
602+
603+
$scope.$digest();
604+
605+
$mdIcon('emptyGroup.svg').then(function(el) {
606+
expect(el).toBeTruthy();
607+
});
608+
609+
$scope.$digest();
610+
});
611+
596612
it('should suffix duplicated ids and refs', function() {
597613
// Just request the icon to be stored in the cache.
598614
$mdIcon('angular-logo.svg');

src/components/icon/js/iconService.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,14 @@ MdIconProvider.prototype = {
350350
/**
351351
* Configuration item stored in the Icon registry; used for lookups
352352
* to load if not already cached in the `loaded` cache
353-
* @param url
354-
* @param viewBoxSize
353+
* @param {string} url
354+
* @param {=number} viewBoxSize
355355
* @constructor
356356
*/
357357
function ConfigurationItem(url, viewBoxSize) {
358-
this.url = url;
359-
this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
360-
}
358+
this.url = url;
359+
this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
360+
}
361361

362362
/**
363363
* @ngdoc service
@@ -473,13 +473,13 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
473473
}
474474

475475
/**
476-
* @param {Icon} cacheElement cached icon from the iconCache
476+
* @param {!Icon} cacheElement cached icon from the iconCache
477477
* @returns {Icon} cloned Icon element with unique ids
478478
*/
479479
function transformClone(cacheElement) {
480480
var clone = cacheElement.clone();
481481
var newUid = $mdUtil.nextUid();
482-
var cacheSuffix;
482+
var cacheSuffix, svgElement;
483483

484484
// Verify that the newUid only contains a number and not some XSS content.
485485
if (!isFinite(Number(newUid))) {
@@ -501,11 +501,19 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
501501
angular.forEach(clone.querySelectorAll('[id]'), function(descendantElem) {
502502
descendantElem.id += cacheSuffix;
503503
});
504-
// Inject the cacheSuffix into all instances of url(id) and xlink:href="#id".
505-
// This use of innerHTML should be safe from XSS attack since we are only injecting the
506-
// cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above.
507-
clone.innerHTML = clone.innerHTML.replace(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId);
508-
clone.innerHTML = clone.innerHTML.replace(/(.*xlink:href="#)(\w*)(".*)/g, addCacheSuffixToId);
504+
// innerHTML of SVG elements is not supported by IE11
505+
if (clone.innerHTML === undefined) {
506+
svgElement = $mdUtil.getInnerHTML(clone);
507+
svgElement = svgElement.replace(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId);
508+
svgElement = svgElement.replace(/(.*xlink:href="#)(\w*)(".*)/g, addCacheSuffixToId);
509+
clone = angular.element(svgElement)[0];
510+
} 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
513+
// 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);
516+
}
509517

510518
return clone;
511519
}
@@ -605,24 +613,37 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
605613

606614
/**
607615
* Check target signature to see if it is an Icon instance.
616+
* @param {Icon|Element} target
617+
* @returns {boolean} true if the specified target is an Icon object, false otherwise.
608618
*/
609619
function isIcon(target) {
610620
return angular.isDefined(target.element) && angular.isDefined(target.config);
611621
}
612622

613623
/**
614-
* Define the Icon class
624+
* Define the Icon class
625+
* @param {Element} el
626+
* @param {=ConfigurationItem} config
627+
* @constructor
615628
*/
616629
function Icon(el, config) {
630+
var elementContents;
617631
// If the node is a <symbol>, it won't be rendered so we have to convert it into <svg>.
618632
if (el && el.tagName.toLowerCase() === 'symbol') {
619633
var viewbox = el.getAttribute('viewBox');
620-
el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').html(el.innerHTML)[0];
634+
// Check if innerHTML is supported as IE11 does not support innerHTML on SVG elements.
635+
if (el.innerHTML) {
636+
elementContents = el.innerHTML;
637+
} else {
638+
elementContents = $mdUtil.getInnerHTML(el);
639+
}
640+
el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(elementContents)[0];
621641
if (viewbox) el.setAttribute('viewBox', viewbox);
622642
}
623643

624644
if (el && el.tagName.toLowerCase() !== 'svg') {
625-
el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
645+
el = angular.element(
646+
'<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
626647
}
627648

628649
// Inject the namespace if not available...

src/core/util/util.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ var nextUniqueId = 0;
1313
* Util
1414
*/
1515
angular
16-
.module('material.core')
17-
.factory('$mdUtil', UtilFactory);
16+
.module('material.core')
17+
.factory('$mdUtil', UtilFactory);
1818

1919
/**
2020
* @ngInject
@@ -128,7 +128,7 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
128128
// or a clientRect: a rect relative to the page
129129
var offsetRect = isOffsetRect ?
130130
offsetParent.getBoundingClientRect() :
131-
{left: 0, top: 0, width: 0, height: 0};
131+
{left: 0, top: 0, width: 0, height: 0};
132132
return {
133133
left: nodeRect.left - offsetRect.left,
134134
top: nodeRect.top - offsetRect.top,
@@ -857,6 +857,19 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
857857
return array.filter(function(value, index, self) {
858858
return self.indexOf(value) === index;
859859
});
860+
},
861+
862+
/**
863+
* Function to get innerHTML of SVG and Symbol elements in IE11
864+
* @param {Element} element
865+
* @returns {string} the innerHTML of the element passed in
866+
*/
867+
getInnerHTML: function(element) {
868+
var serializer = new XMLSerializer();
869+
870+
return Array.prototype.map.call(element.childNodes, function (child) {
871+
return serializer.serializeToString(child);
872+
}).join('');
860873
}
861874
};
862875

@@ -879,14 +892,14 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
879892
*/
880893

881894
angular.element.prototype.focus = angular.element.prototype.focus || function() {
882-
if (this.length) {
883-
this[0].focus();
884-
}
885-
return this;
886-
};
895+
if (this.length) {
896+
this[0].focus();
897+
}
898+
return this;
899+
};
887900
angular.element.prototype.blur = angular.element.prototype.blur || function() {
888-
if (this.length) {
889-
this[0].blur();
890-
}
891-
return this;
892-
};
901+
if (this.length) {
902+
this[0].blur();
903+
}
904+
return this;
905+
};

0 commit comments

Comments
 (0)