diff --git a/src/components/icon/demoLoadSvgIconsFromUrl/script.js b/src/components/icon/demoLoadSvgIconsFromUrl/script.js index b5775f1f26a..b164e1b3c5f 100644 --- a/src/components/icon/demoLoadSvgIconsFromUrl/script.js +++ b/src/components/icon/demoLoadSvgIconsFromUrl/script.js @@ -1,19 +1,15 @@ - angular.module('appDemoSvgIcons', ['ngMaterial']) -.controller('DemoCtrl', function( $scope ) { - + .controller('DemoCtrl', function($scope) { $scope.insertDriveIconURL = 'img/icons/ic_insert_drive_file_24px.svg'; $scope.getAndroid = function() { return 'img/icons/android.svg'; }; - - /* Returns base64 encoded SVG. */ + // Returns base64 encoded SVG $scope.getAndroidEncoded = function() { return 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGcgaWQ9ImFuZHJvaWQiPjxwYXRoIGQ9Ik02IDE4YzAgLjU1LjQ1IDEgMSAxaDF2My41YzAgLjgzLjY3IDEuNSAxLjUgMS41czEuNS0uNjcgMS41LTEuNVYxOWgydjMuNWMwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjVWMTloMWMuNTUgMCAxLS40NSAxLTFWOEg2djEwek0zLjUgOEMyLjY3IDggMiA4LjY3IDIgOS41djdjMCAuODMuNjcgMS41IDEuNSAxLjVTNSAxNy4zMyA1IDE2LjV2LTdDNSA4LjY3IDQuMzMgOCAzLjUgOHptMTcgMGMtLjgzIDAtMS41LjY3LTEuNSAxLjV2N2MwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjV2LTdjMC0uODMtLjY3LTEuNS0xLjUtMS41em0tNC45Ny01Ljg0bDEuMy0xLjNjLjItLjIuMi0uNTEgMC0uNzEtLjItLjItLjUxLS4yLS43MSAwbC0xLjQ4IDEuNDhDMTMuODUgMS4yMyAxMi45NSAxIDEyIDFjLS45NiAwLTEuODYuMjMtMi42Ni42M0w3Ljg1LjE1Yy0uMi0uMi0uNTEtLjItLjcxIDAtLjIuMi0uMi41MSAwIC43MWwxLjMxIDEuMzFDNi45NyAzLjI2IDYgNS4wMSA2IDdoMTJjMC0xLjk5LS45Ny0zLjc1LTIuNDctNC44NHpNMTAgNUg5VjRoMXYxem01IDBoLTFWNGgxdjF6Ii8+PC9nPjwvc3ZnPg=='; }; - - /* Returns decoded SVG */ + // Returns decoded SVG $scope.getCartDecoded = function() { return ''; }; -}); + }); diff --git a/src/components/icon/demoSvgIconSets/script.js b/src/components/icon/demoSvgIconSets/script.js index b815a6d38a0..8be3f4b9b9f 100644 --- a/src/components/icon/demoSvgIconSets/script.js +++ b/src/components/icon/demoSvgIconSets/script.js @@ -1,9 +1,8 @@ - angular.module('appSvgIconSets', ['ngMaterial']) .controller('DemoCtrl', function($scope) {}) - .config(['$mdIconProvider', function($mdIconProvider) { + .config(function($mdIconProvider) { $mdIconProvider .iconSet('social', 'img/icons/sets/social-icons.svg', 24) .iconSet('symbol', 'img/icons/sets/symbol-icons.svg', 24) .defaultIconSet('img/icons/sets/core-icons.svg', 24); - }]); + }); diff --git a/src/components/icon/demoUsingTemplateRequest/script.js b/src/components/icon/demoUsingTemplateRequest/script.js index 6b01221fd24..dde12f1fb13 100644 --- a/src/components/icon/demoUsingTemplateRequest/script.js +++ b/src/components/icon/demoUsingTemplateRequest/script.js @@ -1,18 +1,13 @@ - angular.module('appUsingTemplateCache', ['ngMaterial']) .controller('DemoCtrl', function($scope) {}) .config(function($mdIconProvider) { - // Register icon IDs with sources. Future $mdIcon( ) lookups // will load by url and retrieve the data via the $templateRequest - $mdIconProvider - .iconSet('core', 'img/icons/sets/core-icons.svg',24) - .icon('social:cake', 'img/icons/cake.svg',24); - + .iconSet('core', 'img/icons/sets/core-icons.svg', 24) + .icon('social:cake', 'img/icons/cake.svg', 24); }) .run(function($templateRequest) { - var urls = [ 'img/icons/sets/core-icons.svg', 'img/icons/cake.svg', @@ -21,10 +16,7 @@ angular.module('appUsingTemplateCache', ['ngMaterial']) // Pre-fetch icons sources by URL and cache in the $templateCache... // subsequent $templateRequest calls will look there first. - angular.forEach(urls, function(url) { $templateRequest(url); }); - - }) - ; + }); diff --git a/src/components/icon/icon.spec.js b/src/components/icon/icon.spec.js index d847fbb46d3..cf6ef75ed81 100644 --- a/src/components/icon/icon.spec.js +++ b/src/components/icon/icon.spec.js @@ -419,6 +419,11 @@ describe('MdIcon service', function() { $scope = $rootScope; $templateCache.put('android.svg' , ''); + $templateCache.put('angular-logo.svg', + '' + + '' + + '' + + ''); $templateCache.put('social.svg' , ''); $templateCache.put('symbol.svg' , ''); $templateCache.put('core.svg' , ''); @@ -587,6 +592,25 @@ describe('MdIcon service', function() { $scope.$digest(); }); + + it('should suffix duplicated ids and refs', function() { + // Just request the icon to be stored in the cache. + $mdIcon('angular-logo.svg'); + + $scope.$digest(); + + $mdIcon('angular-logo.svg').then(function(el) { + expect(el.querySelector('defs').firstChild.id).toMatch(/.+_cache\d+/g); + expect(el.querySelectorAll('path')[1].attributes.filter.value.split(/url\(#(.*)\)$/g)[1]) + .toMatch(/.+_cache[0-9]+/g); + expect(el.querySelectorAll('path')[1].attributes.filter.value.split(/url\(#(.*)\)$/g)[1]) + .toEqual(el.querySelector('defs').firstChild.id); + expect(el.querySelector('use').attributes['xlink:href'].value.split(/#(.*)/)[1]).toEqual( + el.querySelector('defs').children[1].id); + }); + + $scope.$digest(); + }); }); describe('icon in a group is not found', function() { diff --git a/src/components/icon/js/iconDirective.js b/src/components/icon/js/iconDirective.js index 2bdfe3d3692..633f017d2bd 100644 --- a/src/components/icon/js/iconDirective.js +++ b/src/components/icon/js/iconDirective.js @@ -24,28 +24,11 @@ angular * `md-icon` lets you consume an icon font by letting you reference specific icons in that font * by name rather than character code. * - * ### SVG - * For SVGs, the problem with using `` or a CSS `background-image` is that you can't take - * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG - * animation. - * - * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `` element in the - * document. The most straightforward way of referencing an SVG icon is via URL, just like a - * traditional ``. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can - * reference it by name instead of URL throughout your templates. - * - * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle - * your SVG icons together and pre-load them with $mdIconProvider as an icon set. An icon set can - * also be given a name, which acts as a namespace for individual icons, so you can reference them - * like `"social:cake"`. - * - * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be - * easily loaded and used. When using font-icons, developers must follow three (3) simple steps: + * When using font-icons, developers must follow three (3) simple steps: * *
    *
  1. Load the font library. e.g.
    - * `` + * `` *
  2. *
  3. * Use either (a) font-icon class names or (b) a fontset and a font ligature to render the font glyph by @@ -63,26 +46,36 @@ angular *
  4. *
* - * Full details for these steps can be found: + * Full details for these steps can be found in the + * + * Material Design Icon font for the web docs. * - * + * You can browse and search the Material Design icon style .material-icons + * in the Material Design Icons tool. * - * The Material Design icon style .material-icons and the icon font references are published in - * Material Design Icons: + * ### SVG + * For SVGs, the problem with using `` or a CSS `background-image` is that you can't take + * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG + * animation. * - * + * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `` element in the + * document. The most straightforward way of referencing an SVG icon is via URL, just like a + * traditional ``. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can + * reference it by name instead of URL throughout your templates. + * + * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle + * your SVG icons together and pre-load them with `$mdIconProvider` as an icon set. An icon set can + * also be given a name, which acts as a namespace for individual icons, so you can reference them + * like `"social:cake"`. + * + * When using SVGs, both external SVGs (via URLs) or sets of SVGs (from icon sets) can be + * easily loaded and used. * * ### Localization * - * Because an `md-icon` element's text content is not intended to be translated, it is recommended to declare the text - * content for an `md-icon` element in its start tag. Instead of using the HTML text content, consider using `ng-bind` - * with a scope variable or literal string. + * Because an `md-icon` element's text content is not intended to be translated, it is recommended + * to declare the text content for an `md-icon` element in its start tag. Instead of using the HTML + * text content, consider using `ng-bind` with a scope variable or literal string. * * Examples: * @@ -91,20 +84,25 @@ angular *
  • `` * * - *

    Material Design Icons

    - * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and - * determine its textual name and character reference code. Click on any icon to see the slide-up information - * panel with details regarding a SVG download or information on the font-icon usage. + *

    Material Design Icons tool

    + * Using the Material Design Icons tool, developers can easily and quickly search for a specific + * open source Material Design icon. The search is in the top left. Below search, you can select + * from the new icon themes or filter by icon category. * - * - * + * + * * * - * - * Click on the image above to link to the - * Material Design Icon-Selector. - * + *
    + * Click on the image above to open the + * Material Design Icons tool. + *
    + * + * Click on any icon, then click on the "Selected Icon" chevron to see the slide-up + * information panel with details regarding a SVG download and information on the font-icon's + * textual name. This panel also allows you to select a black on transparent or white on transparent + * icon and to change the icon size. These settings only affect the downloaded icons. * * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used * to render the icon. Requires the fonts and the named CSS styles to be preloaded. @@ -128,34 +126,35 @@ angular * When using SVGs: * * - * - * + * + * * - * - * - * + * + * + * * * * * Use the $mdIconProvider to configure your application with - * svg iconsets. + * SVG icon sets. * * - * angular.module('appSvgIconSets', ['ngMaterial']) - * .controller('DemoCtrl', function($scope) {}) - * .config(function($mdIconProvider) { - * $mdIconProvider - * .iconSet('social', 'img/icons/sets/social-icons.svg', 24) - * .defaultIconSet('img/icons/sets/core-icons.svg', 24); - * }); + * angular.module('appSvgIconSets', ['ngMaterial']) + * .controller('DemoCtrl', function($scope) {}) + * .config(function($mdIconProvider) { + * $mdIconProvider + * .iconSet('social', 'img/icons/sets/social-icons.svg', 24) + * .defaultIconSet('img/icons/sets/core-icons.svg', 24); + * }); * * * * When using Font Icons with classnames: * * - * - * + * + * * * * diff --git a/src/components/icon/js/iconService.js b/src/components/icon/js/iconService.js index 266b6a9faf8..6bbd34c552e 100644 --- a/src/components/icon/js/iconService.js +++ b/src/components/icon/js/iconService.js @@ -347,11 +347,14 @@ MdIconProvider.prototype = { }] }; -/** - * Configuration item stored in the Icon registry; used for lookups - * to load if not already cached in the `loaded` cache - */ -function ConfigurationItem(url, viewBoxSize) { + /** + * Configuration item stored in the Icon registry; used for lookups + * to load if not already cached in the `loaded` cache + * @param url + * @param viewBoxSize + * @constructor + */ + function ConfigurationItem(url, viewBoxSize) { this.url = url; this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize; } @@ -395,7 +398,7 @@ function ConfigurationItem(url, viewBoxSize) { * }; * * - * > Note: The `` directive internally uses the `$mdIcon` service to query, loaded, + * > Note: The `` directive internally uses the `$mdIcon` service to query, load, * and instantiate SVG DOM elements. */ @@ -414,6 +417,8 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { /** * Actual $mdIcon service is essentially a lookup function + * @param {*} id $sce trust wrapper over a URL string, URL, icon registry id, or icon set id + * @returns {angular.$q.Promise} */ function getIcon(id) { id = id || ''; @@ -437,7 +442,7 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { return loadByURL(id).then(cacheIcon(id)); } - if (id.indexOf(':') == -1) { + if (id.indexOf(':') === -1) { id = '$default:' + id; } @@ -447,38 +452,68 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { } /** - * Lookup registered fontSet style using its alias... - * If not found, + * Lookup a registered fontSet style using its alias. + * @param {string} alias used to lookup the alias in the array of fontSets + * @returns {*} matching fontSet or the defaultFontSet if that alias does not match */ function findRegisteredFontSet(alias) { var useDefault = angular.isUndefined(alias) || !(alias && alias.length); - if (useDefault) return config.defaultFontSet; + if (useDefault) { + return config.defaultFontSet; + } var result = alias; - angular.forEach(config.fontSets, function(it) { - if (it.alias == alias) result = it.fontSet || result; + angular.forEach(config.fontSets, function(fontSet) { + if (fontSet.alias === alias) { + result = fontSet.fontSet || result; + } }); return result; } + /** + * @param {Icon} cacheElement cached icon from the iconCache + * @returns {Icon} cloned Icon element with unique ids + */ function transformClone(cacheElement) { var clone = cacheElement.clone(); - var cacheSuffix = '_cache' + $mdUtil.nextUid(); - - // We need to modify for each cached icon the id attributes. - // This is needed because SVG id's are treated as normal DOM ids - // and should not have a duplicated id. - if (clone.id) clone.id += cacheSuffix; - angular.forEach(clone.querySelectorAll('[id]'), function(item) { - item.id += cacheSuffix; + var newUid = $mdUtil.nextUid(); + var cacheSuffix; + + // Verify that the newUid only contains a number and not some XSS content. + if (!isFinite(Number(newUid))) { + throw new Error('Unsafe and unexpected non-number result from $mdUtil.nextUid().'); + } + + cacheSuffix = '_cache' + newUid; + + // For each cached icon, we need to modify the id attributes and references. + // This is needed because SVG ids are treated as normal DOM ids and should not be duplicated on + // the page. + if (clone.id) { + clone.id += cacheSuffix; + } + + var addCacheSuffixToId = function(match, p1, p2, p3) { + return [p1, p2, cacheSuffix, p3].join(''); + }; + 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); return clone; } /** - * Prepare and cache the loaded icon for the specified `id` + * Prepare and cache the loaded icon for the specified `id`. + * @param {string} id icon cache id + * @returns {function(*=): *} */ function cacheIcon(id) { @@ -492,7 +527,8 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { /** * Lookup the configuration in the registry, if !registered throw an error * otherwise load the icon [on-demand] using the registered URL. - * + * @param {string} id icon registry id + * @returns {angular.$q.Promise} */ function loadByID(id) { var iconConfig = config[id]; @@ -502,8 +538,9 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { } /** - * Loads the file as XML and uses querySelector( ) to find - * the desired node... + * Loads the file as XML and uses querySelector( ) to find the desired node... + * @param {string} id icon id in icon set + * @returns {angular.$q.Promise} */ function loadFromIconSet(id) { var setName = id.substring(0, id.lastIndexOf(':')) || '$default'; @@ -528,6 +565,8 @@ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { /** * Load the icon by URL (may use the $templateCache). * Extract the data for later conversion to Icon + * @param {string} url icon URL + * @returns {angular.$q.Promise} */ function loadByURL(url) { /* Load the icon from embedded data URL. */