Skip to content

Commit 47042bc

Browse files
committed
feature(ShadowDomTransformer): create a compiler step to transform the shadow DOM
1 parent 7bf5ab8 commit 47042bc

15 files changed

+317
-165
lines changed

modules/angular2/src/core/compiler/compiler.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ export class Compiler {
120120
}
121121

122122
_compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> {
123-
this._shadowDomStrategy.processTemplate(template, cmpMetadata);
124123
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
125124
var compileElements = pipeline.process(template);
126125
var protoView = compileElements[0].inheritedProtoView;

modules/angular2/src/core/compiler/pipeline/compile_element.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export class CompileElement {
3939
inheritedElementBinder:ElementBinder;
4040
distanceToParentInjector:number;
4141
compileChildren: boolean;
42+
ignoreBindings: boolean;
43+
4244
constructor(element:Element) {
4345
this.element = element;
4446
this._attrs = null;
@@ -64,6 +66,8 @@ export class CompileElement {
6466
this.inheritedElementBinder = null;
6567
this.distanceToParentInjector = 0;
6668
this.compileChildren = true;
69+
// set to true to ignore all the bindings on the element
70+
this.ignoreBindings = false;
6771
}
6872

6973
refreshAttrs() {

modules/angular2/src/core/compiler/pipeline/default_steps.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {ChangeDetection, Parser} from 'angular2/change_detection';
2-
import {List} from 'angular2/src/facade/collection';
2+
import {List, ListWrapper} from 'angular2/src/facade/collection';
33

44
import {PropertyBindingParser} from './property_binding_parser';
55
import {TextInterpolationParser} from './text_interpolation_parser';
@@ -9,9 +9,11 @@ import {ElementBindingMarker} from './element_binding_marker';
99
import {ProtoViewBuilder} from './proto_view_builder';
1010
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
1111
import {ElementBinderBuilder} from './element_binder_builder';
12+
import {ShadowDomTransformer} from './shadow_dom_transformer';
1213
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
13-
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
14+
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
1415
import {stringify} from 'angular2/src/facade/lang';
16+
import {DOM} from 'angular2/src/facade/dom';
1517

1618
/**
1719
* Default steps used for compiling a template.
@@ -27,14 +29,22 @@ export function createDefaultSteps(
2729

2830
var compilationUnit = stringify(compiledComponent.type);
2931

30-
return [
31-
new ViewSplitter(parser, compilationUnit),
32+
var steps = [new ViewSplitter(parser, compilationUnit)];
33+
34+
if (!(shadowDomStrategy instanceof NativeShadowDomStrategy)) {
35+
var step = new ShadowDomTransformer(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
36+
ListWrapper.push(steps, step);
37+
}
38+
39+
steps = ListWrapper.concat(steps,[
3240
new PropertyBindingParser(parser, compilationUnit),
3341
new DirectiveParser(directives),
3442
new TextInterpolationParser(parser, compilationUnit),
3543
new ElementBindingMarker(),
3644
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
3745
new ProtoElementInjectorBuilder(),
3846
new ElementBinderBuilder(parser, compilationUnit)
39-
];
47+
]);
48+
49+
return steps;
4050
}

modules/angular2/src/core/compiler/pipeline/element_binding_marker.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const NG_BINDING_CLASS = 'ng-binding';
2626
*/
2727
export class ElementBindingMarker extends CompileStep {
2828
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
29+
if (current.ignoreBindings) {
30+
return;
31+
}
32+
2933
var hasBindings =
3034
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
3135
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||

modules/angular2/src/core/compiler/pipeline/property_binding_parser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export class PropertyBindingParser extends CompileStep {
3838
}
3939

4040
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
41+
if (current.ignoreBindings) {
42+
return;
43+
}
44+
4145
var attrs = current.attrs();
4246
MapWrapper.forEach(attrs, (attrValue, attrName) => {
4347
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {CompileStep} from './compile_step';
2+
import {CompileElement} from './compile_element';
3+
import {CompileControl} from './compile_control';
4+
5+
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
6+
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
7+
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
8+
9+
import {DOM, Element} from 'angular2/src/facade/dom';
10+
import {isPresent, isBlank} from 'angular2/src/facade/lang';
11+
import {StringMapWrapper} from 'angular2/src/facade/collection';
12+
13+
var _cssCache = StringMapWrapper.create();
14+
15+
export class ShadowDomTransformer extends CompileStep {
16+
_selector: string;
17+
_strategy: ShadowDomStrategy;
18+
_styleHost: Element;
19+
_lastInsertedStyle: Element;
20+
21+
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) {
22+
super();
23+
this._strategy = strategy;
24+
this._selector = cmpMetadata.annotation.selector;
25+
this._styleHost = styleHost;
26+
this._lastInsertedStyle = null;
27+
}
28+
29+
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
30+
// May be remove the styles
31+
if (DOM.tagName(current.element) == 'STYLE') {
32+
current.ignoreBindings = true;
33+
if (this._strategy.extractStyles()) {
34+
DOM.remove(current.element);
35+
var css = DOM.getText(current.element);
36+
if (this._strategy.shim()) {
37+
// The css generated here is unique for the component (because of the shim).
38+
// Then we do not need to cache it.
39+
css = shimCssText(css, this._selector);
40+
this._insertStyle(this._styleHost, css);
41+
} else {
42+
var seen = isPresent(StringMapWrapper.get(_cssCache, css));
43+
if (!seen) {
44+
StringMapWrapper.set(_cssCache, css, true);
45+
this._insertStyle(this._styleHost, css);
46+
}
47+
}
48+
}
49+
} else {
50+
if (this._strategy.shim()) {
51+
try {
52+
DOM.setAttribute(current.element, this._selector, '');
53+
} catch(e) {
54+
// TODO(vicb): for now only simple selector (tag name) are supported
55+
}
56+
}
57+
}
58+
}
59+
60+
clearCache() {
61+
_cssCache = StringMapWrapper.create();
62+
}
63+
64+
_insertStyle(el: Element, css: string) {
65+
var style = DOM.createStyleElement(css);
66+
if (isBlank(this._lastInsertedStyle)) {
67+
var firstChild = DOM.firstChild(el);
68+
if (isPresent(firstChild)) {
69+
DOM.insertBefore(firstChild, style);
70+
} else {
71+
DOM.appendChild(el, style);
72+
}
73+
} else {
74+
DOM.insertAfter(this._lastInsertedStyle, style);
75+
}
76+
this._lastInsertedStyle = style;
77+
}
78+
}
79+

modules/angular2/src/core/compiler/pipeline/text_interpolation_parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class TextInterpolationParser extends CompileStep {
2323
}
2424

2525
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
26-
if (!current.compileChildren) {
26+
if (!current.compileChildren || current.ignoreBindings) {
2727
return;
2828
}
2929
var element = current.element;
Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
2-
import {DOM, Element, StyleElement} from 'angular2/src/facade/dom';
2+
import {DOM, Element} from 'angular2/src/facade/dom';
33
import {List, ListWrapper} from 'angular2/src/facade/collection';
44
import {View} from './view';
55
import {Content} from './shadow_dom_emulation/content_tag';
66
import {LightDom} from './shadow_dom_emulation/light_dom';
77
import {DirectiveMetadata} from './directive_metadata';
8-
import {shimCssText} from './shadow_dom_emulation/shim_css';
98

109
export class ShadowDomStrategy {
1110
attachTemplate(el:Element, view:View){}
1211
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
1312
polyfillDirectives():List<Type>{ return null; }
14-
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { return null; }
13+
shim(): boolean { return false; }
14+
extractStyles(): boolean { return false; }
1515
}
1616

1717
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
18-
_styleHost: Element;
19-
20-
constructor(styleHost: Element = null) {
18+
constructor() {
2119
super();
22-
if (isBlank(styleHost)) {
23-
styleHost = DOM.defaultDoc().head;
24-
}
25-
this._styleHost = styleHost;
2620
}
2721

2822
attachTemplate(el:Element, view:View){
@@ -38,25 +32,20 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
3832
return [Content];
3933
}
4034

41-
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
42-
var templateRoot = DOM.templateAwareRoot(template);
43-
var attrName = cmpMetadata.annotation.selector;
44-
45-
// Shim CSS for emulated shadow DOM and attach the styles do the document head
46-
var styles = _detachStyles(templateRoot);
47-
for (var i = 0; i < styles.length; i++) {
48-
var style = styles[i];
49-
var processedCss = shimCssText(DOM.getText(style), attrName);
50-
DOM.setText(style, processedCss);
51-
}
52-
_attachStyles(this._styleHost, styles);
35+
shim(): boolean {
36+
return true;
37+
}
5338

54-
// Update the DOM to trigger the CSS
55-
_addAttributeToChildren(templateRoot, attrName);
39+
extractStyles(): boolean {
40+
return true;
5641
}
5742
}
5843

5944
export class NativeShadowDomStrategy extends ShadowDomStrategy {
45+
constructor() {
46+
super();
47+
}
48+
6049
attachTemplate(el:Element, view:View){
6150
moveViewNodesIntoParent(el.createShadowRoot(), view);
6251
}
@@ -69,8 +58,12 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
6958
return [];
7059
}
7160

72-
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
73-
return template;
61+
shim(): boolean {
62+
return false;
63+
}
64+
65+
extractStyles(): boolean {
66+
return false;
7467
}
7568
}
7669

@@ -79,38 +72,3 @@ function moveViewNodesIntoParent(parent, view) {
7972
DOM.appendChild(parent, view.nodes[i]);
8073
}
8174
}
82-
83-
// TODO(vicb): union types: el is an Element or a Document Fragment
84-
function _detachStyles(el): List<StyleElement> {
85-
var nodeList = DOM.querySelectorAll(el, 'style');
86-
var styles = [];
87-
for (var i = 0; i < nodeList.length; i++) {
88-
var style = DOM.remove(nodeList[i]);
89-
ListWrapper.push(styles, style);
90-
}
91-
return styles;
92-
}
93-
94-
// Move the styles as the first children of the template
95-
function _attachStyles(el: Element, styles: List<StyleElement>) {
96-
var firstChild = DOM.firstChild(el);
97-
for (var i = styles.length - 1; i >= 0; i--) {
98-
var style = styles[i];
99-
if (isPresent(firstChild)) {
100-
DOM.insertBefore(firstChild, style);
101-
} else {
102-
DOM.appendChild(el, style);
103-
}
104-
firstChild = style;
105-
}
106-
}
107-
108-
// TODO(vicb): union types: el is an Element or a Document Fragment
109-
function _addAttributeToChildren(el, attrName:string) {
110-
// TODO(vicb): currently the code crashes when the attrName is not an el selector
111-
var children = DOM.querySelectorAll(el, "*");
112-
for (var i = 0; i < children.length; i++) {
113-
var child = children[i];
114-
DOM.setAttribute(child, attrName, '');
115-
}
116-
}

modules/angular2/src/facade/dom.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ class DOM {
103103
el.setAttribute(attrName, attrValue);
104104
return el;
105105
}
106+
static StyleElement createStyleElement(String css, [HtmlDocument doc = null]) {
107+
if (doc == null) doc = document;
108+
var el = doc.createElement("STYLE");
109+
el.text = css;
110+
return el;
111+
}
106112
static clone(Node node) => node.clone(true);
107113
static bool hasProperty(Element element, String name) =>
108114
new JsObject.fromBrowserObject(element).hasProperty(name);

modules/angular2/test/core/compiler/compiler_spec.js

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,6 @@ export function main() {
7070
});
7171
});
7272

73-
it('should use the shadow dom strategy to process the template', (done) => {
74-
// TODO(vicb) test in Dart when the bug is fixed
75-
// https://code.google.com/p/dart/issues/detail?id=18249
76-
if (IS_DARTIUM) {
77-
done();
78-
return;
79-
}
80-
var templateHtml = 'processed template';
81-
var compiler = createCompiler((parent, current, control) => {
82-
current.inheritedProtoView = new ProtoView(current.element, null, null);
83-
}, new FakeShadowDomStrategy(templateHtml));
84-
compiler.compile(MainComponent, null).then( (protoView) => {
85-
expect(DOM.getInnerHTML(protoView.element)).toEqual('processed template');
86-
done();
87-
});
88-
});
89-
9073
it('should load nested components', (done) => {
9174
var mainEl = el('<div></div>');
9275
var compiler = createCompiler( (parent, current, control) => {
@@ -244,15 +227,3 @@ class MockStep extends CompileStep {
244227
this.processClosure(parent, current, control);
245228
}
246229
}
247-
248-
class FakeShadowDomStrategy extends NativeShadowDomStrategy {
249-
templateHtml: string;
250-
constructor(templateHtml: string) {
251-
super();
252-
this.templateHtml = templateHtml;
253-
}
254-
255-
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
256-
DOM.setInnerHTML(template, this.templateHtml);
257-
}
258-
}

0 commit comments

Comments
 (0)