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

Shadowless DOM #936

Closed
wants to merge 2 commits 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
10 changes: 9 additions & 1 deletion lib/core/annotation_src.dart
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ class Component extends Directive {
@deprecated
final String publishAs;

/**
* If set to true, this component will always use shadow DOM.
* If set to false, this component will never use shadow DOM.
* If unset, the compiler's default construction strategy will be used
*/
final bool useShadowDom;

const Component({
this.template,
this.templateUrl,
Expand All @@ -320,7 +327,8 @@ class Component extends Directive {
selector,
visibility,
exportExpressions,
exportExpressionAttrs})
exportExpressionAttrs,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should you update _cloneWithNewMap below ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I wrote a test for this in 0c797cc

Copy link
Contributor

Choose a reason for hiding this comment

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

One minor thing is also to update class level docs "Angular components use shadow-DOM for rendering their templates"

this.useShadowDom})
: _cssUrls = cssUrl,
_applyAuthorStyles = applyAuthorStyles,
_resetStyleInheritance = resetStyleInheritance,
Expand Down
29 changes: 25 additions & 4 deletions lib/core_dom/element_binder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ class TemplateElementBinder extends ElementBinder {
return _directiveCache = [template];
}

TemplateElementBinder(_perf, _expando, _parser, _componentFactory, this.template, this.templateBinder,
TemplateElementBinder(perf, expando, parser, componentFactory,
transcludingComponentFactory, shadowDomComponentFactory,
this.template, this.templateBinder,
onEvents, bindAttrs, childMode)
: super(_perf, _expando, _parser, _componentFactory, null, null, onEvents, bindAttrs, childMode);
: super(perf, expando, parser, componentFactory,
transcludingComponentFactory, shadowDomComponentFactory,
null, null, onEvents, bindAttrs, childMode);

String toString() => "[TemplateElementBinder template:$template]";

Expand All @@ -41,7 +45,11 @@ class ElementBinder {
final Profiler _perf;
final Expando _expando;
final Parser _parser;

// The default component factory
final ComponentFactory _componentFactory;
final TranscludingComponentFactory _transcludingComponentFactory;
final ShadowDomComponentFactory _shadowDomComponentFactory;
final Map onEvents;
final Map bindAttrs;

Expand All @@ -53,7 +61,11 @@ class ElementBinder {
// Can be either COMPILE_CHILDREN or IGNORE_CHILDREN
final String childMode;

ElementBinder(this._perf, this._expando, this._parser, this._componentFactory, this.component, this.decorators,
ElementBinder(this._perf, this._expando, this._parser,
this._componentFactory,
this._transcludingComponentFactory,
this._shadowDomComponentFactory,
this.component, this.decorators,
this.onEvents, this.bindAttrs, this.childMode);

final bool hasTemplate = false;
Expand Down Expand Up @@ -215,7 +227,16 @@ class ElementBinder {
}
nodesAttrsDirectives.add(ref);
} else if (ref.annotation is Component) {
nodeModule.factory(ref.type, _componentFactory.call(node, ref), visibility: visibility);
var factory;
var annotation = ref.annotation as Component;
if (annotation.useShadowDom == true) {
factory = _shadowDomComponentFactory;
} else if (annotation.useShadowDom == false) {
factory = _transcludingComponentFactory;
} else {
factory = _componentFactory;
}
nodeModule.factory(ref.type, factory.call(node, ref), visibility: visibility);
} else {
nodeModule.type(ref.type, visibility: visibility);
}
Expand Down
7 changes: 6 additions & 1 deletion lib/core_dom/element_binder_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ class ElementBinderFactory {
final Profiler _perf;
final Expando _expando;
final ComponentFactory _componentFactory;
final TranscludingComponentFactory _transcludingComponentFactory;
final ShadowDomComponentFactory _shadowDomComponentFactory;

ElementBinderFactory(this._parser, this._perf, this._expando, this._componentFactory);
ElementBinderFactory(this._parser, this._perf, this._expando, this._componentFactory,
this._transcludingComponentFactory, this._shadowDomComponentFactory);

// TODO: Optimize this to re-use a builder.
ElementBinderBuilder builder() => new ElementBinderBuilder(this);

ElementBinder binder(ElementBinderBuilder b) =>
new ElementBinder(_perf, _expando, _parser, _componentFactory,
_transcludingComponentFactory, _shadowDomComponentFactory,
b.component, b.decorators, b.onEvents, b.bindAttrs, b.childMode);
TemplateElementBinder templateBinder(ElementBinderBuilder b, ElementBinder transclude) =>
new TemplateElementBinder(_perf, _expando, _parser, _componentFactory,
_transcludingComponentFactory, _shadowDomComponentFactory,
b.template, transclude, b.onEvents, b.bindAttrs, b.childMode);
}

Expand Down
8 changes: 8 additions & 0 deletions lib/core_dom/module_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ part 'mustache.dart';
part 'node_cursor.dart';
part 'selector.dart';
part 'shadow_dom_component_factory.dart';
part 'shadowless_shadow_root.dart';
part 'tagging_compiler.dart';
part 'tagging_view_factory.dart';
part 'template_cache.dart';
part 'transcluding_component_factory.dart';
part 'tree_sanitizer.dart';
part 'walking_compiler.dart';
part 'ng_element.dart';
Expand All @@ -53,7 +55,13 @@ class CoreDomModule extends Module {
type(AttrMustache);

type(Compiler, implementedBy: TaggingCompiler);

type(ComponentFactory, implementedBy: ShadowDomComponentFactory);
type(ShadowDomComponentFactory);
type(TranscludingComponentFactory);
type(_Content);
value(_ContentPort, null);

type(Http);
type(UrlRewriter);
type(HttpBackend);
Expand Down
38 changes: 22 additions & 16 deletions lib/core_dom/shadow_dom_component_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ part of angular.core.dom_internal;

abstract class ComponentFactory {
FactoryFn call(dom.Node node, DirectiveRef ref);

static _viewFuture(Component component, ViewCache viewCache, DirectiveMap directives) {
Copy link
Contributor

Choose a reason for hiding this comment

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

return type ? (Future)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

if (component.template != null) {
return new async.Future.value(viewCache.fromHtml(component.template, directives));
} else if (component.templateUrl != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

else is not required here

return viewCache.fromUrl(component.templateUrl, directives);
}
return null;
}

static TemplateLoader _setupOnShadowDomAttach(controller, templateLoader, shadowScope) {
if (controller is ShadowRootAware) {
templateLoader.template.then((shadowDom) {
if (!shadowScope.isAttached) return;
(controller as ShadowRootAware).onShadowRoot(shadowDom);
});
}
}
}

class ShadowDomComponentFactory implements ComponentFactory {
Expand All @@ -12,7 +30,6 @@ class ShadowDomComponentFactory implements ComponentFactory {
FactoryFn call(dom.Node node, DirectiveRef ref) {
return (Injector injector) {
var component = ref.annotation as Component;
Compiler compiler = injector.get(Compiler);
Scope scope = injector.get(Scope);
ViewCache viewCache = injector.get(ViewCache);
Http http = injector.get(Http);
Expand Down Expand Up @@ -78,13 +95,7 @@ class _ComponentFactory implements Function {
} else {
cssFutures.add(new async.Future.value(null));
}
var viewFuture;
if (component.template != null) {
viewFuture = new async.Future.value(viewCache.fromHtml(
component.template, directives));
} else if (component.templateUrl != null) {
viewFuture = viewCache.fromUrl(component.templateUrl, directives);
}
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);
TemplateLoader templateLoader = new TemplateLoader(
async.Future.wait(cssFutures).then((Iterable<String> cssList) {
if (cssList != null) {
Expand All @@ -98,19 +109,14 @@ class _ComponentFactory implements Function {
if (viewFuture != null) {
return viewFuture.then((ViewFactory viewFactory) {
return (!shadowScope.isAttached) ?
shadowDom :
attachViewToShadowDom(viewFactory);
shadowDom :
attachViewToShadowDom(viewFactory);
});
}
return shadowDom;
}));
controller = createShadowInjector(injector, templateLoader).get(type);
if (controller is ShadowRootAware) {
templateLoader.template.then((_) {
if (!shadowScope.isAttached) return;
(controller as ShadowRootAware).onShadowRoot(shadowDom);
});
}
ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
return controller;
}

Expand Down
12 changes: 12 additions & 0 deletions lib/core_dom/shadowless_shadow_root.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
part of angular.core.dom_internal;

@proxy
class ShadowlessShadowRoot implements dom.ShadowRoot {
Copy link
Contributor

Choose a reason for hiding this comment

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

ShadowRoot extends DocumentFragment and doesn't add too much more- perhaps just use DocumentFragment instead?

Alternatively this may be a good case for a custom element.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used in onShadowRoot, which needs to be renamed.

We'll rework this later.

dom.Element _element;

ShadowlessShadowRoot(this._element);

noSuchMethod(Invocation invocation) {
throw new UnimplementedError("Not yet implemented in ShadowlessShadowRoot.");
}
}
122 changes: 122 additions & 0 deletions lib/core_dom/transcluding_component_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
part of angular.core.dom_internal;

@Decorator(
selector: 'content'
Copy link
Contributor

Choose a reason for hiding this comment

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

4ws, ")" on the same line - same remarks further below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

below where?

)
class _Content implements AttachAware, DetachAware {
final _ContentPort _port;
final dom.Element _element;
dom.Comment _beginComment;
_Content(this._port, this._element);

attach() {
Copy link
Contributor

Choose a reason for hiding this comment

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

void (for detach also)

if (_port == null) return;
_beginComment = _port.content(_element);
}

detach() {
if (_port == null) return;
_port.detachContent(_beginComment);
}
}

class _ContentPort {
dom.Element _element;
var _childNodes = [];

_ContentPort(this._element);

pullNodes() {
_element.nodes.forEach((n) => _childNodes.add(n));
Copy link
Contributor

Choose a reason for hiding this comment

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

would addAll() work here ?
return type (void)

_element.nodes = [];
}

content(dom.Element elt) {
var hash = elt.hashCode;
var beginComment = new dom.Comment("content $hash");

if (!_childNodes.isEmpty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

isNotEmpty

elt.parent.insertBefore(beginComment, elt);
elt.parent.insertAllBefore(_childNodes, elt);
elt.parent.insertBefore(new dom.Comment("end-content $hash"), elt);
_childNodes = [];
}
elt.remove();
return beginComment;
}

detachContent(dom.Node _beginComment) {
// Search for endComment and extract everything in between.
// TODO optimize -- there may be a better way of pulling out nodes.

var endCommentText = "end-${_beginComment.text}";

var next;
for (next = _beginComment.nextNode;
next.nodeType != dom.Node.COMMENT_NODE && next.text != endCommentText;
next = _beginComment.nextNode) {
_childNodes.add(next);
next.remove();
}
assert(next.nodeType == dom.Node.COMMENT_NODE && next.text == endCommentText);
next.remove();
}
}

class TranscludingComponentFactory implements ComponentFactory {
final Expando _expando;

TranscludingComponentFactory(this._expando);

FactoryFn call(dom.Node node, DirectiveRef ref) {
// CSS is not supported.
assert((ref.annotation as Component).cssUrls == null ||
(ref.annotation as Component).cssUrls.isEmpty);

var element = node as dom.Element;
return (Injector injector) {
var childInjector;
var component = ref.annotation as Component;
Scope scope = injector.get(Scope);
ViewCache viewCache = injector.get(ViewCache);
Http http = injector.get(Http);
TemplateCache templateCache = injector.get(TemplateCache);
DirectiveMap directives = injector.get(DirectiveMap);
NgBaseCss baseCss = injector.get(NgBaseCss);

var contentPort = new _ContentPort(element);

// Append the component's template as children
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);

if (viewFuture != null) {
viewFuture = viewFuture.then((ViewFactory viewFactory) {
contentPort.pullNodes();
element.nodes.addAll(viewFactory(childInjector).nodes);
return element;
});
} else {
viewFuture = new async.Future.microtask(() => contentPort.pullNodes());
}
TemplateLoader templateLoader = new TemplateLoader(viewFuture);

Scope shadowScope = scope.createChild({});

var probe;
var childModule = new Module()
..type(ref.type)
Copy link
Contributor

Choose a reason for hiding this comment

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

indent

..type(NgElement)
..value(_ContentPort, contentPort)
..value(Scope, shadowScope)
..value(TemplateLoader, templateLoader)
..value(dom.ShadowRoot, new ShadowlessShadowRoot(element))
..factory(ElementProbe, (_) => probe);
childInjector = injector.createChild([childModule], name: SHADOW_DOM_INJECTOR_NAME);

var controller = childInjector.get(ref.type);
shadowScope.context[component.publishAs] = controller;
ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
return controller;
};
}
}
2 changes: 2 additions & 0 deletions lib/directive/ng_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ class NgTemplate {
? (element as dom.TemplateElement).content.innerHtml
: element.innerHtml));
}


2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ packages:
barback:
description: barback
source: hosted
version: "0.13.0"
version: "0.12.0"
benchmark_harness:
description: benchmark_harness
source: hosted
Expand Down
Loading