Build shadow_dom package in dart/pkg

BUG=
R=jmesserly@google.com

Review URL: https://codereview.chromium.org//22951003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26231 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
terry@google.com 2013-08-15 22:20:05 +00:00
parent 6cbbf07554
commit 3fb3eb8d3a
20 changed files with 1518 additions and 2 deletions

8
.gitignore vendored
View file

@ -54,6 +54,14 @@ pubspec.lock
# Vim temporary swap files.
*.swp
# Third party files
third_party/nss_pkcs12
third_party/polymer
# Generated files.
tools/out
tools/xcodebuild
pkg/shadow_dom/tool/node_modules

View file

@ -3204,4 +3204,528 @@ var ShadowDOMPolyfill = {};
};
})();
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
var Platform = {};
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/*
This is a limited shim for ShadowDOM css styling.
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
The intention here is to support only the styling features which can be
relatively simply implemented. The goal is to allow users to avoid the
most obvious pitfalls and do so without compromising performance significantly.
For ShadowDOM styling that's not covered here, a set of best practices
can be provided that should allow users to accomplish more complex styling.
The following is a list of specific ShadowDOM styling features and a brief
discussion of the approach used to shim.
Shimmed features:
* @host: ShadowDOM allows styling of the shadowRoot's host element using the
@host rule. To shim this feature, the @host styles are reformatted and
prefixed with a given scope name and promoted to a document level stylesheet.
For example, given a scope name of .foo, a rule like this:
@host {
* {
background: red;
}
}
becomes:
.foo {
background: red;
}
* encapsultion: Styles defined within ShadowDOM, apply only to
dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
this feature.
By default, rules are prefixed with the host element tag name
as a descendant selector. This ensures styling does not leak out of the 'top'
of the element's ShadowDOM. For example,
div {
font-weight: bold;
}
becomes:
x-foo div {
font-weight: bold;
}
becomes:
Alternatively, if Platform.ShadowCSS.strictStyling is set to true then
selectors are scoped by adding an attribute selector suffix to each
simple selector that contains the host element tag name. Each element
in the element's ShadowDOM template is also given the scope attribute.
Thus, these rules match only elements that have the scope attribute.
For example, given a scope name of x-foo, a rule like this:
div {
font-weight: bold;
}
becomes:
div[x-foo] {
font-weight: bold;
}
Note that elements that are dynamically added to a scope must have the scope
selector added to them manually.
* ::pseudo: These rules are converted to rules that take advantage of the
pseudo attribute. For example, a shadowRoot like this inside an x-foo
<div pseudo="x-special">Special</div>
with a rule like this:
x-foo::x-special { ... }
becomes:
x-foo [pseudo=x-special] { ... }
Unaddressed ShadowDOM styling features:
* upper/lower bound encapsulation: Styles which are defined outside a
shadowRoot should not cross the ShadowDOM boundary and should not apply
inside a shadowRoot.
This styling behavior is not emulated. Some possible ways to do this that
were rejected due to complexity and/or performance concerns include: (1) reset
every possible property for every possible selector for a given scope name;
(2) re-implement css in javascript.
As an alternative, users should make sure to use selectors
specific to the scope in which they are working.
* ::distributed: This behavior is not emulated. It's often not necessary
to style the contents of a specific insertion point and instead, descendants
of the host element can be styled selectively. Users can also create an
extra node around an insertion point and style that node's contents
via descendent selectors. For example, with a shadowRoot like this:
<style>
content::-webkit-distributed(div) {
background: red;
}
</style>
<content></content>
could become:
<style>
/ *@polyfill .content-container div * /
content::-webkit-distributed(div) {
background: red;
}
</style>
<div class="content-container">
<content></content>
</div>
Note the use of @polyfill in the comment above a ShadowDOM specific style
declaration. This is a directive to the styling shim to use the selector
in comments in lieu of the next selector when running under polyfill.
*/
(function(scope) {
var ShadowCSS = {
strictStyling: false,
registry: {},
// 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 */
// 4. shim @host and scoping
shimStyling: function(root, name, extendsName) {
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);
}
},
// 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);
},
registerDefinition: function(root, name, extendsName) {
var def = this.registry[name] = {
root: root,
name: name,
extendsName: extendsName
}
var styles = 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);
}
return def;
},
applyScopeToContent: function(root, name) {
if (root) {
// add the name attribute to each node in root.
Array.prototype.forEach.call(root.querySelectorAll('*'),
function(node) {
node.setAttribute(name, '');
});
// and template contents too
Array.prototype.forEach.call(root.querySelectorAll('template'),
function(template) {
this.applyScopeToContent(template.content, name);
},
this);
}
},
/*
* Process styles to convert native ShadowDOM rules that will trip
* up the css parser; we rely on decorating the stylesheet with comments.
*
* For example, we convert this rule:
*
* (comment start) @polyfill @host g-menu-item (comment end)
* shadow::-webkit-distributed(g-menu-item) {
*
* to this:
*
* scopeName g-menu-item {
*
**/
shimPolyfillDirectives: function(styles, name) {
if (styles) {
Array.prototype.forEach.call(styles, function(s) {
s.textContent = this.convertPolyfillDirectives(s.textContent, name);
}, 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;
}
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);
},
// form: @host { .foo { declarations } }
// becomes: scopeName.foo { declarations }
shimAtHost: function(styles, name) {
if (styles) {
return this.convertAtHostStyles(styles, name);
}
},
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));
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;
},
// supports scopig by name and [is=name] syntax
scopeHostSelector: function(selector, name) {
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]
} else if (p.match(hostFixableRe)) {
p = name + p + ', ' + is + p;
}
r.push(p);
}, this);
return r.join(', ');
},
// consider styles that do not include component name in the selector to be
// unscoped and in need of promotion;
// for convenience, also consider keyframe rules this way.
findAtHostRules: function(cssRules, matcher) {
return Array.prototype.filter.call(cssRules,
this.isHostRule.bind(this, matcher));
},
isHostRule: function(matcher, cssRule) {
return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
(cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
(cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
},
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
*
* .foo {... }
*
* and converts this to
*
* scopeName .foo { ... }
*/
shimScoping: function(styles, name) {
if (styles) {
return this.convertScopedStyles(styles, name);
}
},
convertScopedStyles: function(styles, name) {
Array.prototype.forEach.call(styles, function(s) {
if (s.parentNode) {
s.parentNode.removeChild(s);
}
});
var cssText = stylesToCssText(styles).replace(hostRuleRe, '');
cssText = this.convertPseudos(cssText);
var rules = cssToRules(cssText);
cssText = this.scopeRules(rules, name);
return cssText;
},
convertPseudos: function(cssText) {
return cssText.replace(cssPseudoRe, ' [pseudo=$1]');
},
// change a selector like 'div' to 'name div'
scopeRules: function(cssRules, name) {
var cssText = '';
Array.prototype.forEach.call(cssRules, function(rule) {
if (rule.selectorText && (rule.style && rule.style.cssText)) {
cssText += this.scopeSelector(rule.selectorText, name,
this.strictStyling) + ' {\n\t';
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
} else if (rule.media) {
cssText += '@media ' + rule.media.mediaText + ' {\n';
cssText += this.scopeRules(rule.cssRules, name);
cssText += '\n}\n\n';
} else if (rule.cssText) {
cssText += rule.cssText + '\n\n';
}
}, this);
return cssText;
},
scopeSelector: function(selector, name, strict) {
var r = [], parts = selector.split(',');
parts.forEach(function(p) {
p = p.trim();
if (this.selectorNeedsScoping(p, name)) {
p = strict ? this.applyStrictSelectorScope(p, name) :
this.applySimpleSelectorScope(p, name);
}
r.push(p);
}, this);
return r.join(', ');
},
selectorNeedsScoping: function(selector, name) {
var matchScope = '(' + 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;
},
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
applyStrictSelectorScope: function(selector, name) {
var splits = [' ', '>', '+', '~'],
scoped = selector,
attrName = '[' + name + ']';
splits.forEach(function(sep) {
var parts = scoped.split(sep);
scoped = parts.map(function(p) {
var t = p.trim();
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
}
return p;
}).join(sep);
});
return scoped;
},
propertiesFromRule: function(rule) {
var properties = rule.style.cssText;
// TODO(sorvell): Chrome cssom incorrectly removes quotes from the content
// property. (https://code.google.com/p/chromium/issues/detail?id=247231)
if (rule.style.content && !rule.style.content.match(/['"]+/)) {
properties = 'content: \'' + rule.style.content + '\';\n' +
rule.style.cssText.replace(/content:[^;]*;/g, '');
}
return properties;
}
};
var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
selectorRe = /([^{]*)({[\s\S]*?})/gim,
hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/,
hostFixableRe = /^[.\[:]/,
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
cssPseudoRe = /::(x-[^\s{,(]*)/gim,
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
hostRe = /@host/gim;
function stylesToCssText(styles, preserveComments) {
var cssText = '';
Array.prototype.forEach.call(styles, function(s) {
cssText += s.textContent + '\n\n';
});
// strip comments for easier processing
if (!preserveComments) {
cssText = cssText.replace(cssCommentRe, '');
}
return cssText;
}
function cssToRules(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
return rules;
}
function rulesToCss(cssRules) {
for (var i=0, css=[]; i < cssRules.length; i++) {
css.push(cssRules[i].cssText);
}
return css.join('\n\n');
}
function addCssToDocument(cssText) {
if (cssText) {
getSheet().appendChild(document.createTextNode(cssText));
}
}
var sheet;
function getSheet() {
if (!sheet) {
sheet = document.createElement("style");
sheet.setAttribute('ShadowCSSShim', '');
}
return sheet;
}
// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
addCssToDocument('style { display: none !important; }\n');
var head = document.querySelector('head');
head.insertBefore(getSheet(), head.childNodes[0]);
}
// exports
scope.ShadowCSS = ShadowCSS;
})(window.Platform);
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
(function(scope) {
// TODO(terry): Remove shimShadowDOMStyling2 until wrap/unwrap from a
// dart:html Element to a JS DOM node is available.
/**
* Given the content of a STYLE tag and the name of a component shim the CSS
* and return the new scoped CSS to replace the STYLE's content. The content
* is replaced in Dart's implementation of PolymerElement.
*/
function shimShadowDOMStyling2(styleContent, name) {
if (window.ShadowDOMPolyfill) {
var content = this.convertPolyfillDirectives(styleContent, name);
// applyShimming calls shimAtHost and shipScoping
// shimAtHost code:
var r = '', l=0, matches;
while (matches = hostRuleRe.exec(content)) {
r += content.substring(l, matches.index);
r += this.scopeHostCss(matches[1], name);
l = hostRuleRe.lastIndex;
}
r += content.substring(l, content.length);
var re = new RegExp('^' + name + selectorReSuffix, 'm');
var atHostCssText = rulesToCss(this.findAtHostRules(cssToRules(r), re));
// shimScoping code:
// strip comments for easier processing
content = content.replace(cssCommentRe, '');
content = this.convertPseudos(content);
var rules = cssToRules(content);
var cssText = this.scopeRules(rules, name);
return atHostCssText + cssText;
}
}
// Minimal copied code from ShadowCSS, that is not exposed in
// PlatForm.ShadowCSS (local code).
var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
function cssToRules(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
return rules;
}
function rulesToCss(cssRules) {
for (var i=0, css=[]; i < cssRules.length; i++) {
css.push(cssRules[i].cssText);
}
return css.join('\n\n');
}
// exports
scope.ShadowCSS.shimShadowDOMStyling2 = shimShadowDOMStyling2;
})(window.Platform);
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,452 @@
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/*
This is a limited shim for ShadowDOM css styling.
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
The intention here is to support only the styling features which can be
relatively simply implemented. The goal is to allow users to avoid the
most obvious pitfalls and do so without compromising performance significantly.
For ShadowDOM styling that's not covered here, a set of best practices
can be provided that should allow users to accomplish more complex styling.
The following is a list of specific ShadowDOM styling features and a brief
discussion of the approach used to shim.
Shimmed features:
* @host: ShadowDOM allows styling of the shadowRoot's host element using the
@host rule. To shim this feature, the @host styles are reformatted and
prefixed with a given scope name and promoted to a document level stylesheet.
For example, given a scope name of .foo, a rule like this:
@host {
* {
background: red;
}
}
becomes:
.foo {
background: red;
}
* encapsultion: Styles defined within ShadowDOM, apply only to
dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
this feature.
By default, rules are prefixed with the host element tag name
as a descendant selector. This ensures styling does not leak out of the 'top'
of the element's ShadowDOM. For example,
div {
font-weight: bold;
}
becomes:
x-foo div {
font-weight: bold;
}
becomes:
Alternatively, if Platform.ShadowCSS.strictStyling is set to true then
selectors are scoped by adding an attribute selector suffix to each
simple selector that contains the host element tag name. Each element
in the element's ShadowDOM template is also given the scope attribute.
Thus, these rules match only elements that have the scope attribute.
For example, given a scope name of x-foo, a rule like this:
div {
font-weight: bold;
}
becomes:
div[x-foo] {
font-weight: bold;
}
Note that elements that are dynamically added to a scope must have the scope
selector added to them manually.
* ::pseudo: These rules are converted to rules that take advantage of the
pseudo attribute. For example, a shadowRoot like this inside an x-foo
<div pseudo="x-special">Special</div>
with a rule like this:
x-foo::x-special { ... }
becomes:
x-foo [pseudo=x-special] { ... }
Unaddressed ShadowDOM styling features:
* upper/lower bound encapsulation: Styles which are defined outside a
shadowRoot should not cross the ShadowDOM boundary and should not apply
inside a shadowRoot.
This styling behavior is not emulated. Some possible ways to do this that
were rejected due to complexity and/or performance concerns include: (1) reset
every possible property for every possible selector for a given scope name;
(2) re-implement css in javascript.
As an alternative, users should make sure to use selectors
specific to the scope in which they are working.
* ::distributed: This behavior is not emulated. It's often not necessary
to style the contents of a specific insertion point and instead, descendants
of the host element can be styled selectively. Users can also create an
extra node around an insertion point and style that node's contents
via descendent selectors. For example, with a shadowRoot like this:
<style>
content::-webkit-distributed(div) {
background: red;
}
</style>
<content></content>
could become:
<style>
/ *@polyfill .content-container div * /
content::-webkit-distributed(div) {
background: red;
}
</style>
<div class="content-container">
<content></content>
</div>
Note the use of @polyfill in the comment above a ShadowDOM specific style
declaration. This is a directive to the styling shim to use the selector
in comments in lieu of the next selector when running under polyfill.
*/
(function(scope) {
var ShadowCSS = {
strictStyling: false,
registry: {},
// 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 */
// 4. shim @host and scoping
shimStyling: function(root, name, extendsName) {
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);
}
},
// 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);
},
registerDefinition: function(root, name, extendsName) {
var def = this.registry[name] = {
root: root,
name: name,
extendsName: extendsName
}
var styles = 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);
}
return def;
},
applyScopeToContent: function(root, name) {
if (root) {
// add the name attribute to each node in root.
Array.prototype.forEach.call(root.querySelectorAll('*'),
function(node) {
node.setAttribute(name, '');
});
// and template contents too
Array.prototype.forEach.call(root.querySelectorAll('template'),
function(template) {
this.applyScopeToContent(template.content, name);
},
this);
}
},
/*
* Process styles to convert native ShadowDOM rules that will trip
* up the css parser; we rely on decorating the stylesheet with comments.
*
* For example, we convert this rule:
*
* (comment start) @polyfill @host g-menu-item (comment end)
* shadow::-webkit-distributed(g-menu-item) {
*
* to this:
*
* scopeName g-menu-item {
*
**/
shimPolyfillDirectives: function(styles, name) {
if (styles) {
Array.prototype.forEach.call(styles, function(s) {
s.textContent = this.convertPolyfillDirectives(s.textContent, name);
}, 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;
}
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);
},
// form: @host { .foo { declarations } }
// becomes: scopeName.foo { declarations }
shimAtHost: function(styles, name) {
if (styles) {
return this.convertAtHostStyles(styles, name);
}
},
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));
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;
},
// supports scopig by name and [is=name] syntax
scopeHostSelector: function(selector, name) {
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]
} else if (p.match(hostFixableRe)) {
p = name + p + ', ' + is + p;
}
r.push(p);
}, this);
return r.join(', ');
},
// consider styles that do not include component name in the selector to be
// unscoped and in need of promotion;
// for convenience, also consider keyframe rules this way.
findAtHostRules: function(cssRules, matcher) {
return Array.prototype.filter.call(cssRules,
this.isHostRule.bind(this, matcher));
},
isHostRule: function(matcher, cssRule) {
return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
(cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
(cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
},
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
*
* .foo {... }
*
* and converts this to
*
* scopeName .foo { ... }
*/
shimScoping: function(styles, name) {
if (styles) {
return this.convertScopedStyles(styles, name);
}
},
convertScopedStyles: function(styles, name) {
Array.prototype.forEach.call(styles, function(s) {
if (s.parentNode) {
s.parentNode.removeChild(s);
}
});
var cssText = stylesToCssText(styles).replace(hostRuleRe, '');
cssText = this.convertPseudos(cssText);
var rules = cssToRules(cssText);
cssText = this.scopeRules(rules, name);
return cssText;
},
convertPseudos: function(cssText) {
return cssText.replace(cssPseudoRe, ' [pseudo=$1]');
},
// change a selector like 'div' to 'name div'
scopeRules: function(cssRules, name) {
var cssText = '';
Array.prototype.forEach.call(cssRules, function(rule) {
if (rule.selectorText && (rule.style && rule.style.cssText)) {
cssText += this.scopeSelector(rule.selectorText, name,
this.strictStyling) + ' {\n\t';
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
} else if (rule.media) {
cssText += '@media ' + rule.media.mediaText + ' {\n';
cssText += this.scopeRules(rule.cssRules, name);
cssText += '\n}\n\n';
} else if (rule.cssText) {
cssText += rule.cssText + '\n\n';
}
}, this);
return cssText;
},
scopeSelector: function(selector, name, strict) {
var r = [], parts = selector.split(',');
parts.forEach(function(p) {
p = p.trim();
if (this.selectorNeedsScoping(p, name)) {
p = strict ? this.applyStrictSelectorScope(p, name) :
this.applySimpleSelectorScope(p, name);
}
r.push(p);
}, this);
return r.join(', ');
},
selectorNeedsScoping: function(selector, name) {
var matchScope = '(' + 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;
},
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
applyStrictSelectorScope: function(selector, name) {
var splits = [' ', '>', '+', '~'],
scoped = selector,
attrName = '[' + name + ']';
splits.forEach(function(sep) {
var parts = scoped.split(sep);
scoped = parts.map(function(p) {
var t = p.trim();
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
}
return p;
}).join(sep);
});
return scoped;
},
propertiesFromRule: function(rule) {
var properties = rule.style.cssText;
// TODO(sorvell): Chrome cssom incorrectly removes quotes from the content
// property. (https://code.google.com/p/chromium/issues/detail?id=247231)
if (rule.style.content && !rule.style.content.match(/['"]+/)) {
properties = 'content: \'' + rule.style.content + '\';\n' +
rule.style.cssText.replace(/content:[^;]*;/g, '');
}
return properties;
}
};
var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
selectorRe = /([^{]*)({[\s\S]*?})/gim,
hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/,
hostFixableRe = /^[.\[:]/,
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
cssPseudoRe = /::(x-[^\s{,(]*)/gim,
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
hostRe = /@host/gim;
function stylesToCssText(styles, preserveComments) {
var cssText = '';
Array.prototype.forEach.call(styles, function(s) {
cssText += s.textContent + '\n\n';
});
// strip comments for easier processing
if (!preserveComments) {
cssText = cssText.replace(cssCommentRe, '');
}
return cssText;
}
function cssToRules(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
return rules;
}
function rulesToCss(cssRules) {
for (var i=0, css=[]; i < cssRules.length; i++) {
css.push(cssRules[i].cssText);
}
return css.join('\n\n');
}
function addCssToDocument(cssText) {
if (cssText) {
getSheet().appendChild(document.createTextNode(cssText));
}
}
var sheet;
function getSheet() {
if (!sheet) {
sheet = document.createElement("style");
sheet.setAttribute('ShadowCSSShim', '');
}
return sheet;
}
// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
addCssToDocument('style { display: none !important; }\n');
var head = document.querySelector('head');
head.insertBefore(getSheet(), head.childNodes[0]);
}
// exports
scope.ShadowCSS = ShadowCSS;
})(window.Platform);

View file

@ -0,0 +1,65 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
(function(scope) {
// TODO(terry): Remove shimShadowDOMStyling2 until wrap/unwrap from a
// dart:html Element to a JS DOM node is available.
/**
* Given the content of a STYLE tag and the name of a component shim the CSS
* and return the new scoped CSS to replace the STYLE's content. The content
* is replaced in Dart's implementation of PolymerElement.
*/
function shimShadowDOMStyling2(styleContent, name) {
if (window.ShadowDOMPolyfill) {
var content = this.convertPolyfillDirectives(styleContent, name);
// applyShimming calls shimAtHost and shipScoping
// shimAtHost code:
var r = '', l=0, matches;
while (matches = hostRuleRe.exec(content)) {
r += content.substring(l, matches.index);
r += this.scopeHostCss(matches[1], name);
l = hostRuleRe.lastIndex;
}
r += content.substring(l, content.length);
var re = new RegExp('^' + name + selectorReSuffix, 'm');
var atHostCssText = rulesToCss(this.findAtHostRules(cssToRules(r), re));
// shimScoping code:
// strip comments for easier processing
content = content.replace(cssCommentRe, '');
content = this.convertPseudos(content);
var rules = cssToRules(content);
var cssText = this.scopeRules(rules, name);
return atHostCssText + cssText;
}
}
// Minimal copied code from ShadowCSS, that is not exposed in
// PlatForm.ShadowCSS (local code).
var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
function cssToRules(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
return rules;
}
function rulesToCss(cssRules) {
for (var i=0, css=[]; i < cssRules.length; i++) {
css.push(cssRules[i].cssText);
}
return css.join('\n\n');
}
// exports
scope.ShadowCSS.shimShadowDOMStyling2 = shimShadowDOMStyling2;
})(window.Platform);

View file

@ -0,0 +1,13 @@
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// TODO(jmesserly): fix dart:html to use unprefixed name
if (Element.prototype.webkitCreateShadowRoot) {
Element.prototype.webkitCreateShadowRoot = function() {
return window.ShadowDOMPolyfill.wrapIfNeeded(this).createShadowRoot();
};
}
})();

View file

@ -0,0 +1,59 @@
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
var ShadowDOMPolyfill = window.ShadowDOMPolyfill;
var wrap = ShadowDOMPolyfill.wrap;
// patch in prefixed name
Object.defineProperties(HTMLElement.prototype, {
//TODO(sjmiles): review accessor alias with Arv
webkitShadowRoot: {
get: function() {
return this.shadowRoot;
}
}
});
//TODO(sjmiles): review method alias with Arv
HTMLElement.prototype.webkitCreateShadowRoot =
HTMLElement.prototype.createShadowRoot;
// TODO(jmesserly): we need to wrap document somehow (a dart:html hook?)
window.dartExperimentalFixupGetTag = function(originalGetTag) {
var NodeList = ShadowDOMPolyfill.wrappers.NodeList;
var ShadowRoot = ShadowDOMPolyfill.wrappers.ShadowRoot;
var isWrapper = ShadowDOMPolyfill.isWrapper;
var unwrap = ShadowDOMPolyfill.unwrap;
function getTag(obj) {
if (obj instanceof NodeList) return 'NodeList';
if (obj instanceof ShadowRoot) return 'ShadowRoot';
if (obj instanceof MutationRecord) return 'MutationRecord';
if (obj instanceof MutationObserver) return 'MutationObserver';
if (isWrapper(obj)) {
obj = unwrap(obj);
// Fix up class names for Firefox. For some of them like
// HTMLFormElement and HTMLInputElement, the "constructor" property of
// the unwrapped nodes points at the wrapper for some reason.
// TODO(jmesserly): figure out why this is happening.
var ctor = obj.constructor;
if (ctor && ctor._ShadowDOMPolyfill$isGeneratedWrapper) {
var name = ctor._ShadowDOMPolyfill$cacheTag_;
if (!name) {
name = Object.prototype.toString.call(obj);
name = name.substring(8, name.length - 1);
ctor._ShadowDOMPolyfill$cacheTag_ = name;
}
return name;
}
}
return originalGetTag(obj);
}
return getTag;
};
})();

