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

Commit 0168874

Browse files
committed
fix(theming): fix CSS with nested rules parsing when registering a theme
Replace the regex used to split the CSS string with a function that takes into account nested rules. Fixes #9869
1 parent 757d649 commit 0168874

File tree

2 files changed

+130
-5
lines changed

2 files changed

+130
-5
lines changed

src/core/services/theming/theming.js

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -898,11 +898,8 @@ function generateAllThemes($injector, $mdTheming) {
898898
// components as templates
899899

900900
// Break the CSS into individual rules
901-
var rules = themeCss
902-
.split(/\}(?!(\}|'|"|;))/)
903-
.filter(function(rule) { return rule && rule.trim().length; })
904-
.map(function(rule) { return rule.trim() + '}'; });
905-
901+
var rules = splitCss(themeCss)
902+
.map(function(rule) { return rule.trim(); });
906903

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

@@ -1003,6 +1000,48 @@ function generateAllThemes($injector, $mdTheming) {
10031000
}
10041001
});
10051002
}
1003+
1004+
// Split a string representing a CSS file producing an array with a rule in each element of it.
1005+
function splitCss(themeCss) {
1006+
var result = [];
1007+
var currentRule = '';
1008+
var openedCurlies = 0;
1009+
var closedCurlies = 0;
1010+
1011+
for (var i = 0; i < themeCss.length; i++) {
1012+
var character = themeCss.charAt(i);
1013+
1014+
// Check for content in quotes
1015+
if (character === '\'' || character === '\"') {
1016+
// Find closing quote
1017+
var nextQuoteIndex = themeCss.indexOf(character, i + 1);
1018+
var textInQuotes = themeCss.substring(i, nextQuoteIndex);
1019+
currentRule += textInQuotes;
1020+
// Jump to the closing quote char
1021+
i = nextQuoteIndex;
1022+
} else {
1023+
currentRule += character;
1024+
1025+
if (character === '}') {
1026+
closedCurlies++;
1027+
if (closedCurlies === openedCurlies) {
1028+
closedCurlies = 0;
1029+
openedCurlies = 0;
1030+
result.push(currentRule);
1031+
currentRule = '';
1032+
}
1033+
} else if (character === '{') {
1034+
openedCurlies++;
1035+
}
1036+
}
1037+
}
1038+
// Add comments added after last valid rule.
1039+
if (currentRule !== '') {
1040+
result.push(currentRule);
1041+
}
1042+
1043+
return result;
1044+
}
10061045
}
10071046

10081047
function generateTheme(theme, name, nonce) {

src/core/services/theming/theming.spec.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,3 +880,89 @@ describe('md-themable directive', function() {
880880
expect(el.hasClass('md-default-theme')).toBe(true);
881881
}));
882882
});
883+
884+
describe('$mdThemeProvider with custom styles that include nested rules', function() {
885+
it('appends the custom styles taking into account nesting', function() {
886+
module('material.core', function($mdThemingProvider) {
887+
$mdThemingProvider.generateThemesOnDemand(false);
888+
var styles =
889+
"@media (min-width: 0) and (max-width: 700px) {\n\n"
890+
+ " .md-THEME_NAME-theme .layout-row {\n"
891+
+ " background-color: {{primary-500}};\n"
892+
+ " }\n\n"
893+
+ " .md-THEME_NAME-theme .layout-column {\n"
894+
+ " color: blue;\n"
895+
+ " font-weight: bold;\n"
896+
+ " }\n"
897+
+ "}\n";
898+
899+
$mdThemingProvider.registerStyles(styles);
900+
$mdThemingProvider.theme('register-custom-nested-styles');
901+
});
902+
903+
inject(function($MD_THEME_CSS) {
904+
// Verify that $MD_THEME_CSS is still set to '/**/' in the test environment.
905+
// Check angular-material-mocks.js for $MD_THEME_CSS latest value if this test starts to fail.
906+
expect($MD_THEME_CSS).toBe('/**/');
907+
});
908+
909+
var compiledStyles =
910+
"@media (min-width: 0) and (max-width: 700px) {\n\n"
911+
+ " .md-register-custom-nested-styles-theme .layout-row {\n"
912+
+ " background-color: rgb(63,81,181);\n"
913+
+ " }\n\n"
914+
+ " .md-register-custom-nested-styles-theme .layout-column {\n"
915+
+ " color: blue;\n"
916+
+ " font-weight: bold;\n"
917+
+ " }\n"
918+
+ "}";
919+
920+
// Find the string containing nested rules in the head tag.
921+
expect(document.head.innerHTML).toContain(compiledStyles);
922+
});
923+
});
924+
925+
describe('$mdThemeProvider with custom styles that include multiple nested rules', function() {
926+
it('appends the custom styles taking into account multiple nesting', function() {
927+
module('material.core', function($mdThemingProvider) {
928+
$mdThemingProvider.generateThemesOnDemand(false);
929+
var styles =
930+
"@supports (display: bar) {\n\n"
931+
+ " @media (min-width: 0) and (max-width: 700px) {\n\n"
932+
+ " .md-THEME_NAME-theme .layout-row {\n"
933+
+ " background-color: {{primary-500}};\n"
934+
+ " }\n\n"
935+
+ " .md-THEME_NAME-theme .layout-column {\n"
936+
+ " color: blue;\n"
937+
+ " font-weight: bold;\n"
938+
+ " }\n"
939+
+ " }\n"
940+
+ "}\n";
941+
942+
$mdThemingProvider.registerStyles(styles);
943+
$mdThemingProvider.theme('register-custom-multiple-nested-styles');
944+
});
945+
946+
inject(function($MD_THEME_CSS) {
947+
// Verify that $MD_THEME_CSS is still set to '/**/' in the test environment.
948+
// Check angular-material-mocks.js for $MD_THEME_CSS latest value if this test starts to fail.
949+
expect($MD_THEME_CSS).toBe('/**/');
950+
});
951+
952+
var compiledStyles =
953+
"@supports (display: bar) {\n\n"
954+
+ " @media (min-width: 0) and (max-width: 700px) {\n\n"
955+
+ " .md-register-custom-multiple-nested-styles-theme .layout-row {\n"
956+
+ " background-color: rgb(63,81,181);\n"
957+
+ " }\n\n"
958+
+ " .md-register-custom-multiple-nested-styles-theme .layout-column {\n"
959+
+ " color: blue;\n"
960+
+ " font-weight: bold;\n"
961+
+ " }\n"
962+
+ " }\n"
963+
+ "}";
964+
965+
// Find the string containing nested rules in the head tag.
966+
expect(document.head.innerHTML).toContain(compiledStyles);
967+
});
968+
});

0 commit comments

Comments
 (0)