diff --git a/benchmark/launch_chrome.sh b/benchmark/launch_chrome.sh index c9e7ea65b..cf3924587 100755 --- a/benchmark/launch_chrome.sh +++ b/benchmark/launch_chrome.sh @@ -4,5 +4,10 @@ platform=`uname` if [[ "$platform" == 'Linux' ]]; then `google-chrome --js-flags="--expose-gc"` elif [[ "$platform" == 'Darwin' ]]; then - `/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-memory-info --enable-precise-memory-info --enable-memory-benchmarking --js-flags="--expose-gc"` + `/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary \ + --enable-memory-info \ + --enable-precise-memory-info \ + --enable-memory-benchmarking \ + --js-flags="--expose-gc" \ + --remote-debugging-port=9222` fi diff --git a/benchmark/web/wtf.dart b/benchmark/web/wtf.dart new file mode 100644 index 000000000..eb398a27f --- /dev/null +++ b/benchmark/web/wtf.dart @@ -0,0 +1,31 @@ +library wtf_test_app; + +import 'package:angular/wtf.dart'; +import 'dart:html'; +import 'dart:js' show context; + +main() { + traceInit(context); + var _main = traceCreateScope('main()'); + var _querySelector = traceCreateScope('Node#querySelector()'); + var _DivElement = traceCreateScope('DivElement()'); + var _ElementText = traceCreateScope('Element#text'); + var _NodeAppend = traceCreateScope('Node#append()'); + var scope = traceEnter(_main); + var s = traceEnter(_querySelector); + BodyElement body = window.document.querySelector('body'); + traceLeave(s); + + s = traceEnter(_DivElement); + var div = new DivElement(); + traceLeave(s); + + s = traceEnter(_ElementText); + div.text = 'Hello WTF! (enabled: ${wtfEnabled})'; + traceLeave(s); + + s = traceEnter(_NodeAppend); + body.append(div); + traceLeave(s); + traceLeave(scope); +} diff --git a/benchmark/web/wtf.html b/benchmark/web/wtf.html new file mode 100644 index 000000000..44f01bb30 --- /dev/null +++ b/benchmark/web/wtf.html @@ -0,0 +1,12 @@ + + + + + WTF Test page + + + + + + + diff --git a/lib/application.dart b/lib/application.dart index e6d696276..f4068cd71 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -68,6 +68,7 @@ library angular.app; import 'dart:html' as dom; +import 'dart:js' show context; import 'package:intl/date_symbol_data_local.dart'; import 'package:di/di.dart'; @@ -76,12 +77,12 @@ import 'package:angular/perf/module.dart'; import 'package:angular/cache/module.dart'; import 'package:angular/cache/js_cache_register.dart'; import 'package:angular/core/module_internal.dart'; -import 'package:angular/core/registry.dart'; import 'package:angular/core_dom/module_internal.dart'; import 'package:angular/directive/module.dart'; import 'package:angular/formatter/module_internal.dart'; import 'package:angular/routing/module.dart'; import 'package:angular/introspection.dart'; +import 'package:angular/wtf.dart'; import 'package:angular/core_dom/static_keys.dart'; import 'package:angular/core_dom/directive_injector.dart'; @@ -129,6 +130,7 @@ class AngularModule extends Module { * applicationFactory to bootstrap your Angular application. * */ +var _Application_run = traceCreateScope('Application#run()'); abstract class Application { static _find(String selector, [dom.Element defaultElement]) { var element = dom.document.querySelector(selector); @@ -150,6 +152,7 @@ abstract class Application { dom.Element selector(String selector) => element = _find(selector); Application(): element = _find('[ng-app]', dom.window.document.documentElement) { + traceInit(context); modules.add(ngModule); ngModule..bind(VmTurnZone, toValue: zone) ..bind(Application, toValue: this) @@ -172,26 +175,31 @@ abstract class Application { } Injector run() { - publishToJavaScript(); - return zone.run(() { - var rootElements = [element]; - Injector injector = createInjector(); - ExceptionHandler exceptionHandler = injector.getByKey(EXCEPTION_HANDLER_KEY); - // Publish cache register interface - injector.getByKey(JS_CACHE_REGISTER_KEY); - initializeDateFormatting(null, null).then((_) { - try { - Compiler compiler = injector.getByKey(COMPILER_KEY); - DirectiveMap directiveMap = injector.getByKey(DIRECTIVE_MAP_KEY); - RootScope rootScope = injector.getByKey(ROOT_SCOPE_KEY); - ViewFactory viewFactory = compiler(rootElements, directiveMap); - viewFactory(rootScope, injector.get(DirectiveInjector), rootElements); - } catch (e, s) { - exceptionHandler(e, s); - } + var scope = traceEnter(_Application_run); + try { + publishToJavaScript(); + return zone.run(() { + var rootElements = [element]; + Injector injector = createInjector(); + ExceptionHandler exceptionHandler = injector.getByKey(EXCEPTION_HANDLER_KEY); + // Publish cache register interface + injector.getByKey(JS_CACHE_REGISTER_KEY); + initializeDateFormatting(null, null).then((_) { + try { + Compiler compiler = injector.getByKey(COMPILER_KEY); + DirectiveMap directiveMap = injector.getByKey(DIRECTIVE_MAP_KEY); + RootScope rootScope = injector.getByKey(ROOT_SCOPE_KEY); + ViewFactory viewFactory = compiler(rootElements, directiveMap); + viewFactory(rootScope, injector.get(DirectiveInjector), rootElements); + } catch (e, s) { + exceptionHandler(e, s); + } + }); + return injector; }); - return injector; - }); + } finally { + traceLeave(scope); + } } /** diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart index f7bc83a3b..82e4052b9 100644 --- a/lib/change_detection/watch_group.dart +++ b/lib/change_detection/watch_group.dart @@ -2,11 +2,18 @@ library angular.watch_group; import 'package:angular/change_detection/change_detection.dart'; import 'dart:collection'; +import 'package:angular/wtf.dart'; part 'linked_list.dart'; part 'ast.dart'; part 'prototype_map.dart'; +var _WatchGroup_detect = traceCreateScope('WatchGroup#detect()'); +var _WatchGroup_fields = traceCreateScope('WatchGroup#field()'); +var _WatchGroup_field_handler = traceCreateScope('WatchGroup#field_handler()'); +var _WatchGroup_eval = traceCreateScope('WatchGroup#eval()'); +var _WatchGroup_reaction = traceCreateScope('WatchGroup#reaction()'); + /** * A function that is notified of changes to the model. * @@ -392,11 +399,15 @@ class RootWatchGroup extends WatchGroup { AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch}) { // Process the Records from the change detector + var sDetect = traceEnter(_WatchGroup_detect); + var s = traceEnter(_WatchGroup_fields); Iterator> changedRecordIterator = (_changeDetector as ChangeDetector<_Handler>).collectChanges( exceptionHandler:exceptionHandler, stopwatch: fieldStopwatch); + traceLeave(s); if (processStopwatch != null) processStopwatch.start(); + s = traceEnter(_WatchGroup_field_handler); while (changedRecordIterator.moveNext()) { var record = changedRecordIterator.current; if (changeLog != null) changeLog(record.handler.expression, @@ -404,11 +415,13 @@ class RootWatchGroup extends WatchGroup { record.previousValue); record.handler.onChange(record); } + traceLeave(s); if (processStopwatch != null) processStopwatch.stop(); if (evalStopwatch != null) evalStopwatch.start(); // Process our own function evaluations _EvalWatchRecord evalRecord = _evalWatchHead; + s = traceEnter(_WatchGroup_eval); int evalCount = 0; while (evalRecord != null) { try { @@ -423,11 +436,15 @@ class RootWatchGroup extends WatchGroup { } evalRecord = evalRecord._nextEvalWatch; } + + traceLeave(s); + traceLeave(sDetect); if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount); // Because the handler can forward changes between each other synchronously // We need to call reaction functions asynchronously. This processes the // asynchronous reaction function queue. + s = traceEnter(_WatchGroup_reaction); int count = 0; if (processStopwatch != null) processStopwatch.start(); Watch dirtyWatch = _dirtyWatchHead; @@ -451,6 +468,7 @@ class RootWatchGroup extends WatchGroup { _dirtyWatchTail = null; root._removeCount = 0; } + traceLeave(s); if (processStopwatch != null) processStopwatch..stop()..increment(count); return count; } diff --git a/lib/core/module_internal.dart b/lib/core/module_internal.dart index 5b98206b0..2ad07a969 100644 --- a/lib/core/module_internal.dart +++ b/lib/core/module_internal.dart @@ -11,6 +11,7 @@ import 'package:di/annotations.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/lexer.dart'; import 'package:angular/utils.dart'; +import 'package:angular/wtf.dart'; import 'package:angular/core/annotation_src.dart'; diff --git a/lib/core/parser/dynamic_parser.dart b/lib/core/parser/dynamic_parser.dart index d1caf80f2..a46254ee9 100644 --- a/lib/core/parser/dynamic_parser.dart +++ b/lib/core/parser/dynamic_parser.dart @@ -3,7 +3,7 @@ library angular.core.parser.dynamic_parser; import 'package:di/annotations.dart'; import 'package:angular/cache/module.dart'; import 'package:angular/core/annotation_src.dart' hide Formatter; -import 'package:angular/core/module_internal.dart' show FormatterMap; +import 'package:angular/core/formatter.dart' show FormatterMap; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/lexer.dart'; diff --git a/lib/core/parser/eval.dart b/lib/core/parser/eval.dart index c8d3ce3c9..0beab7f33 100644 --- a/lib/core/parser/eval.dart +++ b/lib/core/parser/eval.dart @@ -2,7 +2,7 @@ library angular.core.parser.eval; import 'package:angular/core/parser/syntax.dart' as syntax; import 'package:angular/core/parser/utils.dart'; -import 'package:angular/core/module_internal.dart'; +import 'package:angular/core/formatter.dart' show FormatterMap; export 'package:angular/core/parser/eval_access.dart'; export 'package:angular/core/parser/eval_calls.dart'; diff --git a/lib/core/parser/eval_access.dart b/lib/core/parser/eval_access.dart index 0fdfa61c0..566a69834 100644 --- a/lib/core/parser/eval_access.dart +++ b/lib/core/parser/eval_access.dart @@ -3,7 +3,7 @@ library angular.core.parser.eval_access; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/syntax.dart' as syntax; import 'package:angular/core/parser/utils.dart'; -import 'package:angular/core/module_internal.dart'; +import 'package:angular/core/formatter.dart' show FormatterMap; class AccessScopeFast extends syntax.AccessScope with AccessFast { final Getter getter; diff --git a/lib/core/parser/eval_calls.dart b/lib/core/parser/eval_calls.dart index 7ed2d143b..e1a2445bc 100644 --- a/lib/core/parser/eval_calls.dart +++ b/lib/core/parser/eval_calls.dart @@ -3,7 +3,7 @@ library angular.core.parser.eval_calls; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/syntax.dart' as syntax; import 'package:angular/core/parser/utils.dart'; -import 'package:angular/core/module_internal.dart'; +import 'package:angular/core/formatter.dart' show FormatterMap; class CallScope extends syntax.CallScope { diff --git a/lib/core/parser/syntax.dart b/lib/core/parser/syntax.dart index de2ebc70e..2213c4f35 100644 --- a/lib/core/parser/syntax.dart +++ b/lib/core/parser/syntax.dart @@ -3,7 +3,7 @@ library angular.core.parser.syntax; import 'package:angular/core/parser/parser.dart' show LocalsWrapper; import 'package:angular/core/parser/unparser.dart' show Unparser; import 'package:angular/core/parser/utils.dart' show EvalError; -import 'package:angular/core/module_internal.dart'; +import 'package:angular/core/formatter.dart' show FormatterMap; abstract class Visitor { visit(Expression expression) => expression.accept(this); diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart index 82a898d7b..0da2df0e1 100644 --- a/lib/core/parser/utils.dart +++ b/lib/core/parser/utils.dart @@ -1,7 +1,7 @@ library angular.core.parser.utils; import 'package:angular/core/parser/syntax.dart' show Expression; -import 'package:angular/core/module_internal.dart'; +import 'package:angular/core/formatter.dart' show FormatterMap; export 'package:angular/utils.dart' show relaxFnApply, relaxFnArgs, toBool; /// Marker for an uninitialized value. diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 914c02638..a8a286f41 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -3,6 +3,14 @@ part of angular.core_internal; typedef EvalFunction0(); typedef EvalFunction1(context); +var _Scope_apply = traceCreateScope('Scope#apply()'); +var _Scope_digest = traceCreateScope('Scope#digest()'); +var _Scope_flush = traceCreateScope('Scope#flush()'); +var _Scope_domWrite = traceCreateScope('Scope#domWrite()'); +var _Scope_domRead = traceCreateScope('Scope#domRead()'); +var _Scope_assert = traceCreateScope('Scope#assert()'); +var _Scope_runAsync = traceCreateScope('Scope#runAsync()'); +var _Scope_createChild = traceCreateScope('Scope#createChild()'); /** * Injected into the listener function within [Scope.on] to provide event-specific details to the * scope listener. @@ -330,6 +338,7 @@ class Scope { /// Creates a child [Scope] with the given [childContext] Scope createChild(Object childContext) { + var s = traceEnter(_Scope_createChild); assert(isAttached); var child = new Scope(childContext, rootScope, this, _readWriteGroup.newGroup(childContext), @@ -341,6 +350,7 @@ class Scope { child._prev = prev; if (prev == null) _childHead = child; else prev._next = child; _childTail = child; + traceLeave(s); return child; } @@ -593,6 +603,7 @@ class RootScope extends Scope { final ScopeStats _scopeStats; String _state; + var _state_wtf_scope; /** * While processing data bindings, Angular passes through multiple states. When testing or @@ -735,6 +746,7 @@ class RootScope extends Scope { try { do { if (_domWriteHead != null) _stats.domWriteStart(); + var s = traceEnter(_Scope_domWrite); while (_domWriteHead != null) { try { _domWriteHead.fn(); @@ -744,6 +756,7 @@ class RootScope extends Scope { _domWriteHead = _domWriteHead._next; if (_domWriteHead == null) _stats.domWriteEnd(); } + traceLeave(s); _domWriteTail = null; if (runObservers) { runObservers = false; @@ -753,6 +766,7 @@ class RootScope extends Scope { processStopwatch: _scopeStats.processStopwatch); } if (_domReadHead != null) _stats.domReadStart(); + s = traceEnter(_Scope_domRead); while (_domReadHead != null) { try { _domReadHead.fn(); @@ -763,6 +777,7 @@ class RootScope extends Scope { if (_domReadHead == null) _stats.domReadEnd(); } _domReadTail = null; + traceLeave(s); _runAsyncFns(); } while (_domWriteHead != null || _domReadHead != null || _runAsyncHead != null); _stats.flushEnd(); @@ -808,6 +823,7 @@ class RootScope extends Scope { } _runAsyncFns() { + var s = traceEnter(_Scope_runAsync); var count = 0; while (_runAsyncHead != null) { try { @@ -819,6 +835,7 @@ class RootScope extends Scope { _runAsyncHead = _runAsyncHead._next; } _runAsyncTail = null; + traceLeave(s); return count; } @@ -846,6 +863,13 @@ class RootScope extends Scope { assert(isAttached); if (_state != from) throw "$_state already in progress can not enter $to."; _state = to; + if (_state_wtf_scope != null) traceLeave(_state_wtf_scope); + var wtfScope = null; + if (to == STATE_APPLY) wtfScope = _Scope_apply; + else if (to == STATE_DIGEST) wtfScope = _Scope_digest; + else if (to == STATE_FLUSH) wtfScope = _Scope_flush; + else if (to == STATE_FLUSH_ASSERT) wtfScope = _Scope_assert; + _state_wtf_scope = wtfScope == null ? null : traceEnter(wtfScope); } } diff --git a/lib/core/zone.dart b/lib/core/zone.dart index ba9cbdaf5..f0ea4aa03 100644 --- a/lib/core/zone.dart +++ b/lib/core/zone.dart @@ -39,6 +39,9 @@ class LongStackTrace { } } +var _VmTurnZone_onRunBase = traceCreateScope('VmTurnZone#onRun()'); +var _VmTurnZone_onScheduleMicrotask = traceCreateScope('VmTurnZone#onScheduleMicrotask()'); + /** * A [Zone] wrapper that lets you schedule tasks after its private microtask * queue is exhausted but before the next "turn", i.e. event loop iteration. @@ -89,6 +92,7 @@ class VmTurnZone { var _currentlyInTurn = false; dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) { + var scope = traceEnter(_VmTurnZone_onRunBase); _runningInTurn++; try { if (!_currentlyInTurn) { @@ -103,6 +107,7 @@ class VmTurnZone { } finally { _runningInTurn--; if (_runningInTurn == 0) _finishTurn(zone, delegate); + traceLeave(scope); } } @@ -115,8 +120,13 @@ class VmTurnZone { _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); void _onScheduleMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) { - onScheduleMicrotask(() => delegate.run(zone, fn)); - if (_runningInTurn == 0 && !_inFinishTurn) _finishTurn(zone, delegate); + var s = traceEnter(_VmTurnZone_onScheduleMicrotask); + try { + onScheduleMicrotask(() => delegate.run(zone, fn)); + if (_runningInTurn == 0 && !_inFinishTurn) _finishTurn(zone, delegate); + } finally { + traceLeave(s); + } } void _uncaughtError(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, diff --git a/lib/core_dom/compiler.dart b/lib/core_dom/compiler.dart index e465dc72d..8f9557121 100644 --- a/lib/core_dom/compiler.dart +++ b/lib/core_dom/compiler.dart @@ -1,5 +1,8 @@ part of angular.core.dom_internal; +var _Compiler_call = traceCreateScope('Compiler#call()'); +var _Compiler_subTemplate = traceCreateScope('Compiler#subTemplate()'); + @Injectable() class Compiler implements Function { final Profiler _perf; @@ -8,6 +11,7 @@ class Compiler implements Function { Compiler(this._perf, this._expando); ViewFactory call(List elements, DirectiveMap directives) { + var s = traceEnter(_Compiler_call); var timerId; assert((timerId = _perf.startTimer('ng.compile', _html(elements))) != false); final elementBinders = []; @@ -19,6 +23,7 @@ class Compiler implements Function { elements, _removeUnusedBinders(elementBinders), _perf); assert(_perf.stopTimer(timerId) != false); + traceLeave(s); return viewFactory; } @@ -128,7 +133,7 @@ class Compiler implements Function { isTopLevel, directParentElementBinder); } while (domCursor.moveNext()); - return elementBinders; + return elementBinders; } ViewFactory _compileTransclusion( @@ -136,6 +141,7 @@ class Compiler implements Function { DirectiveRef directiveRef, ElementBinder transcludedElementBinder, DirectiveMap directives) { + var s = traceEnter(_Compiler_subTemplate); var anchorName = directiveRef.annotation.selector + (directiveRef.value != null ? '=' + directiveRef.value : ''); @@ -146,7 +152,7 @@ class Compiler implements Function { var viewFactory = new ViewFactory(transcludeCursor.elements, _removeUnusedBinders(elementBinders), _perf); - + traceLeave(s); return viewFactory; } diff --git a/lib/core_dom/directive.dart b/lib/core_dom/directive.dart index 8738fe273..aa32724ce 100644 --- a/lib/core_dom/directive.dart +++ b/lib/core_dom/directive.dart @@ -22,7 +22,7 @@ class NodeAttrs { NodeAttrs(this.element); - operator [](String attrName) => element.attributes[attrName]; + operator [](String attrName) => element.getAttribute(attrName); void operator []=(String attrName, String value) { if (_mustacheAttrs.containsKey(attrName)) { @@ -31,7 +31,7 @@ class NodeAttrs { if (value == null) { element.attributes.remove(attrName); } else { - element.attributes[attrName] = value; + element.setAttribute(attrName, value); } if (_observers != null && _observers.containsKey(attrName)) { @@ -86,9 +86,18 @@ class NodeAttrs { * ShadowRoot is ready. */ class TemplateLoader { - final async.Future template; + async.Future _template; + List _futures; + final dom.Node _shadowRoot; - TemplateLoader(this.template); + TemplateLoader(this._shadowRoot, this._futures); + + async.Future get template { + if (_template == null) { + _template = async.Future.wait(_futures).then((_) => _shadowRoot); + } + return _template; + } } class _MustacheAttr { diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 814bd9148..5c90afe77 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -1,5 +1,8 @@ part of angular.core.dom_internal; +var _ElementBinder_directive = traceCreateScope('ElementBinder#createDirective(ascii name)'); +var _ElementBinder_setupBindings = traceCreateScope('ElementBinder#setupBindings(ascii name)'); + class TemplateElementBinder extends ElementBinder { final DirectiveRef template; ViewFactory templateViewFactory; @@ -186,39 +189,53 @@ class ElementBinder { } void _link(DirectiveInjector directiveInjector, Scope scope, nodeAttrs) { + var s; for(var i = 0; i < _usableDirectiveRefs.length; i++) { DirectiveRef ref = _usableDirectiveRefs[i]; var key = ref.typeKey; + var wtfArgs = wtfEnabled ? [ref.typeKey.toString()] : null; if (identical(key, TEXT_MUSTACHE_KEY) || identical(key, ATTR_MUSTACHE_KEY)) continue; - var directive = directiveInjector.getByKey(ref.typeKey); - if (ref.annotation is Controller) { - scope.parentScope.context[(ref.annotation as Controller).publishAs] = directive; + s = traceEnter(_ElementBinder_directive, wtfArgs); + var directive; + try { + directive = directiveInjector.getByKey(ref.typeKey); + } finally { + traceLeave(s); } - var tasks = directive is AttachAware ? new _TaskList(() { - if (scope.isAttached) directive.attach(); - }) : null; + s = traceEnter(_ElementBinder_setupBindings, wtfArgs); + try { + if (ref.annotation is Controller) { + scope.parentScope.context[(ref.annotation as Controller).publishAs] = directive; + } - if (ref.mappings.isNotEmpty) { - if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref); - _createAttrMappings(directive, scope, ref.mappings, nodeAttrs, tasks); - } + var tasks = directive is AttachAware ? new _TaskList(() { + if (scope.isAttached) directive.attach(); + }) : null; - if (directive is AttachAware) { - var taskId = (tasks != null) ? tasks.registerTask() : 0; - Watch watch; - watch = scope.watch('1', // Cheat a bit. - (_, __) { - watch.remove(); - if (tasks != null) tasks.completeTask(taskId); - }); - } + if (ref.mappings.isNotEmpty) { + if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref); + _createAttrMappings(directive, scope, ref.mappings, nodeAttrs, tasks); + } - if (tasks != null) tasks.doneRegistering(); + if (directive is AttachAware) { + var taskId = (tasks != null) ? tasks.registerTask() : 0; + Watch watch; + watch = scope.watch('1', // Cheat a bit. + (_, __) { + watch.remove(); + if (tasks != null) tasks.completeTask(taskId); + }); + } + + if (tasks != null) tasks.doneRegistering(); - if (directive is DetachAware) { - scope.on(ScopeEvent.DESTROY).listen((_) => directive.detach()); + if (directive is DetachAware) { + scope.on(ScopeEvent.DESTROY).listen((_) => directive.detach()); + } + } finally { + traceLeave(s); } } } diff --git a/lib/core_dom/http.dart b/lib/core_dom/http.dart index 0cc6ae918..5123188b7 100644 --- a/lib/core_dom/http.dart +++ b/lib/core_dom/http.dart @@ -445,6 +445,7 @@ class Http { cache, timeout }) { + var range = wtfEnabled ? traceAsyncStart('http:$method', url) : null; if (timeout != null) { throw ['timeout not implemented']; } @@ -541,9 +542,17 @@ class Http { // Depending on the implementation of HttpBackend (e.g. with a local cache) the entire // chain could finish synchronously with a non-Future result. - return chainResult is async.Future + var result = chainResult is async.Future ? chainResult : new async.Future.value(chainResult); + if (wtfEnabled) { + return new async.Future(() { + traceAsyncEnd(range); + return result; + }); + } else { + return result; + } } /** diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index 61d132066..b7cd7bbbd 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -22,6 +22,7 @@ export 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector import 'package:angular/change_detection/watch_group.dart' show Watch, PrototypeMap; import 'package:angular/change_detection/ast_parser.dart'; import 'package:angular/core/registry.dart'; +import 'package:angular/wtf.dart'; import 'package:angular/directive/module.dart' show NgBaseCss; import 'dart:collection'; diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 7c8a48c9a..b6c5ae242 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -1,5 +1,8 @@ part of angular.core.dom_internal; +var _ComponentFactory_call = traceCreateScope('ComponentFactory#call()'); +var _ComponentFactory_styles = traceCreateScope('ComponentFactory#styles()'); + abstract class ComponentFactory { BoundComponentFactory bind(DirectiveRef ref, directives); } @@ -11,7 +14,7 @@ abstract class BoundComponentFactory { List get callArgs; Function call(dom.Element element); - static async.Future _viewFuture( + static async.Future _viewFactoryFuture( Component component, ViewCache viewCache, DirectiveMap directives) { if (component.template != null) { return new async.Future.value(viewCache.fromHtml(component.template, directives)); @@ -58,34 +61,41 @@ class ShadowDomComponentFactory implements ComponentFactory { class BoundShadowDomComponentFactory implements BoundComponentFactory { - final ShadowDomComponentFactory _f; + final ShadowDomComponentFactory _componentFactory; final DirectiveRef _ref; final DirectiveMap _directives; Component get _component => _ref.annotation as Component; String _tag; - async.Future> _styleElementsFuture; - async.Future _viewFuture; + async.Future> _styleElementsFuture; + List _styleElements; + async.Future _shadowViewFactoryFuture; + ViewFactory _shadowViewFactory; - BoundShadowDomComponentFactory(this._f, this._ref, this._directives) { + BoundShadowDomComponentFactory(this._componentFactory, this._ref, this._directives) { _tag = _component.selector.toLowerCase(); - _styleElementsFuture = async.Future.wait(_component.cssUrls.map(_styleFuture)); + _styleElementsFuture = async.Future.wait(_component.cssUrls.map(_urlToStyle)) + ..then((stylesElements) => _styleElements = stylesElements); - _viewFuture = BoundComponentFactory._viewFuture( + _shadowViewFactoryFuture = BoundComponentFactory._viewFactoryFuture( _component, - new PlatformViewCache(_f.viewCache, _tag, _f.platform), + // TODO(misko): Why do we create a new one per Component. This kind of defeats the caching. + new PlatformViewCache(_componentFactory.viewCache, _tag, _componentFactory.platform), _directives); + if (_shadowViewFactoryFuture != null) { + _shadowViewFactoryFuture.then((viewFactory) => _shadowViewFactory = viewFactory); + } } - async.Future _styleFuture(cssUrl) { - Http http = _f.http; - TemplateCache templateCache = _f.templateCache; - WebPlatform platform = _f.platform; - ComponentCssRewriter componentCssRewriter = _f.componentCssRewriter; - dom.NodeTreeSanitizer treeSanitizer = _f.treeSanitizer; + async.Future _urlToStyle(cssUrl) { + Http http = _componentFactory.http; + TemplateCache templateCache = _componentFactory.templateCache; + WebPlatform platform = _componentFactory.platform; + ComponentCssRewriter componentCssRewriter = _componentFactory.componentCssRewriter; + dom.NodeTreeSanitizer treeSanitizer = _componentFactory.treeSanitizer; - return _f.styleElementCache.putIfAbsent( + return _componentFactory.styleElementCache.putIfAbsent( new _ComponentAssetKey(_tag, cssUrl), () => http.get(cssUrl, cache: templateCache) .then((resp) => resp.responseText, @@ -107,7 +117,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { // If the css shim is required, it means that scoping does not // work, and adding the style to the head of the document is - // preferrable. + // preferable. if (platform.cssShimRequired) { dom.document.head.append(styleElement); return null; @@ -124,62 +134,101 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { Function call(dom.Element element) { return (DirectiveInjector injector, Scope scope, NgBaseCss baseCss, EventHandler eventHandler) { - var shadowDom = element.createShadowRoot() - ..applyAuthorStyles = _component.applyAuthorStyles - ..resetStyleInheritance = _component.resetStyleInheritance; - - var shadowScope = scope.createChild(new HashMap()); // Isolate - - async.Future> cssFuture; - if (_component.useNgBaseCss == true) { - cssFuture = async.Future.wait( - [async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]) - .then((twoLists) { - assert(twoLists.length == 2); - return []..addAll(twoLists[0])..addAll(twoLists[1]); - }); - } else { - cssFuture = _styleElementsFuture; - } + var s = traceEnter(_ComponentFactory_call); + try { + var shadowScope = scope.createChild(new HashMap()); // Isolate + ComponentDirectiveInjector shadowInjector; + dom.ShadowRoot shadowRoot = element.createShadowRoot(); + shadowRoot + ..applyAuthorStyles = _component.applyAuthorStyles + ..resetStyleInheritance = _component.resetStyleInheritance; + + List futures = []; + TemplateLoader templateLoader = new TemplateLoader(shadowRoot, futures); + shadowInjector = new ShadowDomComponentDirectiveInjector( + injector, injector.appInjector, shadowScope, templateLoader, shadowRoot); + shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, + _ref.annotation.visibility); + dom.Node firstViewNode = null; + + // Load ngBase CSS + if (_component.useNgBaseCss == true && baseCss.urls.isNotEmpty) { + if (baseCss.styles == null) { + futures.add(async.Future + .wait(baseCss.urls.map(_urlToStyle)) + .then((List cssList) { + baseCss.styles = cssList; + _insertCss(cssList, shadowRoot, shadowRoot.firstChild); + })); + } else { + _insertCss(baseCss.styles, shadowRoot, shadowRoot.firstChild); + } + } + + if (_styleElementsFuture != null) { + if (_styleElements == null) { + futures.add(_styleElementsFuture .then((List styles) => + _insertCss(styles, shadowRoot, firstViewNode))); + } else { + _insertCss(_styleElements, shadowRoot); + } + } - ComponentDirectiveInjector shadowInjector; - - TemplateLoader templateLoader = new TemplateLoader( - cssFuture.then((Iterable cssList) { - cssList - .where((styleElement) => styleElement != null) - .forEach((styleElement) { - shadowDom.append(styleElement.clone(true)); - }); - if (_viewFuture != null) { - return _viewFuture.then((ViewFactory viewFactory) { - if (shadowScope.isAttached) { - shadowDom.nodes.addAll( - viewFactory.call(shadowInjector.scope, shadowInjector).nodes); - } - return shadowDom; - }); - } - return shadowDom; - })); - - var probe; - shadowInjector = new ShadowDomComponentDirectiveInjector(injector, injector.appInjector, - shadowScope, templateLoader, shadowDom); - shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); - - if (_f.config.elementProbeEnabled) { - probe = _f.expando[shadowDom] = shadowInjector.elementProbe; - shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _f.expando[shadowDom] = null); - } - var controller = shadowInjector.getByKey(_ref.typeKey); - BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); - shadowScope.context[_component.publishAs] = controller; + if (_shadowViewFactoryFuture != null) { + if (_shadowViewFactory == null) { + futures.add(_shadowViewFactoryFuture.then((ViewFactory viewFactory) => + firstViewNode = _insertView(viewFactory, shadowRoot, shadowScope, shadowInjector))); + } else { + _insertView(_shadowViewFactory, shadowRoot, shadowScope, shadowInjector); + } + } + + if (_componentFactory.config.elementProbeEnabled) { + ElementProbe probe = _componentFactory.expando[shadowRoot] = shadowInjector.elementProbe; + shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowRoot] = null); + } + + var controller = shadowInjector.getByKey(_ref.typeKey); + BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); + shadowScope.context[_component.publishAs] = controller; - return controller; + return controller; + } finally { + traceLeave(s); + } }; } + + _insertCss(List cssList, + dom.ShadowRoot shadowRoot, + [dom.Node insertBefore = null]) { + var s = traceEnter(_ComponentFactory_styles); + for(int i = 0; i < cssList.length; i++) { + var styleElement = cssList[i]; + if (styleElement != null) { + shadowRoot.insertBefore(styleElement.clone(true), insertBefore); + } + } + traceLeave(s); + } + + dom.Node _insertView(ViewFactory viewFactory, + dom.ShadowRoot shadowRoot, + Scope shadowScope, + ShadowDomComponentDirectiveInjector shadowInjector) { + dom.Node first = null; + if (shadowScope.isAttached) { + View shadowView = viewFactory.call(shadowScope, shadowInjector); + List shadowViewNodes = shadowView.nodes; + for (var j = 0; j < shadowViewNodes.length; j++) { + var node = shadowViewNodes[j]; + if (j == 0) first = node; + shadowRoot.append(node); + } + } + return first; + } } class _ComponentAssetKey { diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index 29f8f2663..cc0e40c73 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -87,13 +87,14 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { final DirectiveMap _directives; Component get _component => _ref.annotation as Component; - async.Future _viewFuture; + async.Future _viewFactoryFuture; + ViewFactory _viewFactory; BoundTranscludingComponentFactory(this._f, this._ref, this._directives) { - _viewFuture = BoundComponentFactory._viewFuture( - _component, - _f.viewCache, - _directives); + _viewFactoryFuture = BoundComponentFactory._viewFactoryFuture(_component, _f.viewCache, _directives); + if (_viewFactoryFuture != null) { + _viewFactoryFuture.then((viewFactory) => _viewFactory = viewFactory); + } } List get callArgs => _CALL_ARGS; @@ -110,51 +111,39 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { ViewCache viewCache, Http http, TemplateCache templateCache, DirectiveMap directives, NgBaseCss baseCss, EventHandler eventHandler) { - DirectiveInjector childInjector; - var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector. - - var component = _component; + List futures = []; var contentPort = new ContentPort(element); - - // Append the component's template as children - var elementFuture; - - if (_viewFuture != null) { - elementFuture = _viewFuture.then((ViewFactory viewFactory) { - contentPort.pullNodes(); - if (childInjector != null) { - element.nodes.addAll( - viewFactory.call(childInjector.scope, childInjector).nodes); - return element; - } else { - childInjectorCompleter = new async.Completer(); - return childInjectorCompleter.future.then((childInjector) { - element.nodes.addAll( - viewFactory.call(childInjector.scope, childInjector).nodes); - return element; - }); - } - }); - } else { - elementFuture = new async.Future.microtask(() => contentPort.pullNodes()); - } - TemplateLoader templateLoader = new TemplateLoader(elementFuture); - + TemplateLoader templateLoader = new TemplateLoader(element, futures); Scope shadowScope = scope.createChild(new HashMap()); - - childInjector = new ShadowlessComponentDirectiveInjector(injector, injector.appInjector, - eventHandler, shadowScope, templateLoader, new ShadowlessShadowRoot(element), - contentPort); + DirectiveInjector childInjector = new ShadowlessComponentDirectiveInjector( + injector, injector.appInjector, eventHandler, shadowScope, templateLoader, + new ShadowlessShadowRoot(element), contentPort); childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); - if (childInjectorCompleter != null) { - childInjectorCompleter.complete(childInjector); - } - var controller = childInjector.getByKey(_ref.typeKey); - shadowScope.context[component.publishAs] = controller; + shadowScope.context[_component.publishAs] = controller; BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); + + if (_viewFactoryFuture != null && _viewFactory == null) { + futures.add(_viewFactoryFuture.then((ViewFactory viewFactory) => + _insert(viewFactory, element, childInjector, contentPort))); + } else { + scope.rootScope.runAsync(() { + _insert(_viewFactory, element, childInjector, contentPort); + }); + } return controller; }; } + + _insert(ViewFactory viewFactory, dom.Element element, DirectiveInjector childInjector, + ContentPort contentPort) { + contentPort.pullNodes(); + if (viewFactory != null) { + var viewNodes = viewFactory.call(childInjector.scope, childInjector).nodes; + for(var i = 0; i < viewNodes.length; i++) { + element.append(viewNodes[i]); + } + } + } } diff --git a/lib/core_dom/view_factory.dart b/lib/core_dom/view_factory.dart index 9ef9cc1b4..a3e2f07d5 100644 --- a/lib/core_dom/view_factory.dart +++ b/lib/core_dom/view_factory.dart @@ -1,5 +1,8 @@ part of angular.core.dom_internal; +var _ViewFactory_call = traceCreateScope('ViewFactory#call(ascii html)'); +var _ViewFactory_bind = traceCreateScope('ViewFactory#bind()'); +var _ViewFactory_querySelectorAll = traceCreateScope('ViewFactory#querySelectorAll()'); /** * BoundViewFactory is a [ViewFactory] which does not need Injector because @@ -24,10 +27,24 @@ class ViewFactory implements Function { final List templateNodes; final List nodeLinkingInfos; final Profiler _perf; + List _wtfArgs; ViewFactory(templateNodes, this.elementBinders, this._perf) : nodeLinkingInfos = computeNodeLinkingInfos(templateNodes), - templateNodes = templateNodes; + templateNodes = templateNodes + { + if (wtfEnabled) { + _wtfArgs = [templateNodes.map((dom.Node e) { + if (e is dom.Element) { + return (e as dom.Element).outerHtml; + } else if (e is dom.Comment) { + return ''; + } else { + return e.text; + } + }).toList().join('')]; + } + } @deprecated BoundViewFactory bind(DirectiveInjector directiveInjector) => @@ -37,21 +54,20 @@ class ViewFactory implements Function { View call(Scope scope, DirectiveInjector directiveInjector, [List nodes /* TODO: document fragment */]) { + var s = traceEnter(_ViewFactory_call, _wtfArgs); assert(scope != null); if (nodes == null) { nodes = cloneElements(templateNodes); } - var timerId; - try { - assert((timerId = _perf.startTimer('ng.view')) != false); - Animate animate = directiveInjector.getByKey(ANIMATE_KEY); - EventHandler eventHandler = directiveInjector.getByKey(EVENT_HANDLER_KEY); - var view = new View(nodes, scope, eventHandler); - _link(view, scope, nodes, eventHandler, animate, directiveInjector); - return view; - } finally { - assert(_perf.stopTimer(timerId) != false); - } + Animate animate = directiveInjector.getByKey(ANIMATE_KEY); + EventHandler eventHandler = directiveInjector.getByKey(EVENT_HANDLER_KEY); + var view = new View(nodes, scope, eventHandler); + var sBind = traceEnter(_ViewFactory_bind); + _link(view, scope, nodes, eventHandler, animate, directiveInjector); + traceLeave(sBind); + traceLeave(s); + + return view; } void _bindTagged(TaggedElementBinder tagged, int elementBinderIndex, @@ -78,10 +94,12 @@ class ViewFactory implements Function { } elementInjectors[elementBinderIndex] = elementInjector; - if (tagged.textBinders != null) { - for (var k = 0; k < tagged.textBinders.length; k++) { - TaggedTextBinder taggedText = tagged.textBinders[k]; - var childNode = boundNode.childNodes[taggedText.offsetIndex]; + var textBinders = tagged.textBinders; + if (textBinders != null && textBinders.length > 0) { + var childNodes = boundNode.childNodes; + for (var k = 0; k < textBinders.length; k++) { + TaggedTextBinder taggedText = textBinders[k]; + var childNode = childNodes[taggedText.offsetIndex]; taggedText.binder.bind(view, scope, elementInjector, childNode, eventHandler, animate); } } @@ -97,15 +115,6 @@ class ViewFactory implements Function { dom.Node node = nodeList[i]; NodeLinkingInfo linkingInfo = nodeLinkingInfos[i]; - // if node isn't attached to the DOM, create a parent for it. - var parentNode = node.parentNode; - var fakeParent = false; - if (parentNode == null) { - fakeParent = true; - parentNode = new dom.DivElement(); - parentNode.append(node); - } - if (linkingInfo.isElement) { if (linkingInfo.containsNgBinding) { var tagged = elementBinders[elementBinderIndex]; @@ -115,7 +124,9 @@ class ViewFactory implements Function { } if (linkingInfo.ngBindingChildren) { + var s = traceEnter(_ViewFactory_querySelectorAll); var elts = (node as dom.Element).querySelectorAll('.ng-binding'); + traceLeave(s); for (int j = 0; j < elts.length; j++, elementBinderIndex++) { TaggedElementBinder tagged = elementBinders[elementBinderIndex]; _bindTagged(tagged, elementBinderIndex, rootInjector, elementInjectors, @@ -131,11 +142,6 @@ class ViewFactory implements Function { } elementBinderIndex++; } - - if (fakeParent) { - // extract the node from the parentNode. - nodeList[i] = parentNode.nodes[0]; - } } return view; } diff --git a/lib/core_dom/web_platform.dart b/lib/core_dom/web_platform.dart index f40845622..f30aa8c3d 100644 --- a/lib/core_dom/web_platform.dart +++ b/lib/core_dom/web_platform.dart @@ -42,7 +42,7 @@ class WebPlatform { // // TODO Remove the try-catch once https://github.com/angular/angular.dart/issues/1189 is fixed. try { - root.querySelectorAll("*").forEach((n) => n.attributes[selector] = ""); + root.querySelectorAll("*").forEach((dom.Element n) => n.setAttribute(selector, "")); } catch (e, s) { print("WARNING: Failed to set up Shadow DOM shim for $selector.\n$e\n$s"); } @@ -69,6 +69,7 @@ class PlatformViewCache implements ViewCache { if (selector != null && selector != "" && platform.shadowDomShimRequired) { // By adding a comment with the tag name we ensure the template html is unique per selector // name when used as a key in the view factory cache. + //TODO(misko): This will always be miss, since we never put it in cache under such key. viewFactory = viewFactoryCache.get("$html"); } else { viewFactory = viewFactoryCache.get(html); diff --git a/lib/directive/ng_base_css.dart b/lib/directive/ng_base_css.dart index 74e41406d..34b9eade6 100644 --- a/lib/directive/ng_base_css.dart +++ b/lib/directive/ng_base_css.dart @@ -12,10 +12,14 @@ part of angular.directive; selector: '[ng-base-css]', visibility: Visibility.CHILDREN) class NgBaseCss { + List styles; List _urls = const []; @NgAttr('ng-base-css') - set urls(v) => _urls = v is List ? v : [v]; + set urls(v) { + _urls = v is List ? v : [v]; + styles = null; + } List get urls => _urls; } diff --git a/lib/introspection.dart b/lib/introspection.dart index 066d50612..c3b3712d0 100644 --- a/lib/introspection.dart +++ b/lib/introspection.dart @@ -277,7 +277,7 @@ class _Testability implements _JsObjectProxyable { _Testability(this.node, this.probe); whenStable(callback) { - probe.injector.get(VmTurnZone).run( + (probe.injector.get(VmTurnZone) as VmTurnZone).run( () => new async.Timer(Duration.ZERO, callback)); } diff --git a/lib/wtf.dart b/lib/wtf.dart new file mode 100644 index 000000000..b21b1152c --- /dev/null +++ b/lib/wtf.dart @@ -0,0 +1,85 @@ +library angular.wtf; + +import "dart:profiler"; + +bool wtfEnabled = false; +dynamic /* JsObject */ _trace; +dynamic /* JsObject */ _events; +dynamic /* JsFunction */ _createScope; +dynamic /* JsFunction */ _enterScope; +dynamic /* JsFunction */ _leaveScope; +dynamic /* JsFunction */ _beginTimeRange; +dynamic /* JsFunction */ _endTimeRange; +final List _arg1 = [null]; +final List _arg2 = [null, null]; + +/** + * Use this method to initialize the WTF. It would be + * nice if this file could depend on dart:js, but that would + * make it not possible to refer to it in Dart VM. For this + * reason we expect the init caller to pass in the context + * JsObject. + */ +traceInit(dynamic /* JsObject */ context) { + if (context.hasProperty('wtf')) { + dynamic /* JsObject */ wtf = context['wtf']; + if (wtf.hasProperty('trace')) { + wtfEnabled = true; + _trace = wtf['trace']; + _events = _trace['events']; + _createScope = _events['createScope']; + _enterScope = _trace['enterScope']; + _leaveScope = _trace['leaveScope']; + _beginTimeRange = _trace['beginTimeRange']; + _endTimeRange = _trace['endTimeRange']; + } + + } +} + +// WTF.trace.events.createScope(string signature, opt_flags) +dynamic /* JsFunction */ traceCreateScope(signature, [flags]) { + if (wtfEnabled) { + _arg2[0] = signature; + _arg2[1] = flags; + return _createScope.apply(_arg2, thisArg: _events); + } else { + return new UserTag(signature); + } +} + +dynamic /* JsObject */ traceEnter(dynamic /* JsFunction */ scope, [args = const []]) { + if (wtfEnabled) { + return scope.apply(args); + } else { + return scope.makeCurrent(); + } +} + +dynamic /* JsObject */ traceLeave(dynamic /* JsObject */ scope) { + if (wtfEnabled) { + _arg1[0] = scope; + _leaveScope.apply(_arg1, thisArg: _trace); + } else { + scope.makeCurrent(); + } +} + +// WTF.trace.beginTimeRange('my.Type:job', actionName); +dynamic /* JsObject */ traceAsyncStart(String rangeType, String action) { + if (wtfEnabled) { + _arg2[0] = rangeType; + _arg2[1] = action; + return _beginTimeRange.apply(_arg2, thisArg: _trace); + } + return null; +} + +dynamic /* JsObject */ traceAsyncEnd(dynamic /* JsObject */ range) { + if (wtfEnabled) { + _arg1[0] = range; + return _endTimeRange.apply(_arg1, thisArg: _trace); + } + return null; +} + diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 6e637fa29..916225cdc 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -872,7 +872,7 @@ void main() { })); /* - This test is dissabled becouse I (misko) thinks it has no real use case. It is easier + This test is disabled because I (misko) thinks it has no real use case. It is easier to understand in terms of ng-repeat @@ -886,7 +886,7 @@ void main() { the DirectChild between tabs and pane. It is not clear to me (misko) that there is a use case for getting hold of the - tranrscluding directive such a ng-repeat. + transcluding directive such a ng-repeat. */ xit('should reuse controllers for transclusions', async((Logger log) { _.compile('
view
'); diff --git a/test/directive/ng_base_css_spec.dart b/test/directive/ng_base_css_spec.dart index bc56214be..73225445a 100644 --- a/test/directive/ng_base_css_spec.dart +++ b/test/directive/ng_base_css_spec.dart @@ -23,22 +23,35 @@ main() => describe('NgBaseCss', () { ..bind(_NoBaseCssComponent); }); - it('should load css urls from ng-base-css', async((TestBed _, MockHttpBackend backend) { + it('should load css urls from ng-base-css', async((TestBed _, MockHttpBackend backend, + DirectiveMap directiveMap) { backend ..expectGET('simple.css').respond(200, '.simple{}') ..expectGET('simple.html').respond(200, '
Simple!
') ..expectGET('base.css').respond(200, '.base{}'); - var element = e('
ignore
'); - _.compile(element); + NgBaseCss ngBaseCss = new NgBaseCss(); + ngBaseCss.urls = 'base.css'; + DirectiveInjector directiveInjector = new DirectiveInjector( + _.directiveInjector, _.injector, null, null, null, null, null); + directiveInjector.bind(NgBaseCss, toValue: ngBaseCss, visibility: Visibility.CHILDREN); + var elements = es('
ignore
'); + ViewFactory viewFactory = _.compiler(elements, directiveMap); + View view = viewFactory.call(_.rootScope, directiveInjector); microLeap(); backend.flush(); microLeap(); - expect(element.children[0].shadowRoot).toHaveHtml( - '
Simple!
' - ); + expect((view.nodes[0].firstChild as Element).shadowRoot).toHaveHtml( + '
Simple!
'); + expect(ngBaseCss.styles.first.innerHtml).toEqual('.base{}'); + + // Now it should be sync + view = viewFactory.call(_.rootScope, directiveInjector); + expect((view.nodes[0].firstChild as Element).shadowRoot).toHaveHtml( + '
Simple!
'); + })); it('ng-base-css should overwrite parent ng-base-csses', async((TestBed _, MockHttpBackend backend) {