diff --git a/src/core/services/theming/theming.js b/src/core/services/theming/theming.js index 326ea9b0a3..25a7314d7e 100644 --- a/src/core/services/theming/theming.js +++ b/src/core/services/theming/theming.js @@ -12,6 +12,13 @@ angular.module('material.core.theming', ['material.core.theming.palette']) * @description Provider to configure the `$mdTheming` service. */ +/** + * @ngdoc method + * @name $mdThemingProvider#setNonce + * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags. + * Setting a value allows the use CSP policy without using the unsafe-inline directive. + */ + /** * @ngdoc method * @name $mdThemingProvider#setDefaultTheme @@ -125,6 +132,9 @@ var VALID_HUE_VALUES = [ // Whether or not themes are to be generated on-demand (vs. eagerly). var generateOnDemand = false; +// Nonce to be added as an attribute to the generated themes style tags. +var nonce = null; + function ThemingProvider($mdColorPalette) { PALETTES = { }; THEMES = { }; @@ -143,6 +153,9 @@ function ThemingProvider($mdColorPalette) { extendPalette: extendPalette, theme: registerTheme, + setNonce: function(nonceValue) { + nonce = nonceValue; + }, setDefaultTheme: function(theme) { defaultTheme = theme; }, @@ -355,7 +368,7 @@ function ThemingProvider($mdColorPalette) { applyTheme.THEMES = angular.extend({}, THEMES); applyTheme.defaultTheme = function() { return defaultTheme; }; applyTheme.registered = registered; - applyTheme.generateTheme = generateTheme; + applyTheme.generateTheme = function(name) { generateTheme(name, nonce); }; return applyTheme; @@ -516,7 +529,7 @@ function generateAllThemes($injector) { angular.forEach(THEMES, function(theme) { if (!GENERATED[theme.name]) { - generateTheme(theme.name); + generateTheme(theme.name, nonce); } }); @@ -581,7 +594,7 @@ function generateAllThemes($injector) { } } -function generateTheme(name) { +function generateTheme(name, nonce) { var theme = THEMES[name]; var head = document.head; var firstChild = head ? head.firstElementChild : null; @@ -596,6 +609,9 @@ function generateTheme(name) { if (styleContent) { var style = document.createElement('style'); style.setAttribute('md-theme-style', ''); + if (nonce) { + style.setAttribute('nonce', nonce); + } style.appendChild(document.createTextNode(styleContent)); head.insertBefore(style, firstChild); } diff --git a/src/core/services/theming/theming.spec.js b/src/core/services/theming/theming.spec.js index e8c978c7c4..25faf9ae75 100644 --- a/src/core/services/theming/theming.spec.js +++ b/src/core/services/theming/theming.spec.js @@ -369,6 +369,68 @@ describe('$mdThemeProvider with on-demand generation', function() { }); }); +describe('$mdThemeProvider with nonce', function() { + beforeEach(function() { + + module('material.core', function($provide) { + /** + * material-mocks.js clears the $MD_THEME_CSS for Karma testing performance + * performance optimizations. Here inject some length into our theme_css so that + * palettes are parsed/generated + */ + $provide.constant('$MD_THEME_CSS', '/**/'); + }); + }); + + describe('and auto-generated themes', function() { + beforeEach(function() { + module('material.core', function($mdThemingProvider) { + $mdThemingProvider.generateThemesOnDemand(false); + + $mdThemingProvider.theme('auto-nonce') + .primaryPalette('light-blue') + .accentPalette('yellow'); + + $mdThemingProvider.setNonce('1'); + }); + inject(); + }); + + it('should add a nonce', function() { + var styles = document.head.querySelectorAll('style[nonce="1"]'); + expect(styles.length).toBe(4); + }); + }); + + describe('and on-demand generated themes', function() { + var $mdTheming; + + beforeEach(function() { + module('material.core', function($mdThemingProvider) { + $mdThemingProvider.generateThemesOnDemand(true); + + $mdThemingProvider.theme('nonce') + .primaryPalette('light-blue') + .accentPalette('yellow'); + + $mdThemingProvider.setNonce('2'); + }); + inject(function(_$mdTheming_) { + $mdTheming = _$mdTheming_; + }); + }); + + it('should add a nonce', function() { + var styles = document.head.querySelectorAll('style[nonce="2"]'); + expect(styles.length).toBe(0); + + $mdTheming.generateTheme('nonce'); + styles = document.head.querySelectorAll('style[nonce="2"]'); + expect(styles.length).toBe(4); + }); + }); +}); + describe('$mdTheming service', function() { var $mdThemingProvider; beforeEach(module('material.core', function(_$mdThemingProvider_) {