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

fix(theming): fix CSS with nested rules parsing when registering a theme #9872

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions src/core/services/theming/theming.js
Original file line number Diff line number Diff line change
Expand Up @@ -898,11 +898,8 @@ function generateAllThemes($injector, $mdTheming) {
// 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(); });

var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g');

Expand Down Expand Up @@ -1003,6 +1000,48 @@ function generateAllThemes($injector, $mdTheming) {
}
});
}

// Split a string representing a CSS file producing an array with a rule in each element of it.
function splitCss(themeCss) {
var result = [];
var currentRule = '';
var openedCurlies = 0;
var closedCurlies = 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 === '}') {
closedCurlies++;
if (closedCurlies === openedCurlies) {
closedCurlies = 0;
openedCurlies = 0;
result.push(currentRule);
currentRule = '';
}
} else if (character === '{') {
openedCurlies++;
}
}
}
// 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 @@ -880,3 +880,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);
});
});