From 354b5c56260461d7e61193b8fe6d28de5502ac4d Mon Sep 17 00:00:00 2001 From: Rado Kirov Date: Mon, 28 Jul 2014 11:32:51 -0700 Subject: [PATCH 01/23] chore(test-bed): add fromInjector constructor to TestBed Some clients do not generate static factories for test code, and instead directly depend on TestBed constructor. This commit adds a new constructor is basically the injector factory method, requiring only injector vs a full set of parameters. Once clients have been moved to use fromInjector method (or even better a transformer run for test code), we can change the constructor dependencies in a non-breaking way. Closes #1271 --- lib/mock/test_bed.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/mock/test_bed.dart b/lib/mock/test_bed.dart index d1c54da81..e0cf5064d 100644 --- a/lib/mock/test_bed.dart +++ b/lib/mock/test_bed.dart @@ -20,6 +20,10 @@ class TestBed { TestBed(this.injector, this.directiveInjector, this.rootScope, this.compiler, this._parser, this.expando); + TestBed.fromInjector(Injector i) : + this(i, i.get(DirectiveInjector), i.get(RootScope), i.get(Compiler), + i.get(Parser), i.get(Expando)); + /** * Use to compile HTML and activate its directives. From 052b3cf49a1d4b434f901b2a07a7abff99861578 Mon Sep 17 00:00:00 2001 From: Alan Knight Date: Wed, 2 Jul 2014 10:22:57 -0700 Subject: [PATCH 02/23] chore(intl): support newer version of "intl" Angular's pubspec for intl spans versions with breaking changes. For Angular, a change in date formatting would be a breaking change but changes do to CLDR updates are not breaking changes. This commit updates a test to swap out a locale with a CLDR update to one that wasn't updated to enable it to pass across the CLDR breaking change. Closes #1203 --- pubspec.yaml | 2 +- test/formatter/date_spec.dart | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9ef6dafa1..916fb6454 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: collection: '>=0.9.1 <1.0.0' di: '>=2.0.1 <3.0.0' html5lib: '>=0.10.0 <0.11.0' - intl: '>=0.8.7 <0.10.0' + intl: '>=0.8.7 <0.12.0' perf_api: '>=0.0.8 <0.1.0' route_hierarchical: '>=0.4.21 <0.5.0' web_components: '>=0.3.3 <0.4.0' diff --git a/test/formatter/date_spec.dart b/test/formatter/date_spec.dart index f207b1e58..895532d84 100644 --- a/test/formatter/date_spec.dart +++ b/test/formatter/date_spec.dart @@ -47,7 +47,13 @@ void main() { }); it('should accept various locales', async(() { - expect(Intl.withLocale('de', () => date(noon, "medium"))).toEqual('3. Sep 2010 12:05:08'); + // Angular's pubspec for intl spans versions with breaking changes. This + // is ok for Angular, because they are not breaking changes for us (e.g. + // date formatting changes would be breaking changes, but CLDR updates are + // not.) The tests below use SV and FR which are mostly stable across + // different versions of the intl package (as opposed to DE which received + // CLDR updates.) + expect(Intl.withLocale('sv', () => date(noon, "medium"))).toEqual('3 sep 2010 12:05:08'); expect(Intl.withLocale('fr', () => date(noon, "medium"))).toEqual('3 sept. 2010 12:05:08'); })); }); From 3817b6e64555d358ebe126ffae2fd9b08345ac01 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Tue, 29 Jul 2014 15:52:30 -0700 Subject: [PATCH 03/23] fix(benchmark): Remove obsolete DI call --- benchmark/web/tree.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/benchmark/web/tree.dart b/benchmark/web/tree.dart index 97ba9786e..2710b73f7 100644 --- a/benchmark/web/tree.dart +++ b/benchmark/web/tree.dart @@ -246,7 +246,6 @@ class NgFreeTreeClass implements ShadowRootAware { // Main function runs the benchmark. main() { - setupModuleTypeReflector(); var cleanup, createDom; var module = new Module() From 40e2d9b07d6e76614e1abbe92442bf16d0ed29a5 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Tue, 29 Jul 2014 09:57:42 -0700 Subject: [PATCH 04/23] refactor(element_binder): Remove bindAttrName --- lib/core_dom/common.dart | 5 +---- lib/core_dom/element_binder.dart | 2 +- lib/core_dom/selector.dart | 8 ++++++-- test/core_dom/selector_spec.dart | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart index 24905af25..556f56ea8 100644 --- a/lib/core_dom/common.dart +++ b/lib/core_dom/common.dart @@ -6,15 +6,12 @@ List cloneElements(elements) { class MappingParts { final String attrName; - final String bindAttrName; final AST attrValueAST; final String mode; final AST dstAST; final String originalValue; - MappingParts(attrName, this.attrValueAST, this.mode, this.dstAST, this.originalValue) - : attrName = attrName, - bindAttrName = "bind-" + attrName; + MappingParts(this.attrName, this.attrValueAST, this.mode, this.dstAST, this.originalValue); } class DirectiveRef { diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 75ed412e8..79f4e919d 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -119,7 +119,7 @@ class ElementBinder { } // Check if there is a bind attribute for this mapping. - var bindAttr = bindAttrs[p.bindAttrName]; + var bindAttr = bindAttrs[p.attrName]; if (bindAttr != null) { if (p.mode == '<=>') { if (directiveScope == null) { diff --git a/lib/core_dom/selector.dart b/lib/core_dom/selector.dart index 7794694dc..1bb1b9977 100644 --- a/lib/core_dom/selector.dart +++ b/lib/core_dom/selector.dart @@ -21,6 +21,9 @@ part of angular.core.dom_internal; * * [*=/abc/] */ class DirectiveSelector { + static String BIND_PREFIX = "bind-"; + static int BIND_PREFIX_LENGTH = 5; + ElementBinderFactory _binderFactory; DirectiveMap _directives; Interpolate _interpolate; @@ -86,8 +89,9 @@ class DirectiveSelector { if (attrName.startsWith("on-")) { builder.onEvents[attrName] = value; - } else if (attrName.startsWith("bind-")) { - builder.bindAttrs[attrName] = _astParser(value, formatters: _formatters); + } else if (attrName.startsWith(BIND_PREFIX)) { + builder.bindAttrs[attrName.substring(BIND_PREFIX_LENGTH)] = + _astParser(value, formatters: _formatters); } attrs[attrName] = value; diff --git a/test/core_dom/selector_spec.dart b/test/core_dom/selector_spec.dart index 524359a4c..856c33b45 100644 --- a/test/core_dom/selector_spec.dart +++ b/test/core_dom/selector_spec.dart @@ -215,8 +215,8 @@ main() { it('should collect bind-* attributes', () { ElementBinder binder = selector(e('')); expect(binder.bindAttrs.keys.length).toEqual(2); - expect(binder.bindAttrs['bind-x'].expression).toEqual('y'); - expect(binder.bindAttrs['bind-z'].expression).toEqual('yy'); + expect(binder.bindAttrs['x'].expression).toEqual('y'); + expect(binder.bindAttrs['z'].expression).toEqual('yy'); }); }); From e3f17f53a0a90bb8b1838589008fe973b5d8fd48 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Thu, 24 Jul 2014 17:06:55 -0700 Subject: [PATCH 05/23] chore(examples): Add a PolymerJS paper component using attribute mappings --- example/bower.json | 24 ++++++++++++++++++++++++ example/web/bower_components | 1 + example/web/index.html | 1 + example/web/paper_progress.dart | 13 +++++++++++++ example/web/paper_progress.html | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 example/bower.json create mode 120000 example/web/bower_components create mode 100644 example/web/paper_progress.dart create mode 100644 example/web/paper_progress.html diff --git a/example/bower.json b/example/bower.json new file mode 100644 index 000000000..df727d27d --- /dev/null +++ b/example/bower.json @@ -0,0 +1,24 @@ +{ + "name": "paper-example", + "version": "0.0.1", + "homepage": "https://github.com/angular/angular.dart", + "authors": [ + "James deBoer " + ], + "description": "Paper with AngularDart", + "main": "web/index.html", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "polymer": "Polymer/polymer#~0.3.4", + "paper-elements": "Polymer/paper-elements#~0.3.4", + "core-elements": "Polymer/core-elements#~0.3.4" + } +} diff --git a/example/web/bower_components b/example/web/bower_components new file mode 120000 index 000000000..84cb58a3f --- /dev/null +++ b/example/web/bower_components @@ -0,0 +1 @@ +../bower_components/ \ No newline at end of file diff --git a/example/web/index.html b/example/web/index.html index c7a6b6860..fa87b6f9b 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -6,6 +6,7 @@
  • hello_world.html
  • todo.html
  • shadow_dom_components.html
  • +
  • paper_progress.html
  • diff --git a/example/web/paper_progress.dart b/example/web/paper_progress.dart new file mode 100644 index 000000000..039629dad --- /dev/null +++ b/example/web/paper_progress.dart @@ -0,0 +1,13 @@ +import 'package:angular/angular.dart'; +import 'package:angular/application_factory.dart'; + + +main() { + var injector = applicationFactory() + .run(); + var scope = injector.get(Scope); + scope.context['text'] = "Hello future"; + scope.context['max'] = 20; + scope.context['curValue'] = 12; + scope.apply(); +} diff --git a/example/web/paper_progress.html b/example/web/paper_progress.html new file mode 100644 index 000000000..55cb967d3 --- /dev/null +++ b/example/web/paper_progress.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + +

    A quick demo of the Polymer paper-progress widget in an AngularDart app.

    +

    The max ({{max}}) and value ({{curValue}}) properties are bound through attribute intropolation

    + +

    Text from Angular: {{text}}

    + +
    + +
    + +

    + +

    + + From bbb2244777d330ff5b2aa5eddf25b699c31394c7 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Fri, 25 Jul 2014 06:27:37 -0700 Subject: [PATCH 06/23] feat(element binder): Bind to Web Component properties Closes #1277 --- example/web/paper_progress.html | 7 ++- lib/core_dom/element_binder.dart | 10 ++++- test/core_dom/web_components_spec.dart | 62 ++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 test/core_dom/web_components_spec.dart diff --git a/example/web/paper_progress.html b/example/web/paper_progress.html index 55cb967d3..422f6f78a 100644 --- a/example/web/paper_progress.html +++ b/example/web/paper_progress.html @@ -12,15 +12,18 @@ href="bower_components/paper-progress/paper-progress.html"> +

    A quick demo of the Polymer paper-progress widget in an AngularDart app.

    -

    The max ({{max}}) and value ({{curValue}}) properties are bound through attribute intropolation

    +

    The max ({{max}}) and value ({{curValue}}) properties are bound through bind-* semantics

    Text from Angular: {{text}}

    - +

    diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 79f4e919d..0a8fac939 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -62,7 +62,7 @@ class ElementBinder { } bool get hasDirectivesOrEvents => - _usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty; + _usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty || bindAttrs.isNotEmpty; void _bindTwoWay(tasks, AST ast, scope, directiveScope, controller, AST dstAST) { @@ -283,6 +283,14 @@ class ElementBinder { _link(nodeInjector, scope, nodeAttrs); + var jsNode; + bindAttrs.forEach((String prop, ast) { + if (jsNode == null) jsNode = new js.JsObject.fromBrowserObject(node); + scope.watchAST(ast, (v, _) { + jsNode[prop] = v; + }); + }); + if (onEvents.isNotEmpty) { onEvents.forEach((event, value) { view.registerEvent(EventHandler.attrNameToEventName(event)); diff --git a/test/core_dom/web_components_spec.dart b/test/core_dom/web_components_spec.dart new file mode 100644 index 000000000..e20fbaa46 --- /dev/null +++ b/test/core_dom/web_components_spec.dart @@ -0,0 +1,62 @@ +library angular.dom.web_components_spec; + +import '../_specs.dart'; +import 'dart:js' as js; + +registerElement(String name, prototype) { + return (new js.JsObject.fromBrowserObject(document)).callMethod('registerElement', + [name, new js.JsObject.jsify({"prototype": prototype })]); +} + + + +main() { + describe('WebComponent support', () { + TestBed _; + + customProp(String prop, [elt]) { + if (elt == null) elt = _.rootElement; + return (new js.JsObject.fromBrowserObject(elt))[prop]; + } + + beforeEach((TestBed tb) { + _ = tb; + }); + + it('should create custom elements', () { + registerElement('tests-basic', {'prop-x': 6}); + + // Create a web component + _.compile(''); + expect(customProp('prop-x')).toEqual(6); + }); + + + it('should bind to Custom Element properties', () { + registerElement('tests-bound', {'prop-y': 10}); + _.compile(''); + + // Scope has not been digested yet + expect(customProp('prop-y')).toEqual(10); + + _.rootScope.apply(); + expect(customProp('prop-y')).toEqual(27); + }); + + + it('should bind to a non-existent property', () { + registerElement('tests-empty', {}); + _.compile(''); + _.rootScope.apply(); + expect(customProp('new-prop')).toEqual(27); + }); + + it('should bind to both directives and properties', () { + registerElement('tests-double', {}); + _.compile(''); + _.rootScope.apply(); + expect(customProp('ng-bind')).toEqual("hello"); + expect(_.rootElement).toHaveText('hello'); + }); + }); +} From 3948fd6d4bd5ba5badcfc66a6d434d8a776aa9cd Mon Sep 17 00:00:00 2001 From: James deBoer Date: Wed, 30 Jul 2014 11:07:04 -0700 Subject: [PATCH 07/23] chore(example): Add paper-checkbox to the paper demo --- example/pubspec.lock | 16 ++++++++++++---- example/web/index.html | 2 +- example/web/{paper_progress.dart => paper.dart} | 0 example/web/{paper_progress.html => paper.html} | 17 +++++++++++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) rename example/web/{paper_progress.dart => paper.dart} (100%) rename example/web/{paper_progress.html => paper.html} (56%) diff --git a/example/pubspec.lock b/example/pubspec.lock index 0e0236b9e..e83147353 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -4,7 +4,7 @@ packages: analyzer: description: analyzer source: hosted - version: "0.13.6" + version: "0.18.0" angular: description: path: ".." @@ -26,7 +26,7 @@ packages: code_transformers: description: code_transformers source: hosted - version: "0.1.3" + version: "0.1.6" collection: description: collection source: hosted @@ -34,7 +34,7 @@ packages: di: description: di source: hosted - version: "1.1.0" + version: "2.0.1" html5lib: description: html5lib source: hosted @@ -42,7 +42,7 @@ packages: intl: description: intl source: hosted - version: "0.9.9" + version: "0.8.10+4" logging: description: logging source: hosted @@ -51,6 +51,10 @@ packages: description: matcher source: hosted version: "0.10.0" + meta: + description: meta + source: hosted + version: "0.8.8" mock: description: mock source: hosted @@ -75,6 +79,10 @@ packages: description: stack_trace source: hosted version: "0.9.3+1" + typed_mock: + description: typed_mock + source: hosted + version: "0.0.4" unittest: description: unittest source: hosted diff --git a/example/web/index.html b/example/web/index.html index fa87b6f9b..760fd9e06 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -6,7 +6,7 @@

  • hello_world.html
  • todo.html
  • shadow_dom_components.html
  • -
  • paper_progress.html
  • +
  • paper.html
  • diff --git a/example/web/paper_progress.dart b/example/web/paper.dart similarity index 100% rename from example/web/paper_progress.dart rename to example/web/paper.dart diff --git a/example/web/paper_progress.html b/example/web/paper.html similarity index 56% rename from example/web/paper_progress.html rename to example/web/paper.html index 422f6f78a..dae6262a4 100644 --- a/example/web/paper_progress.html +++ b/example/web/paper.html @@ -10,14 +10,18 @@ - + + -

    A quick demo of the Polymer paper-progress widget in an AngularDart app.

    +

    Polymer components inside a AngularDart app

    +

    paper-progress

    +

    This is a simple component that doesn't generate events; the only things that is required is property binding

    The max ({{max}}) and value ({{curValue}}) properties are bound through bind-* semantics

    Text from Angular: {{text}}

    @@ -32,5 +36,14 @@

    A quick demo of the Polymer paper-progress widget in an AngularDart app.

    + +

    paper-checkbox

    +

    The checkbox will generate an event every time the value is changed

    +

    AngularDart can listen to these events through the on-* syntax

    + +
    + +
    +

    Every the value changes, the curValue ({{curValue}}) scope variable is multiplied by 1.02

    From ce1aae32f96c6a46d33cede5570182f729dd5f48 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Wed, 30 Jul 2014 12:27:15 -0700 Subject: [PATCH 08/23] chore(karma): Restrict the numbers of files watched for faster startup --- karma.conf.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index 34c915965..4a62c722b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -13,7 +13,10 @@ module.exports = function(config) { 'test/*.dart', 'test/**/*_spec.dart', 'test/config/init_guinness.dart', - {pattern: '**/*.dart', watched: true, included: false, served: true}, + {pattern: 'packages/**/*.dart', watched: true, included: false, served: true}, + {pattern: 'test/**/*.dart', watched: true, included: false, served: true}, + {pattern: 'bin/**/*.dart', watched: true, included: false, served: true}, + {pattern: 'lib/**/*.dart', watched: true, included: false, served: true}, 'packages/browser/dart.js' ], From 0f1226efdb96d2c4eb703721d721dd53b1fb7248 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Wed, 30 Jul 2014 13:45:54 -0700 Subject: [PATCH 09/23] chore(example): In the Paper example, make the checkbox less confusing --- example/web/paper.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/web/paper.html b/example/web/paper.html index dae6262a4..3e2ea0de4 100644 --- a/example/web/paper.html +++ b/example/web/paper.html @@ -42,8 +42,8 @@

    paper-checkbox

    AngularDart can listen to these events through the on-* syntax

    - +
    -

    Every the value changes, the curValue ({{curValue}}) scope variable is multiplied by 1.02

    +

    Every the value changes, the curValue ({{curValue}}) scope variable will update

    From 34682738225768242038f2e69c4f9dc501a6cf7a Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 23 Jul 2014 16:29:29 +0200 Subject: [PATCH 10/23] perf(dccd): Drop useless checks in _recordAdd(), previous could never be null as the list is never empty (it has at least a marker). in _recordRemove(), when the list is composed of a single record, it must be the record we're trying to remove (and we assert that). Closes #1256 --- lib/change_detection/dirty_checking_change_detector.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index fd86624cb..dbc178bb3 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -175,12 +175,12 @@ class DirtyCheckingChangeDetectorGroup implements ChangeDetectorGroup { DirtyCheckingRecord _recordAdd(DirtyCheckingRecord record) { DirtyCheckingRecord previous = _recordTail; - DirtyCheckingRecord next = previous == null ? null : previous._nextRecord; + DirtyCheckingRecord next = previous._nextRecord; record._nextRecord = next; record._prevRecord = previous; - if (previous != null) previous._nextRecord = record; + previous._nextRecord = record; if (next != null) next._prevRecord = record; _recordTail = record; @@ -194,7 +194,8 @@ class DirtyCheckingChangeDetectorGroup implements ChangeDetectorGroup { DirtyCheckingRecord previous = record._prevRecord; DirtyCheckingRecord next = record._nextRecord; - if (record == _recordHead && record == _recordTail) { + if (_recordHead == _recordTail) { + assert(record == _recordHead); // we are the last one, must leave marker behind. _recordHead = _recordTail = _marker; _marker._nextRecord = next; From 9aba6fb0813d13aa2e8854140b955cad8b6579e9 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 9 Jun 2014 21:48:03 +0200 Subject: [PATCH 11/23] refactor(dccd): Drop the insertBefore arg from DuplicateMap.put() Fixes #1128 Closes #1132 The insertBefore argument is never used in the current code --- .../dirty_checking_change_detector.dart | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index dbc178bb3..6d7c3e4f5 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -1303,40 +1303,22 @@ class _DuplicateItemRecordList { ItemRecord _head, _tail; /** - * Add the [record] before the [insertBefore] in the list of duplicates or at the end of the list - * when no [insertBefore] is specified. + * Append the [record] to the list of duplicates. * * Note: by design all records in the list of duplicates hold the save value in [record.item]. */ - void add(ItemRecord record, ItemRecord insertBefore) { - assert(insertBefore == null || insertBefore.item == record.item); + void add(ItemRecord record) { if (_head == null) { - /// pushing the first [ItemRecord] to the list - assert(insertBefore == null); _head = _tail = record; record._nextDup = null; record._prevDup = null; } else { - // adding a duplicate [ItemRecord] to the list assert(record.item == _head.item || record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN); - if (insertBefore == null) { - _tail._nextDup = record; - record._prevDup = _tail; - record._nextDup = null; - _tail = record; - } else { - var prev = insertBefore._prevDup; - var next = insertBefore; - record._prevDup = prev; - record._nextDup = next; - if (prev == null) { - _head = record; - } else { - prev._nextDup = record; - } - next._prevDup = record; - } + _tail._nextDup = record; + record._prevDup = _tail; + record._nextDup = null; + _tail = record; } } @@ -1390,16 +1372,16 @@ class _DuplicateItemRecordList { * The list of duplicates is implemented by [_DuplicateItemRecordList]. */ class DuplicateMap { - static final nanKey = const Object(); + static final _nanKey = const Object(); final map = new HashMap(); - void put(ItemRecord record, [ItemRecord insertBefore = null]) { + void put(ItemRecord record) { var key = _getKey(record.item); _DuplicateItemRecordList duplicates = map[key]; if (duplicates == null) { duplicates = map[key] = new _DuplicateItemRecordList(); } - duplicates.add(record, insertBefore); + duplicates.add(record); } /** @@ -1436,7 +1418,7 @@ class DuplicateMap { } /// Required to handle num.NAN as a Map value - dynamic _getKey(value) => value is num && value.isNaN ? nanKey : value; + dynamic _getKey(value) => value is num && value.isNaN ? _nanKey : value; String toString() => "DuplicateMap($map)"; } From dc2c48062621f7b21a5bbc3963ddc5248bb284b9 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 31 Jul 2014 12:54:56 +0200 Subject: [PATCH 12/23] refactor: code cleanup Closes #1061 --- lib/core/annotation_src.dart | 6 +++--- lib/core/formatter.dart | 2 +- lib/core/interpolate.dart | 3 +-- lib/core/scope.dart | 2 +- lib/core_dom/common.dart | 6 ++---- lib/core_dom/directive_map.dart | 12 ++++++------ lib/core_dom/element_binder.dart | 2 +- lib/core_dom/node_cursor.dart | 2 +- 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/core/annotation_src.dart b/lib/core/annotation_src.dart index 11357d563..d40b126be 100644 --- a/lib/core/annotation_src.dart +++ b/lib/core/annotation_src.dart @@ -15,8 +15,7 @@ typedef void DirectiveBinderFn(DirectiveBinder module); RegExp _ATTR_NAME = new RegExp(r'\[([^\]]+)\]$'); -Directive cloneWithNewMap(Directive annotation, map) - => annotation._cloneWithNewMap(map); +Directive cloneWithNewMap(Directive annotation, map) => annotation._cloneWithNewMap(map); String mappingSpec(DirectiveAnnotation annotation) => annotation._mappingSpec; @@ -219,7 +218,8 @@ abstract class Directive { this.exportExpressionAttrs: const [] }); - toString() => selector; + String toString() => selector; + Directive _cloneWithNewMap(newMap); } diff --git a/lib/core/formatter.dart b/lib/core/formatter.dart index 484282b67..0bbcb2e99 100644 --- a/lib/core/formatter.dart +++ b/lib/core/formatter.dart @@ -24,7 +24,7 @@ class FormatterMap { }); } - call(String name) => _injector.get(this[name]); + Function call(String name) => _injector.get(this[name]); Type operator[](String name) { Type formatterType = _map[name]; diff --git a/lib/core/interpolate.dart b/lib/core/interpolate.dart index 792ef82d9..2e043c08f 100644 --- a/lib/core/interpolate.dart +++ b/lib/core/interpolate.dart @@ -61,8 +61,7 @@ class Interpolate implements Function { // formatter expParts.add(_wrapInQuotes(template.substring(index, startIdx))); } - expParts.add('(' + template.substring(startIdx + startLen, endIdx) + - '|stringify)'); + expParts.add('(' + template.substring(startIdx + startLen, endIdx) + '|stringify)'); index = endIdx + endLen; hasInterpolation = true; diff --git a/lib/core/scope.dart b/lib/core/scope.dart index fdf194264..978abaa5d 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -217,7 +217,7 @@ class Scope { } else if (expression.startsWith(':')) { expression = expression.substring(1); fn = (value, last) { - if (value != null) reactionFn(value, last); + if (value != null) reactionFn(value, last); }; } } diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart index 556f56ea8..1dc25fd04 100644 --- a/lib/core_dom/common.dart +++ b/lib/core_dom/common.dart @@ -1,8 +1,6 @@ part of angular.core.dom_internal; -List cloneElements(elements) { - return elements.map((el) => el.clone(true)).toList(); -} +List cloneElements(elements) => elements.map((el) => el.clone(true)).toList(); class MappingParts { final String attrName; @@ -23,7 +21,7 @@ class DirectiveRef { final Directive annotation; final String value; final AST valueAST; - final mappings = new List(); + final mappings = []; DirectiveRef(this.element, type, this.annotation, this.typeKey, [ this.value, this.valueAST ]) : type = type, diff --git a/lib/core_dom/directive_map.dart b/lib/core_dom/directive_map.dart index 12a195c5a..576587763 100644 --- a/lib/core_dom/directive_map.dart +++ b/lib/core_dom/directive_map.dart @@ -4,13 +4,13 @@ class DirectiveTypeTuple { final Directive directive; final Type type; DirectiveTypeTuple(this.directive, this.type); - toString() => '@$directive#$type'; + String toString() => '@$directive#$type'; } @Injectable() class DirectiveMap { final Map> map = new HashMap>(); - DirectiveSelectorFactory _directiveSelectorFactory; + final DirectiveSelectorFactory _directiveSelectorFactory; FormatterMap _formatters; DirectiveSelector _selector; @@ -20,10 +20,10 @@ class DirectiveMap { this._directiveSelectorFactory) { (injector as ModuleInjector).types.forEach((type) { metadataExtractor(type) - .where((annotation) => annotation is Directive) - .forEach((Directive directive) { - map.putIfAbsent(directive.selector, () => []).add(new DirectiveTypeTuple(directive, type)); - }); + .where((annotation) => annotation is Directive) + .forEach((Directive dir) { + map.putIfAbsent(dir.selector, () => []).add(new DirectiveTypeTuple(dir, type)); + }); }); } diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 0a8fac939..737b635fa 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -91,7 +91,7 @@ class ElementBinder { } } - _bindOneWay(tasks, ast, scope, AST dstAST, controller) { + void _bindOneWay(tasks, ast, scope, AST dstAST, controller) { var taskId = (tasks != null) ? tasks.registerTask() : 0; scope.watchAST(ast, (v, _) { diff --git a/lib/core_dom/node_cursor.dart b/lib/core_dom/node_cursor.dart index 5b6109a8b..ae71a7252 100644 --- a/lib/core_dom/node_cursor.dart +++ b/lib/core_dom/node_cursor.dart @@ -45,5 +45,5 @@ class NodeCursor { NodeCursor remove() => new NodeCursor([elements.removeAt(index)..remove()]); - toString() => "[NodeCursor: $elements $index]"; + String toString() => "[NodeCursor: $elements $index]"; } From 781b2771de60d8f56c1b585ca77f9bb8da9ed8eb Mon Sep 17 00:00:00 2001 From: James deBoer Date: Wed, 30 Jul 2014 12:28:09 -0700 Subject: [PATCH 13/23] feat(element binder): Two way binding for Web Components Closes #1282 --- example/web/paper.html | 14 ++++++++++++-- lib/core_dom/element_binder.dart | 15 ++++++++++++++- test/core_dom/web_components_spec.dart | 25 ++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/example/web/paper.html b/example/web/paper.html index 3e2ea0de4..167b712fb 100644 --- a/example/web/paper.html +++ b/example/web/paper.html @@ -12,15 +12,18 @@ href="bower_components/paper-progress/paper-progress.html"> +

    Polymer components inside a AngularDart app

    -

    paper-progress

    +

    Property binding: paper-progress

    This is a simple component that doesn't generate events; the only things that is required is property binding

    The max ({{max}}) and value ({{curValue}}) properties are bound through bind-* semantics

    @@ -37,7 +40,7 @@

    paper-progress

    -

    paper-checkbox

    +

    Events: paper-checkbox

    The checkbox will generate an event every time the value is changed

    AngularDart can listen to these events through the on-* syntax

    @@ -45,5 +48,12 @@

    paper-checkbox

    Every the value changes, the curValue ({{curValue}}) scope variable will update

    + +

    Two-way binding: paper-slider

    +

    The slide is bound to the curValue scope variable ({{curValue}})

    + +
    + +
    diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 737b635fa..814bd9148 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -284,13 +284,26 @@ class ElementBinder { _link(nodeInjector, scope, nodeAttrs); var jsNode; - bindAttrs.forEach((String prop, ast) { + List bindAssignableProps = []; + bindAttrs.forEach((String prop, AST ast) { if (jsNode == null) jsNode = new js.JsObject.fromBrowserObject(node); scope.watchAST(ast, (v, _) { jsNode[prop] = v; }); + + if (ast.parsedExp.isAssignable) { + bindAssignableProps.add([prop, ast.parsedExp]); + } }); + if (bindAssignableProps.isNotEmpty) { + node.addEventListener('change', (_) { + bindAssignableProps.forEach((propAndExp) { + propAndExp[1].assign(scope.context, jsNode[propAndExp[0]]); + }); + }); + } + if (onEvents.isNotEmpty) { onEvents.forEach((event, value) { view.registerEvent(EventHandler.attrNameToEventName(event)); diff --git a/test/core_dom/web_components_spec.dart b/test/core_dom/web_components_spec.dart index e20fbaa46..e7a63c934 100644 --- a/test/core_dom/web_components_spec.dart +++ b/test/core_dom/web_components_spec.dart @@ -14,11 +14,24 @@ main() { describe('WebComponent support', () { TestBed _; - customProp(String prop, [elt]) { + /** + * Returns the property [prop] as read through the JS interface. + * [elt] is optional and defaults to the [TestBed]'s rootElement. + */ + customProp(String prop, [Element elt]) { if (elt == null) elt = _.rootElement; return (new js.JsObject.fromBrowserObject(elt))[prop]; } + /** + * Sets the property [prop] to [value] through the JS interface. + * [elt] is optional and defaults to the [TestBed]'s rootElement. + */ + void setCustomProp(String prop, value, [Element elt]) { + if (elt == null) elt = _.rootElement; + (new js.JsObject.fromBrowserObject(_.rootElement))[prop] = value; + } + beforeEach((TestBed tb) { _ = tb; }); @@ -58,5 +71,15 @@ main() { expect(customProp('ng-bind')).toEqual("hello"); expect(_.rootElement).toHaveText('hello'); }); + + it('should support two-way bindings for components that trigger a change event', () { + registerElement('tests-twoway', {}); + _.compile(''); + + setCustomProp('prop', 6); + _.rootElement.dispatchEvent(new Event.eventType('CustomEvent', 'change')); + + expect(_.rootScope.context['x']).toEqual(6); + }); }); } From 307c537690607ce156f7ea2a8b080509d8a7f690 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Thu, 31 Jul 2014 09:20:24 -0700 Subject: [PATCH 14/23] chore(presubmit): Do not use the g3v1x task for presubmit Closes #1214 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7079fefbf..aa0313577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,15 @@ node_js: - '0.11' env: matrix: + - JOB=unit-stable + CHANNEL=stable + TESTS=vm + BROWSERS=DartiumWithWebPlatform - JOB=unit-g3stable CHANNEL=stable TESTS=vm BROWSERS=DartiumWithWebPlatform USE_G3=YES - - JOB=unit-stable - CHANNEL=stable - TESTS=vm - BROWSERS=DartiumWithWebPlatform - JOB=unit-dev CHANNEL=dev TESTS=vm From 45144a10bd1f01da8ee02c1d73b465b51c8e7b2f Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 25 Jul 2014 14:00:24 -0700 Subject: [PATCH 15/23] chore(release): v0.13.0 (see the v0.13.0 tag for the exact history of this version) --- CHANGELOG.md | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 250 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4a2945a..08f621f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,252 @@ + +# v0.13.0 tempus-fugitification (2014-07-25) + +## Bug Fixes + +- **DynamicParser:** Correctly handle throwing exceptions from method. + ([82ca6bad](https://github.com/angular/angular.dart/commit/82ca6bad7d930d645e57e5d0f1d795e0b97501dc), + [#971](https://github.com/angular/angular.dart/issues/971), [#1064](https://github.com/angular/angular.dart/issues/1064)) +- **Http:** + - Auxiliary methods like get, put, post, etc. do not set type restriction on data being sent. + ([68c3e80a](https://github.com/angular/angular.dart/commit/68c3e80afa258f5307e1655beadcf85257b35b11), + [#1051](https://github.com/angular/angular.dart/issues/1051)) + - Fix NoSuchMethodError in Http when cache set to true and update documentation about cache usage. + ([5b483324](https://github.com/angular/angular.dart/commit/5b483324bd73955ed799b9ba363dbc2557e2b09a), + [#1066](https://github.com/angular/angular.dart/issues/1066)) +- **NgModel:** Read the view value in the flush phase + ([75c2f170](https://github.com/angular/angular.dart/commit/75c2f170e1d4de3f40666835c176cff73b08ee82)) +- **WatchGroup:** Handle watching elements of array that were removed. + ([8c271f78](https://github.com/angular/angular.dart/commit/8c271f78941438059b0e71577e363fa48146441f), + [#1046](https://github.com/angular/angular.dart/issues/1046)) +- **benchmark:** + - typo in element tag names + ([86de17c2](https://github.com/angular/angular.dart/commit/86de17c2432fb92cdc8efcb7d671d1830649b300), + [#1220](https://github.com/angular/angular.dart/issues/1220)) + - incorrect mirrors import + ([b74d5682](https://github.com/angular/angular.dart/commit/b74d5682b9437880029bdc456466ade9ba3e7722)) + - fix standard deviation calculation + ([1f59d114](https://github.com/angular/angular.dart/commit/1f59d1149f962e6b4008fd7ab6a050f9a39108cd)) +- **cache:** + - Make UnboundedCache extend Cache + ([08e73054](https://github.com/angular/angular.dart/commit/08e73054a4efc06861ea85481e7bbd4c07f502d3), + [#1174](https://github.com/angular/angular.dart/issues/1174)) + - Do not export the Cache symbol. + ([016d463c](https://github.com/angular/angular.dart/commit/016d463c1975e04e71139310c297399f5e6c14cb)) +- **change_detector:** fix NaN move detection in collections + ([f01e2867](https://github.com/angular/angular.dart/commit/f01e2867543cb439dd1ad1db41d9c616f418baab), + [#1136](https://github.com/angular/angular.dart/issues/1136), [#1149](https://github.com/angular/angular.dart/issues/1149)) +- **component factory:** Only create a single ShadowDomComponentFactory + ([707f701c](https://github.com/angular/angular.dart/commit/707f701c3cf8a96425888f61dd3d753dad5e28a7)) +- **dccd:** + - fix false positive for collection moves + ([ea3eb1e0](https://github.com/angular/angular.dart/commit/ea3eb1e03eea9033f9bc7177941e41bbb5a32c2a), + [#1105](https://github.com/angular/angular.dart/issues/1105)) + - fix removals reporting + ([8dbeefc4](https://github.com/angular/angular.dart/commit/8dbeefc4abb412706991f99f5c8d21d1bebd8237)) +- **di:** Remove deprecated calls to DI bind(Type, inject[]). +- **directive:** Support multiple directives with same selector. + ([01488977](https://github.com/angular/angular.dart/commit/014889771ae4a80f7f934b60f02cbb1d37d5ed41)) +- **element binder:** + - New-style Module.bind for AttrMustache + ([cfa11e05](https://github.com/angular/angular.dart/commit/cfa11e050fcbc43539e37b51571c40e3270c999a)) + - Use the new-style Module.bind(toFactory) syntax + ([bed6bcbe](https://github.com/angular/angular.dart/commit/bed6bcbec3a635b9193ac79a7d8249a13f67ea95)) +- **examples:** do not use shadow DOM + ([0cf209bb](https://github.com/angular/angular.dart/commit/0cf209bb091839b8e1192e2fd54a0ced1a057201)) +- **expression extractor:** Do not use implicit DI + ([105b41f4](https://github.com/angular/angular.dart/commit/105b41f4247dac8b59738ad740fff545320431a2)) +- **introspection:** + - getTestability should throw when ElementProbes are unavailable + ([158e9aa7](https://github.com/angular/angular.dart/commit/158e9aa70cf38141959a185b2c39e33ead09e543)) + - work around http://dartbug.com/17752 + ([384039a1](https://github.com/angular/angular.dart/commit/384039a1c07ee66ac2a886e58b3eb3c12bcba04d)) +- **karma:** remove saucelabs from default browser list. + ([989992de](https://github.com/angular/angular.dart/commit/989992deec9f5253ea9028eeb6b74022736fae36)) +- **ng-model:** Turn off failing test until Dart 1.6 is stable. + ([e8825165](https://github.com/angular/angular.dart/commit/e88251651fb466e48e6f1a208cd24df7fade59f1), + [#1234](https://github.com/angular/angular.dart/issues/1234)) +- **ng-view:** cleanup should not destroy an already destroyed scope + ([5cb46a16](https://github.com/angular/angular.dart/commit/5cb46a1608f41f8a1c7db1f473c7efccc265598f), + [#1182](https://github.com/angular/angular.dart/issues/1182)) +- **ng_repeat:** fix ng_repeat not moving views for elements that have not moved + ([559a685e](https://github.com/angular/angular.dart/commit/559a685e218c453eea158eee068d2e0afaf718eb), + [#1154](https://github.com/angular/angular.dart/issues/1154), [#1155](https://github.com/angular/angular.dart/issues/1155)) +- **parser:** ensure only one instance of dynamic parser + ([f2c45758](https://github.com/angular/angular.dart/commit/f2c457580de28eb250ddce455edd3ede4af6c847)) +- **pubspec:** Add missing upper bound version constraints + ([cee0d727](https://github.com/angular/angular.dart/commit/cee0d727d9b18e6d21350bf74e023565e3908f57)) +- **registry_dynamic:** Do not use HashMaps. + ([04624f21](https://github.com/angular/angular.dart/commit/04624f21813be8efc5bffaa8daf0029e777958a2)) +- **scope:** + - remove deprecation warning for scope.watch with context + ([1c7c0ba3](https://github.com/angular/angular.dart/commit/1c7c0ba3c8745a73a03c14845c70e83662c55d3e)) + - Use runAsync for microtasks in digest. + ([d1e745e0](https://github.com/angular/angular.dart/commit/d1e745e04ba1c9fdf23b416d299e193b77e3cc53)) +- **transcluding component:** Perfer getByKey over get + ([6f3587d2](https://github.com/angular/angular.dart/commit/6f3587d21d00da296a40c4879c7e8c248c0b06a8)) +- **travis:** Work around Travis breakages + ([be76be4f](https://github.com/angular/angular.dart/commit/be76be4fde537bf9c52ae307541f045e8b06c8f3)) +- **various:** Use the new-style Module.bind(toFactory) syntax + ([a30c0a57](https://github.com/angular/angular.dart/commit/a30c0a5726e71491646506b9306ce53138c47c44)) +- **watch_group:** fix for NaN !== NaN + ([d24ff897](https://github.com/angular/angular.dart/commit/d24ff8979e3b2c6a72f388f478ea0aab655f35cb), + [#1146](https://github.com/angular/angular.dart/issues/1146)) +- **web platform:** Do not barf on attribute selectors. + ([f2b83930](https://github.com/angular/angular.dart/commit/f2b83930f2473f7d6b1f1796a8e1f07ec4e0422e)) + + +## Features + +- **animate:** Allowed property to turn off all animations + ([b3f2e6ca](https://github.com/angular/angular.dart/commit/b3f2e6caa5bc4047af59f730c17fcb5a34f1bc9f)) +- **benchmark:** + - improve layout of data by moving averages to dedicated row + ([3330d657](https://github.com/angular/angular.dart/commit/3330d657cd5c592924816a4a88876881de2460da)) + - calculate coefficient of variation + ([fc09af2c](https://github.com/angular/angular.dart/commit/fc09af2c880bf741abe8c9284805a92bd63f8742)) + - add standard deviation to report + ([13e87e06](https://github.com/angular/angular.dart/commit/13e87e06fb3972b7c0b6d35b08351a639a329eba)) + - calculate relative margin of error and simplify report + ([4614cc06](https://github.com/angular/angular.dart/commit/4614cc061011b01809eb884d7ad127d89fe4e547)) + - add confidence and stability info to averages + ([bd17bbe7](https://github.com/angular/angular.dart/commit/bd17bbe7e61b83789f4cc3f36d6d4222b37f0279)) + - add statistical functions to calculate confidence interval + ([26f7defe](https://github.com/angular/angular.dart/commit/26f7defe4bc2b70c9fd746b42053e4fa9cefc63b)) + - add ability to profile memory usage per iteration + ([afe55814](https://github.com/angular/angular.dart/commit/afe558141802f84f8cd34d61bfd4dd1e7ca9eba4)) + - improve sampling UI + ([e1c17d90](https://github.com/angular/angular.dart/commit/e1c17d904177c186825d939742800d94e6bd6304)) + - change samples input type from range to text + ([387933d0](https://github.com/angular/angular.dart/commit/387933d03fa908d7fe29fc8d673847cd18d824cc)) + - record gc time for each test run + ([dfdf67b5](https://github.com/angular/angular.dart/commit/dfdf67b50921b6ff69d9fe2360fbb7b55bf6d1ec)) + - add ability to adjust sample quantity + ([a98663d9](https://github.com/angular/angular.dart/commit/a98663d9bd148839d14e1f95d887560b58bc63f2)) + - add automatic gc before each test + ([fe1f74d0](https://github.com/angular/angular.dart/commit/fe1f74d0d845bcfe3b416ada4bb8849a1ecba3ff), + [#1133](https://github.com/angular/angular.dart/issues/1133)) +- **cache:** + - Add a JS interface to CacheRegister + ([435d9987](https://github.com/angular/angular.dart/commit/435d9987beaf2965a75ed13a18f29bb672c32806), + [#1181](https://github.com/angular/angular.dart/issues/1181)) + - Add existing caches to CacheRegister + ([59003705](https://github.com/angular/angular.dart/commit/59003705f7225760151136951361d1e71a453db4), + [#1165](https://github.com/angular/angular.dart/issues/1165)) + - Move cache out of core, add a CacheRegister + ([be62c48e](https://github.com/angular/angular.dart/commit/be62c48e3b4c9dbe5a0b4cb7fcadec4e29255861)) +- **compiler:** + - Backport DirectiveBinder API from #1178 to allow gradual migration. + ([1f3cca42](https://github.com/angular/angular.dart/commit/1f3cca429e2074400ce7e0f476d972d6b54e2774)) +- **element binder:** Use a child scope instead of Scope.watch(context:o) + ([6051340b](https://github.com/angular/angular.dart/commit/6051340bb583382ff0157e255eafa537ab5564aa)) +- **form:** Add support for `input[type=color]` + ([0064ef5c](https://github.com/angular/angular.dart/commit/0064ef5c7db5ce2286cc7d626b2cc620429e4da3), + [#611](https://github.com/angular/angular.dart/issues/611), [#1080](https://github.com/angular/angular.dart/issues/1080)) +- **http:** + - support coalescing http requests + ([3e44a542](https://github.com/angular/angular.dart/commit/3e44a542faccd3f5e5f7861cff44e221279bcc42)) + - run interceptors synchronously until first non-sync interceptor + ([38d3cfd6](https://github.com/angular/angular.dart/commit/38d3cfd6ff680dd92ce3b34018fdb7b148a29ea9)) +- **mock:** Add timer queue checks in mock zone + ([98e61b77](https://github.com/angular/angular.dart/commit/98e61b77d91a58fe13406ded2b94167b595701d5), + [#1157](https://github.com/angular/angular.dart/issues/1157)) +- **router:** added vetoable preLeave event + ([ddd9e414](https://github.com/angular/angular.dart/commit/ddd9e4147c0c88a374345bf8b32df6cf57740ac4), + [#1070](https://github.com/angular/angular.dart/issues/1070)) +- **scope:** + - Expose Scope.watchAST as a public API + ([ecd75ce7](https://github.com/angular/angular.dart/commit/ecd75ce71460a3bb1e87e0c9371c8d8ac892c512)) + - Deprecate Scope.watch's context parameter. + ([e8a5ce73](https://github.com/angular/angular.dart/commit/e8a5ce734b00fd22f700e2fc1be5507dda195638)) + - Instrument Scope to use User tags in Dart Obervatory + ([c21ac7ea](https://github.com/angular/angular.dart/commit/c21ac7eaec3fd15bba7a124fdfb3e9680092a0f2), + [#1138](https://github.com/angular/angular.dart/issues/1138)) + - Use VmTurnZone.onScheduleMicrotask in Scope + ([81667aad](https://github.com/angular/angular.dart/commit/81667aad6a2da1efa39adaf333b8ebfe297c88b1), + [#984](https://github.com/angular/angular.dart/issues/984)) +- **testability:** + - whenStable replaces notifyWhenNoOutstandingRequests + ([5ef596d1](https://github.com/angular/angular.dart/commit/5ef596d10f50d2739eba6a7be9555f240f12abf6)) + - implement the testability for ProtractorDart + ([f2d1f2e9](https://github.com/angular/angular.dart/commit/f2d1f2e9e64d18fe5407f67555eb1c30110bd419)) + + +## Performance Improvements + +- **ChangeDetector:** + - create _EvalWatchRecord#namedArgs lazily + ([42e53b86](https://github.com/angular/angular.dart/commit/42e53b8663936b3637b2d13de4f3f56db27e94cf)) + - lazy initialize DuplicateMap + ([11629dee](https://github.com/angular/angular.dart/commit/11629deea92bb0f0b341ba6eb2f04a5072fbdcd2)) +- **View:** Improve View instantiation speed and memory consumption. + ([494deda5](https://github.com/angular/angular.dart/commit/494deda594f39b422e4c2f5def1a2cbaf749efba)) +- **cd:** fewer string concatenations (10% improvement) + ([a6526803](https://github.com/angular/angular.dart/commit/a6526803fb07f126414f0259d4d0baf8e89d70b1)) +- **compiler:** + - +6% Pre-compute ViewFactories, styles for components. + ([be3cdd41](https://github.com/angular/angular.dart/commit/be3cdd4147d7917dea5fcca301cb73057f0f604d), + [#1134](https://github.com/angular/angular.dart/issues/1134)) + - An option to disable the ElementProbe. + ([9f0c7bca](https://github.com/angular/angular.dart/commit/9f0c7bcab2915ffa7509a28eaff1d1762f1d9bf0), + [#1118](https://github.com/angular/angular.dart/issues/1118), [#1131](https://github.com/angular/angular.dart/issues/1131)) + - Pre-compile Scope.watch ASTs for attribute mustaches + ([90df4eb2](https://github.com/angular/angular.dart/commit/90df4eb2012a0bf21f33bf219be544bd535f765c), + [#1088](https://github.com/angular/angular.dart/issues/1088)) + - Precompute Scope.watch AST for TextMustache + ([daf8d5af](https://github.com/angular/angular.dart/commit/daf8d5afdc8f0f0f66eed5de6eea4a5df2280a9f)) +- **element binder:** Do not create tasklists when not needed + ([a33891ea](https://github.com/angular/angular.dart/commit/a33891ea915a4faf98caf78725dfc093b213744b)) +- **scope:** Cache the Scope.watch AST. + ([05e2c576](https://github.com/angular/angular.dart/commit/05e2c57625f1cc2c56c3cc91534420d066e41389), + [#1173](https://github.com/angular/angular.dart/issues/1173)) +- **various:** Avoid putIfAbsent + ([57da29d7](https://github.com/angular/angular.dart/commit/57da29d7ea0e20aecde60ac42788b30aedcaa3b6)) +- **view cache:** Avoid http.get + ([db72a4fc](https://github.com/angular/angular.dart/commit/db72a4fc99e1d2b9921b361198e0c57d93091f5e), + [#1108](https://github.com/angular/angular.dart/issues/1108)) +- **view factory:** 14% Precompute linking information for nodes + ([eac36d1d](https://github.com/angular/angular.dart/commit/eac36d1d26a283560742b3555886f5db99ee9c65), + [#1194](https://github.com/angular/angular.dart/issues/1194), [#1196](https://github.com/angular/angular.dart/issues/1196)) + + +## Breaking Changes + +- **Scope:** due to [81667aad](https://github.com/angular/angular.dart/commit/81667aad6a2da1efa39adaf333b8ebfe297c88b1), + + +Previously a micro task registered in flush phase would cause a new +digest cycle after the current digest cycle. The new behavior +will cause an error. + +Closes #984 +- **View:** due to [494deda5](https://github.com/angular/angular.dart/commit/494deda594f39b422e4c2f5def1a2cbaf749efba), + + +- Injector no longer supports visibility +- The Directive:module instead of returning Module now takes + DirectiveModule (which supports visibility) +- Application Injector and DirectiveInjector now have separate trees. + (The root if DirectiveInjector is ApplicationInjector) +- **scope:** due to [d1e745e0](https://github.com/angular/angular.dart/commit/d1e745e04ba1c9fdf23b416d299e193b77e3cc53), + + +Microtasks scheduled in flush will process in current cycle, but they +are not allowed to do model changes. + +Microtasks scheduled in digest will be executed in digest, counting +towards the ScopeDigestTTL. +- **testability:** due to [5ef596d1](https://github.com/angular/angular.dart/commit/5ef596d10f50d2739eba6a7be9555f240f12abf6), + + NOTE: This only affects you if you are calling this API directly. If + you are using ProtractorDart, then you are insulated from this change. + + To update your code, rename all references to the + notifyWhenNoOutstandingRequests(callback) method on the testability + object to whenStable(callback). + + + + # v0.12.0 sprightly-argentinosaurus (2014-06-03) diff --git a/pubspec.yaml b/pubspec.yaml index 916fb6454..148f9a1d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angular -version: 0.12.0 +version: 0.13.0 authors: - Misko Hevery - Pavel Jbanov From 9ae97d316a57e8f2fd4d7a8c6bb745c8b09fb2fc Mon Sep 17 00:00:00 2001 From: James deBoer Date: Thu, 31 Jul 2014 12:30:08 -0700 Subject: [PATCH 16/23] fix(web components): Support Polymer quirks Closes #1292 --- karma.conf.js | 1 + test/core_dom/web_components_spec.dart | 22 +++++++++++++++------- test/core_dom/web_components_support.js | 10 ++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 test/core_dom/web_components_support.js diff --git a/karma.conf.js b/karma.conf.js index 4a62c722b..3797fa676 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -10,6 +10,7 @@ module.exports = function(config) { files: [ 'packages/web_components/platform.js', 'packages/web_components/dart_support.js', + 'test/core_dom/web_components_support.js', 'test/*.dart', 'test/**/*_spec.dart', 'test/config/init_guinness.dart', diff --git a/test/core_dom/web_components_spec.dart b/test/core_dom/web_components_spec.dart index e7a63c934..6cad2a403 100644 --- a/test/core_dom/web_components_spec.dart +++ b/test/core_dom/web_components_spec.dart @@ -4,8 +4,8 @@ import '../_specs.dart'; import 'dart:js' as js; registerElement(String name, prototype) { - return (new js.JsObject.fromBrowserObject(document)).callMethod('registerElement', - [name, new js.JsObject.jsify({"prototype": prototype })]); + js.context['angularTestsRegisterElement'].apply( + [name, new js.JsObject.jsify(prototype)]); } @@ -32,6 +32,14 @@ main() { (new js.JsObject.fromBrowserObject(_.rootElement))[prop] = value; } + compileAndUpgrade(String html) { + _.compile(html); + var CustomElements = js.context['CustomElements']; + if (CustomElements != null) { + CustomElements['upgradeAll'].apply([new js.JsObject.fromBrowserObject(_.rootElement)]); + } + } + beforeEach((TestBed tb) { _ = tb; }); @@ -40,14 +48,14 @@ main() { registerElement('tests-basic', {'prop-x': 6}); // Create a web component - _.compile(''); + compileAndUpgrade(''); expect(customProp('prop-x')).toEqual(6); }); it('should bind to Custom Element properties', () { registerElement('tests-bound', {'prop-y': 10}); - _.compile(''); + compileAndUpgrade(''); // Scope has not been digested yet expect(customProp('prop-y')).toEqual(10); @@ -59,14 +67,14 @@ main() { it('should bind to a non-existent property', () { registerElement('tests-empty', {}); - _.compile(''); + compileAndUpgrade(''); _.rootScope.apply(); expect(customProp('new-prop')).toEqual(27); }); it('should bind to both directives and properties', () { registerElement('tests-double', {}); - _.compile(''); + compileAndUpgrade(''); _.rootScope.apply(); expect(customProp('ng-bind')).toEqual("hello"); expect(_.rootElement).toHaveText('hello'); @@ -74,7 +82,7 @@ main() { it('should support two-way bindings for components that trigger a change event', () { registerElement('tests-twoway', {}); - _.compile(''); + compileAndUpgrade(''); setCustomProp('prop', 6); _.rootElement.dispatchEvent(new Event.eventType('CustomEvent', 'change')); diff --git a/test/core_dom/web_components_support.js b/test/core_dom/web_components_support.js new file mode 100644 index 000000000..4f09d1190 --- /dev/null +++ b/test/core_dom/web_components_support.js @@ -0,0 +1,10 @@ +/** + * Used to create Javascript Web Components from Dart tests + */ +function angularTestsRegisterElement(name, prototype) { + // Polymer requires that all prototypes are chained to HTMLElement + // https://github.com/Polymer/CustomElements/issues/121 + prototype.__proto__ = HTMLElement.prototype; + prototype.createdCallback = function() {}; + document.registerElement(name, {prototype: prototype}); +} From 4c8543057a3775b22b35caede705b490fd753370 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 1 Aug 2014 10:00:01 +0200 Subject: [PATCH 17/23] doc(change): Add the "Highlights" section to the latest releases --- CHANGELOG.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f621f46..fb05e1a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # v0.13.0 tempus-fugitification (2014-07-25) +## Highlights + +This release is focused on performance and significantly speeds up rendering. We optimized our +entire rendering pipeline and now component rendering is 2.8 times (those with inlined templates) +to 6.3 times faster (those with template files) than the previous 0.12.0 release. + +To accomplish these performance improvements, we +- fixed a number of performance bugs +- moved more compilation work out of the ViewFactories, which stamps out DOM nodes, and into the + Compiler, which sets up the ViewFactories. +- implemented a custom "directive injector" to optimize Dependency Injection calls from the + ViewFactories +- optimized Dependency Injection, eliminating slow APIs + +Also, we have given apps more knobs to tune performance +- The Http service now supports coalescing http requests. This means that all the HTTP responses + that arrive within a particular interval can all be processed in a single digest. +- The ElementProbe can be disabled for apps that do not use animation +- Along with the existing ScopeStats, Angular now exposes cache statistics through the ngCaches + global object. This also allows developers to clear the caches and measure memory usage +- Along with these changes, we have also added support for ProtractorDart. + ## Bug Fixes - **DynamicParser:** Correctly handle throwing exceptions from method. @@ -244,12 +266,20 @@ towards the ScopeDigestTTL. notifyWhenNoOutstandingRequests(callback) method on the testability object to whenStable(callback). - - - # v0.12.0 sprightly-argentinosaurus (2014-06-03) +## Highlights + +- A 20% performance improvement from caching interpolated expressions. +- Http service can make cross-site requests (get, post, put, etc.) which use credentials (such as + cookies or authorization headers). +- **Breaking change**: vetoing is no longer allowed on leave (RouteLeaveEvent). This change corrects + an issue with routes unable to recover from another route vetoing a leave event. +- **Breaking change**: Zone.defaultOnScheduleMicrotask is now named Zone.onScheduleMicrotask +- **Breaking change**: OneWayOneTime bindings will continue to accept value assignments until their + stabilized value is non-null. + ## Bug Fixes - **NgStyle:** make NgStyle export expressions @@ -339,13 +369,61 @@ towards the ScopeDigestTTL. - **VmTurnZone:** due to [a8699da0](https://github.com/angular/angular.dart/commit/a8699da016c754e08502ae24034a86bd8d6e0d8e), - `Zone.defaultOnScheduleMicrotask` is now named `Zone.onScheduleMicrotask` # v0.11.0 ungulate-funambulism (2014-05-06) +## Highlights + +### Breaking Change + +The breaking change first: `Http.getString()` is gone. + +If you said: `Http.getString('data.txt').then((String data) { ... })` before, now say +`Http.get('data.txt').then((HttpResponse resp) { var data = resp.data; ... });` + +### New Features + +- Shadow DOM-less components + +Shadow DOM is still enabled by default for components. Now, its use can be controlled through the +new `useShadowDom` option in the Component annotation. + +For example: + +```dart +@Component( + selector: 'my-comp', + templateUrl: 'my-comp.html', + useShadowDom: false) +class MyComp {} +``` + +will disable Shadow DOM for that component and construct the template in the "light" DOM. Either +omitting the `useShadowDom` option or explicitly setting it to `true` will cause Angular to +construct the template in the component's shadow DOM. + +Adding cssUrls to Components with Shadow DOM disabled is not allowed. Since they aren't using Shadow +DOM, there is no style encapsulation and per-component CSS doesn't make sense. The component has +access to the styles in the `documentFragment` where it was created. Style encapsulation is a +feature we are thinking about, so this design will likely change in the future. + +- bind-* syntax + +We have shipped an early "preview" of the upcoming bind-* syntax. In 0.11.0, you may bind an +expression to any mapped attribute, even if that attribute is a `@NgAttr` mapping which typically +takes a string. + +### Performance improvements + +There are two significant performance improvements: +- We now cache CSS as `StyleElement`s instead of string, saving a `setInnerHtml` call on each styled + component instantiation. In a benchmark where components used unminified Bootstrap styles (124kB), + this sped up component creation by 31%. +- Changes in the DI package sped up View instantiation by 200%. This change makes AngularDart + rendering significantly faster. ## Bug Fixes From a46a51e09b2164dd6e088a3af1990c2f3148d8d6 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Thu, 31 Jul 2014 11:59:27 -0700 Subject: [PATCH 18/23] chore(scripts): fail on pipe failures Closes #922 Closes #1172 Closes #1289 Closes #1291 --- scripts/analyze.sh | 2 +- scripts/env.sh | 2 +- scripts/run-e2e-test.sh | 2 +- scripts/run-test.sh | 2 +- scripts/sauce/sauce_connect_setup.sh | 2 +- scripts/test-expression-extractor.sh | 2 +- scripts/travis/after-success.sh | 2 +- scripts/travis/build.sh | 2 +- scripts/travis/install.sh | 2 +- scripts/travis/presubmit.sh | 2 +- scripts/travis/setup.sh | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/analyze.sh b/scripts/analyze.sh index 5b99839c7..2cf759e73 100755 --- a/scripts/analyze.sh +++ b/scripts/analyze.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail . $(dirname $0)/env.sh diff --git a/scripts/env.sh b/scripts/env.sh index cd66f418c..823605a1e 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -1,5 +1,5 @@ #!/bin/false -set -e +set -e -o pipefail if [[ -z $ENV_SET ]]; then export ENV_SET=1 diff --git a/scripts/run-e2e-test.sh b/scripts/run-e2e-test.sh index 9c2d1608f..6a932ed6d 100755 --- a/scripts/run-e2e-test.sh +++ b/scripts/run-e2e-test.sh @@ -2,7 +2,7 @@ # Run E2E / Protractor tests. -set -e +set -e -o pipefail . $(dirname $0)/env.sh diff --git a/scripts/run-test.sh b/scripts/run-test.sh index 71c1d307a..beef3c293 100755 --- a/scripts/run-test.sh +++ b/scripts/run-test.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail . $(dirname $0)/env.sh diff --git a/scripts/sauce/sauce_connect_setup.sh b/scripts/sauce/sauce_connect_setup.sh index 840b3e7f7..faed4e07c 100755 --- a/scripts/sauce/sauce_connect_setup.sh +++ b/scripts/sauce/sauce_connect_setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail # Setup and start Sauce Connect for your TravisCI build # This script requires your .travis.yml to include the following two private env variables: diff --git a/scripts/test-expression-extractor.sh b/scripts/test-expression-extractor.sh index 9c2ada1fb..0721a9812 100755 --- a/scripts/test-expression-extractor.sh +++ b/scripts/test-expression-extractor.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail . $(dirname $0)/env.sh diff --git a/scripts/travis/after-success.sh b/scripts/travis/after-success.sh index 21415f3e3..87a7da859 100755 --- a/scripts/travis/after-success.sh +++ b/scripts/travis/after-success.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -e -o pipefail echo '*******************' echo '** AFTER_SUCCESS **' diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index cf08d989a..6ed159732 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail . "$(dirname $0)/../env.sh" echo '===========' diff --git a/scripts/travis/install.sh b/scripts/travis/install.sh index 9a12f3e83..926697051 100755 --- a/scripts/travis/install.sh +++ b/scripts/travis/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail sh -e /etc/init.d/xvfb start diff --git a/scripts/travis/presubmit.sh b/scripts/travis/presubmit.sh index adab21725..321f865d0 100755 --- a/scripts/travis/presubmit.sh +++ b/scripts/travis/presubmit.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -e -o pipefail # If we're on the presubmit branch, the dev Dart release, and all unit # tests pass, merge the presubmit branch into master and push it. diff --git a/scripts/travis/setup.sh b/scripts/travis/setup.sh index c026f2a36..06e4db907 100755 --- a/scripts/travis/setup.sh +++ b/scripts/travis/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -e -o pipefail echo Fetch Dart channel: $CHANNEL From 4553d4bca56c63c2a872e7d65fe483c77faa23a4 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Sat, 2 Aug 2014 16:45:55 +0200 Subject: [PATCH 19/23] doc(WebPlatform): improve the inline doc --- lib/core_dom/web_platform.dart | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/core_dom/web_platform.dart b/lib/core_dom/web_platform.dart index 490bea96f..7f6cbbd7b 100644 --- a/lib/core_dom/web_platform.dart +++ b/lib/core_dom/web_platform.dart @@ -26,6 +26,12 @@ class WebPlatform { } } + /** + * Because this code uses `strictStyling` for the polymer css shim, it is required to add the + * custom element’s name as an attribute on all DOM nodes in the shadowRoot (e.g. ). + * + * See http://www.polymer-project.org/docs/polymer/styling.html#strictstyling + */ String shimCss(String css, { String selector, String cssUrl }) { if (!cssShimRequired) return css; @@ -38,11 +44,9 @@ class WebPlatform { // This adds an empty attribute with the name of the component tag onto // each element in the shadow root. // - // Remove the try-catch once https://github.com/angular/angular.dart/issues/1189 is - // fixed. + // 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((n) => n.attributes[selector] = ""); } catch (e, s) { print("WARNING: Failed to set up Shadow DOM shim for $selector.\n$e\n$s"); } @@ -66,14 +70,10 @@ class PlatformViewCache implements ViewCache { ViewFactory fromHtml(String html, DirectiveMap directives) { ViewFactory viewFactory; - 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. - viewFactory = viewFactoryCache.get( - "$html"); + 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. + viewFactory = viewFactoryCache.get("$html"); } else { viewFactory = viewFactoryCache.get(html); } @@ -82,11 +82,9 @@ class PlatformViewCache implements ViewCache { var div = new dom.DivElement(); div.setInnerHtml(html, treeSanitizer: treeSanitizer); - if (selector != null && selector != "" - && platform.shadowDomShimRequired) { - // This MUST happen before the compiler is called so that every dom - // element gets touched before the compiler removes them for - // transcluding directives like ng-if. + if (selector != null && selector != "" && platform.shadowDomShimRequired) { + // This MUST happen before the compiler is called so that every dom element gets touched + // before the compiler removes them for transcluding directives like `ng-if` platform.shimShadowDom(div, selector); } From 37ceacea0c751bc94d1bd87a78a737bfe3cf7c72 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Sun, 3 Aug 2014 15:50:53 +0200 Subject: [PATCH 20/23] refactor(WebPlatform): remove an unused property --- lib/core_dom/web_platform.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/core_dom/web_platform.dart b/lib/core_dom/web_platform.dart index 7f6cbbd7b..15f4ce50d 100644 --- a/lib/core_dom/web_platform.dart +++ b/lib/core_dom/web_platform.dart @@ -9,20 +9,16 @@ part of angular.core.dom_internal; */ @Injectable() class WebPlatform { - js.JsObject _platformJs; js.JsObject _shadowCss; bool get cssShimRequired => _shadowCss != null; bool get shadowDomShimRequired => _shadowCss != null; WebPlatform() { - var _platformJs = js.context['Platform']; - if (_platformJs != null) { - _shadowCss = _platformJs['ShadowCSS']; - - if (_shadowCss != null) { - _shadowCss['strictStyling'] = true; - } + var platformJs = js.context['Platform']; + if (platformJs != null) { + _shadowCss = platformJs['ShadowCSS']; + if (_shadowCss != null) _shadowCss['strictStyling'] = true; } } From d88eecbdc8da1a31e0adc416cda6f3a73921cb22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 5 Aug 2014 15:01:11 -0400 Subject: [PATCH 21/23] chore: update pubspec.lock --- pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 1660722b2..97acdd816 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -24,7 +24,7 @@ packages: code_transformers: description: code_transformers source: hosted - version: "0.1.5" + version: "0.1.6" collection: description: collection source: hosted @@ -36,7 +36,7 @@ packages: guinness: description: guinness source: hosted - version: "0.1.9" + version: "0.1.14" html5lib: description: html5lib source: hosted @@ -44,7 +44,7 @@ packages: intl: description: intl source: hosted - version: "0.8.10+4" + version: "0.11.4" js: description: js source: hosted @@ -52,15 +52,11 @@ packages: logging: description: logging source: hosted - version: "0.9.1+1" + version: "0.9.2" matcher: description: matcher source: hosted - version: "0.11.0" - meta: - description: meta - source: hosted - version: "0.8.8" + version: "0.11.1" mock: description: mock source: hosted @@ -68,7 +64,7 @@ packages: path: description: path source: hosted - version: "1.2.1" + version: "1.2.2" perf_api: description: perf_api source: hosted @@ -84,7 +80,11 @@ packages: source_maps: description: source_maps source: hosted - version: "0.9.3" + version: "0.9.4" + source_span: + description: source_span + source: hosted + version: "1.0.0" stack_trace: description: stack_trace source: hosted @@ -100,7 +100,7 @@ packages: utf: description: utf source: hosted - version: "0.9.0" + version: "0.9.0+1" web_components: description: web_components source: hosted From a4ff2d117695c2359ff7ca605efe5ed4c4ffe256 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Tue, 5 Aug 2014 16:07:19 -0700 Subject: [PATCH 22/23] Revert "chore: update pubspec.lock" This reverts commit 1eebe45b492dcf2fcab4dfc18fe9207a5aa65177. Travis fails: https://travis-ci.org/angular/angular.dart/builds/31741920 --- pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 97acdd816..1660722b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -24,7 +24,7 @@ packages: code_transformers: description: code_transformers source: hosted - version: "0.1.6" + version: "0.1.5" collection: description: collection source: hosted @@ -36,7 +36,7 @@ packages: guinness: description: guinness source: hosted - version: "0.1.14" + version: "0.1.9" html5lib: description: html5lib source: hosted @@ -44,7 +44,7 @@ packages: intl: description: intl source: hosted - version: "0.11.4" + version: "0.8.10+4" js: description: js source: hosted @@ -52,11 +52,15 @@ packages: logging: description: logging source: hosted - version: "0.9.2" + version: "0.9.1+1" matcher: description: matcher source: hosted - version: "0.11.1" + version: "0.11.0" + meta: + description: meta + source: hosted + version: "0.8.8" mock: description: mock source: hosted @@ -64,7 +68,7 @@ packages: path: description: path source: hosted - version: "1.2.2" + version: "1.2.1" perf_api: description: perf_api source: hosted @@ -80,11 +84,7 @@ packages: source_maps: description: source_maps source: hosted - version: "0.9.4" - source_span: - description: source_span - source: hosted - version: "1.0.0" + version: "0.9.3" stack_trace: description: stack_trace source: hosted @@ -100,7 +100,7 @@ packages: utf: description: utf source: hosted - version: "0.9.0+1" + version: "0.9.0" web_components: description: web_components source: hosted From 53dbb5809c7193dc42c7260b1d8dbed34329b9f5 Mon Sep 17 00:00:00 2001 From: Diana Salsbury Date: Wed, 16 Jul 2014 12:37:07 -0700 Subject: [PATCH 23/23] feat(urls): support relative CSS / template URLs in components Example: When defining a component called 'foo' in file '/myproject/has/many/folders/foo.dart', prior to the change you would do so thusly: @Component( selector: 'foo', templateUrl: '/myproject/has/many/folders/foo.html', cssUrl: '/myproject/has/many/folders/foo.css' ) The above is still valid, but now can also be defined rather more succinctly as: @Component( selector: 'foo', templateUrl: 'foo.html', cssUrl: 'foo.css' ) A full table of what URLs will be changed/unchanged is below: You Say: Transformer replies: 'packages/foo/bar.html' 'packages/foo/bar.html' 'foo.html' 'packages/foo/bar.html' './foo.html' 'packages/foo/bar.html' '/foo.html' '/foo.html' 'http://google.com/foo.html' 'http://google.com/foo.html' *Note that as shown any absolute paths you define will not be bothered by the transformer, so if you have a templateUrl living at /foo.html, you can still reach it with '/foo.html'! This feature is defaulted to an "off" state through the addition of the ResourceResolverConfig class. To turn the feature on, bind a new ResourceResolverConfig in your module: module.bind(ResourceResolverConfig, toValue: new ResourceResolverConfig(useRelativeUrls: true)); --- lib/application_factory.dart | 3 + lib/application_factory_static.dart | 11 +- lib/core/module.dart | 1 + lib/core_dom/absolute_uris.dart | 153 +++++++++++++++++ lib/core_dom/module_internal.dart | 6 + .../shadow_dom_component_factory.dart | 32 +++- .../transcluding_component_factory.dart | 10 +- lib/core_dom/type_to_uri_mapper.dart | 16 ++ lib/core_dom/type_to_uri_mapper_dynamic.dart | 15 ++ lib/core_dom/view_factory.dart | 22 ++- lib/core_dom/web_platform.dart | 30 ++-- lib/directive/ng_include.dart | 2 +- lib/routing/ng_view.dart | 2 +- .../type_relative_uri_generator.dart | 134 +++++++++++++++ test/_specs.dart | 1 + test/angular_spec.dart | 1 + test/core/templateurl_spec.dart | 82 ++++++--- test/core_dom/absolute_uris_spec.dart | 147 ++++++++++++++++ test/core_dom/event_handler_spec.dart | 2 +- test/core_dom/view_cache_spec.dart | 2 +- test/tools/transformer/all.dart | 2 + .../type_relative_uri_generator_spec.dart | 160 ++++++++++++++++++ 22 files changed, 780 insertions(+), 54 deletions(-) create mode 100644 lib/core_dom/absolute_uris.dart create mode 100644 lib/core_dom/type_to_uri_mapper.dart create mode 100644 lib/core_dom/type_to_uri_mapper_dynamic.dart create mode 100644 lib/tools/transformer/type_relative_uri_generator.dart create mode 100644 test/core_dom/absolute_uris_spec.dart create mode 100644 test/tools/transformer/type_relative_uri_generator_spec.dart diff --git a/lib/application_factory.dart b/lib/application_factory.dart index 0baa76921..58a606f5a 100644 --- a/lib/application_factory.dart +++ b/lib/application_factory.dart @@ -16,6 +16,8 @@ import 'package:angular/change_detection/change_detection.dart'; import 'package:angular/change_detection/dirty_checking_change_detector_dynamic.dart'; import 'package:angular/core/registry_dynamic.dart'; import 'package:angular/core/parser/parser_dynamic.dart'; +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +import 'package:angular/core_dom/type_to_uri_mapper_dynamic.dart'; import 'dart:html'; /** @@ -59,6 +61,7 @@ import 'dart:mirrors' show MirrorsUsed; class _DynamicApplication extends Application { _DynamicApplication() { ngModule + ..bind(TypeToUriMapper, toImplementation: DynamicTypeToUriMapper) ..bind(MetadataExtractor, toImplementation: DynamicMetadataExtractor) ..bind(FieldGetterFactory, toImplementation: DynamicFieldGetterFactory) ..bind(ClosureMap, toImplementation: DynamicClosureMap); diff --git a/lib/application_factory_static.dart b/lib/application_factory_static.dart index a44c3f490..a0e082823 100644 --- a/lib/application_factory_static.dart +++ b/lib/application_factory_static.dart @@ -35,6 +35,7 @@ import 'package:angular/core/registry.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/parser_static.dart'; import 'package:angular/core/parser/dynamic_parser.dart'; +import 'package:angular/core_dom/type_to_uri_mapper.dart'; import 'package:angular/core/registry_static.dart'; import 'package:angular/change_detection/change_detection.dart'; import 'package:angular/change_detection/dirty_checking_change_detector_static.dart'; @@ -48,8 +49,10 @@ class _StaticApplication extends Application { Map metadata, Map fieldGetters, Map fieldSetters, - Map symbols) { + Map symbols, + TypeToUriMapper uriMapper) { ngModule + ..bind(TypeToUriMapper, toValue: uriMapper) ..bind(MetadataExtractor, toValue: new StaticMetadataExtractor(metadata)) ..bind(FieldGetterFactory, toValue: new StaticFieldGetterFactory(fieldGetters)) ..bind(ClosureMap, toValue: new StaticClosureMap(fieldGetters, fieldSetters, symbols)); @@ -87,6 +90,8 @@ Application staticApplicationFactory( Map metadata, Map fieldGetters, Map fieldSetters, - Map symbols) { - return new _StaticApplication(metadata, fieldGetters, fieldSetters, symbols); + Map symbols, + TypeToUriMapper uriMapper) { + return new _StaticApplication(metadata, fieldGetters, fieldSetters, + symbols, uriMapper); } diff --git a/lib/core/module.dart b/lib/core/module.dart index 41ec0a6ad..a965b0c77 100644 --- a/lib/core/module.dart +++ b/lib/core/module.dart @@ -59,6 +59,7 @@ export "package:angular/core_dom/module_internal.dart" show RequestInterceptor, Response, ResponseError, + ResourceResolverConfig, UrlRewriter, TemplateCache, View, diff --git a/lib/core_dom/absolute_uris.dart b/lib/core_dom/absolute_uris.dart new file mode 100644 index 000000000..b60fe6c82 --- /dev/null +++ b/lib/core_dom/absolute_uris.dart @@ -0,0 +1,153 @@ +/** + * Dart port of + * https://github.com/Polymer/platform-dev/blob/896e245a0046a397bfc0190d958d2bd162e8f53c/src/url.js + * + * This converts URIs within a document from relative URIs to being absolute + * URIs. + */ + +library angular.core_dom.absolute_uris; + +import 'dart:html'; +import 'dart:js' as js; + +import 'package:angular/core_dom/type_to_uri_mapper.dart'; + +class ResourceUrlResolver { + static final RegExp _cssUrlRegexp = new RegExp(r'(\burl\()([^)]*)(\))'); + static final RegExp _cssImportRegexp = new RegExp(r'(@import[\s]+(?!url\())([^;]*)(;)'); + static const List _urlAttrs = const ['href', 'src', 'action']; + static final String _urlAttrsSelector = '[${_urlAttrs.join('],[')}]'; + static final RegExp _urlTemplateSearch = new RegExp('{{.*}}'); + static final RegExp _quotes = new RegExp('["\']'); + + final TypeToUriMapper _uriMapper; + final ResourceResolverConfig _config; + + ResourceUrlResolver(this._uriMapper, this._config); + + String resolveHtml(String html, [Uri baseUri]) { + if (baseUri == null) { + return html; + } + HtmlDocument document = new DomParser().parseFromString( + "$html", "text/html"); + _resolveDom(document.body, baseUri); + return document.body.innerHtml; + } + + /** + * Resolves all relative URIs within the DOM from being relative to + * [originalBase] to being absolute. + */ + void _resolveDom(Node root, Uri baseUri) { + _resolveAttributes(root, baseUri); + _resolveStyles(root, baseUri); + + // handle template.content + for (var template in _querySelectorAll(root, 'template')) { + if (template.content != null) { + _resolveDom(template.content, baseUri); + } + } + } + + Iterable _querySelectorAll(Node node, String selectors) { + if (node is DocumentFragment) { + return node.querySelectorAll(selectors); + } + if (node is Element) { + return node.querySelectorAll(selectors); + } + return const []; + } + + void _resolveStyles(Node node, Uri baseUri) { + var styles = _querySelectorAll(node, 'style'); + for (var style in styles) { + _resolveStyle(style, baseUri); + } + } + + void _resolveStyle(StyleElement style, Uri baseUri) { + style.text = resolveCssText(style.text, baseUri); + } + + String resolveCssText(String cssText, Uri baseUri) { + cssText = _replaceUrlsInCssText(cssText, baseUri, _cssUrlRegexp); + return _replaceUrlsInCssText(cssText, baseUri, _cssImportRegexp); + } + + void _resolveAttributes(Node root, Uri baseUri) { + if (root is Element) { + _resolveElementAttributes(root, baseUri); + } + + for (var node in _querySelectorAll(root, _urlAttrsSelector)) { + _resolveElementAttributes(node, baseUri); + } + } + + void _resolveElementAttributes(Element element, Uri baseUri) { + var attrs = element.attributes; + for (var attr in _urlAttrs) { + if (attrs.containsKey(attr)) { + var value = attrs[attr]; + if (!value.contains(_urlTemplateSearch)) { + attrs[attr] = combine(baseUri, value).toString(); + } + } + } + } + + String _replaceUrlsInCssText(String cssText, Uri baseUri, RegExp regexp) { + return cssText.replaceAllMapped(regexp, (match) { + var url = match[2]; + url = url.replaceAll(_quotes, ''); + var urlPath = combine(baseUri, url).toString(); + return '${match[1]}\'$urlPath\'${match[3]}'; + }); + } + /// Combines a type-based URI with a relative URI. + /// + /// [baseUri] is assumed to use package: syntax for package-relative + /// URIs, while [uri] is assumed to use 'packages/' syntax for + /// package-relative URIs. Resulting URIs will use 'packages/' to indicate + /// package-relative URIs. + String combine(Uri baseUri, String uri) { + if (!_config.useRelativeUrls) { + return uri; + } + + if (uri == null) { + uri = baseUri.path; + } else { + // if it's absolute but not package-relative, then just use that + if (uri.startsWith("/") || uri.startsWith('packages/')) { + return uri; + } + } + // If it's not absolute, then resolve it first + Uri resolved = baseUri.resolve(uri); + + // If it's package-relative, tack on 'packages/' - Note that eventually + // we may want to change this to be '/packages/' to make it truly absolute + if (resolved.scheme == 'package') { + return 'packages/${resolved.path}'; + } else if (uri.startsWith(Uri.base.origin.toString())) { + return uri; + } else { + return resolved.toString(); + } + } + + String combineWithType(Type type, String uri) { + return combine(_uriMapper.uriForType(type), uri); + } +} + +class ResourceResolverConfig { + bool useRelativeUrls; + + ResourceResolverConfig({this.useRelativeUrls}); +} \ No newline at end of file diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index e599d30ae..a3fab2f5b 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -19,6 +19,9 @@ import 'package:angular/core_dom/static_keys.dart'; import 'package:angular/core_dom/directive_injector.dart'; export 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector; +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +import 'package:angular/core_dom/absolute_uris.dart'; + 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'; @@ -80,6 +83,7 @@ class CoreDomModule extends Module { bind(ComponentCssRewriter); bind(WebPlatform); + bind(ResourceUrlResolver); bind(Http); bind(UrlRewriter); bind(HttpBackend); @@ -87,6 +91,8 @@ class CoreDomModule extends Module { bind(HttpDefaults); bind(HttpInterceptors); bind(HttpConfig, toValue: new HttpConfig()); + bind(ResourceResolverConfig, toValue: + new ResourceResolverConfig(useRelativeUrls: false)); bind(Animate); bind(ViewCache); bind(BrowserCookies); diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 7c8a48c9a..7a05bbef0 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -12,12 +12,16 @@ abstract class BoundComponentFactory { Function call(dom.Element element); static async.Future _viewFuture( - Component component, ViewCache viewCache, DirectiveMap directives) { + Component component, ViewCache viewCache, DirectiveMap directives, + TypeToUriMapper uriMapper, ResourceUrlResolver resourceResolver, Type type) { if (component.template != null) { - return new async.Future.value(viewCache.fromHtml(component.template, directives)); + var baseUri = uriMapper.uriForType(type); + return new async.Future.value(viewCache.fromHtml(component.template, directives, baseUri)); } if (component.templateUrl != null) { - return viewCache.fromUrl(component.templateUrl, directives); + var url = resourceResolver.combineWithType(type, component.templateUrl); + var baseUri = Uri.parse(url); + return viewCache.fromUrl(url, directives, baseUri); } return null; } @@ -43,12 +47,15 @@ class ShadowDomComponentFactory implements ComponentFactory { final dom.NodeTreeSanitizer treeSanitizer; final Expando expando; final CompilerConfig config; + final TypeToUriMapper uriMapper; + final ResourceUrlResolver resourceResolver; final Map<_ComponentAssetKey, async.Future> styleElementCache = {}; ShadowDomComponentFactory(this.viewCache, this.http, this.templateCache, this.platform, this.componentCssRewriter, this.treeSanitizer, this.expando, - this.config, CacheRegister cacheRegister) { + this.config, this.uriMapper, this.resourceResolver, + CacheRegister cacheRegister) { cacheRegister.registerCache("ShadowDomComponentFactoryStyles", styleElementCache); } @@ -75,20 +82,26 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { _viewFuture = BoundComponentFactory._viewFuture( _component, new PlatformViewCache(_f.viewCache, _tag, _f.platform), - _directives); + _directives, + _f.uriMapper, + _f.resourceResolver, + _ref.type); } - async.Future _styleFuture(cssUrl) { + async.Future _styleFuture(cssUrl, {resolveUri: true}) { + if (resolveUri) + cssUrl = _f.resourceResolver.combineWithType(_ref.type, cssUrl); + Http http = _f.http; TemplateCache templateCache = _f.templateCache; WebPlatform platform = _f.platform; ComponentCssRewriter componentCssRewriter = _f.componentCssRewriter; - dom.NodeTreeSanitizer treeSanitizer = _f.treeSanitizer; + dom.NodeTreeSanitizer treeSanitizer = _f.treeSanitizer; return _f.styleElementCache.putIfAbsent( new _ComponentAssetKey(_tag, cssUrl), () => http.get(cssUrl, cache: templateCache) - .then((resp) => resp.responseText, + .then((resp) => _f.resourceResolver.resolveCssText(resp.responseText, Uri.parse(cssUrl)), onError: (e) => '/*\n$e\n*/\n') .then((String css) { @@ -133,7 +146,8 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { async.Future> cssFuture; if (_component.useNgBaseCss == true) { cssFuture = async.Future.wait( - [async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]) + [async.Future.wait(baseCss.urls.map((cssUrl) => + _styleFuture(cssUrl, resolveUri: false))), _styleElementsFuture]) .then((twoLists) { assert(twoLists.length == 2); return []..addAll(twoLists[0])..addAll(twoLists[1]); diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index 29f8f2663..da779f8ef 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -74,8 +74,11 @@ class TranscludingComponentFactory implements ComponentFactory { final Expando expando; final ViewCache viewCache; final CompilerConfig config; + final TypeToUriMapper uriMapper; + final ResourceUrlResolver resourceResolver; - TranscludingComponentFactory(this.expando, this.viewCache, this.config); + TranscludingComponentFactory(this.expando, this.viewCache, this.config, + this.uriMapper, this.resourceResolver); bind(DirectiveRef ref, directives) => new BoundTranscludingComponentFactory(this, ref, directives); @@ -93,7 +96,10 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { _viewFuture = BoundComponentFactory._viewFuture( _component, _f.viewCache, - _directives); + _directives, + _f.uriMapper, + _f.resourceResolver, + _ref.type); } List get callArgs => _CALL_ARGS; diff --git a/lib/core_dom/type_to_uri_mapper.dart b/lib/core_dom/type_to_uri_mapper.dart new file mode 100644 index 000000000..1fd36fc34 --- /dev/null +++ b/lib/core_dom/type_to_uri_mapper.dart @@ -0,0 +1,16 @@ +library angular.core_dom.type_to_uri_mapper; + +import 'package:path/path.dart' as native_path; + +final _path = native_path.url; + +/// Utility to convert type-relative URIs to be page-relative. +abstract class TypeToUriMapper { + + TypeToUriMapper(); + + static final RegExp _libraryRegExp = new RegExp(r'/packages/'); + + // to be rewritten for dynamic and static cases + Uri uriForType(Type type); +} diff --git a/lib/core_dom/type_to_uri_mapper_dynamic.dart b/lib/core_dom/type_to_uri_mapper_dynamic.dart new file mode 100644 index 000000000..8800130f8 --- /dev/null +++ b/lib/core_dom/type_to_uri_mapper_dynamic.dart @@ -0,0 +1,15 @@ +library angular.core_dom.type_to_uri_mapper_dynamic; + +import 'dart:html' as dom; +import 'dart:mirrors'; + +import 'type_to_uri_mapper.dart'; + +/// Resolves type-relative URIs +class DynamicTypeToUriMapper extends TypeToUriMapper { + Uri uriForType(Type type) { + var typeMirror = reflectType(type); + LibraryMirror lib = typeMirror.owner; + return lib.uri; + } +} \ No newline at end of file diff --git a/lib/core_dom/view_factory.dart b/lib/core_dom/view_factory.dart index 2f79624f5..1e60161ec 100644 --- a/lib/core_dom/view_factory.dart +++ b/lib/core_dom/view_factory.dart @@ -134,27 +134,37 @@ class ViewCache { final TemplateCache templateCache; final Compiler compiler; final dom.NodeTreeSanitizer treeSanitizer; + final dom.HtmlDocument parseDocument = + dom.document.implementation.createHtmlDocument(''); + final ResourceUrlResolver resourceResolver; - ViewCache(this.http, this.templateCache, this.compiler, this.treeSanitizer, CacheRegister cacheRegister) { + ViewCache(this.http, this.templateCache, this.compiler, this.treeSanitizer, this.resourceResolver, CacheRegister cacheRegister) { cacheRegister.registerCache('viewCache', viewFactoryCache); } - ViewFactory fromHtml(String html, DirectiveMap directives) { + ViewFactory fromHtml(String html, DirectiveMap directives, [Uri baseUri]) { ViewFactory viewFactory = viewFactoryCache.get(html); + if (baseUri != null) + html = resourceResolver.resolveHtml(html, baseUri); + else + html = resourceResolver.resolveHtml(html); + + var div = parseDocument.createElement('div'); + div.setInnerHtml(html, treeSanitizer: treeSanitizer); + if (viewFactory == null) { - var div = new dom.DivElement(); - div.setInnerHtml(html, treeSanitizer: treeSanitizer); viewFactory = compiler(div.nodes, directives); viewFactoryCache.put(html, viewFactory); } return viewFactory; } - async.Future fromUrl(String url, DirectiveMap directives) { + async.Future fromUrl(String url, DirectiveMap directives, [Uri baseUri]) { ViewFactory viewFactory = viewFactoryCache.get(url); if (viewFactory == null) { return http.get(url, cache: templateCache).then((resp) { - var viewFactoryFromHttp = fromHtml(resp.responseText, directives); + var viewFactoryFromHttp = fromHtml(resourceResolver.resolveHtml( + resp.responseText, baseUri), directives); viewFactoryCache.put(url, viewFactoryFromHttp); return viewFactoryFromHttp; }); diff --git a/lib/core_dom/web_platform.dart b/lib/core_dom/web_platform.dart index 15f4ce50d..b24f49c79 100644 --- a/lib/core_dom/web_platform.dart +++ b/lib/core_dom/web_platform.dart @@ -54,7 +54,10 @@ class PlatformViewCache implements ViewCache { final ViewCache cache; final String selector; final WebPlatform platform; + final dom.HtmlDocument parseDocument = + dom.document.implementation.createHtmlDocument(''); + get resourceResolver => cache.resourceResolver; get viewFactoryCache => cache.viewFactoryCache; Http get http => cache.http; TemplateCache get templateCache => cache.templateCache; @@ -63,7 +66,7 @@ class PlatformViewCache implements ViewCache { PlatformViewCache(this.cache, this.selector, this.platform); - ViewFactory fromHtml(String html, DirectiveMap directives) { + ViewFactory fromHtml(String html, DirectiveMap directives, [Uri baseUri]) { ViewFactory viewFactory; if (selector != null && selector != "" && platform.shadowDomShimRequired) { @@ -74,27 +77,32 @@ class PlatformViewCache implements ViewCache { viewFactory = viewFactoryCache.get(html); } - if (viewFactory == null) { - var div = new dom.DivElement(); - div.setInnerHtml(html, treeSanitizer: treeSanitizer); + if (baseUri != null) + html = resourceResolver.resolveHtml(html, baseUri); + else + html = resourceResolver.resolveHtml(html); - if (selector != null && selector != "" && platform.shadowDomShimRequired) { - // This MUST happen before the compiler is called so that every dom element gets touched - // before the compiler removes them for transcluding directives like `ng-if` - platform.shimShadowDom(div, selector); - } + var div = parseDocument.createElement('div'); + div.setInnerHtml(html, treeSanitizer: treeSanitizer); + + if (selector != null && selector != "" && platform.shadowDomShimRequired) { + // This MUST happen before the compiler is called so that every dom element gets touched + // before the compiler removes them for transcluding directives like `ng-if` + platform.shimShadowDom(div, selector); + } + if (viewFactory == null) { viewFactory = compiler(div.nodes, directives); viewFactoryCache.put(html, viewFactory); } return viewFactory; } - async.Future fromUrl(String url, DirectiveMap directives) { + async.Future fromUrl(String url, DirectiveMap directives, [Uri baseUri]) { ViewFactory viewFactory = viewFactoryCache.get(url); if (viewFactory == null) { return http.get(url, cache: templateCache).then((resp) { - var viewFactoryFromHttp = fromHtml(resp.responseText, directives); + var viewFactoryFromHttp = fromHtml(resp.responseText, directives, baseUri); viewFactoryCache.put(url, viewFactoryFromHttp); return viewFactoryFromHttp; }); diff --git a/lib/directive/ng_include.dart b/lib/directive/ng_include.dart index 2f4a0f2ce..938120acd 100644 --- a/lib/directive/ng_include.dart +++ b/lib/directive/ng_include.dart @@ -55,7 +55,7 @@ class NgInclude { set url(value) { _cleanUp(); if (value != null && value != '') { - viewCache.fromUrl(value, directives).then(_updateContent); + viewCache.fromUrl(value, directives, Uri.base).then(_updateContent); } } } diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index 3a60fc05f..60de1f827 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -119,7 +119,7 @@ class NgView implements DetachAware, RouteProvider { var newDirectives = viewInjector.getByKey(DIRECTIVE_MAP_KEY); var viewFuture = viewDef.templateHtml != null ? new Future.value(_viewCache.fromHtml(viewDef.templateHtml, newDirectives)) : - _viewCache.fromUrl(viewDef.template, newDirectives); + _viewCache.fromUrl(viewDef.template, newDirectives, Uri.base); viewFuture.then((ViewFactory viewFactory) { _cleanUp(); _childScope = _scope.createChild(new PrototypeMap(_scope.context)); diff --git a/lib/tools/transformer/type_relative_uri_generator.dart b/lib/tools/transformer/type_relative_uri_generator.dart new file mode 100644 index 000000000..ff29b0bb4 --- /dev/null +++ b/lib/tools/transformer/type_relative_uri_generator.dart @@ -0,0 +1,134 @@ +library angular.tools.transformer.relative_uri_generator; + +import 'dart:async'; +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/element.dart'; +import 'package:angular/tools/transformer/options.dart'; +import 'package:barback/barback.dart'; +import 'package:code_transformers/resolver.dart'; +import 'package:path/path.dart' as path; + + +class TypeRelativeUriGenerator extends Transformer with ResolverTransformer { + final TransformOptions options; + + static const String componentAnnotationName = + 'angular.core.annotation_src.Component'; + + TypeRelativeUriGenerator(this.options, Resolvers resolvers) { + this.resolvers = resolvers; + } + + void applyResolver(Transform transform, Resolver resolver) { + var asset = transform.primaryInput; + var id = asset.id; + var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' + '_type_relative_uris.dart'; + var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); + var outputId = new AssetId(id.package, outputPath); + + var componentAnnotationType = resolver.getType(componentAnnotationName); + ConstructorElement componentAnnotation; + if (componentAnnotationType != null && + componentAnnotationType.unnamedConstructor != null) { + componentAnnotation = componentAnnotationType.unnamedConstructor; + } else { + transform.logger.warning('Unable to resolve $componentAnnotationName.'); + } + + var annotatedTypes = resolver.libraries + .expand((lib) => lib.units) + .expand((unit) => unit.types) + .where((type) => type.node != null) + .expand(_AnnotatedElement.fromElement) + .where((e) => e.annotation.element == componentAnnotation) + .map((e) => e.element); + + var outputBuffer = new StringBuffer(); + _writeHeader(asset.id, outputBuffer); + + var libs = annotatedTypes.map((type) => type.library) + .toSet(); + + var importPrefixes = {}; + var index = 0; + for (var lib in libs) { + if (lib.isDartCore) { + importPrefixes[lib] = ''; + continue; + } + + var prefix = 'import_${index++}'; + var url = resolver.getImportUri(lib, from: outputId); + outputBuffer.write('import \'$url\' as $prefix;\n'); + importPrefixes[lib] = '$prefix.'; + } + + _writePreamble(outputBuffer); + + for (var type in annotatedTypes) { + outputBuffer.write(' ${importPrefixes[type.library]}${type.name}: '); + + var uri = resolver.getImportUri(type.library, + from: transform.primaryInput.id); + + outputBuffer.write('Uri.parse(\'$uri\'),\n'); + } + _writeFooter(outputBuffer); + + transform.addOutput( + new Asset.fromString(outputId, outputBuffer.toString())); + transform.addOutput(asset); + } +} + +void _writeHeader(AssetId id, StringSink sink) { + var libPath = path.withoutExtension(id.path).replaceAll('/', '.'); + sink.write(''' +library ${id.package}.$libPath.generated_type_uris; + +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +'''); +} + +void _writePreamble(StringSink sink) { + sink.write(''' + +/// Used when URIs have been converted to be page-relative at build time. +class _StaticAnnotationUriResolver implements AnnotationUriResolver { + Uri uriForType(Type type) { + var uri = _uriMapping[type]; + if (uri == null) { + throw new StateError('Unable to find URI mapping for \$type'); + } + uri; + } +} + +final uriResolver = new _StaticAnnotationUriResolver(); + +final Map _uriMapping = { +'''); +} + +void _writeFooter(StringSink sink) { + sink.write(''' +}; +'''); +} + +/// Wrapper for annotation AST nodes to track the element they were declared on. +class _AnnotatedElement { + /// The annotation node. + final Annotation annotation; + /// The element which the annotation was declared on. + final Element element; + + _AnnotatedElement(this.annotation, this.element); + + static Iterable<_AnnotatedElement> fromElement(Element element) { + AnnotatedNode node = element.node; + return node.metadata.map( + (annotation) => new _AnnotatedElement(annotation, element)); + } +} diff --git a/test/_specs.dart b/test/_specs.dart index f6c024973..5da2d3534 100644 --- a/test/_specs.dart +++ b/test/_specs.dart @@ -23,6 +23,7 @@ export 'package:angular/core/annotation.dart'; export 'package:angular/core/registry.dart'; export 'package:angular/core/module_internal.dart'; export 'package:angular/core_dom/module_internal.dart'; +export 'package:angular/core_dom/type_to_uri_mapper.dart'; export 'package:angular/core/parser/parser.dart'; export 'package:angular/core/parser/lexer.dart'; export 'package:angular/directive/module.dart'; diff --git a/test/angular_spec.dart b/test/angular_spec.dart index f3ee12120..abd60da7c 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -140,6 +140,7 @@ main() { "angular.core.dom_internal.RequestErrorInterceptor", "angular.core.dom_internal.RequestInterceptor", "angular.core.dom_internal.Response", + "angular.core.dom_internal.ResourceResolverConfig", "angular.core.dom_internal.ResponseError", "angular.core.dom_internal.TemplateCache", "angular.core.dom_internal.UrlRewriter", diff --git a/test/core/templateurl_spec.dart b/test/core/templateurl_spec.dart index 6f28f62d3..9151c2792 100644 --- a/test/core/templateurl_spec.dart +++ b/test/core/templateurl_spec.dart @@ -1,6 +1,27 @@ library templateurl_spec; import '../_specs.dart'; +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +import 'package:angular/core_dom/type_to_uri_mapper_dynamic.dart'; +import 'package:angular/core_dom/absolute_uris.dart'; + +class StaticTypeToUriMapper extends TypeToUriMapper { + DynamicTypeToUriMapper dynamicMapper; + + StaticTypeToUriMapper(this.dynamicMapper); + + // to be rewritten for dynamic and static cases + Uri uriForType(Type type) { + if (type == SimpleUrlComponent || + type == HtmlAndCssComponent || + type == HtmlAndMultipleCssComponent || + type == InlineWithCssComponent || + type == OnlyCssComponent) { + return Uri.parse("package:test.angular.core_dom/templateUrlSpec.dart"); + } + return dynamicMapper.uriForType(type); + } +} @Component( selector: 'simple-url', @@ -39,8 +60,24 @@ class PrefixedUrlRewriter extends UrlRewriter { call(url) => "PREFIX:$url"; } -void main() { - describe('template url', () { +_run({useRelativeUrls, staticMode}) { + var prefix; + if (!useRelativeUrls) prefix = ""; + else if (staticMode) prefix = "packages/test.angular.core_dom/"; + else prefix = "base/test/core/"; + + describe('template url useRelativeUrls=${useRelativeUrls}, staticMode=${staticMode}', () { + + beforeEachModule((Module m) { + m.bind(ResourceResolverConfig, toValue: + new ResourceResolverConfig(useRelativeUrls: useRelativeUrls)); + + if (staticMode) { + m.bind(TypeToUriMapper, toImplementation: StaticTypeToUriMapper); + m.bind(DynamicTypeToUriMapper); + } + }); + afterEach((MockHttpBackend backend) { backend.verifyNoOutstandingExpectation(); backend.verifyNoOutstandingRequest(); @@ -59,8 +96,8 @@ void main() { DirectiveMap directives) { backend - ..whenGET('PREFIX:simple.html').respond('
    Simple!
    ') - ..whenGET('PREFIX:simple.css').respond('.hello{}'); + ..whenGET('PREFIX:${prefix}simple.html').respond('
    Simple!
    ') + ..whenGET('PREFIX:${prefix}simple.css').respond('.hello{}'); var element = e('
    ignore
    '); zone.run(() { @@ -91,8 +128,8 @@ void main() { it('should replace element with template from url', async( (Http http, Compiler compile, Scope rootScope, Logger log, Injector injector, MockHttpBackend backend, DirectiveMap directives) { - backend.expectGET('simple.html').respond(200, '
    Simple!
    '); - + backend.expectGET('${prefix}simple.html').respond(200, '
    Simple!
    '); + var element = es('
    ignore
    '); compile(element, directives)(rootScope, injector.get(DirectiveInjector), element); @@ -109,7 +146,7 @@ void main() { it('should load template from URL once', async( (Http http, Compiler compile, Scope rootScope, Logger log, Injector injector, MockHttpBackend backend, DirectiveMap directives) { - backend.whenGET('simple.html').respond(200, '
    Simple!
    '); + backend.whenGET('${prefix}simple.html').respond(200, '
    Simple!
    '); var element = es( '
    ' @@ -133,8 +170,8 @@ void main() { (Http http, Compiler compile, Scope rootScope, Logger log, Injector injector, MockHttpBackend backend, DirectiveMap directives) { backend - ..expectGET('simple.css').respond(200, '.hello{}') - ..expectGET('simple.html').respond(200, '
    Simple!
    '); + ..expectGET('${prefix}simple.css').respond(200, '.hello{}') + ..expectGET('${prefix}simple.html').respond(200, '
    Simple!
    '); var element = e('
    ignore
    '); compile([element], directives)(rootScope, injector.get(DirectiveInjector), [element]); @@ -156,7 +193,7 @@ void main() { (Http http, Compiler compile, Scope rootScope, Injector injector, MockHttpBackend backend, DirectiveMap directives) { var element = es('
    ignore
    '); - backend.expectGET('simple.css').respond(200, '.hello{}'); + backend.expectGET('${prefix}simple.css').respond(200, '.hello{}'); compile(element, directives)(rootScope, injector.get(DirectiveInjector), element); microLeap(); @@ -169,7 +206,7 @@ void main() { (Http http, Compiler compile, Scope rootScope, Injector injector, MockHttpBackend backend, DirectiveMap directives) { var element = es('
    ignore
    '); - backend.expectGET('simple.css').respond(500, 'some error'); + backend.expectGET('${prefix}simple.css').respond(500, 'some error'); compile(element, directives)(rootScope, injector.get(DirectiveInjector), element); microLeap(); @@ -186,7 +223,7 @@ void main() { (Http http, Compiler compile, Scope rootScope, Injector injector, MockHttpBackend backend, DirectiveMap directives) { var element = es('
    ignore
    '); - backend.expectGET('simple.css').respond(200, '.hello{}'); + backend.expectGET('${prefix}simple.css').respond(200, '.hello{}'); compile(element, directives)(rootScope, injector.get(DirectiveInjector), element); microLeap(); @@ -199,8 +236,8 @@ void main() { (Http http, Compiler compile, Scope rootScope, Injector injector, MockHttpBackend backend, DirectiveMap directives) { backend - ..expectGET('simple.css').respond(200, '.hello{}') - ..expectGET('simple.html').respond(200, '
    Simple!
    '); + ..expectGET('${prefix}simple.css').respond(200, '.hello{}') + ..expectGET('${prefix}simple.html').respond(200, '
    Simple!
    '); var element = es('ignore'); compile(element, directives)(rootScope, injector.get(DirectiveInjector), element); @@ -223,9 +260,9 @@ void main() { (Http http, Compiler compile, Scope rootScope, Logger log, Injector injector, MockHttpBackend backend, DirectiveMap directives) { backend - ..expectGET('simple.css').respond(200, '.hello{}') - ..expectGET('another.css').respond(200, '.world{}') - ..expectGET('simple.html').respond(200, '
    Simple!
    '); + ..expectGET('${prefix}simple.css').respond(200, '.hello{}') + ..expectGET('${prefix}another.css').respond(200, '.world{}') + ..expectGET('${prefix}simple.html').respond(200, '
    Simple!
    '); var element = e('
    ignore
    '); compile([element], directives)(rootScope, injector.get(DirectiveInjector), [element]); @@ -255,8 +292,8 @@ void main() { (Http http, Compiler compile, MockHttpBackend backend, RootScope rootScope, DirectiveMap directives, Injector injector) { backend - ..expectGET('simple.css').respond(200, '.hello{}') - ..expectGET('simple.html').respond(200, '
    Simple!
    '); + ..expectGET('${prefix}simple.css').respond(200, '.hello{}') + ..expectGET('${prefix}simple.html').respond(200, '
    Simple!
    '); var element = e('
    ignore
    '); compile([element], directives)(rootScope, injector.get(DirectiveInjector), [element]); @@ -281,3 +318,10 @@ void main() { }); }); } + +void main() { + _run(useRelativeUrls: true, staticMode: true); + _run(useRelativeUrls: true, staticMode: false); + _run(useRelativeUrls: false, staticMode: true); + _run(useRelativeUrls: false, staticMode: false); +} \ No newline at end of file diff --git a/test/core_dom/absolute_uris_spec.dart b/test/core_dom/absolute_uris_spec.dart new file mode 100644 index 000000000..80bb9098d --- /dev/null +++ b/test/core_dom/absolute_uris_spec.dart @@ -0,0 +1,147 @@ +library angular.test.core_dom.uri_resolver_spec; + +import 'package:angular/core_dom/absolute_uris.dart'; +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +import 'package:angular/core_dom/type_to_uri_mapper_dynamic.dart'; +import '../_specs.dart'; + + +_run({useRelativeUrls, staticMode}) { + describe("url resolution: staticMode=$staticMode, useRelativeUrls=$useRelativeUrls", () { + var prefix = useRelativeUrls ? "packages/angular/test/core_dom/" : ""; + var container; + var urlResolver; + + beforeEach((ResourceUrlResolver _urlResolver) { + urlResolver = _urlResolver; + container = document.createElement('div'); + document.body.append(container); + }); + + afterEach(() { + urlResolver = null; + container.remove(); + }); + + beforeEachModule((Module module) { + module + ..bind(ResourceResolverConfig, toValue: + new ResourceResolverConfig(useRelativeUrls: useRelativeUrls)) + ..bind(TypeToUriMapper, toImplementation: DynamicTypeToUriMapper); + }); + + var originalBase = Uri.base; + var setHttp = true; + testResolution(url, expected) { + var http = setHttp; + var title = ""; + if (!useRelativeUrls || http) { + expected = url; + title += "should not resolve"; + } + + var baseUri = originalBase; + title += staticMode ? " staticMode: true" : " staticMode: false"; + title += useRelativeUrls ? " should go through resolution: true " : " should go through resolution: false"; + it('${title}: resolves attribute URIs $url to $expected', (ResourceUrlResolver resourceResolver) { + var html = resourceResolver.resolveHtml("", baseUri); + expect(html).toEqual(''); + }); + } + + // Set originalBase to an http URL type instead of a 'package:' URL (because of + // the way karma runs the tests) and ensure that after resolution, the result doesn't + // have a protocol or domain but contains the full path + testResolution(Uri.base.resolve('packages/angular/test/core_dom/foo.html').toString(), + 'packages/angular/test/core_dom/foo.html'); + testResolution(Uri.base.resolve('foo.html').toString(), + 'packages/angular/test/core_dom/foo.html'); + testResolution(Uri.base.resolve('./foo.html').toString(), + 'packages/angular/test/core_dom/foo.html'); + testResolution(Uri.base.resolve('/foo.html').toString(), + '/foo.html'); + + + originalBase = Uri.parse('package:angular/test/core_dom/absolute_uris_spec.dart'); + setHttp = false; + + testResolution('packages/angular/test/core_dom/foo.html', 'packages/angular/test/core_dom/foo.html'); + testResolution('foo.html', 'packages/angular/test/core_dom/foo.html'); + testResolution('./foo.html', 'packages/angular/test/core_dom/foo.html'); + testResolution('/foo.html', '/foo.html'); + testResolution('http://google.com/foo.html', 'http://google.com/foo.html'); + + + + // Set originalBase back + + templateResolution(url, expected) { + if (!useRelativeUrls) + expected = url; + expect(urlResolver.resolveHtml(''' + ''', originalBase)).toEqual(''' + '''); + } + + it('resolves template contents', () { + templateResolution('foo.png', 'packages/angular/test/core_dom/foo.png'); + }); + + it('does not change absolute urls when they are resolved', () { + templateResolution('/foo/foo.png', '/foo/foo.png'); + }); + + it('resolves CSS URIs', (ResourceUrlResolver resourceResolver) { + var html_style = (''' + '''); + + html_style = resourceResolver.resolveHtml(html_style, originalBase).toString(); + + var resolved_style = (''' + '''); + expect(html_style).toEqual(resolved_style); + }); + + it('resolves @import URIs', (ResourceUrlResolver resourceResolver) { + var html_style = (''' + '''); + + html_style = resourceResolver.resolveHtml(html_style, originalBase).toString(); + + var resolved_style = (''' + '''); + expect(html_style).toEqual(resolved_style); + }); + }); +} + +void main() { + describe('url_resolver', () { + _run(useRelativeUrls: true, staticMode: true); + _run(useRelativeUrls: false, staticMode: true);//TODO get rid of staticMode + }); +} + +class NullSanitizer implements NodeValidator { + bool allowsElement(Element element) => true; + bool allowsAttribute(Element element, String attributeName, String value) => + true; +} \ No newline at end of file diff --git a/test/core_dom/event_handler_spec.dart b/test/core_dom/event_handler_spec.dart index 68a6784a6..07aeee8ca 100644 --- a/test/core_dom/event_handler_spec.dart +++ b/test/core_dom/event_handler_spec.dart @@ -71,7 +71,7 @@ main() { '''
    {{ctrl.description}}
    '''); - var el = document.querySelector('[on-abc]'); + var el = e.querySelector('[on-abc]'); el.dispatchEvent(new Event('abc')); _.rootScope.apply(); expect(el.text).toEqual("new description"); diff --git a/test/core_dom/view_cache_spec.dart b/test/core_dom/view_cache_spec.dart index 2ecfaaa43..a812be408 100644 --- a/test/core_dom/view_cache_spec.dart +++ b/test/core_dom/view_cache_spec.dart @@ -24,7 +24,7 @@ main() { backend.whenGET('template.url').respond(200, HTML); var httpFactory; - cache.fromUrl('template.url', directives).then((f) => httpFactory = f); + cache.fromUrl('template.url', directives, Uri.base).then((f) => httpFactory = f); microLeap(); backend.flush(); diff --git a/test/tools/transformer/all.dart b/test/tools/transformer/all.dart index fa81732f4..f043aa329 100644 --- a/test/tools/transformer/all.dart +++ b/test/tools/transformer/all.dart @@ -3,9 +3,11 @@ library all_transformer_tests; import 'expression_generator_spec.dart' as expression_generator_spec; import 'metadata_generator_spec.dart' as metadata_generator_spec; import 'static_angular_generator_spec.dart' as static_angular_generator_spec; +import 'type_relative_uri_generator_spec.dart' as type_relative_uri_generator_spec; main() { expression_generator_spec.main(); metadata_generator_spec.main(); static_angular_generator_spec.main(); + type_relative_uri_generator_spec.main(); } diff --git a/test/tools/transformer/type_relative_uri_generator_spec.dart b/test/tools/transformer/type_relative_uri_generator_spec.dart new file mode 100644 index 000000000..c04eb2ad0 --- /dev/null +++ b/test/tools/transformer/type_relative_uri_generator_spec.dart @@ -0,0 +1,160 @@ +library angular.test.tools.transformer.type_relative_uri_generator_spec; + +import 'dart:async'; + +import 'package:angular/tools/transformer/type_relative_uri_generator.dart'; +import 'package:angular/tools/transformer/options.dart'; +import 'package:barback/barback.dart'; +import 'package:code_transformers/resolver.dart'; +import 'package:code_transformers/tests.dart' as tests; + +import 'package:unittest/unittest.dart' hide expect; +import 'package:guinness/guinness.dart'; + +main() { + describe('TypeRelativeUriGenerator', () { + var options = new TransformOptions(sdkDirectory: dartSdkDirectory); + var resolvers = new Resolvers(dartSdkDirectory); + + var phases = [ + [new TypeRelativeUriGenerator(options, resolvers)] + ]; + + it('should map types in web directory', () { + return generates(phases, + inputs: { + 'a|web/main.dart': ''' + import 'package:angular/angular.dart'; + import 'dir/foo.dart'; + + @Component() + class A {} + + main() {} + ''', + 'a|web/dir/foo.dart': ''' + import 'package:angular/angular.dart'; + + @Component() + class B {} + ''', + 'angular|lib/angular.dart': libAngular, + }, + imports: [ + 'import \'main.dart\' as import_0', + 'import \'dir/foo.dart\' as import_1', + ], + types: { + 'import_0.A': 'main.dart', + 'import_1.B': 'dir/foo.dart', + }); + }); + + it('should map package imports', () { + return generates(phases, + inputs: { + 'a|web/main.dart': ''' + import 'package:b/foo.dart'; + + main() {} + ''', + 'b|lib/foo.dart': ''' + import 'package:angular/angular.dart'; + + @Component() + class B {} + ''', + 'angular|lib/angular.dart': libAngular, + }, + imports: [ + 'import \'package:b/foo.dart\' as import_0', + ], + types: { + 'import_0.B': 'package:b/foo.dart', + }); + }); + + it('should handle no mapped types', () { + return generates(phases, + inputs: { + 'a|web/main.dart': ''' + import 'package:angular/angular.dart'; + + main() {} + ''', + 'angular|lib/angular.dart': libAngular, + }); + }); + + it('should warn on no angular imports', () { + return generates(phases, + inputs: { + 'a|web/main.dart': ''' + main() {} + ''', + 'angular|lib/angular.dart': libAngular, + }, + messages: [ + 'warning: Unable to resolve ' + 'angular.core.annotation_src.Component.' + ]); + }); + }); +} + +Future generates(List> phases, + { Map inputs, + List imports: const [], + Map types: const {}, + Iterable messages: const []}) { + + var buffer = new StringBuffer(); + buffer.write(header); + for (var i in imports) { + buffer.write('$i;\n'); + } + buffer.write(preamble); + types.forEach((type, uri) { + buffer.write(' $type: Uri.parse(\'$uri\'),\n'); + }); + buffer.write('};\n'); + + return tests.applyTransformers(phases, + inputs: inputs, + results: { + 'a|web/main_type_relative_uris.dart': buffer.toString() + }, + messages: messages); +} + +const String header = ''' +library a.web.main.generated_type_uris; + +import 'package:angular/core_dom/type_to_uri_mapper.dart'; +'''; + +const String preamble = ''' + +/// Used when URIs have been converted to be page-relative at build time. +class _StaticAnnotationUriResolver implements AnnotationUriResolver { + String resolve(String path, Type type) {\ + var uri = _uriMapping[type]; + if (uri == null) { + throw new StateError('Unable to find URI mapping for \$type'); + } + return AnnotationUriResolver.combine(uri, path); + } +} + +final uriResolver = new _StaticAnnotationUriResolver(); + +final Map _uriMapping = { +'''; + +const String libAngular = ''' +library angular.core.annotation_src; + +class Component { + const Component({String templateUrl, String selector}); +} +''';