View file

@ -0,0 +1,5 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
var Platform = {};

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<!--
Copyright 2012 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<meta charset="utf-8">
<link rel="stylesheet" href="../tool/node_modules/mocha/mocha.css">
<script src="../tool/node_modules/chai/chai.js"></script>
<script src="../tool/node_modules/mocha/mocha.js"></script>
<script src="../tools/test/mocha-htmltest.js"></script>
<script src="../tool/shadowdom.js"></script>
<script src="../../../third_party/polymer/ShadowDOM/test/test.main.js"></script>
<div id="mocha"></div>
<script>
mocha.run();
</script>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
Copyright 2012 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<meta charset="utf-8">
<link rel="stylesheet" href="../tool/node_modules/mocha/mocha.css">
<script src="../tool/node_modules/chai/chai.js"></script>
<script src="../tool/node_modules/mocha/mocha.js"></script>
<script src="../tools/test/mocha-htmltest.js"></script>
<script src="../tool/shadowdom.min.js"></script>
<script src="../../../third_party/polymer/ShadowDOM/test/test.main.js"></script>
<div id="mocha"></div>
<script>
mocha.run();
</script>

View file

@ -0,0 +1 @@
} else {

View file

@ -0,0 +1 @@
}

View file

@ -0,0 +1,3 @@
if ((!HTMLElement.prototype.createShadowRoot &&
!HTMLElement.prototype.webkitCreateShadowRoot) ||
window.__forceShadowDomPolyfill) {

View file

@ -0,0 +1,89 @@
// Sample Karma configuration file, that contain pretty much all the available options
// It's used for running client tests on Travis (http://travis-ci.org/#!/karma-runner/karma)
// Most of the options can be overriden by cli arguments (see karma --help)
//
// For all available config options and default values, see:
// https://github.com/karma-runner/karma/blob/stable/lib/config.js#L54
// base path, that will be used to resolve files and exclude
basePath = '../';
// list of files / patterns to load in the browser
files = [
'tools/test/mocha-htmltest.js',
'conf/mocha.conf.js',
'node_modules/chai/chai.js',
'shadowdom.js',
'test/test.main.js',
{pattern: 'src/**/*.js', included: false},
{pattern: 'test/**/*.js', included: false},
{pattern: 'test/**/*.html', included: false},
{pattern: 'tools/**/*.js', included: false}
];
// list of files to exclude
exclude = [];
frameworks = ['mocha'];
// use dots reporter, as travis terminal does not support escaping sequences
// possible values: 'dots', 'progress', 'junit', 'teamcity'
// CLI --reporters progress
reporters = ['progress'];
// web server port
// CLI --port 9876
port = 9876;
// cli runner port
// CLI --runner-port 9100
runnerPort = 9100;
// enable / disable colors in the output (reporters and logs)
// CLI --colors --no-colors
colors = true;
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
// CLI --log-level debug
logLevel = LOG_INFO;
// enable / disable watching file and executing tests whenever any file changes
// CLI --auto-watch --no-auto-watch
autoWatch = true;
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
// CLI --browsers Chrome,Firefox,Safari
browsers = ['ChromeCanary'];
// If browser does not capture in given timeout [ms], kill it
// CLI --capture-timeout 5000
captureTimeout = 50000;
// Auto run tests on start (when browsers are captured) and exit
// CLI --single-run --no-single-run
singleRun = true;
// report which specs are slower than 500ms
// CLI --report-slower-than 500
reportSlowerThan = 500;
// compile coffee scripts
preprocessors = {
};
plugins = [
'karma-mocha',
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-script-launcher',
'karma-crbot-reporter'
]

View file

@ -0,0 +1 @@
mocha.setup({ui:'tdd',htmlbase: '/base/test/'});

View file

@ -0,0 +1,143 @@
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
module.exports = function(grunt) {
ShadowDOMPolyfill = [
'sidetable.js',
'wrappers.js',
'wrappers/events.js',
'wrappers/NodeList.js',
'wrappers/Node.js',
'querySelector.js',
'wrappers/node-interfaces.js',
'wrappers/CharacterData.js',
'wrappers/Element.js',
'wrappers/HTMLElement.js',
'wrappers/HTMLContentElement.js',
'wrappers/HTMLShadowElement.js',
'wrappers/HTMLTemplateElement.js',
'wrappers/HTMLUnknownElement.js',
'wrappers/generic.js',
'wrappers/ShadowRoot.js',
'ShadowRenderer.js',
'wrappers/Document.js',
'wrappers/Window.js',
'wrappers/MutationObserver.js',
'wrappers/override-constructors.js'
];
ShadowDOMPolyfill = ShadowDOMPolyfill.map(function(p) {
return '../../../third_party/polymer/ShadowDOM/src/' + p;
});
// Apply partial patch from Polymer/Platform, dart2js, CSS
// polyfill from platform and dart2js CSS patches:
ShadowDOMPolyfill.unshift(
'../lib/src/platform/patches-shadowdom-polyfill-before.js'
);
ShadowDOMPolyfill.push(
'../lib/src/platform/patches-shadowdom-polyfill.js',
'../lib/src/platform/platform-init.js',
'../lib/src/platform/ShadowCSS.js',
'../lib/src/platform/patches-shadow-css.js'
);
// Only load polyfill if not natively present.
ConditionalShadowDOM = [].concat(
'build/if-poly.js',
ShadowDOMPolyfill,
'build/end-if.js'
);
// karma setup
var browsers;
(function() {
try {
var config = grunt.file.readJSON('local.json');
if (config.browsers) {
browsers = config.browsers;
}
} catch (e) {
var os = require('os');
browsers = ['Chrome', 'Firefox'];
if (os.type() === 'Darwin') {
browsers.push('ChromeCanary');
}
if (os.type() === 'Windows_NT') {
browsers.push('IE');
}
}
})();
grunt.initConfig({
karma: {
options: {
configFile: 'conf/karma.conf.js',
keepalive: true,
browsers: browsers
},
buildbot: {
browsers: browsers,
reporters: ['crbot'],
logLevel: 'OFF'
},
ShadowDOM: {
browsers: browsers
}
},
concat: {
ShadowDOM: {
src: ConditionalShadowDOM,
dest: '../lib/shadow_dom.debug.js',
nonull: true
}
},
uglify: {
ShadowDOM: {
options: {
compress: {
// TODO(sjmiles): should be false by default (?)
// https://github.com/mishoo/UglifyJS2/issues/165
unsafe: false
}
//compress: true, Xmangle: true, beautify: true, unsafe: false
},
files: {
'../lib/shadow_dom.min.js': ['../lib/shadow_dom.debug.js']
}
}
},
yuidoc: {
compile: {
name: '<%= pkg.name %>',
description: '<%= pkg.description %>',
version: '<%= pkg.version %>',
url: '<%= pkg.homepage %>',
options: {
exclude: 'third_party',
paths: '.',
outdir: 'docs',
linkNatives: 'true',
tabtospace: 2,
themedir: '../docs/doc_themes/simple'
}
}
},
pkg: grunt.file.readJSON('package.json')
});
// plugins
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-karma-0.9.1');
// tasks
grunt.registerTask('default', ['concat', 'uglify']);
grunt.registerTask('minify', ['concat', 'uglify']);
grunt.registerTask('docs', ['yuidoc']);
grunt.registerTask('test', ['karma:ShadowDOM']);
grunt.registerTask('test-buildbot', ['karma:buildbot']);
};

View file

@ -0,0 +1,16 @@
{
"name": "ShadowDOM",
"version": "0.0.1",
"devDependencies": {
"mocha": ">=1.9",
"chai": "*",
"grunt": "*",
"grunt-contrib-concat": "*",
"grunt-contrib-uglify": "*",
"grunt-contrib-yuidoc": "~0.4.0",
"grunt-karma-0.9.1": "~0.4.3",
"karma-mocha": "*",
"karma-script-launcher": "*",
"karma-crbot-reporter": "*"
}
}

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<!--
Copyright 2012 The Polymer Authors. All rights reserved.
Use of this source code is goverened by a BSD-style
license that can be found in the LICENSE file.
-->
<meta charset="utf-8">
<script src="shadowdom.js"></script>
<style>
.el,
.sr {
height: 300px;
width: 300px;
}
</style>
<textarea class="el" placeholder="Element host"></textarea>
<textarea class="sr" placeholder="Shadow Root"></textarea>
<button onclick="update()">Update</button>
<div class="test">Real</div>
<script>
var el = document.querySelector('.test');
var sr = el.createShadowRoot();
sr.innerHTML = 'Before <content></content> After';
function update() {
el.innerHTML = document.querySelector('.el').value;
sr.innerHTML = document.querySelector('.sr').value;
}
document.querySelector('.el').value = el.innerHTML;
document.querySelector('.sr').value = sr.innerHTML;
</script>

View file

@ -0,0 +1,18 @@
How to build shadow_dom package:
- Install nodejs and npm
sudo apt-get install nodejs
sudo apt-get install npm
- Install grunt http://gruntjs.com/getting-started
- Change to the shadow_dom tool directory
cd pkg/shadow_dom/tool
- Install project dependencies
npm install
- Run grunt to generate the shadow_dom packages in pkg/shadow_dom/lib
grunt

View file

@ -0,0 +1,44 @@
// Copyright 2013 The Polymer Authors. All rights reserved.
// Use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE file.
(function() {
var thisFile = 'shadowdom.js';
var base = '';
Array.prototype.forEach.call(document.querySelectorAll('script[src]'), function(s) {
var src = s.getAttribute('src');
var re = new RegExp(thisFile + '[^\\\\]*');
var match = src.match(re);
if (match) {
base = src.slice(0, -match[0].length);
}
});
base += '../../../third_party/polymer/ShadowDOM/src/';
[
'sidetable.js',
'wrappers.js',
'wrappers/events.js',
'wrappers/NodeList.js',
'wrappers/Node.js',
'querySelector.js',
'wrappers/node-interfaces.js',
'wrappers/CharacterData.js',
'wrappers/Element.js',
'wrappers/HTMLElement.js',
'wrappers/HTMLContentElement.js',
'wrappers/HTMLShadowElement.js',
'wrappers/HTMLTemplateElement.js',
'wrappers/HTMLUnknownElement.js',
'wrappers/generic.js',
'wrappers/ShadowRoot.js',
'ShadowRenderer.js',
'wrappers/Document.js',
'wrappers/Window.js',
'wrappers/MutationObserver.js',
'wrappers/override-constructors.js'
].forEach(function(src) {
document.write('<script src="' + base + src + '"></script>');
});
})();