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

Commit

Permalink
fix(theming): fix CSS with nested rules parsing when registering a theme
Browse files Browse the repository at this point in the history
- replace the regex used to split the CSS string with a function that takes into account nested rules
- remove unused `ruleMatchRegex`

Fixes #9869
  • Loading branch information
natete authored and Splaktar committed Dec 9, 2020
1 parent c609385 commit 71dc4eb
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 5 deletions.
55 changes: 50 additions & 5 deletions src/core/services/theming/theming.js
Original file line number Diff line number Diff line change
Expand Up @@ -1138,14 +1138,13 @@ function generateAllThemes($injector, $mdTheming) {
// Expose contrast colors for palettes to ensure that text is always readable
angular.forEach(PALETTES, sanitizePalette);

// MD_THEME_CSS is a string generated by the build process that includes all the themable
// MD_THEME_CSS is a string generated by the build process that includes all the themeable
// components as templates

// Break the CSS into individual rules
var rules = themeCss
.split(/}(?!([}'";]))/)
.filter(function(rule) { return rule && rule.trim().length; })
.map(function(rule) { return rule.trim() + '}'; });
var rules = splitCss(themeCss).map(function(rule) {
return rule.trim();
});

THEME_COLOR_TYPES.forEach(function(type) {
rulesByType[type] = '';
Expand Down Expand Up @@ -1277,6 +1276,52 @@ function generateAllThemes($injector, $mdTheming) {
};
});
}

/**
* @param {string} themeCss
* @returns {[]} a string representing a CSS file that is split, producing an array with a rule
* at each index.
*/
function splitCss(themeCss) {
var result = [];
var currentRule = '';
var openedCurlyBrackets = 0;
var closedCurlyBrackets = 0;

for (var i = 0; i < themeCss.length; i++) {
var character = themeCss.charAt(i);

// Check for content in quotes
if (character === '\'' || character === '"') {
// Append text in quotes to current rule
var textInQuotes = themeCss.substring(i, themeCss.indexOf(character, i + 1));
currentRule += textInQuotes;

// Jump to the closing quote char
i += textInQuotes.length;
} else {
currentRule += character;

if (character === '}') {
closedCurlyBrackets++;
if (closedCurlyBrackets === openedCurlyBrackets) {
closedCurlyBrackets = 0;
openedCurlyBrackets = 0;
result.push(currentRule);
currentRule = '';
}
} else if (character === '{') {
openedCurlyBrackets++;
}
}
}
// Add comments added after last valid rule.
if (currentRule !== '') {
result.push(currentRule);
}

return result;
}
}

function generateTheme(theme, name, nonce) {
Expand Down
86 changes: 86 additions & 0 deletions src/core/services/theming/theming.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1055,3 +1055,89 @@ describe('md-themable directive', function() {
expect(el.hasClass('md-default-theme')).toBe(true);
}));
});

describe('$mdThemeProvider with custom styles that include nested rules', function() {
it('appends the custom styles taking into account nesting', function() {
module('material.core', function($mdThemingProvider) {
$mdThemingProvider.generateThemesOnDemand(false);
var styles =
'@media (min-width: 0) and (max-width: 700px) {'
+ ' .md-THEME_NAME-theme .layout-row {'
+ ' background-color: "{{primary-500}}";'
+ ' }'
+ ' .md-THEME_NAME-theme .layout-column {'
+ ' color: blue;'
+ ' font-weight: bold;'
+ ' }'
+ '}';

$mdThemingProvider.registerStyles(styles);
$mdThemingProvider.theme('register-custom-nested-styles');
});

inject(function($MD_THEME_CSS) {
// Verify that $MD_THEME_CSS is still set to '/**/' in the test environment.
// Check angular-material-mocks.js for $MD_THEME_CSS latest value if this test starts to fail.
expect($MD_THEME_CSS).toBe('/**/');
});

var compiledStyles =
'@media (min-width: 0) and (max-width: 700px) {'
+ ' .md-register-custom-nested-styles-theme .layout-row {'
+ ' background-color: rgb(63,81,181);'
+ ' }'
+ ' .md-register-custom-nested-styles-theme .layout-column {'
+ ' color: blue;'
+ ' font-weight: bold;'
+ ' }'
+ '}';

// Find the string containing nested rules in the head tag.
expect(document.head.innerHTML).toContain(compiledStyles);
});
});

describe('$mdThemeProvider with custom styles that include multiple nested rules', function() {
it('appends the custom styles taking into account multiple nesting', function() {
module('material.core', function($mdThemingProvider) {
$mdThemingProvider.generateThemesOnDemand(false);
var styles =
'@supports (display: bar) {'
+ ' @media (min-width: 0) and (max-width: 700px) {'
+ ' .md-THEME_NAME-theme .layout-row {'
+ ' background-color: "{{primary-500}}";'
+ ' }'
+ ' .md-THEME_NAME-theme .layout-column {'
+ ' color: blue;'
+ ' font-weight: bold;'
+ ' }'
+ ' }'
+ '}';

$mdThemingProvider.registerStyles(styles);
$mdThemingProvider.theme('register-custom-multiple-nested-styles');
});

inject(function($MD_THEME_CSS) {
// Verify that $MD_THEME_CSS is still set to '/**/' in the test environment.
// Check angular-material-mocks.js for $MD_THEME_CSS latest value if this test starts to fail.
expect($MD_THEME_CSS).toBe('/**/');
});

var compiledStyles =
'@supports (display: bar) {'
+ ' @media (min-width: 0) and (max-width: 700px) {'
+ ' .md-register-custom-multiple-nested-styles-theme .layout-row {'
+ ' background-color: rgb(63,81,181);'
+ ' }'
+ ' .md-register-custom-multiple-nested-styles-theme .layout-column {'
+ ' color: blue;'
+ ' font-weight: bold;'
+ ' }'
+ ' }'
+ '}';

// Find the string containing nested rules in the head tag.
expect(document.head.innerHTML).toContain(compiledStyles);
});
});

0 comments on commit 71dc4eb

Please sign in to comment.