Skip to content

Commit 821af77

Browse files
committed
feat(components): implement css encapsulation for transcluding components
* Extract ComponentCssLoader so it can be shared by ShadowDomComponentFactory and TranscludingComponentFactory * Implement a CSS transformer similar to the one provided by Platform.JS * Rename EventHandler into ShadowBoundary * Provide a built-in implementation of WebPlatformShim Closes dart-archive#1300
1 parent f56a272 commit 821af77

35 files changed

+1155
-632
lines changed

lib/core/module.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export "package:angular/core_dom/module_internal.dart" show
3939
BoundViewFactory,
4040
DirectiveMap,
4141
ElementProbe,
42-
EventHandler,
42+
ShadowBoundary,
4343
Http,
4444
HttpBackend,
4545
HttpConfig,
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
part of angular.core.dom_internal;
2+
3+
class ComponentCssLoader {
4+
final Http http;
5+
final TemplateCache templateCache;
6+
final WebPlatformShim platformShim;
7+
final ComponentCssRewriter componentCssRewriter;
8+
final dom.NodeTreeSanitizer treeSanitizer;
9+
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>> styleElementCache;
10+
11+
ComponentCssLoader(this.http, this.templateCache, this.platformShim,
12+
this.componentCssRewriter, this.treeSanitizer,
13+
this.styleElementCache);
14+
15+
async.Future<List<dom.StyleElement>> call(String tag, List<String> cssUrls) =>
16+
async.Future.wait(cssUrls.map((url) => _styleElement(tag, url)));
17+
18+
async.Future<dom.StyleElement> _styleElement(String tag, String cssUrl) {
19+
final element = styleElementCache.putIfAbsent(
20+
new _ComponentAssetKey(tag, cssUrl),
21+
() => _loadNewCss(tag, cssUrl));
22+
return element.then((e) => e.clone(true));
23+
}
24+
25+
async.Future _loadNewCss(String tag, String cssUrl) {
26+
return _fetch(cssUrl)
27+
.then((css) => _shim(css, tag, cssUrl))
28+
.then(_buildStyleElement);
29+
}
30+
31+
async.Future<String> _fetch(String cssUrl) {
32+
return http.get(cssUrl, cache: templateCache)
33+
.then((resp) => resp.responseText, onError: (e) => '/*\n$e\n*/\n');
34+
}
35+
36+
String _shim(String css, String tag, String cssUrl) {
37+
final shimmed = platformShim.shimCss(css, selector: tag, cssUrl: cssUrl);
38+
return componentCssRewriter(shimmed, selector: tag, cssUrl: cssUrl);
39+
}
40+
41+
dom.StyleElement _buildStyleElement(String css) {
42+
var styleElement = new dom.StyleElement()..appendText(css);
43+
treeSanitizer.sanitizeTree(styleElement);
44+
return styleElement;
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
part of angular.core.dom_internal;
2+
3+
@Injectable()
4+
class ComponentTypeIdGenerator {
5+
final Map<Type, String> _uniqIds = {};
6+
int _index = 0;
7+
8+
idFor(DirectiveRef ref) =>
9+
_uniqIds.putIfAbsent(ref.type, () => "${ref.type}-${_index++}");
10+
}

lib/core_dom/css_shim.dart

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
library css_shim;
2+
3+
import 'package:angular/core/parser/characters.dart';
4+
5+
String shimCssText(String css, String uniqId) {
6+
final res = new StringBuffer();
7+
8+
final tokens = new _Lexer(css).parse();
9+
for(var i = 0; i < tokens.length; i += 2) {
10+
if (tokens.length == i + 1 ||
11+
! tokens[i].isSelector ||
12+
! tokens[i + 1].isBody
13+
) {
14+
throw new InvalidCss();
15+
}
16+
17+
res.write("${tokens[i].string}[${uniqId}] ${tokens[i + 1].string}");
18+
}
19+
return res.toString();
20+
}
21+
22+
class InvalidCss implements Exception {}
23+
24+
class _Token {
25+
static final _Token EOF = new _Token(null);
26+
27+
final String string;
28+
_Token(this.string);
29+
30+
bool get isSelector => false;
31+
bool get isBody => false;
32+
33+
String toString() => "TOKEN: $string";
34+
}
35+
36+
class _SelectorToken extends _Token {
37+
_SelectorToken(str) : super(str);
38+
bool get isSelector => true;
39+
}
40+
41+
class _BodyToken extends _Token {
42+
_BodyToken(str) : super(str);
43+
bool get isBody => true;
44+
}
45+
46+
class _Lexer {
47+
int peek = 0;
48+
int index = -1;
49+
String input;
50+
int length;
51+
52+
_Lexer(this.input) {
53+
length = input.length;
54+
advance();
55+
}
56+
57+
List<_Token> parse() {
58+
final res = [];
59+
var t = scanToken();
60+
while (t != _Token.EOF) {
61+
res.add(t);
62+
t = scanToken();
63+
}
64+
return res;
65+
}
66+
67+
_Token scanToken() {
68+
skipWhitespace();
69+
70+
if (peek == $EOF) return _Token.EOF;
71+
if (isSelector(peek)) return scanSelector();
72+
if (isBodyStart(peek)) return scanBody();
73+
74+
String character = new String.fromCharCode(peek);
75+
throw new InvalidCss();
76+
}
77+
78+
bool isSelector(int v) => !isWhitespace(v) && !isBodyStart(v) && v != $EOF;
79+
bool isBodyStart(int v) => v == $LBRACE;
80+
bool isBodyEnd(int v) => v == $RBRACE;
81+
82+
void skipWhitespace() {
83+
while (isWhitespace(peek)) {
84+
if (++index >= length) {
85+
peek = $EOF;
86+
return null;
87+
} else {
88+
peek = input.codeUnitAt(index);
89+
}
90+
}
91+
}
92+
93+
_Token scanSelector() {
94+
int start = index;
95+
advance();
96+
while (isSelector(peek)) advance();
97+
String string = input.substring(start, index);
98+
return new _SelectorToken(string);
99+
}
100+
101+
_Token scanBody() {
102+
int start = index;
103+
advance();
104+
while (!isBodyEnd(peek)) advance();
105+
advance();
106+
String string = input.substring(start, index);
107+
return new _BodyToken(string);
108+
}
109+
110+
void advance() {
111+
peek = ++index >= length ? $EOF : input.codeUnitAt(index);
112+
}
113+
}

lib/core_dom/directive_injector.dart

+15-15
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:angular/core/module.dart' show Scope, RootScope;
1414
import 'package:angular/core/annotation.dart' show Visibility, DirectiveBinder;
1515
import 'package:angular/core_dom/module_internal.dart'
1616
show Animate, View, ViewFactory, BoundViewFactory, ViewPort, NodeAttrs, ElementProbe,
17-
NgElement, ContentPort, TemplateLoader, ShadowRootEventHandler, EventHandler;
17+
NgElement, ContentPort, TemplateLoader, ShadowRootBoundary, ShadowBoundary;
1818

1919
var _TAG_GET = new UserTag('DirectiveInjector.get()');
2020
var _TAG_INSTANTIATE = new UserTag('DirectiveInjector.instantiate()');
@@ -49,7 +49,7 @@ const int ELEMENT_PROBE_KEY_ID = 13;
4949
const int TEMPLATE_LOADER_KEY_ID = 14;
5050
const int SHADOW_ROOT_KEY_ID = 15;
5151
const int CONTENT_PORT_KEY_ID = 16;
52-
const int EVENT_HANDLER_KEY_ID = 17;
52+
const int SHADOW_BOUNDARY_KEY_ID = 17;
5353
const int KEEP_ME_LAST = 18;
5454

5555
class DirectiveInjector implements DirectiveBinder {
@@ -72,7 +72,7 @@ class DirectiveInjector implements DirectiveBinder {
7272
TEMPLATE_LOADER_KEY.uid = TEMPLATE_LOADER_KEY_ID;
7373
SHADOW_ROOT_KEY.uid = SHADOW_ROOT_KEY_ID;
7474
CONTENT_PORT_KEY.uid = CONTENT_PORT_KEY_ID;
75-
EVENT_HANDLER_KEY.uid = EVENT_HANDLER_KEY_ID;
75+
SHADOW_BOUNDARY_KEY.uid = SHADOW_BOUNDARY_KEY_ID;
7676
ANIMATE_KEY.uid = ANIMATE_KEY_ID;
7777
for(var i = 1; i < KEEP_ME_LAST; i++) {
7878
if (_KEYS[i].uid != i) throw 'MISSORDERED KEYS ARRAY: ${_KEYS} at $i';
@@ -96,7 +96,7 @@ class DirectiveInjector implements DirectiveBinder {
9696
, TEMPLATE_LOADER_KEY
9797
, SHADOW_ROOT_KEY
9898
, CONTENT_PORT_KEY
99-
, EVENT_HANDLER_KEY
99+
, SHADOW_BOUNDARY_KEY
100100
, KEEP_ME_LAST
101101
];
102102

@@ -105,7 +105,7 @@ class DirectiveInjector implements DirectiveBinder {
105105
final Node _node;
106106
final NodeAttrs _nodeAttrs;
107107
final Animate _animate;
108-
final EventHandler _eventHandler;
108+
final ShadowBoundary _shadowBoundary;
109109
Scope scope; //TODO(misko): this should be final after we get rid of controller
110110

111111
NgElement _ngElement;
@@ -140,15 +140,15 @@ class DirectiveInjector implements DirectiveBinder {
140140

141141
static Binding _tempBinding = new Binding();
142142

143-
DirectiveInjector(parent, appInjector, this._node, this._nodeAttrs, this._eventHandler,
143+
DirectiveInjector(parent, appInjector, this._node, this._nodeAttrs, this._shadowBoundary,
144144
this.scope, this._animate)
145145
: appInjector = appInjector,
146146
parent = parent == null ? new DefaultDirectiveInjector(appInjector) : parent;
147147

148148
DirectiveInjector._default(this.parent, this.appInjector)
149149
: _node = null,
150150
_nodeAttrs = null,
151-
_eventHandler = null,
151+
_shadowBoundary = null,
152152
scope = null,
153153
_animate = null;
154154

@@ -264,7 +264,7 @@ class DirectiveInjector implements DirectiveBinder {
264264
case SCOPE_KEY_ID: return scope;
265265
case ELEMENT_PROBE_KEY_ID: return elementProbe;
266266
case NG_ELEMENT_KEY_ID: return ngElement;
267-
case EVENT_HANDLER_KEY_ID: return _eventHandler;
267+
case SHADOW_BOUNDARY_KEY_ID: return _shadowBoundary;
268268
case CONTENT_PORT_KEY_ID: return parent._getById(keyId);
269269
default: new NoProviderError(_KEYS[keyId]);
270270
}
@@ -344,9 +344,9 @@ class TemplateDirectiveInjector extends DirectiveInjector {
344344
BoundViewFactory _boundViewFactory;
345345

346346
TemplateDirectiveInjector(DirectiveInjector parent, Injector appInjector,
347-
Node node, NodeAttrs nodeAttrs, EventHandler eventHandler,
347+
Node node, NodeAttrs nodeAttrs, ShadowBoundary shadowBoundary,
348348
Scope scope, Animate animate, this._viewFactory)
349-
: super(parent, appInjector, node, nodeAttrs, eventHandler, scope, animate);
349+
: super(parent, appInjector, node, nodeAttrs, shadowBoundary, scope, animate);
350350

351351

352352
Object _getById(int keyId) {
@@ -368,9 +368,9 @@ abstract class ComponentDirectiveInjector extends DirectiveInjector {
368368
final ShadowRoot _shadowRoot;
369369

370370
ComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector,
371-
EventHandler eventHandler, Scope scope,
371+
ShadowBoundary shadowBoundary, Scope scope,
372372
this._templateLoader, this._shadowRoot)
373-
: super(parent, appInjector, parent._node, parent._nodeAttrs, eventHandler, scope,
373+
: super(parent, appInjector, parent._node, parent._nodeAttrs, shadowBoundary, scope,
374374
parent._animate);
375375

376376
Object _getById(int keyId) {
@@ -389,9 +389,9 @@ class ShadowlessComponentDirectiveInjector extends ComponentDirectiveInjector {
389389
final ContentPort _contentPort;
390390

391391
ShadowlessComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector,
392-
EventHandler eventHandler, Scope scope,
392+
ShadowBoundary shadowBoundary, Scope scope,
393393
templateLoader, shadowRoot, this._contentPort)
394-
: super(parent, appInjector, eventHandler, scope, templateLoader, shadowRoot);
394+
: super(parent, appInjector, shadowBoundary, scope, templateLoader, shadowRoot);
395395

396396
Object _getById(int keyId) {
397397
switch(keyId) {
@@ -404,7 +404,7 @@ class ShadowlessComponentDirectiveInjector extends ComponentDirectiveInjector {
404404
class ShadowDomComponentDirectiveInjector extends ComponentDirectiveInjector {
405405
ShadowDomComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector,
406406
Scope scope, templateLoader, shadowRoot)
407-
: super(parent, appInjector, new ShadowRootEventHandler(shadowRoot,
407+
: super(parent, appInjector, new ShadowRootBoundary(shadowRoot,
408408
parent.getByKey(EXPANDO_KEY),
409409
parent.getByKey(EXCEPTION_HANDLER_KEY)),
410410
scope, templateLoader, shadowRoot);

lib/core_dom/element_binder.dart

+4-4
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class ElementBinder {
243243

244244
DirectiveInjector bind(View view, Scope scope,
245245
DirectiveInjector parentInjector,
246-
dom.Node node, EventHandler eventHandler, Animate animate) {
246+
dom.Node node, ShadowBoundary shadowBoundary, Animate animate) {
247247
var nodeAttrs = node is dom.Element ? new NodeAttrs(node) : null;
248248

249249
var directiveRefs = _usableDirectiveRefs;
@@ -252,11 +252,11 @@ class ElementBinder {
252252
DirectiveInjector nodeInjector;
253253
if (this is TemplateElementBinder) {
254254
nodeInjector = new TemplateDirectiveInjector(parentInjector, parentInjector.appInjector,
255-
node, nodeAttrs, eventHandler, scope, animate,
255+
node, nodeAttrs, shadowBoundary, scope, animate,
256256
(this as TemplateElementBinder).templateViewFactory);
257257
} else {
258258
nodeInjector = new DirectiveInjector(parentInjector, parentInjector.appInjector,
259-
node, nodeAttrs, eventHandler, scope, animate);
259+
node, nodeAttrs, shadowBoundary, scope, animate);
260260
}
261261

262262
for(var i = 0; i < directiveRefs.length; i++) {
@@ -306,7 +306,7 @@ class ElementBinder {
306306

307307
if (onEvents.isNotEmpty) {
308308
onEvents.forEach((event, value) {
309-
view.registerEvent(EventHandler.attrNameToEventName(event));
309+
view.registerEvent(ShadowBoundary.attrNameToEventName(event));
310310
});
311311
}
312312
return nodeInjector;

0 commit comments

Comments
 (0)