Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

feat(css_shim): implement polyfill-unscoped-next-selector and polyfill-non-strict #1556

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
106 changes: 78 additions & 28 deletions lib/core_dom/css_shim.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,12 @@ String shimCssText(String css, String tag) =>
* * `:host`
* * `:host(.x)`
*
* When the shim is not powerful enough, you can fall back on the polyfill-next-selector
* directive.
* When the shim is not powerful enough, you can fall back on the polyfill-next-selector,
* polyfill-unscoped-next-selector, and polyfill-non-strict directives.
*
* polyfill-next-selector {content: 'x > y'}
* z {}
*
* Becomes:
*
* x[tag] > y[tag]
* * `polyfill-next-selector {content: 'x > y'}` z {} becomes `x[tag] > y[tag] {}`
* * `polyfill-unscoped-next-selector {content: 'x > y'} z {}` becomes `x > y {}`
* * `polyfill-non-strict {} z {}` becomes `tag z {}`
*
* See http://www.polymer-project.org/docs/polymer/styling.html#at-polyfill
*
Expand All @@ -54,17 +51,15 @@ String shimCssText(String css, String tag) =>
*/
class _CssShim {
static final List SELECTOR_SPLITS = const [' ', '>', '+', '~'];
static final RegExp POLYFILL_NEXT_SELECTOR_DIRECTIVE = new RegExp(
r"polyfill-next-selector"

static final RegExp CONTENT = new RegExp(
r"[^}]*"
r"content\:[\s]*"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: [\s]* -> \s*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also : should not need to be escaped

r"'([^']*)'"
r"[^}]*}"
r"([^{]*)",
r"[^}]*}",
caseSensitive: false,
multiLine: true
);
static final int NEXT_SELECTOR_CONTENT = 1;

static final String HOST_TOKEN = '-host-element';
static final RegExp COLON_SELECTORS = new RegExp(r'(' + HOST_TOKEN + r')(\(.*\)){0,1}(.*)',
Expand All @@ -79,21 +74,29 @@ class _CssShim {
static final RegExp COLON_HOST = new RegExp('($HOST_TOKEN$PAREN_SUFFIX',
caseSensitive: false, multiLine: true);

static final String POLYFILL_NON_STRICT = "polyfill-non-strict";
static final String POLYFILL_UNSCOPED_NEXT_SELECTOR = "polyfill-unscoped-next-selector";
static final String POLYFILL_NEXT_SELECTOR = "polyfill-next-selector";

static final List<RegExp> COMBINATORS = [
new RegExp(r'/shadow/', caseSensitive: false),
new RegExp(r'/shadow-deep/', caseSensitive: false),
new RegExp(r'::shadow', caseSensitive: false),
new RegExp(r'/deep/', caseSensitive: false)
];

final String tag;
final String attr;

_CssShim(String tag)
: tag = tag, attr = "[$tag]";

String shimCssText(String css) {
final preprocessed = convertColonHost(applyPolyfillNextSelectorDirective(css));
final preprocessed = convertColonHost(css);
final rules = cssToRules(preprocessed);
return scopeRules(rules);
}

String applyPolyfillNextSelectorDirective(String css) =>
css.replaceAllMapped(POLYFILL_NEXT_SELECTOR_DIRECTIVE, (m) => m[NEXT_SELECTOR_CONTENT]);

String convertColonHost(String css) {
css = css.replaceAll(":host", HOST_TOKEN);

Expand All @@ -120,35 +123,82 @@ class _CssShim {
List<_Rule> cssToRules(String css) =>
new _Parser(css).parse();

String scopeRules(List<_Rule> rules) =>
rules.map(scopeRule).join("\n");
String scopeRules(List<_Rule> rules) {
final scopedRules = [];

var prevRule;
rules.forEach((rule) {
if (prevRule != null && prevRule.selectorText == POLYFILL_NON_STRICT) {
scopedRules.add(scopeNonStrictMode(rule));

} else if (prevRule != null && prevRule.selectorText == POLYFILL_UNSCOPED_NEXT_SELECTOR) {
final content = extractContent(prevRule);
scopedRules.add(ruleToString(new _Rule(content, body: rule.body)));

} else if (prevRule != null && prevRule.selectorText == POLYFILL_NEXT_SELECTOR) {
final content = extractContent(prevRule);
scopedRules.add(scopeStrictMode(new _Rule(content, body: rule.body)));

} else if (rule.selectorText != POLYFILL_NON_STRICT &&
rule.selectorText != POLYFILL_UNSCOPED_NEXT_SELECTOR &&
rule.selectorText != POLYFILL_NEXT_SELECTOR) {
scopedRules.add(scopeStrictMode(rule));
}

prevRule = rule;
});

return scopedRules.join("\n");
}

String extractContent(_Rule rule) {
return CONTENT.firstMatch(rule.body)[1];
}

String ruleToString(_Rule rule) {
return "${rule.selectorText} ${rule.body}";
}

String scopeRule(_Rule rule) {
String scopeStrictMode(_Rule rule) {
if (rule.hasNestedRules) {
final selector = rule.selectorText;
final rules = scopeRules(rule.rules);
return '$selector {\n$rules\n}';
} else {
final scopedSelector = scopeSelector(rule.selectorText);
final scopedSelector = scopeSelector(rule.selectorText, strict: true);
final scopedBody = cssText(rule);
return "$scopedSelector $scopedBody";
}
}

String scopeSelector(String selector) {
final parts = selector.split(",");
String scopeNonStrictMode(_Rule rule) {
final scopedBody = cssText(rule);
final scopedSelector = scopeSelector(rule.selectorText, strict: false);
return "${scopedSelector} $scopedBody";
}

String scopeSelector(String selector, {bool strict}) {
final parts = replaceCombinators(selector).split(",");
final scopedParts = parts.fold([], (res, p) {
res.add(scopeSimpleSelector(p.trim()));
res.add(scopeSimpleSelector(p.trim(), strict: strict));
return res;
});
return scopedParts.join(", ");
}

String scopeSimpleSelector(String selector) {
String replaceCombinators(String selector) {
return COMBINATORS.fold(selector, (sel, combinator) {
return sel.replaceAll(combinator, ' ');
});
}

String scopeSimpleSelector(String selector, {bool strict}) {
if (selector.contains(HOST_TOKEN)) {
return replaceColonSelectors(selector);
} else if (strict) {
return insertTagToEverySelectorPart(selector);
} else {
return insertTag(selector);
return "$tag $selector";
}
}

Expand All @@ -162,7 +212,7 @@ class _CssShim {
});
}

String insertTag(String selector) {
String insertTagToEverySelectorPart(String selector) {
selector = handleIsSelector(selector);

SELECTOR_SPLITS.forEach((split) {
Expand Down Expand Up @@ -258,7 +308,7 @@ class _Lexer {
int start = index;
advance();
while (isSelector(peek)) advance();
String string = input.substring(start, index);
String string = input.substring(start, index).trim();
return new _Token(string, "selector");
}

Expand Down
22 changes: 21 additions & 1 deletion test/core_dom/css_shim_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,29 @@ main() {
expect(s(':host(.x,.y) {}', "a")).toEqual('a.x, a.y {}');
});

it("should insert directives", () {
it("should support polyfill-next-selector", () {
var css = s("polyfill-next-selector {content: 'x > y'} z {}", "a");
expect(css).toEqual('x[a]>y[a] {}');
});

it("should support polyfill-unscoped-next-selector", () {
var css = s("polyfill-unscoped-next-selector {content: 'x > y'} z {}", "a");
expect(css).toEqual('x > y {}');
});

it("should support polyfill-non-strict-next-selector", () {
var css = s("polyfill-non-strict {} one, two {}", "a");
expect(css).toEqual('a one, a two {}');
});

it("should handle ::shadow", () {
var css = s("polyfill-non-strict {} x::shadow > y {}", "a");
expect(css).toEqual('a x > y {}');
});

it("should handle /deep/", () {
var css = s("polyfill-non-strict {} x /deep/ y {}", "a");
expect(css).toEqual('a x y {}');
});
});
}