Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(288)

Unified Diff: pkg/shadow_dom/lib/src/platform/ShadowCSS.js

Issue 34873005: use ShadowCSS directly (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/shadow_dom/lib/shadow_dom.min.js ('k') | pkg/shadow_dom/lib/src/platform/patches-shadow-css.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/shadow_dom/lib/src/platform/ShadowCSS.js
diff --git a/pkg/shadow_dom/lib/src/platform/ShadowCSS.js b/pkg/shadow_dom/lib/src/platform/ShadowCSS.js
index 96861a13640a6de205b2b21c1c67d1b9c431ba5d..49fde56a8a2796a4425558070b5c282b3db68da9 100644
--- a/pkg/shadow_dom/lib/src/platform/ShadowCSS.js
+++ b/pkg/shadow_dom/lib/src/platform/ShadowCSS.js
@@ -90,6 +90,19 @@
x-foo [pseudo=x-special] { ... }
+ * ::part(): These rules are converted to rules that take advantage of the
+ part attribute. For example, a shadowRoot like this inside an x-foo
+
+ <div part="special">Special</div>
+
+ with a rule like this:
+
+ x-foo::part(special) { ... }
+
+ becomes:
+
+ x-foo [part=special] { ... }
+
Unaddressed ShadowDOM styling features:
* upper/lower bound encapsulation: Styles which are defined outside a
@@ -141,27 +154,37 @@ var ShadowCSS = {
// Shim styles for a given root associated with a name and extendsName
// 1. cache root styles by name
// 2. optionally tag root nodes with scope name
- // 3. shim polyfill directives /* @polyfill */
+ // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */
// 4. shim @host and scoping
shimStyling: function(root, name, extendsName) {
+ var typeExtension = this.isTypeExtension(extendsName);
+ // use caching to make working with styles nodes easier and to facilitate
+ // lookup of extendee
+ var def = this.registerDefinition(root, name, extendsName);
+ // find styles and apply shimming...
+ if (this.strictStyling) {
+ this.applyScopeToContent(root, name);
+ }
+ // insert @polyfill and @polyfill-rule rules into style elements
+ // scoping process takes care of shimming these
+ this.insertPolyfillDirectives(def.rootStyles);
+ this.insertPolyfillRules(def.rootStyles);
+ var cssText = this.stylesToShimmedCssText(def.scopeStyles, name,
+ typeExtension);
+ // note: we only need to do rootStyles since these are unscoped.
+ cssText += this.extractPolyfillUnscopedRules(def.rootStyles);
+ // provide shimmedStyle for user extensibility
+ def.shimmedStyle = cssTextToStyle(cssText);
if (root) {
- // use caching to make working with styles nodes easier and to facilitate
- // lookup of extendee
- var def = this.registerDefinition(root, name, extendsName);
- // find styles and apply shimming...
- if (this.strictStyling) {
- this.applyScopeToContent(root, name);
- }
- this.shimPolyfillDirectives(def.rootStyles, name);
- this.applyShimming(def.scopeStyles, name);
+ root.shimmedStyle = def.shimmedStyle;
}
- },
- // Shim styles to be placed inside a shadowRoot.
- // 1. shim polyfill directives /* @polyfill */
- // 2. shim @host and scoping
- shimShadowDOMStyling: function(styles, name) {
- this.shimPolyfillDirectives(styles, name);
- this.applyShimming(styles, name);
+ // remove existing style elements
+ for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]);
+ i++) {
+ s.parentNode.removeChild(s);
+ }
+ // add style to document
+ addCssToDocument(cssText);
},
registerDefinition: function(root, name, extendsName) {
var def = this.registry[name] = {
@@ -169,16 +192,19 @@ var ShadowCSS = {
name: name,
extendsName: extendsName
}
- var styles = root.querySelectorAll('style');
+ var styles = root ? root.querySelectorAll('style') : [];
styles = styles ? Array.prototype.slice.call(styles, 0) : [];
def.rootStyles = styles;
def.scopeStyles = def.rootStyles;
var extendee = this.registry[def.extendsName];
- if (extendee) {
- def.scopeStyles = def.scopeStyles.concat(extendee.scopeStyles);
+ if (extendee && (!root || root.querySelector('shadow'))) {
+ def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles);
}
return def;
},
+ isTypeExtension: function(extendsName) {
+ return extendsName && extendsName.indexOf('-') < 0;
+ },
applyScopeToContent: function(root, name) {
if (root) {
// add the name attribute to each node in root.
@@ -200,78 +226,123 @@ var ShadowCSS = {
*
* For example, we convert this rule:
*
- * (comment start) @polyfill @host g-menu-item (comment end)
- * shadow::-webkit-distributed(g-menu-item) {
+ * (comment start) @polyfill :host menu-item (comment end)
+ * shadow::-webkit-distributed(menu-item) {
*
* to this:
*
- * scopeName g-menu-item {
+ * scopeName menu-item {
*
**/
- shimPolyfillDirectives: function(styles, name) {
+ insertPolyfillDirectives: function(styles) {
if (styles) {
Array.prototype.forEach.call(styles, function(s) {
- s.textContent = this.convertPolyfillDirectives(s.textContent, name);
+ s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent);
}, this);
}
},
- convertPolyfillDirectives: function(cssText, name) {
- var r = '', l = 0, matches, selector;
- while (matches = cssPolyfillCommentRe.exec(cssText)) {
- r += cssText.substring(l, matches.index);
- // remove end comment delimiter (*/)
- selector = matches[1].slice(0, -2).replace(hostRe, name);
- r += this.scopeSelector(selector, name) + '{';
- l = cssPolyfillCommentRe.lastIndex;
+ insertPolyfillDirectivesInCssText: function(cssText) {
+ return cssText.replace(cssPolyfillCommentRe, function(match, p1) {
+ // remove end comment delimiter and add block start
+ return p1.slice(0, -2) + '{';
+ });
+ },
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ *
+ * For example, we convert this rule:
+ *
+ * (comment start) @polyfill-rule :host menu-item {
+ * ... } (comment end)
+ *
+ * to this:
+ *
+ * scopeName menu-item {...}
+ *
+ **/
+ insertPolyfillRules: function(styles) {
+ if (styles) {
+ Array.prototype.forEach.call(styles, function(s) {
+ s.textContent = this.insertPolyfillRulesInCssText(s.textContent);
+ }, this);
+ }
+ },
+ insertPolyfillRulesInCssText: function(cssText) {
+ return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) {
+ // remove end comment delimiter
+ return p1.slice(0, -1);
+ });
+ },
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ * and do not process via CSSOM. (CSSOM is destructive to rules on rare
+ * occasions, e.g. -webkit-calc on Safari.)
+ * For example, we convert this rule:
+ *
+ * (comment start) @polyfill-unscoped-rule menu-item {
+ * ... } (comment end)
+ *
+ * to this:
+ *
+ * menu-item {...}
+ *
+ **/
+ extractPolyfillUnscopedRules: function(styles) {
+ var cssText = '';
+ if (styles) {
+ Array.prototype.forEach.call(styles, function(s) {
+ cssText += this.extractPolyfillUnscopedRulesFromCssText(
+ s.textContent) + '\n\n';
+ }, this);
+ }
+ return cssText;
+ },
+ extractPolyfillUnscopedRulesFromCssText: function(cssText) {
+ var r = '', matches;
+ while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) {
+ r += matches[1].slice(0, -1) + '\n\n';
}
- r += cssText.substring(l, cssText.length);
return r;
},
// apply @host and scope shimming
- applyShimming: function(styles, name) {
- var cssText = this.shimAtHost(styles, name);
- cssText += this.shimScoping(styles, name);
- addCssToDocument(cssText);
+ stylesToShimmedCssText: function(styles, name, typeExtension) {
+ return this.shimAtHost(styles, name, typeExtension) +
+ this.shimScoping(styles, name, typeExtension);
},
// form: @host { .foo { declarations } }
// becomes: scopeName.foo { declarations }
- shimAtHost: function(styles, name) {
+ shimAtHost: function(styles, name, typeExtension) {
if (styles) {
- return this.convertAtHostStyles(styles, name);
+ return this.convertAtHostStyles(styles, name, typeExtension);
}
},
- convertAtHostStyles: function(styles, name) {
- var cssText = stylesToCssText(styles);
- var r = '', l=0, matches;
- while (matches = hostRuleRe.exec(cssText)) {
- r += cssText.substring(l, matches.index);
- r += this.scopeHostCss(matches[1], name);
- l = hostRuleRe.lastIndex;
- }
- r += cssText.substring(l, cssText.length);
- var re = new RegExp('^' + name + selectorReSuffix, 'm');
- var cssText = rulesToCss(this.findAtHostRules(cssToRules(r),
- re));
+ convertAtHostStyles: function(styles, name, typeExtension) {
+ var cssText = stylesToCssText(styles), self = this;
+ cssText = cssText.replace(hostRuleRe, function(m, p1) {
+ return self.scopeHostCss(p1, name, typeExtension);
+ });
+ cssText = rulesToCss(this.findAtHostRules(cssToRules(cssText),
+ new RegExp('^' + name + selectorReSuffix, 'm')));
return cssText;
},
- scopeHostCss: function(cssText, name) {
- var r = '', matches;
- while (matches = selectorRe.exec(cssText)) {
- r += this.scopeHostSelector(matches[1], name) +' ' + matches[2] + '\n\t';
- }
- return r;
+ scopeHostCss: function(cssText, name, typeExtension) {
+ var self = this;
+ return cssText.replace(selectorRe, function(m, p1, p2) {
+ return self.scopeHostSelector(p1, name, typeExtension) + ' ' + p2 + '\n\t';
+ });
},
// supports scopig by name and [is=name] syntax
- scopeHostSelector: function(selector, name) {
+ scopeHostSelector: function(selector, name, typeExtension) {
var r = [], parts = selector.split(','), is = '[is=' + name + ']';
parts.forEach(function(p) {
p = p.trim();
// selector: *|:scope -> name
if (p.match(hostElementRe)) {
- p = p.replace(hostElementRe, name + '$1$3, ' + is + '$1$3');
- // selector: .foo -> name.foo, [bar] -> name[bar]
+ p = p.replace(hostElementRe, typeExtension ? is + '$1$3' :
+ name + '$1$3');
+ // selector: .foo -> name.foo (OR) [bar] -> name[bar]
} else if (p.match(hostFixableRe)) {
- p = name + p + ', ' + is + p;
+ p = typeExtension ? is + p : name + p;
}
r.push(p);
}, this);
@@ -297,32 +368,57 @@ var ShadowCSS = {
*
* scopeName .foo { ... }
*/
- shimScoping: function(styles, name) {
+ shimScoping: function(styles, name, typeExtension) {
if (styles) {
- return this.convertScopedStyles(styles, name);
+ return this.convertScopedStyles(styles, name, typeExtension);
}
},
- convertScopedStyles: function(styles, name) {
- Array.prototype.forEach.call(styles, function(s) {
- if (s.parentNode) {
- s.parentNode.removeChild(s);
- }
- });
+ convertScopedStyles: function(styles, name, typeExtension) {
var cssText = stylesToCssText(styles).replace(hostRuleRe, '');
+ cssText = this.insertPolyfillHostInCssText(cssText);
+ cssText = this.convertColonHost(cssText);
cssText = this.convertPseudos(cssText);
+ cssText = this.convertParts(cssText);
+ cssText = this.convertCombinators(cssText);
var rules = cssToRules(cssText);
- cssText = this.scopeRules(rules, name);
+ cssText = this.scopeRules(rules, name, typeExtension);
return cssText;
},
convertPseudos: function(cssText) {
return cssText.replace(cssPseudoRe, ' [pseudo=$1]');
},
+ convertParts: function(cssText) {
+ return cssText.replace(cssPartRe, ' [part=$1]');
+ },
+ /*
+ * convert a rule like :host(.foo) > .bar { }
+ *
+ * to
+ *
+ * scopeName.foo > .bar, .foo scopeName > .bar { }
+ * TODO(sorvell): file bug since native impl does not do the former yet.
+ * http://jsbin.com/OganOCI/2/edit
+ */
+ convertColonHost: function(cssText) {
+ // p1 = :host, p2 = contents of (), p3 rest of rule
+ return cssText.replace(cssColonHostRe, function(m, p1, p2, p3) {
+ return p2 ? polyfillHostNoCombinator + p2 + p3 + ', '
+ + p2 + ' ' + p1 + p3 :
+ p1 + p3;
+ });
+ },
+ /*
+ * Convert ^ and ^^ combinators by replacing with space.
+ */
+ convertCombinators: function(cssText) {
+ return cssText.replace('^^', ' ').replace('^', ' ');
+ },
// change a selector like 'div' to 'name div'
- scopeRules: function(cssRules, name) {
+ scopeRules: function(cssRules, name, typeExtension) {
var cssText = '';
Array.prototype.forEach.call(cssRules, function(rule) {
if (rule.selectorText && (rule.style && rule.style.cssText)) {
- cssText += this.scopeSelector(rule.selectorText, name,
+ cssText += this.scopeSelector(rule.selectorText, name, typeExtension,
this.strictStyling) + ' {\n\t';
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
} else if (rule.media) {
@@ -335,26 +431,32 @@ var ShadowCSS = {
}, this);
return cssText;
},
- scopeSelector: function(selector, name, strict) {
+ scopeSelector: function(selector, name, typeExtension, strict) {
var r = [], parts = selector.split(',');
parts.forEach(function(p) {
p = p.trim();
- if (this.selectorNeedsScoping(p, name)) {
+ if (this.selectorNeedsScoping(p, name, typeExtension)) {
p = strict ? this.applyStrictSelectorScope(p, name) :
- this.applySimpleSelectorScope(p, name);
+ this.applySimpleSelectorScope(p, name, typeExtension);
}
r.push(p);
}, this);
return r.join(', ');
},
- selectorNeedsScoping: function(selector, name) {
- var matchScope = '(' + name + '|\\[is=' + name + '\\])';
- var re = new RegExp('^' + matchScope + selectorReSuffix, 'm');
+ selectorNeedsScoping: function(selector, name, typeExtension) {
+ var matchScope = typeExtension ? name : '\\[is=' + name + '\\]';
+ var re = new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm');
return !selector.match(re);
},
// scope via name and [is=name]
- applySimpleSelectorScope: function(selector, name) {
- return name + ' ' + selector + ', ' + '[is=' + name + '] ' + selector;
+ applySimpleSelectorScope: function(selector, name, typeExtension) {
+ var scoper = typeExtension ? '[is=' + name + ']' : name;
+ if (selector.match(polyfillHostRe)) {
+ selector = selector.replace(polyfillHostNoCombinator, scoper);
+ return selector.replace(polyfillHostRe, scoper + ' ');
+ } else {
+ return scoper + ' ' + selector;
+ }
},
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
@@ -365,7 +467,8 @@ var ShadowCSS = {
splits.forEach(function(sep) {
var parts = scoped.split(sep);
scoped = parts.map(function(p) {
- var t = p.trim();
+ // remove :host since it should be unnecessary
+ var t = p.trim().replace(polyfillHostRe, '');
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
}
@@ -374,6 +477,10 @@ var ShadowCSS = {
});
return scoped;
},
+ insertPolyfillHostInCssText: function(selector) {
+ return selector.replace(hostRe, polyfillHost).replace(colonHostRe,
+ polyfillHost);
+ },
propertiesFromRule: function(rule) {
var properties = rule.style.cssText;
// TODO(sorvell): Chrome cssom incorrectly removes quotes from the content
@@ -392,9 +499,19 @@ var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
hostFixableRe = /^[.\[:]/,
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
+ cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
+ cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
cssPseudoRe = /::(x-[^\s{,(]*)/gim,
+ cssPartRe = /::part\(([^)]*)\)/gim,
+ // note: :host pre-processed to -host.
+ cssColonHostRe = /(-host)(?:\(([^)]*)\))?([^,{]*)/gim,
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
- hostRe = /@host/gim;
+ hostRe = /@host/gim,
+ colonHostRe = /\:host/gim,
+ polyfillHost = '-host',
+ /* host name without combinator */
+ polyfillHostNoCombinator = '-host-no-combinator',
+ polyfillHostRe = /-host/gim;
function stylesToCssText(styles, preserveComments) {
var cssText = '';
@@ -408,9 +525,14 @@ function stylesToCssText(styles, preserveComments) {
return cssText;
}
-function cssToRules(cssText) {
+function cssTextToStyle(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
+ return style;
+}
+
+function cssToRules(cssText) {
+ var style = cssTextToStyle(cssText);
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
@@ -449,4 +571,4 @@ if (window.ShadowDOMPolyfill) {
// exports
scope.ShadowCSS = ShadowCSS;
-})(window.Platform);
+})(window.Platform);
« no previous file with comments | « pkg/shadow_dom/lib/shadow_dom.min.js ('k') | pkg/shadow_dom/lib/src/platform/patches-shadow-css.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698