From ba7d40ef7bb3e293076289a54666f2f618f28d60 Mon Sep 17 00:00:00 2001
From: Tony Childs
Date: Sat, 12 Mar 2016 17:11:56 -0600
Subject: [PATCH] fix(icon): Allow using data URLs
Fixes #4126
---
.../icon/demoLoadSvgIconsFromUrl/index.html | 22 ++++++++
.../icon/demoLoadSvgIconsFromUrl/script.js | 4 ++
src/components/icon/icon.spec.js | 54 +++++++++++++++++--
src/components/icon/js/iconService.js | 32 ++++++++---
4 files changed, 99 insertions(+), 13 deletions(-)
diff --git a/src/components/icon/demoLoadSvgIconsFromUrl/index.html b/src/components/icon/demoLoadSvgIconsFromUrl/index.html
index a6bae8daf96..f6423a665d5 100644
--- a/src/components/icon/demoLoadSvgIconsFromUrl/index.html
+++ b/src/components/icon/demoLoadSvgIconsFromUrl/index.html
@@ -12,5 +12,27 @@
+
+ Use data URLs (base64 or un-encoded):
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/icon/demoLoadSvgIconsFromUrl/script.js b/src/components/icon/demoLoadSvgIconsFromUrl/script.js
index ad367a18eef..6b07c2ed0fb 100644
--- a/src/components/icon/demoLoadSvgIconsFromUrl/script.js
+++ b/src/components/icon/demoLoadSvgIconsFromUrl/script.js
@@ -6,4 +6,8 @@ angular.module('appDemoSvgIcons', ['ngMaterial'])
$scope.getAndroid = function() {
return 'img/icons/android.svg';
}
+ /* Returns base64 encoded SVG. */
+ $scope.getAndroidEncoded = function() {
+ return 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGcgaWQ9ImFuZHJvaWQiPjxwYXRoIGQ9Ik02IDE4YzAgLjU1LjQ1IDEgMSAxaDF2My41YzAgLjgzLjY3IDEuNSAxLjUgMS41czEuNS0uNjcgMS41LTEuNVYxOWgydjMuNWMwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjVWMTloMWMuNTUgMCAxLS40NSAxLTFWOEg2djEwek0zLjUgOEMyLjY3IDggMiA4LjY3IDIgOS41djdjMCAuODMuNjcgMS41IDEuNSAxLjVTNSAxNy4zMyA1IDE2LjV2LTdDNSA4LjY3IDQuMzMgOCAzLjUgOHptMTcgMGMtLjgzIDAtMS41LjY3LTEuNSAxLjV2N2MwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjV2LTdjMC0uODMtLjY3LTEuNS0xLjUtMS41em0tNC45Ny01Ljg0bDEuMy0xLjNjLjItLjIuMi0uNTEgMC0uNzEtLjItLjItLjUxLS4yLS43MSAwbC0xLjQ4IDEuNDhDMTMuODUgMS4yMyAxMi45NSAxIDEyIDFjLS45NiAwLTEuODYuMjMtMi42Ni42M0w3Ljg1LjE1Yy0uMi0uMi0uNTEtLjItLjcxIDAtLjIuMi0uMi41MSAwIC43MWwxLjMxIDEuMzFDNi45NyAzLjI2IDYgNS4wMSA2IDdoMTJjMC0xLjk5LS45Ny0zLjc1LTIuNDctNC44NHpNMTAgNUg5VjRoMXYxem01IDBoLTFWNGgxdjF6Ii8+PC9nPjwvc3ZnPg==';
+ }
});
diff --git a/src/components/icon/icon.spec.js b/src/components/icon/icon.spec.js
index 5d7e6b6cd04..879febd03cd 100644
--- a/src/components/icon/icon.spec.js
+++ b/src/components/icon/icon.spec.js
@@ -177,11 +177,20 @@ describe('mdIcon directive', function() {
return {
then: function(fn) {
switch(id) {
- case 'android' : fn('');
- case 'cake' : fn('');
- case 'android.svg' : fn('');
- case 'cake.svg' : fn('');
- case 'image:android': fn('');
+ case 'android' : fn('');
+ break;
+ case 'cake' : fn('');
+ break;
+ case 'android.svg' : fn('');
+ break;
+ case 'cake.svg' : fn('');
+ break;
+ case 'image:android' : fn('');
+ break;
+ default :
+ if (/^data:/.test(id)) {
+ fn(window.atob(id.split(',')[1]));
+ }
}
}
}
@@ -240,6 +249,18 @@ describe('mdIcon directive', function() {
expect(el.html()).toEqual('');
}));
+ describe('with a data URL', function() {
+ it('should set mdSvgSrc from a function expression', inject(function() {
+ var svgData = '';
+ $scope.getData = function() {
+ return 'data:image/svg+xml;base64,' + window.btoa(svgData);
+ }
+ console.log(svgData)
+ el = make('');
+ $scope.$digest();
+ expect(el[0].innerHTML).toEqual(svgData);
+ }));
+ })
});
describe('with ARIA support', function() {
@@ -419,6 +440,29 @@ describe('mdIcon service', function() {
$scope.$digest();
});
+ describe('and the URL is a data URL', function() {
+ var svgData = '';
+
+ describe('and the data is base64 encoded', function() {
+ it('should return correct SVG markup', function() {
+ var data = 'data:image/svg+xml;base64,' + btoa(svgData);
+ $mdIcon(data).then(function(el) {
+ expect(el.outerHTML).toEqual( updateDefaults(svgData) );
+ })
+ $scope.$digest();
+ });
+ });
+
+ describe('and the data is un-encoded', function() {
+ it('should return correct SVG markup', function() {
+ var data = 'data:image/svg+xml,' + svgData;
+ $mdIcon(data).then(function(el) {
+ expect(el.outerHTML).toEqual( updateDefaults(svgData) );
+ })
+ $scope.$digest();
+ });
+ });
+ });
});
describe('icon set URL is not found', function() {
diff --git a/src/components/icon/js/iconService.js b/src/components/icon/js/iconService.js
index 053fec1d25a..35050ef11a5 100644
--- a/src/components/icon/js/iconService.js
+++ b/src/components/icon/js/iconService.js
@@ -375,7 +375,8 @@
/* @ngInject */
function MdIconService(config, $http, $q, $log, $templateCache) {
var iconCache = {};
- var urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
+ var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
+ var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
Icon.prototype = { clone : cloneSVG, prepare: prepareAndStyle };
getIcon.fontSet = findRegisteredFontSet;
@@ -392,8 +393,8 @@
// If already loaded and cached, use a clone of the cached icon.
// Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
- if ( iconCache[id] ) return $q.when( iconCache[id].clone() );
- if ( urlRegex.test(id) ) return loadByURL(id).then( cacheIcon(id) );
+ if ( iconCache[id] ) return $q.when( iconCache[id].clone() );
+ if ( urlRegex.test(id) || dataUrlRegex.test(id) ) return loadByURL(id).then( cacheIcon(id) );
if ( id.indexOf(':') == -1 ) id = '$default:' + id;
var load = config[id] ? loadByID : loadFromIconSet;
@@ -481,11 +482,26 @@
* Extract the data for later conversion to Icon
*/
function loadByURL(url) {
- return $http
- .get(url, { cache: $templateCache })
- .then(function(response) {
- return angular.element('').append(response.data).find('svg')[0];
- }).catch(announceNotFound);
+ /* Load the icon from embedded data URL. */
+ function loadByDataUrl(url) {
+ var results = dataUrlRegex.exec(url);
+ var isBase64 = /base64/i.test(url);
+ var data = isBase64 ? window.atob(results[2]) : results[2];
+ return $q.when(angular.element(data)[0]);
+ }
+
+ /* Load the icon by URL using HTTP. */
+ function loadByHttpUrl(url) {
+ return $http
+ .get(url, { cache: $templateCache })
+ .then(function(response) {
+ return angular.element('
').append(response.data).find('svg')[0];
+ }).catch(announceNotFound);
+ }
+
+ return dataUrlRegex.test(url)
+ ? loadByDataUrl(url)
+ : loadByHttpUrl(url);
}
/**