diff --git a/src/components/icon/icon.spec.js b/src/components/icon/icon.spec.js index cf6ef75ed8..2cd82d508a 100644 --- a/src/components/icon/icon.spec.js +++ b/src/components/icon/icon.spec.js @@ -593,6 +593,22 @@ describe('MdIcon service', function() { $scope.$digest(); }); + // This covers a case where we saw a g3 test using an empty and it could + // throw an exception "Looking up elements via selectors is not supported by jqLite!" + // if proper checks weren't in place in the transformClone() code. + it('should handle empty SVGs', function() { + // Just request the icon to be stored in the cache. + $mdIcon('emptyGroup.svg'); + + $scope.$digest(); + + $mdIcon('emptyGroup.svg').then(function(el) { + expect(el).toBeTruthy(); + }); + + $scope.$digest(); + }); + it('should suffix duplicated ids and refs', function() { // Just request the icon to be stored in the cache. $mdIcon('angular-logo.svg'); diff --git a/src/components/icon/js/iconService.js b/src/components/icon/js/iconService.js index 6bbd34c552..987c9d3999 100644 --- a/src/components/icon/js/iconService.js +++ b/src/components/icon/js/iconService.js @@ -350,14 +350,14 @@ MdIconProvider.prototype = { /** * Configuration item stored in the Icon registry; used for lookups * to load if not already cached in the `loaded` cache - * @param url - * @param viewBoxSize + * @param {string} url + * @param {=number} viewBoxSize * @constructor */ function ConfigurationItem(url, viewBoxSize) { - this.url = url; - this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize; -} + this.url = url; + this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize; + } /** * @ngdoc service @@ -473,13 +473,13 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { } /** - * @param {Icon} cacheElement cached icon from the iconCache + * @param {!Icon} cacheElement cached icon from the iconCache * @returns {Icon} cloned Icon element with unique ids */ function transformClone(cacheElement) { var clone = cacheElement.clone(); var newUid = $mdUtil.nextUid(); - var cacheSuffix; + var cacheSuffix, svgElement; // Verify that the newUid only contains a number and not some XSS content. if (!isFinite(Number(newUid))) { @@ -501,11 +501,19 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { angular.forEach(clone.querySelectorAll('[id]'), function(descendantElem) { descendantElem.id += cacheSuffix; }); - // Inject the cacheSuffix into all instances of url(id) and xlink:href="#id". - // This use of innerHTML should be safe from XSS attack since we are only injecting the - // cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above. - clone.innerHTML = clone.innerHTML.replace(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId); - clone.innerHTML = clone.innerHTML.replace(/(.*xlink:href="#)(\w*)(".*)/g, addCacheSuffixToId); + // innerHTML of SVG elements is not supported by IE11 + if (clone.innerHTML === undefined) { + svgElement = $mdUtil.getInnerHTML(clone); + svgElement = svgElement.replace(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId); + svgElement = svgElement.replace(/(.*xlink:href="#)(\w*)(".*)/g, addCacheSuffixToId); + clone = angular.element(svgElement)[0]; + } else { + // Inject the cacheSuffix into all instances of url(id) and xlink:href="#id". + // This use of innerHTML should be safe from XSS attack since we are only injecting the + // cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above. + clone.innerHTML = clone.innerHTML.replace(/(.*url\(#)(\w*)(\).*)/g, addCacheSuffixToId); + clone.innerHTML = clone.innerHTML.replace(/(.*xlink:href="#)(\w*)(".*)/g, addCacheSuffixToId); + } return clone; } @@ -605,24 +613,37 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { /** * Check target signature to see if it is an Icon instance. + * @param {Icon|Element} target + * @returns {boolean} true if the specified target is an Icon object, false otherwise. */ function isIcon(target) { return angular.isDefined(target.element) && angular.isDefined(target.config); } /** - * Define the Icon class + * Define the Icon class + * @param {Element} el + * @param {=ConfigurationItem} config + * @constructor */ function Icon(el, config) { + var elementContents; // If the node is a , it won't be rendered so we have to convert it into . if (el && el.tagName.toLowerCase() === 'symbol') { var viewbox = el.getAttribute('viewBox'); - el = angular.element('').html(el.innerHTML)[0]; + // Check if innerHTML is supported as IE11 does not support innerHTML on SVG elements. + if (el.innerHTML) { + elementContents = el.innerHTML; + } else { + elementContents = $mdUtil.getInnerHTML(el); + } + el = angular.element('').append(elementContents)[0]; if (viewbox) el.setAttribute('viewBox', viewbox); } if (el && el.tagName.toLowerCase() !== 'svg') { - el = angular.element('').append(el.cloneNode(true))[0]; + el = angular.element( + '').append(el.cloneNode(true))[0]; } // Inject the namespace if not available... diff --git a/src/core/util/util.js b/src/core/util/util.js index e0db315b9a..84eae487c9 100644 --- a/src/core/util/util.js +++ b/src/core/util/util.js @@ -13,8 +13,8 @@ var nextUniqueId = 0; * Util */ angular - .module('material.core') - .factory('$mdUtil', UtilFactory); +.module('material.core') +.factory('$mdUtil', UtilFactory); /** * @ngInject @@ -128,7 +128,7 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in // or a clientRect: a rect relative to the page var offsetRect = isOffsetRect ? offsetParent.getBoundingClientRect() : - {left: 0, top: 0, width: 0, height: 0}; + {left: 0, top: 0, width: 0, height: 0}; return { left: nodeRect.left - offsetRect.left, top: nodeRect.top - offsetRect.top, @@ -857,6 +857,19 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in return array.filter(function(value, index, self) { return self.indexOf(value) === index; }); + }, + + /** + * Function to get innerHTML of SVG and Symbol elements in IE11 + * @param {Element} element + * @returns {string} the innerHTML of the element passed in + */ + getInnerHTML: function(element) { + var serializer = new XMLSerializer(); + + return Array.prototype.map.call(element.childNodes, function (child) { + return serializer.serializeToString(child); + }).join(''); } }; @@ -879,14 +892,14 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in */ angular.element.prototype.focus = angular.element.prototype.focus || function() { - if (this.length) { - this[0].focus(); - } - return this; - }; + if (this.length) { + this[0].focus(); + } + return this; +}; angular.element.prototype.blur = angular.element.prototype.blur || function() { - if (this.length) { - this[0].blur(); - } - return this; - }; + if (this.length) { + this[0].blur(); + } + return this; +};