From 16e3d7e96efaf37f7523508ea7dc02f1f6336404 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 24 Jul 2015 15:28:44 -0700 Subject: [PATCH] refactor(shadow_dom): remove `ShadowDomStrategy` in favor of `@View(encapsulation)` BREAKING CHANGES: - `ShadowDomStrategy` was removed. To specify the encapsulation of a component use `@View(encapsulation: ViewEncapsulation.NONE | ViewEncapsulation.EMULATED | ViewEncapsulation.NATIVE)` - The default encapsulation strategy is now `ViewEncapsulation.EMULATED` if a component contains styles and `ViewEncapsulation.NONE` if it does not. Before this was always `NONE`. - `ViewLoader` now returns the template as a string and the styles as a separate array --- modules/angular2/annotations.ts | 2 +- modules/angular2/render.ts | 1 + .../src/core/annotations/decorators.ts | 5 +- modules/angular2/src/core/annotations/view.ts | 2 +- .../src/core/annotations_impl/view.ts | 17 +- .../angular2/src/core/application_common.ts | 22 ++- .../angular2/src/core/compiler/compiler.ts | 3 +- modules/angular2/src/dom/html_adapter.dart | 4 +- modules/angular2/src/facade/collection.dart | 3 + modules/angular2/src/facade/collection.ts | 1 + modules/angular2/src/render/api.ts | 29 ++- .../render/dom/compiler/compile_control.ts | 2 +- .../render/dom/compiler/compile_pipeline.ts | 33 ++-- .../src/render/dom/compiler/compile_step.ts | 6 +- .../dom/compiler/compile_step_factory.ts | 8 +- .../src/render/dom/compiler/compiler.ts | 73 ++++++-- .../render/dom/compiler/directive_parser.ts | 4 +- .../dom/compiler/property_binding_parser.ts | 4 +- .../{shadow_dom => compiler}/shadow_css.ts | 0 .../render/dom/compiler/style_encapsulator.ts | 73 ++++++++ .../dom/compiler/text_interpolation_parser.ts | 4 +- .../src/render/dom/compiler/view_loader.ts | 121 +++++------- .../src/render/dom/compiler/view_splitter.ts | 4 +- .../angular2/src/render/dom/dom_renderer.ts | 22 ++- modules/angular2/src/render/dom/dom_tokens.ts | 24 +++ .../emulated_scoped_shadow_dom_strategy.ts | 53 ------ .../emulated_unscoped_shadow_dom_strategy.ts | 24 --- .../shadow_dom/native_shadow_dom_strategy.ts | 13 -- .../dom/shadow_dom/shadow_dom_compile_step.ts | 30 --- .../dom/shadow_dom/shadow_dom_strategy.ts | 13 -- .../src/render/dom/shadow_dom/util.ts | 67 ------- modules/angular2/src/render/dom/util.ts | 19 +- .../src/render/dom/view/proto_view.ts | 18 +- .../src/render/dom/view/proto_view_builder.ts | 15 +- .../src/render/dom/view/proto_view_merger.ts | 62 +++--- .../src/render/dom/view/shared_styles_host.ts | 52 ++++++ modules/angular2/src/render/render.ts | 7 +- .../src/test_lib/test_component_builder.ts | 2 +- .../angular2/src/test_lib/test_injector.ts | 22 +-- .../template_compiler/generator.dart | 5 +- .../src/web-workers/shared/serializer.ts | 6 +- .../src/web-workers/ui/di_bindings.ts | 24 +-- .../angular2/test/core/application_spec.ts | 2 +- .../compiler/dynamic_component_loader_spec.ts | 2 +- .../test/core/compiler/integration_spec.ts | 2 +- .../compiler/projection_integration_spec.ts | 44 +++-- .../dom/compiler/compiler_common_tests.ts | 164 +++++++++++++--- .../dom/compiler/directive_parser_spec.ts | 28 ++- .../test/render/dom/compiler/pipeline_spec.ts | 77 +++++--- .../compiler/property_binding_parser_spec.ts | 29 ++- .../shadow_css_spec.ts | 2 +- .../dom/compiler/style_encapsulator_spec.ts | 135 ++++++++++++++ .../text_interpolation_parser_spec.ts | 8 +- .../render/dom/compiler/view_loader_spec.ts | 176 ++++++++---------- .../render/dom/compiler/view_splitter_spec.ts | 63 ++++--- .../dom/dom_renderer_integration_spec.ts | 72 +++++-- .../angular2/test/render/dom/dom_testbed.ts | 3 +- ...mulated_scoped_shadow_dom_strategy_spec.ts | 86 --------- ...lated_unscoped_shadow_dom_strategy_spec.ts | 63 ------- .../native_shadow_dom_strategy_spec.ts | 28 --- .../dom/view/proto_view_builder_spec.ts | 8 +- .../proto_view_merger_integration_spec.ts | 64 +++++-- .../dom/view/shared_styles_host_spec.ts | 58 ++++++ .../test/render/dom/view/view_spec.ts | 2 +- .../angular2/test/router/route_config_spec.ts | 2 +- .../test/router/router_integration_spec.ts | 2 +- .../test/web-workers/worker/renderer_spec.ts | 9 +- .../src/components/button/button.ts | 12 +- .../src/components/checkbox/checkbox.ts | 5 +- .../src/components/dialog/dialog.ts | 4 +- .../src/components/grid_list/grid_list.ts | 12 +- .../progress-circular/progress_circular.ts | 5 +- .../progress-linear/progress_linear.ts | 5 +- .../src/components/radio/radio_button.ts | 18 +- .../src/components/switcher/switch.ts | 9 +- .../src/compiler/compiler_benchmark.ts | 18 +- modules/examples/src/material/dialog/index.ts | 2 +- 77 files changed, 1228 insertions(+), 890 deletions(-) rename modules/angular2/src/render/dom/{shadow_dom => compiler}/shadow_css.ts (100%) create mode 100644 modules/angular2/src/render/dom/compiler/style_encapsulator.ts create mode 100644 modules/angular2/src/render/dom/dom_tokens.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/shadow_dom_compile_step.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.ts delete mode 100644 modules/angular2/src/render/dom/shadow_dom/util.ts create mode 100644 modules/angular2/src/render/dom/view/shared_styles_host.ts rename modules/angular2/test/render/dom/{shadow_dom => compiler}/shadow_css_spec.ts (98%) create mode 100644 modules/angular2/test/render/dom/compiler/style_encapsulator_spec.ts delete mode 100644 modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.ts delete mode 100644 modules/angular2/test/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy_spec.ts delete mode 100644 modules/angular2/test/render/dom/shadow_dom/native_shadow_dom_strategy_spec.ts create mode 100644 modules/angular2/test/render/dom/view/shared_styles_host_spec.ts diff --git a/modules/angular2/annotations.ts b/modules/angular2/annotations.ts index 91b54f33e9..0844ada308 100644 --- a/modules/angular2/annotations.ts +++ b/modules/angular2/annotations.ts @@ -15,7 +15,7 @@ export { LifecycleEvent } from './src/core/annotations/annotations'; -export {ViewAnnotation} from 'angular2/src/core/annotations/view'; +export {ViewAnnotation, ViewEncapsulation} from 'angular2/src/core/annotations/view'; export {QueryAnnotation, AttributeAnnotation} from 'angular2/src/core/annotations/di'; export { diff --git a/modules/angular2/render.ts b/modules/angular2/render.ts index 67e883f667..daf054d482 100644 --- a/modules/angular2/render.ts +++ b/modules/angular2/render.ts @@ -14,5 +14,6 @@ export { RenderViewWithFragments, DomRenderer, DOCUMENT_TOKEN, + APP_ID_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES } from './src/render/render'; diff --git a/modules/angular2/src/core/annotations/decorators.ts b/modules/angular2/src/core/annotations/decorators.ts index e8badcde4d..5ef72c098d 100644 --- a/modules/angular2/src/core/annotations/decorators.ts +++ b/modules/angular2/src/core/annotations/decorators.ts @@ -9,6 +9,7 @@ import { Class } from '../../util/decorators'; import {Type} from 'angular2/src/facade/lang'; +import {ViewEncapsulation} from 'angular2/src/render/api'; /** * Interface for the {@link Directive} decorator function. @@ -226,7 +227,7 @@ export interface ViewFactory { templateUrl?: string, template?: string, directives?: List>, - renderer?: string, + encapsulation?: ViewEncapsulation, styles?: List, styleUrls?: List, }): ViewDecorator; @@ -234,7 +235,7 @@ export interface ViewFactory { templateUrl?: string, template?: string, directives?: List>, - renderer?: string, + encapsulation?: ViewEncapsulation, styles?: List, styleUrls?: List, }): ViewAnnotation; diff --git a/modules/angular2/src/core/annotations/view.ts b/modules/angular2/src/core/annotations/view.ts index edb8902a3b..231296f66e 100644 --- a/modules/angular2/src/core/annotations/view.ts +++ b/modules/angular2/src/core/annotations/view.ts @@ -1 +1 @@ -export {View as ViewAnnotation} from '../annotations_impl/view'; +export {View as ViewAnnotation, ViewEncapsulation} from '../annotations_impl/view'; diff --git a/modules/angular2/src/core/annotations_impl/view.ts b/modules/angular2/src/core/annotations_impl/view.ts index 110daeae85..73592ac0cd 100644 --- a/modules/angular2/src/core/annotations_impl/view.ts +++ b/modules/angular2/src/core/annotations_impl/view.ts @@ -1,4 +1,7 @@ import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang'; +import {ViewEncapsulation} from 'angular2/src/render/api'; + +export {ViewEncapsulation} from 'angular2/src/render/api'; /** * Declares the available HTML templates for an application. @@ -85,17 +88,17 @@ export class View { directives: List>; /** - * Specify a custom renderer for this View. - * If this is set, neither `template`, `templateUrl`, `styles`, `styleUrls` nor `directives` are - * used. + * Specify how the template and the styles should be encapsulated. + * The default is {@link ViewEncapsulation.EMULATED} if the view has styles, + * otherwise {@link ViewEncapsulation.NONE}. */ - renderer: string; + encapsulation: ViewEncapsulation; - constructor({templateUrl, template, directives, renderer, styles, styleUrls}: { + constructor({templateUrl, template, directives, encapsulation, styles, styleUrls}: { templateUrl?: string, template?: string, directives?: List>, - renderer?: string, + encapsulation?: ViewEncapsulation, styles?: List, styleUrls?: List, } = {}) { @@ -104,6 +107,6 @@ export class View { this.styleUrls = styleUrls; this.styles = styles; this.directives = directives; - this.renderer = renderer; + this.encapsulation = encapsulation; } } diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index 501b1094de..6508196a13 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -34,10 +34,6 @@ import {List, ListWrapper} from 'angular2/src/facade/collection'; import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/facade/async'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; -import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import { - EmulatedUnscopedShadowDomStrategy -} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; import {XHR} from 'angular2/src/render/xhr'; import {XHRImpl} from 'angular2/src/render/xhr_impl'; import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; @@ -61,9 +57,14 @@ import {Renderer, RenderCompiler} from 'angular2/src/render/api'; import { DomRenderer, DOCUMENT_TOKEN, - DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES -} from 'angular2/src/render/dom/dom_renderer'; -import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, + DefaultDomCompiler, + APP_ID_RANDOM_BINDING +} from 'angular2/src/render/render'; +import { + SharedStylesHost, + DomSharedStylesHost +} from 'angular2/src/render/dom/view/shared_styles_host'; import {internalView} from 'angular2/src/core/compiler/view_ref'; import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens'; @@ -108,12 +109,13 @@ function _injectorBindings(appComponentType): List> { return new EventManager(plugins, ngZone); }, [NgZone]), - bind(ShadowDomStrategy) - .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]), DomRenderer, - DefaultDomCompiler, bind(Renderer).toAlias(DomRenderer), + APP_ID_RANDOM_BINDING, + DefaultDomCompiler, bind(RenderCompiler).toAlias(DefaultDomCompiler), + DomSharedStylesHost, + bind(SharedStylesHost).toAlias(DomSharedStylesHost), ProtoViewFactory, AppViewPool, bind(APP_VIEW_POOL_CAPACITY).toValue(10000), diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index 8a454d6a0c..7daa976896 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -307,7 +307,8 @@ export class Compiler { templateAbsUrl: templateAbsUrl, template: view.template, styleAbsUrls: styleAbsUrls, styles: view.styles, - directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata) + directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata), + encapsulation: view.encapsulation }); } diff --git a/modules/angular2/src/dom/html_adapter.dart b/modules/angular2/src/dom/html_adapter.dart index 0082b082a9..b304de3ba3 100644 --- a/modules/angular2/src/dom/html_adapter.dart +++ b/modules/angular2/src/dom/html_adapter.dart @@ -317,10 +317,10 @@ class Html5LibDomAdapter implements DomAdapter { throw 'not implemented'; } bool supportsDOMEvents() { - throw 'not implemented'; + return false; } bool supportsNativeShadowDOM() { - throw 'not implemented'; + return false; } getHistory() { throw 'not implemented'; diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index f9c1fc2a74..259e2a75b8 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -212,4 +212,7 @@ void iterateListLike(iter, fn(item)) { class SetWrapper { static Set createFromList(List l) => new Set.from(l); static bool has(Set s, key) => s.contains(key); + static void delete(Set m, k) { + m.remove(k); + } } diff --git a/modules/angular2/src/facade/collection.ts b/modules/angular2/src/facade/collection.ts index 8f4e19d7a3..448bab07b8 100644 --- a/modules/angular2/src/facade/collection.ts +++ b/modules/angular2/src/facade/collection.ts @@ -290,4 +290,5 @@ var createSetFromList: {(lst: List): Set} = (function() { export class SetWrapper { static createFromList(lst: List): Set { return createSetFromList(lst); } static has(s: Set, key: T): boolean { return s.has(key); } + static delete(m: Set, k: K) { m.delete(k); } } diff --git a/modules/angular2/src/render/api.ts b/modules/angular2/src/render/api.ts index 7404e9f580..03f6b4d198 100644 --- a/modules/angular2/src/render/api.ts +++ b/modules/angular2/src/render/api.ts @@ -273,6 +273,25 @@ export class RenderFragmentRef {} // An opaque reference to a view export class RenderViewRef {} +/** + * How the template and styles of a view should be encapsulated. + */ +export enum ViewEncapsulation { + /** + * Emulate scoping of styles by preprocessing the style rules + * and adding additional attributes to elements. This is the default. + */ + EMULATED, + /** + * Uses the native mechanism of the renderer. For the DOM this means creating a ShadowRoot. + */ + NATIVE, + /** + * Don't scope the template nor the styles. + */ + NONE +} + export class ViewDefinition { componentId: string; templateAbsUrl: string; @@ -280,21 +299,25 @@ export class ViewDefinition { directives: List; styleAbsUrls: List; styles: List; + encapsulation: ViewEncapsulation; - constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives}: { + constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives, + encapsulation}: { componentId?: string, templateAbsUrl?: string, template?: string, styleAbsUrls?: List, styles?: List, - directives?: List - }) { + directives?: List, + encapsulation?: ViewEncapsulation + } = {}) { this.componentId = componentId; this.templateAbsUrl = templateAbsUrl; this.template = template; this.styleAbsUrls = styleAbsUrls; this.styles = styles; this.directives = directives; + this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.EMULATED; } } diff --git a/modules/angular2/src/render/dom/compiler/compile_control.ts b/modules/angular2/src/render/dom/compiler/compile_control.ts index 05bec0a852..e9365f702e 100644 --- a/modules/angular2/src/render/dom/compiler/compile_control.ts +++ b/modules/angular2/src/render/dom/compiler/compile_control.ts @@ -29,7 +29,7 @@ export class CompileControl { var step = this._steps[i]; this._parent = parent; this._currentStepIndex = i; - step.process(parent, current, this); + step.processElement(parent, current, this); parent = this._parent; } diff --git a/modules/angular2/src/render/dom/compiler/compile_pipeline.ts b/modules/angular2/src/render/dom/compiler/compile_pipeline.ts index 095e609864..7cc8f1f865 100644 --- a/modules/angular2/src/render/dom/compiler/compile_pipeline.ts +++ b/modules/angular2/src/render/dom/compiler/compile_pipeline.ts @@ -5,7 +5,7 @@ import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; import {CompileStep} from './compile_step'; import {ProtoViewBuilder} from '../view/proto_view_builder'; -import {ProtoViewDto, ViewType} from '../../api'; +import {ProtoViewDto, ViewType, ViewDefinition} from '../../api'; /** * CompilePipeline for executing CompileSteps recursively for @@ -13,26 +13,29 @@ import {ProtoViewDto, ViewType} from '../../api'; */ export class CompilePipeline { _control: CompileControl; - constructor(steps: List, private _useNativeShadowDom: boolean = false) { - this._control = new CompileControl(steps); + constructor(public steps: List) { this._control = new CompileControl(steps); } + + processStyles(styles: string[]): string[] { + return styles.map(style => { + this.steps.forEach(step => { style = step.processStyle(style); }); + return style; + }); } - process(rootElement: HTMLElement, protoViewType: ViewType = null, - compilationCtxtDescription: string = ''): List { - if (isBlank(protoViewType)) { - protoViewType = ViewType.COMPONENT; - } - var results = []; + processElements(rootElement: Element, protoViewType: ViewType, + viewDef: ViewDefinition): CompileElement[] { + var results: CompileElement[] = []; + var compilationCtxtDescription = viewDef.componentId; var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription); rootCompileElement.inheritedProtoView = - new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom); + new ProtoViewBuilder(rootElement, protoViewType, viewDef.encapsulation); rootCompileElement.isViewRoot = true; - this._process(results, null, rootCompileElement, compilationCtxtDescription); + this._processElement(results, null, rootCompileElement, compilationCtxtDescription); return results; } - _process(results, parent: CompileElement, current: CompileElement, - compilationCtxtDescription: string = '') { + _processElement(results: CompileElement[], parent: CompileElement, current: CompileElement, + compilationCtxtDescription: string = '') { var additionalChildren = this._control.internalProcess(results, 0, parent, current); if (current.compileChildren) { @@ -46,7 +49,7 @@ export class CompilePipeline { childCompileElement.inheritedProtoView = current.inheritedProtoView; childCompileElement.inheritedElementBinder = current.inheritedElementBinder; childCompileElement.distanceToInheritedBinder = current.distanceToInheritedBinder + 1; - this._process(results, current, childCompileElement); + this._processElement(results, current, childCompileElement); } node = nextNode; } @@ -54,7 +57,7 @@ export class CompilePipeline { if (isPresent(additionalChildren)) { for (var i = 0; i < additionalChildren.length; i++) { - this._process(results, current, additionalChildren[i]); + this._processElement(results, current, additionalChildren[i]); } } } diff --git a/modules/angular2/src/render/dom/compiler/compile_step.ts b/modules/angular2/src/render/dom/compiler/compile_step.ts index 9084d690d0..cfe1b94b70 100644 --- a/modules/angular2/src/render/dom/compiler/compile_step.ts +++ b/modules/angular2/src/render/dom/compiler/compile_step.ts @@ -6,6 +6,8 @@ import * as compileControlModule from './compile_control'; * Is guaranteed to be called in depth first order */ export interface CompileStep { - process(parent: CompileElement, current: CompileElement, - control: compileControlModule.CompileControl): void; + processElement(parent: CompileElement, current: CompileElement, + control: compileControlModule.CompileControl): void; + + processStyle(style: string): string; } diff --git a/modules/angular2/src/render/dom/compiler/compile_step_factory.ts b/modules/angular2/src/render/dom/compiler/compile_step_factory.ts index 5fb3092f41..70f30671e8 100644 --- a/modules/angular2/src/render/dom/compiler/compile_step_factory.ts +++ b/modules/angular2/src/render/dom/compiler/compile_step_factory.ts @@ -6,15 +6,15 @@ import {PropertyBindingParser} from './property_binding_parser'; import {TextInterpolationParser} from './text_interpolation_parser'; import {DirectiveParser} from './directive_parser'; import {ViewSplitter} from './view_splitter'; -import {ShadowDomCompileStep} from '../shadow_dom/shadow_dom_compile_step'; -import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; +import {StyleEncapsulator} from './style_encapsulator'; export class CompileStepFactory { createSteps(view: ViewDefinition): List { return null; } } export class DefaultStepFactory extends CompileStepFactory { - constructor(public _parser: Parser, public _shadowDomStrategy: ShadowDomStrategy) { super(); } + private _componentUIDsCache: Map = new Map(); + constructor(private _parser: Parser, private _appId: string) { super(); } createSteps(view: ViewDefinition): List { return [ @@ -22,7 +22,7 @@ export class DefaultStepFactory extends CompileStepFactory { new PropertyBindingParser(this._parser), new DirectiveParser(this._parser, view.directives), new TextInterpolationParser(this._parser), - new ShadowDomCompileStep(this._shadowDomStrategy, view) + new StyleEncapsulator(this._appId, view, this._componentUIDsCache) ]; } } diff --git a/modules/angular2/src/render/dom/compiler/compiler.ts b/modules/angular2/src/render/dom/compiler/compiler.ts index 5d003fbb91..f36033ff85 100644 --- a/modules/angular2/src/render/dom/compiler/compiler.ts +++ b/modules/angular2/src/render/dom/compiler/compiler.ts @@ -1,7 +1,7 @@ import {Injectable} from 'angular2/di'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; -import {BaseException, isPresent} from 'angular2/src/facade/lang'; +import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang'; import {DOM} from 'angular2/src/dom/dom_adapter'; import { @@ -11,14 +11,18 @@ import { DirectiveMetadata, RenderCompiler, RenderProtoViewRef, - RenderProtoViewMergeMapping + RenderProtoViewMergeMapping, + ViewEncapsulation } from '../../api'; import {CompilePipeline} from './compile_pipeline'; -import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; +import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader'; import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory'; import {Parser} from 'angular2/src/change_detection/change_detection'; -import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; import * as pvm from '../view/proto_view_merger'; +import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens'; +import {Inject} from 'angular2/di'; +import {SharedStylesHost} from '../view/shared_styles_host'; +import {prependAll} from '../util'; /** * The compiler loads and translates the html templates of components into @@ -26,15 +30,17 @@ import * as pvm from '../view/proto_view_merger'; * the CompilePipeline and the CompileSteps. */ export class DomCompiler extends RenderCompiler { - constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader, - public _useNativeShadowDom: boolean) { + constructor(private _stepFactory: CompileStepFactory, private _viewLoader: ViewLoader, + private _sharedStylesHost: SharedStylesHost) { super(); } compile(view: ViewDefinition): Promise { var tplPromise = this._viewLoader.load(view); return PromiseWrapper.then( - tplPromise, (el) => this._compileTemplate(view, el, ViewType.COMPONENT), (e) => { + tplPromise, (tplAndStyles: TemplateAndStyles) => + this._compileView(view, tplAndStyles, ViewType.COMPONENT), + (e) => { throw new BaseException(`Failed to load the template for "${view.componentId}" : ${e}`); return null; }); @@ -46,11 +52,13 @@ export class DomCompiler extends RenderCompiler { templateAbsUrl: null, template: null, styles: null, styleAbsUrls: null, - directives: [directiveMetadata] + directives: [directiveMetadata], + encapsulation: ViewEncapsulation.NONE }); - var template = DOM.createTemplate(''); - DOM.appendChild(DOM.content(template), DOM.createElement(directiveMetadata.selector)); - return this._compileTemplate(hostViewDef, template, ViewType.HOST); + return this._compileView( + hostViewDef, new TemplateAndStyles( + `<${directiveMetadata.selector}>`, []), + ViewType.HOST); } mergeProtoViewsRecursively( @@ -58,20 +66,47 @@ export class DomCompiler extends RenderCompiler { return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs)); } - _compileTemplate(viewDef: ViewDefinition, tplElement, - protoViewType: ViewType): Promise { - var pipeline = - new CompilePipeline(this._stepFactory.createSteps(viewDef), this._useNativeShadowDom); - var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId); + _compileView(viewDef: ViewDefinition, templateAndStyles: TemplateAndStyles, + protoViewType: ViewType): Promise { + if (viewDef.encapsulation === ViewEncapsulation.EMULATED && + templateAndStyles.styles.length === 0) { + viewDef = this._normalizeViewEncapsulationIfThereAreNoStyles(viewDef); + } + var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef)); + + var compiledStyles = pipeline.processStyles(templateAndStyles.styles); + var compileElements = pipeline.processElements(DOM.createTemplate(templateAndStyles.template), + protoViewType, viewDef); + if (viewDef.encapsulation === ViewEncapsulation.NATIVE) { + prependAll(DOM.content(compileElements[0].element), + compiledStyles.map(style => DOM.createStyleElement(style))); + } else { + this._sharedStylesHost.addStyles(compiledStyles); + } return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build()); } + + _normalizeViewEncapsulationIfThereAreNoStyles(viewDef: ViewDefinition): ViewDefinition { + if (viewDef.encapsulation === ViewEncapsulation.EMULATED) { + return new ViewDefinition({ + componentId: viewDef.componentId, + templateAbsUrl: viewDef.templateAbsUrl, template: viewDef.template, + styleAbsUrls: viewDef.styleAbsUrls, + styles: viewDef.styles, + directives: viewDef.directives, + encapsulation: ViewEncapsulation.NONE + }); + } else { + return viewDef; + } + } } @Injectable() export class DefaultDomCompiler extends DomCompiler { - constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) { - super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader, - shadowDomStrategy.hasNativeContentElement()); + constructor(parser: Parser, viewLoader: ViewLoader, sharedStylesHost: SharedStylesHost, + @Inject(APP_ID_TOKEN) appId: any) { + super(new DefaultStepFactory(parser, appId), viewLoader, sharedStylesHost); } } diff --git a/modules/angular2/src/render/dom/compiler/directive_parser.ts b/modules/angular2/src/render/dom/compiler/directive_parser.ts index 1afa147a41..1b7cda1164 100644 --- a/modules/angular2/src/render/dom/compiler/directive_parser.ts +++ b/modules/angular2/src/render/dom/compiler/directive_parser.ts @@ -29,6 +29,8 @@ export class DirectiveParser implements CompileStep { } } + processStyle(style: string): string { return style; } + _ensureComponentOnlyHasElementSelector(selector, directive) { var isElementSelector = selector.length === 1 && selector[0].isElementSelector(); if (!isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) { @@ -37,7 +39,7 @@ export class DirectiveParser implements CompileStep { } } - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { var attrs = current.attrs(); var classList = current.classList(); var cssSelector = new CssSelector(); diff --git a/modules/angular2/src/render/dom/compiler/property_binding_parser.ts b/modules/angular2/src/render/dom/compiler/property_binding_parser.ts index 66a4bd22fb..848d0f1fa2 100644 --- a/modules/angular2/src/render/dom/compiler/property_binding_parser.ts +++ b/modules/angular2/src/render/dom/compiler/property_binding_parser.ts @@ -25,7 +25,9 @@ var BIND_NAME_REGEXP = export class PropertyBindingParser implements CompileStep { constructor(private _parser: Parser) {} - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processStyle(style: string): string { return style; } + + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { var attrs = current.attrs(); var newAttrs = new Map(); diff --git a/modules/angular2/src/render/dom/shadow_dom/shadow_css.ts b/modules/angular2/src/render/dom/compiler/shadow_css.ts similarity index 100% rename from modules/angular2/src/render/dom/shadow_dom/shadow_css.ts rename to modules/angular2/src/render/dom/compiler/shadow_css.ts diff --git a/modules/angular2/src/render/dom/compiler/style_encapsulator.ts b/modules/angular2/src/render/dom/compiler/style_encapsulator.ts new file mode 100644 index 0000000000..4185d71bd7 --- /dev/null +++ b/modules/angular2/src/render/dom/compiler/style_encapsulator.ts @@ -0,0 +1,73 @@ +import {CompileStep} from '../compiler/compile_step'; +import {CompileElement} from '../compiler/compile_element'; +import {CompileControl} from '../compiler/compile_control'; +import {ViewDefinition, ViewEncapsulation, ViewType} from '../../api'; +import {NG_CONTENT_ELEMENT_NAME, isElementWithTag} from '../util'; +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {isBlank, isPresent} from 'angular2/src/facade/lang'; +import {ShadowCss} from './shadow_css'; + +export class StyleEncapsulator implements CompileStep { + constructor(private _appId: string, private _view: ViewDefinition, + private _componentUIDsCache: Map) {} + + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { + if (isElementWithTag(current.element, NG_CONTENT_ELEMENT_NAME)) { + current.inheritedProtoView.bindNgContent(); + } else { + if (this._view.encapsulation === ViewEncapsulation.EMULATED) { + this._processEmulatedScopedElement(current, parent); + } + } + } + + processStyle(style: string): string { + var encapsulation = this._view.encapsulation; + if (encapsulation === ViewEncapsulation.EMULATED) { + return this._shimCssForComponent(style, this._view.componentId); + } else { + return style; + } + } + + _processEmulatedScopedElement(current: CompileElement, parent: CompileElement): void { + var element = current.element; + var hostComponentId = this._view.componentId; + var viewType = current.inheritedProtoView.type; + // Shim the element as a child of the compiled component + if (viewType !== ViewType.HOST && isPresent(hostComponentId)) { + var contentAttribute = getContentAttribute(this._getComponentId(hostComponentId)); + DOM.setAttribute(element, contentAttribute, ''); + // also shim the host + if (isBlank(parent) && viewType == ViewType.COMPONENT) { + var hostAttribute = getHostAttribute(this._getComponentId(hostComponentId)); + current.inheritedProtoView.setHostAttribute(hostAttribute, ''); + } + } + } + + _shimCssForComponent(cssText: string, componentId: string): string { + var id = this._getComponentId(componentId); + var shadowCss = new ShadowCss(); + return shadowCss.shimCssText(cssText, getContentAttribute(id), getHostAttribute(id)); + } + + _getComponentId(componentStringId: string): string { + var id = this._componentUIDsCache.get(componentStringId); + if (isBlank(id)) { + id = `${this._appId}-${this._componentUIDsCache.size}`; + this._componentUIDsCache.set(componentStringId, id); + } + return id; + } +} + +// Return the attribute to be added to the component +function getHostAttribute(compId: string): string { + return `_nghost-${compId}`; +} + +// Returns the attribute to be added on every single element nodes in the component +function getContentAttribute(compId: string): string { + return `_ngcontent-${compId}`; +} diff --git a/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts b/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts index df0b5ca914..4e734c7c3b 100644 --- a/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts +++ b/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts @@ -13,7 +13,9 @@ import {CompileControl} from './compile_control'; export class TextInterpolationParser implements CompileStep { constructor(public _parser: Parser) {} - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processStyle(style: string): string { return style; } + + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { if (!current.compileChildren) { return; } diff --git a/modules/angular2/src/render/dom/compiler/view_loader.ts b/modules/angular2/src/render/dom/compiler/view_loader.ts index d248c7222b..1f806d503a 100644 --- a/modules/angular2/src/render/dom/compiler/view_loader.ts +++ b/modules/angular2/src/render/dom/compiler/view_loader.ts @@ -10,14 +10,17 @@ import { import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {DOM} from 'angular2/src/dom/dom_adapter'; +import {ViewDefinition} from '../../api'; import {XHR} from 'angular2/src/render/xhr'; -import {ViewDefinition} from '../../api'; - import {StyleInliner} from './style_inliner'; import {StyleUrlResolver} from './style_url_resolver'; +export class TemplateAndStyles { + constructor(public template: string, public styles: string[]) {} +} + /** * Strategy to load component views. * TODO: Make public API once we are more confident in this approach. @@ -29,33 +32,32 @@ export class ViewLoader { constructor(private _xhr: XHR, private _styleInliner: StyleInliner, private _styleUrlResolver: StyleUrlResolver) {} - load(view: ViewDefinition): Promise { - let tplElAndStyles: List> = [this._loadHtml(view)]; - - if (isPresent(view.styles)) { - view.styles.forEach((cssText: string) => { - let textOrPromise = this._resolveAndInlineCssText(cssText, view.templateAbsUrl); - tplElAndStyles.push(textOrPromise); + load(viewDef: ViewDefinition): Promise { + let tplAndStyles: List| Promise| string> = + [this._loadHtml(viewDef.template, viewDef.templateAbsUrl)]; + if (isPresent(viewDef.styles)) { + viewDef.styles.forEach((cssText: string) => { + let textOrPromise = this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl); + tplAndStyles.push(textOrPromise); }); } - if (isPresent(view.styleAbsUrls)) { - view.styleAbsUrls.forEach(url => { + if (isPresent(viewDef.styleAbsUrls)) { + viewDef.styleAbsUrls.forEach(url => { let promise = this._loadText(url).then( - cssText => this._resolveAndInlineCssText(cssText, view.templateAbsUrl)); - tplElAndStyles.push(promise); + cssText => this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl)); + tplAndStyles.push(promise); }); } - // Inline the styles from the @View annotation and return a template element - return PromiseWrapper.all(tplElAndStyles) - .then((res: List) => { - let tplEl = res[0]; - let cssTexts = ListWrapper.slice(res, 1); + // Inline the styles from the @View annotation + return PromiseWrapper.all(tplAndStyles) + .then((res: List) => { + let loadedTplAndStyles = res[0]; + let styles = ListWrapper.slice(res, 1); - _insertCssTexts(DOM.content(tplEl), cssTexts); - - return tplEl; + return new TemplateAndStyles(loadedTplAndStyles.template, + loadedTplAndStyles.styles.concat(styles)); }); } @@ -77,40 +79,54 @@ export class ViewLoader { } // Load the html and inline any style tags - private _loadHtml(view: ViewDefinition): Promise { + private _loadHtml(template: string, templateAbsUrl: string): Promise { let html; // Load the HTML - if (isPresent(view.template)) { - html = PromiseWrapper.resolve(view.template); - } else if (isPresent(view.templateAbsUrl)) { - html = this._loadText(view.templateAbsUrl); + if (isPresent(template)) { + html = PromiseWrapper.resolve(template); + } else if (isPresent(templateAbsUrl)) { + html = this._loadText(templateAbsUrl); } else { throw new BaseException('View should have either the templateUrl or template property set'); } return html.then(html => { var tplEl = DOM.createTemplate(html); - // Replace $baseUrl with the base url for the template - let templateAbsUrl = view.templateAbsUrl; if (isPresent(templateAbsUrl) && templateAbsUrl.indexOf("/") >= 0) { let baseUrl = templateAbsUrl.substring(0, templateAbsUrl.lastIndexOf("/")); this._substituteBaseUrl(DOM.content(tplEl), baseUrl); } + let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE'); + let unresolvedStyles: string[] = []; + for (let i = 0; i < styleEls.length; i++) { + var styleEl = styleEls[i]; + unresolvedStyles.push(DOM.getText(styleEl)); + DOM.remove(styleEl); + } + + let syncStyles: string[] = []; + let asyncStyles: Promise[] = []; // Inline the style tags from the html - let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE'); - - let promises: List> = []; for (let i = 0; i < styleEls.length; i++) { - let promise = this._resolveAndInlineElement(styleEls[i], view.templateAbsUrl); - if (isPromise(promise)) { - promises.push(promise); + let styleEl = styleEls[i]; + let resolvedStyled = this._resolveAndInlineCssText(DOM.getText(styleEl), templateAbsUrl); + if (isPromise(resolvedStyled)) { + asyncStyles.push(>resolvedStyled); + } else { + syncStyles.push(resolvedStyled); } } - return promises.length > 0 ? PromiseWrapper.all(promises).then(_ => tplEl) : tplEl; + if (asyncStyles.length === 0) { + return PromiseWrapper.resolve(new TemplateAndStyles(DOM.getInnerHTML(tplEl), syncStyles)); + } else { + return PromiseWrapper.all(asyncStyles) + .then(loadedStyles => new TemplateAndStyles(DOM.getInnerHTML(tplEl), + syncStyles.concat(loadedStyles))); + } }); } @@ -139,43 +155,8 @@ export class ViewLoader { } } - /** - * Inlines a style element. - * - * @param styleEl The style element - * @param baseUrl The base url - * @returns {Promise} null when no @import rule exist in the css or a Promise - * @private - */ - private _resolveAndInlineElement(styleEl, baseUrl: string): Promise { - let textOrPromise = this._resolveAndInlineCssText(DOM.getText(styleEl), baseUrl); - - if (isPromise(textOrPromise)) { - return (>textOrPromise).then(css => { DOM.setText(styleEl, css); }); - } else { - DOM.setText(styleEl, textOrPromise); - return null; - } - } - private _resolveAndInlineCssText(cssText: string, baseUrl: string): string | Promise { cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl); return this._styleInliner.inlineImports(cssText, baseUrl); } } - -function _insertCssTexts(element, cssTexts: List): void { - if (cssTexts.length == 0) return; - - let insertBefore = DOM.firstChild(element); - - for (let i = cssTexts.length - 1; i >= 0; i--) { - let styleEl = DOM.createStyleElement(cssTexts[i]); - if (isPresent(insertBefore)) { - DOM.insertBefore(insertBefore, styleEl); - } else { - DOM.appendChild(element, styleEl); - } - insertBefore = styleEl; - } -} diff --git a/modules/angular2/src/render/dom/compiler/view_splitter.ts b/modules/angular2/src/render/dom/compiler/view_splitter.ts index 10a94cafe8..6df3a755a6 100644 --- a/modules/angular2/src/render/dom/compiler/view_splitter.ts +++ b/modules/angular2/src/render/dom/compiler/view_splitter.ts @@ -28,7 +28,9 @@ import {dashCaseToCamelCase} from '../util'; export class ViewSplitter implements CompileStep { constructor(public _parser: Parser) {} - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processStyle(style: string): string { return style; } + + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { var attrs = current.attrs(); var templateBindings = attrs.get('template'); var hasTemplateBinding = isPresent(templateBindings); diff --git a/modules/angular2/src/render/dom/dom_renderer.ts b/modules/angular2/src/render/dom/dom_renderer.ts index 602b810285..7b8fb6a105 100644 --- a/modules/angular2/src/render/dom/dom_renderer.ts +++ b/modules/angular2/src/render/dom/dom_renderer.ts @@ -15,6 +15,7 @@ import {EventManager} from './events/event_manager'; import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view'; import {DomView, DomViewRef, resolveInternalDomView} from './view/view'; import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment'; +import {DomSharedStylesHost} from './view/shared_styles_host'; import { NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, @@ -31,9 +32,8 @@ import { RenderViewWithFragments } from '../api'; -export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken')); -export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken = - CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes')); +import {DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens'; + const REFLECT_PREFIX: string = 'ng-reflect-'; @Injectable() @@ -41,7 +41,8 @@ export class DomRenderer extends Renderer { _document; _reflectPropertiesAsAttributes: boolean; - constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document, + constructor(public _eventManager: EventManager, private _domSharedStylesHost: DomSharedStylesHost, + @Inject(DOCUMENT_TOKEN) document, @Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes: boolean) { super(); @@ -65,7 +66,14 @@ export class DomRenderer extends Renderer { } destroyView(viewRef: RenderViewRef) { - // noop for now + var view = resolveInternalDomView(viewRef); + var elementBinders = view.proto.elementBinders; + for (var i = 0; i < elementBinders.length; i++) { + var binder = elementBinders[i]; + if (binder.hasNativeShadowRoot) { + this._domSharedStylesHost.removeHost(DOM.getShadowRoot(view.boundElements[i])); + } + } } getNativeElementSync(location: RenderElementRef): any { @@ -226,7 +234,9 @@ export class DomRenderer extends Renderer { // native shadow DOM if (binder.hasNativeShadowRoot) { var shadowRootWrapper = DOM.firstChild(element); - moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element)); + var shadowRoot = DOM.createShadowRoot(element); + this._domSharedStylesHost.addHost(shadowRoot); + moveChildNodes(shadowRootWrapper, shadowRoot); DOM.remove(shadowRootWrapper); } diff --git a/modules/angular2/src/render/dom/dom_tokens.ts b/modules/angular2/src/render/dom/dom_tokens.ts new file mode 100644 index 0000000000..1369fcbe15 --- /dev/null +++ b/modules/angular2/src/render/dom/dom_tokens.ts @@ -0,0 +1,24 @@ +import {OpaqueToken, bind, Binding} from 'angular2/di'; +import {CONST_EXPR, StringWrapper, Math} from 'angular2/src/facade/lang'; + +export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken')); + +export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken = + CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes')); + + +/** + * A unique id (string) for an angular application. + */ +export const APP_ID_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('AppId')); + +/** + * Bindings that will generate a random APP_ID_TOKEN. + */ +export var APP_ID_RANDOM_BINDING: Binding = + bind(APP_ID_TOKEN).toFactory(() => `${randomChar()}${randomChar()}${randomChar()}`, []); + + +function randomChar(): string { + return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25)); +} diff --git a/modules/angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy.ts b/modules/angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy.ts deleted file mode 100644 index a2fa5c5bc7..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {isBlank, isPresent} from 'angular2/src/facade/lang'; - -import {DOM} from 'angular2/src/dom/dom_adapter'; - -import {EmulatedUnscopedShadowDomStrategy} from './emulated_unscoped_shadow_dom_strategy'; -import { - getContentAttribute, - getHostAttribute, - getComponentId, - shimCssForComponent, - insertStyleElement -} from './util'; - -/** - * This strategy emulates the Shadow DOM for the templates, styles **included**: - * - components templates are added as children of their component element, - * - both the template and the styles are modified so that styles are scoped to the component - * they belong to, - * - styles are moved from the templates to the styleHost (i.e. the document head). - * - * Notes: - * - styles are scoped to their component and will apply only to it, - * - a common subset of shadow DOM selectors are supported, - * - see `ShadowCss` for more information and limitations. - */ -export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy { - constructor(styleHost) { super(styleHost); } - - processStyleElement(hostComponentId: string, templateUrl: string, styleEl: Element): void { - let cssText = DOM.getText(styleEl); - cssText = shimCssForComponent(cssText, hostComponentId); - DOM.setText(styleEl, cssText); - this._moveToStyleHost(styleEl); - } - - _moveToStyleHost(styleEl): void { - DOM.remove(styleEl); - insertStyleElement(this.styleHost, styleEl); - } - - processElement(hostComponentId: string, elementComponentId: string, element: Element): void { - // Shim the element as a child of the compiled component - if (isPresent(hostComponentId)) { - var contentAttribute = getContentAttribute(getComponentId(hostComponentId)); - DOM.setAttribute(element, contentAttribute, ''); - } - // If the current element is also a component, shim it as a host - if (isPresent(elementComponentId)) { - var hostAttribute = getHostAttribute(getComponentId(elementComponentId)); - DOM.setAttribute(element, hostAttribute, ''); - } - } -} diff --git a/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.ts b/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.ts deleted file mode 100644 index 93b9980540..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; - -import {ShadowDomStrategy} from './shadow_dom_strategy'; -import {insertSharedStyleText} from './util'; - -/** - * This strategy emulates the Shadow DOM for the templates, styles **excluded**: - * - components templates are added as children of their component element, - * - styles are moved from the templates to the styleHost (i.e. the document head). - * - * Notes: - * - styles are **not** scoped to their component and will apply to the whole document, - * - you can **not** use shadow DOM specific selectors in the styles - */ -export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy { - constructor(public styleHost) { super(); } - - hasNativeContentElement(): boolean { return false; } - - processStyleElement(hostComponentId: string, templateUrl: string, styleEl: Element): void { - var cssText = DOM.getText(styleEl); - insertSharedStyleText(cssText, this.styleHost, styleEl); - } -} diff --git a/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.ts b/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.ts deleted file mode 100644 index 1190676818..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Injectable} from 'angular2/di'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; - -/** - * This strategies uses the native Shadow DOM support. - * - * The templates for the component are inserted in a Shadow Root created on the component element. - * Hence they are strictly isolated. - */ -@Injectable() -export class NativeShadowDomStrategy extends ShadowDomStrategy { - hasNativeContentElement(): boolean { return true; } -} diff --git a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_compile_step.ts b/modules/angular2/src/render/dom/shadow_dom/shadow_dom_compile_step.ts deleted file mode 100644 index 8c393ec370..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_compile_step.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {CompileStep} from '../compiler/compile_step'; -import {CompileElement} from '../compiler/compile_element'; -import {CompileControl} from '../compiler/compile_control'; -import {ViewDefinition} from '../../api'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; -import {NG_CONTENT_ELEMENT_NAME, isElementWithTag} from '../util'; - -export class ShadowDomCompileStep implements CompileStep { - constructor(public _shadowDomStrategy: ShadowDomStrategy, public _view: ViewDefinition) {} - - process(parent: CompileElement, current: CompileElement, control: CompileControl) { - if (isElementWithTag(current.element, NG_CONTENT_ELEMENT_NAME)) { - current.inheritedProtoView.bindNgContent(); - } else if (isElementWithTag(current.element, 'style')) { - this._processStyleElement(current, control); - } else { - var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null; - this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element); - } - } - - _processStyleElement(current: CompileElement, control: CompileControl) { - this._shadowDomStrategy.processStyleElement(this._view.componentId, this._view.templateAbsUrl, - current.element); - - // Style elements should not be further processed by the compiler, as they can not contain - // bindings. Skipping further compiler steps allow speeding up the compilation process. - control.ignoreCurrentElement(); - } -} diff --git a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.ts b/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.ts deleted file mode 100644 index 5de0746a6f..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {isBlank, isPresent} from 'angular2/src/facade/lang'; - -export class ShadowDomStrategy { - // Whether the strategy understands the native tag - hasNativeContentElement(): boolean { return true; } - - // An optional step that can modify the template style elements. - processStyleElement(hostComponentId: string, templateUrl: string, styleElement: HTMLStyleElement): - void {} - - // An optional step that can modify the template elements (style elements exlcuded). - processElement(hostComponentId: string, elementComponentId: string, element: HTMLElement): void {} -} diff --git a/modules/angular2/src/render/dom/shadow_dom/util.ts b/modules/angular2/src/render/dom/shadow_dom/util.ts deleted file mode 100644 index 98a18f6820..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/util.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {isBlank, isPresent} from 'angular2/src/facade/lang'; -import {MapWrapper, Map} from 'angular2/src/facade/collection'; - -import {DOM} from 'angular2/src/dom/dom_adapter'; - -import {ShadowCss} from './shadow_css'; - -var _componentUIDs: Map = new Map(); -var _nextComponentUID: int = 0; -var _sharedStyleTexts: Map = new Map(); -var _lastInsertedStyleEl; - -export function getComponentId(componentStringId: string): number { - var id = _componentUIDs.get(componentStringId); - if (isBlank(id)) { - id = _nextComponentUID++; - _componentUIDs.set(componentStringId, id); - } - return id; -} - -export function insertSharedStyleText(cssText, styleHost, styleEl) { - if (!_sharedStyleTexts.has(cssText)) { - // Styles are unscoped and shared across components, only append them to the head - // when there are not present yet - _sharedStyleTexts.set(cssText, true); - insertStyleElement(styleHost, styleEl); - } -} - -export function insertStyleElement(host, styleEl) { - if (isBlank(_lastInsertedStyleEl)) { - var firstChild = DOM.firstChild(host); - if (isPresent(firstChild)) { - DOM.insertBefore(firstChild, styleEl); - } else { - DOM.appendChild(host, styleEl); - } - } else { - DOM.insertAfter(_lastInsertedStyleEl, styleEl); - } - _lastInsertedStyleEl = styleEl; -} - -// Return the attribute to be added to the component -export function getHostAttribute(id: int): string { - return `_nghost-${id}`; -} - -// Returns the attribute to be added on every single element nodes in the component -export function getContentAttribute(id: int): string { - return `_ngcontent-${id}`; -} - -export function shimCssForComponent(cssText: string, componentId: string): string { - var id = getComponentId(componentId); - var shadowCss = new ShadowCss(); - return shadowCss.shimCssText(cssText, getContentAttribute(id), getHostAttribute(id)); -} - -// Reset the caches - used for tests only -export function resetShadowDomCache() { - _componentUIDs.clear(); - _nextComponentUID = 0; - _sharedStyleTexts.clear(); - _lastInsertedStyleEl = null; -} diff --git a/modules/angular2/src/render/dom/util.ts b/modules/angular2/src/render/dom/util.ts index 9aa8f1627f..a208efa355 100644 --- a/modules/angular2/src/render/dom/util.ts +++ b/modules/angular2/src/render/dom/util.ts @@ -122,4 +122,21 @@ export function queryBoundTextNodeIndices(parentNode: Node, boundTextNodes: Map< resultCallback(node, j, boundTextNodes.get(node)); } } -} \ No newline at end of file +} + +export function prependAll(parentNode: Node, nodes: Node[]) { + var lastInsertedNode = null; + nodes.forEach(node => { + if (isBlank(lastInsertedNode)) { + var firstChild = DOM.firstChild(parentNode); + if (isPresent(firstChild)) { + DOM.insertBefore(firstChild, node); + } else { + DOM.appendChild(parentNode, node); + } + } else { + DOM.insertAfter(lastInsertedNode, node); + } + lastInsertedNode = node; + }); +} diff --git a/modules/angular2/src/render/dom/view/proto_view.ts b/modules/angular2/src/render/dom/view/proto_view.ts index 81a4dd7865..9998b31df3 100644 --- a/modules/angular2/src/render/dom/view/proto_view.ts +++ b/modules/angular2/src/render/dom/view/proto_view.ts @@ -1,7 +1,7 @@ import {List, ListWrapper} from 'angular2/src/facade/collection'; import {DomElementBinder} from './element_binder'; -import {RenderProtoViewRef, ViewType} from '../../api'; +import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api'; import {DOM} from 'angular2/src/dom/dom_adapter'; @@ -14,9 +14,10 @@ export class DomProtoViewRef extends RenderProtoViewRef { } export class DomProtoView { - static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[], - rootTextNodeIndices: number[], - elementBinders: List): DomProtoView { + static create(type: ViewType, rootElement: Element, viewEncapsulation: ViewEncapsulation, + fragmentsRootNodeCount: number[], rootTextNodeIndices: number[], + elementBinders: List, + hostAttributes: Map): DomProtoView { var boundTextNodeCount = rootTextNodeIndices.length; for (var i = 0; i < elementBinders.length; i++) { boundTextNodeCount += elementBinders[i].textNodeIndices.length; @@ -24,12 +25,15 @@ export class DomProtoView { var isSingleElementFragment = fragmentsRootNodeCount.length === 1 && fragmentsRootNodeCount[0] === 1 && DOM.isElementNode(DOM.firstChild(DOM.content(rootElement))); - return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices, - boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment); + return new DomProtoView(type, rootElement, viewEncapsulation, elementBinders, hostAttributes, + rootTextNodeIndices, boundTextNodeCount, fragmentsRootNodeCount, + isSingleElementFragment); } constructor(public type: ViewType, public rootElement: Element, - public elementBinders: List, public rootTextNodeIndices: number[], + public encapsulation: ViewEncapsulation, + public elementBinders: List, + public hostAttributes: Map, public rootTextNodeIndices: number[], public boundTextNodeCount: number, public fragmentsRootNodeCount: number[], public isSingleElementFragment: boolean) {} } diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.ts b/modules/angular2/src/render/dom/view/proto_view_builder.ts index 11ef7a1e59..2462ea42be 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/render/dom/view/proto_view_builder.ts @@ -35,9 +35,10 @@ export class ProtoViewBuilder { elements: List = []; rootTextBindings: Map = new Map(); ngContentCount: number = 0; + hostAttributes: Map = new Map(); constructor(public rootElement, public type: api.ViewType, - public useNativeShadowDom: boolean = false) {} + public viewEncapsulation: api.ViewEncapsulation) {} bindElement(element: HTMLElement, description: string = null): ElementBinderBuilder { var builder = new ElementBinderBuilder(this.elements.length, element, description); @@ -65,6 +66,8 @@ export class ProtoViewBuilder { bindNgContent() { this.ngContentCount++; } + setHostAttribute(name: string, value: string) { this.hostAttributes.set(name, value); } + build(): api.ProtoViewDto { var domElementBinders = []; @@ -119,7 +122,7 @@ export class ProtoViewBuilder { domElementBinders.push(new DomElementBinder({ textNodeIndices: textNodeIndices, hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId), - hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom, + hasNativeShadowRoot: false, eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()), localEvents: ebb.eventBuilder.buildLocalEvents(), globalEvents: ebb.eventBuilder.buildGlobalEvents() @@ -127,8 +130,9 @@ export class ProtoViewBuilder { }); var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length; return new api.ProtoViewDto({ - render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount], - rootTextNodeIndices, domElementBinders)), + render: new DomProtoViewRef( + DomProtoView.create(this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount], + rootTextNodeIndices, domElementBinders, this.hostAttributes)), type: this.type, elementBinders: apiElementBinders, variableBindings: this.variableBindings, @@ -178,7 +182,8 @@ export class ElementBinderBuilder { if (isPresent(this.nestedProtoView)) { throw new BaseException('Only one nested view per element is allowed'); } - this.nestedProtoView = new ProtoViewBuilder(rootElement, api.ViewType.EMBEDDED); + this.nestedProtoView = + new ProtoViewBuilder(rootElement, api.ViewType.EMBEDDED, api.ViewEncapsulation.NONE); return this.nestedProtoView; } diff --git a/modules/angular2/src/render/dom/view/proto_view_merger.ts b/modules/angular2/src/render/dom/view/proto_view_merger.ts index 8224950344..4d9cef3760 100644 --- a/modules/angular2/src/render/dom/view/proto_view_merger.ts +++ b/modules/angular2/src/render/dom/view/proto_view_merger.ts @@ -1,10 +1,15 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang'; -import {ListWrapper} from 'angular2/src/facade/collection'; +import {ListWrapper, SetWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view'; import {DomElementBinder} from './element_binder'; -import {RenderProtoViewMergeMapping, RenderProtoViewRef, ViewType} from '../../api'; +import { + RenderProtoViewMergeMapping, + RenderProtoViewRef, + ViewType, + ViewEncapsulation +} from '../../api'; import { NG_BINDING_CLASS, NG_CONTENT_ELEMENT_NAME, @@ -12,7 +17,9 @@ import { cloneAndQueryProtoView, queryBoundElements, queryBoundTextNodeIndices, - NG_SHADOW_ROOT_ELEMENT_NAME + NG_SHADOW_ROOT_ELEMENT_NAME, + isElementWithTag, + prependAll } from '../util'; export function mergeProtoViewsRecursively(protoViewRefs: List>): @@ -26,7 +33,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List = new Set(); + mergeComponents(clonedProtoViews, hostViewAndBinderIndices, fragments, + elementsWithNativeShadowRoot); // Note: Need to remark parent elements of bound text nodes // so that we can find them later via queryBoundElements! markBoundTextNodeParentsAsBoundElements(clonedProtoViews); @@ -42,8 +51,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List = indexBoundTextNodes(clonedProtoViews); var rootTextNodeIndices = calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices); - var mergedElementBinders = calcElementBinders(clonedProtoViews, mergedBoundElements, - boundTextNodeMap, mergedBoundTextIndices); + var mergedElementBinders = + calcElementBinders(clonedProtoViews, mergedBoundElements, elementsWithNativeShadowRoot, + boundTextNodeMap, mergedBoundTextIndices); // create element / text index mappings var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements); @@ -53,9 +63,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List) { var hostProtoView = clonedProtoViews[0]; hostProtoView.fragments.forEach((fragment) => targetFragments.push(fragment)); @@ -153,13 +164,15 @@ function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderI var hostProtoView = clonedProtoViews[hostViewIdx]; var clonedProtoView = clonedProtoViews[viewIdx]; if (clonedProtoView.original.type === ViewType.COMPONENT) { - mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments); + mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments, + targetElementsWithNativeShadowRoot); } } } function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number, - nestedProtoView: ClonedProtoView, targetFragments: Node[][]) { + nestedProtoView: ClonedProtoView, targetFragments: Node[][], + targetElementsWithNativeShadowRoot: Set) { var hostElement = hostProtoView.boundElements[binderIdx]; // We wrap the fragments into elements so that we can expand @@ -176,8 +189,14 @@ function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number, // unwrap the fragment elements into arrays of nodes after projecting var fragments = extractFragmentNodesFromElements(fragmentElements); - appendComponentNodesToHost(hostProtoView, binderIdx, fragments[0]); - + var useNativeShadowRoot = nestedProtoView.original.encapsulation === ViewEncapsulation.NATIVE; + if (useNativeShadowRoot) { + targetElementsWithNativeShadowRoot.add(hostElement); + } + MapWrapper.forEach(nestedProtoView.original.hostAttributes, (attrValue, attrName) => { + DOM.setAttribute(hostElement, attrName, attrValue); + }); + appendComponentNodesToHost(hostProtoView, binderIdx, fragments[0], useNativeShadowRoot); for (var i = 1; i < fragments.length; i++) { targetFragments.push(fragments[i]); } @@ -209,10 +228,9 @@ function findContentElements(fragmentElements: Element[]): Element[] { } function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: number, - componentRootNodes: Node[]) { + componentRootNodes: Node[], useNativeShadowRoot: boolean) { var hostElement = hostProtoView.boundElements[binderIdx]; - var binder = hostProtoView.original.elementBinders[binderIdx]; - if (binder.hasNativeShadowRoot) { + if (useNativeShadowRoot) { var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME); for (var i = 0; i < componentRootNodes.length; i++) { DOM.appendChild(shadowRootWrapper, componentRootNodes[i]); @@ -298,6 +316,7 @@ function calcRootTextNodeIndices(rootNode: Node, boundTextNodes: Map, } function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[], + elementsWithNativeShadowRoot: Set, boundTextNodes: Map, targetBoundTextIndices: Map): DomElementBinder[] { var elementBinderByElement: Map = @@ -311,7 +330,8 @@ function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElem targetBoundTextIndices.set(textNode, targetBoundTextIndices.size); }); mergedElementBinders.push( - updateElementBinderTextNodeIndices(elementBinderByElement.get(element), textNodeIndices)); + updateElementBinders(elementBinderByElement.get(element), textNodeIndices, + SetWrapper.has(elementsWithNativeShadowRoot, element))); } return mergedElementBinders; } @@ -330,8 +350,8 @@ function indexElementBindersByElement(mergableProtoViews: ClonedProtoView[]): return elementBinderByElement; } -function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder, - textNodeIndices: number[]): DomElementBinder { +function updateElementBinders(elementBinder: DomElementBinder, textNodeIndices: number[], + hasNativeShadowRoot: boolean): DomElementBinder { var result; if (isBlank(elementBinder)) { result = new DomElementBinder({ @@ -349,7 +369,7 @@ function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder, eventLocals: elementBinder.eventLocals, localEvents: elementBinder.localEvents, globalEvents: elementBinder.globalEvents, - hasNativeShadowRoot: elementBinder.hasNativeShadowRoot + hasNativeShadowRoot: hasNativeShadowRoot }); } return result; diff --git a/modules/angular2/src/render/dom/view/shared_styles_host.ts b/modules/angular2/src/render/dom/view/shared_styles_host.ts new file mode 100644 index 0000000000..3fe44c4c92 --- /dev/null +++ b/modules/angular2/src/render/dom/view/shared_styles_host.ts @@ -0,0 +1,52 @@ +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {Inject, Injectable} from 'angular2/di'; +import {SetWrapper} from 'angular2/src/facade/collection'; +import {DOCUMENT_TOKEN} from '../dom_tokens'; + +@Injectable() +export class SharedStylesHost { + protected _styles: string[] = []; + protected _stylesSet: Set = new Set(); + + constructor() {} + + addStyles(styles: string[]) { + var additions = []; + styles.forEach(style => { + if (!SetWrapper.has(this._stylesSet, style)) { + this._stylesSet.add(style); + this._styles.push(style); + additions.push(style); + } + }); + this.onStylesAdded(additions); + } + + protected onStylesAdded(additions: string[]) {} + + getAllStyles(): string[] { return this._styles; } +} + +@Injectable() +export class DomSharedStylesHost extends SharedStylesHost { + private _hostNodes: Set = new Set(); + constructor(@Inject(DOCUMENT_TOKEN) doc: any) { + super(); + this._hostNodes.add(doc.head); + } + _addStylesToHost(styles: string[], host: Node) { + for (var i = 0; i < styles.length; i++) { + var style = styles[i]; + DOM.appendChild(host, DOM.createStyleElement(style)); + } + } + addHost(hostNode: Node) { + this._addStylesToHost(this._styles, hostNode); + this._hostNodes.add(hostNode); + } + removeHost(hostNode: Node) { SetWrapper.delete(this._hostNodes, hostNode); } + + onStylesAdded(additions: string[]) { + this._hostNodes.forEach((hostNode) => { this._addStylesToHost(additions, hostNode); }); + } +} diff --git a/modules/angular2/src/render/render.ts b/modules/angular2/src/render/render.ts index 83409e3910..6aeb4ca956 100644 --- a/modules/angular2/src/render/render.ts +++ b/modules/angular2/src/render/render.ts @@ -5,9 +5,8 @@ */ export * from './dom/compiler/view_loader'; +export * from './dom/view/shared_styles_host'; +export * from './dom/compiler/compiler'; export * from './dom/dom_renderer'; -export * from './dom/shadow_dom/shadow_dom_strategy'; -export * from './dom/shadow_dom/native_shadow_dom_strategy'; -export * from './dom/shadow_dom/emulated_scoped_shadow_dom_strategy'; -export * from './dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; +export * from './dom/dom_tokens'; export * from './api'; diff --git a/modules/angular2/src/test_lib/test_component_builder.ts b/modules/angular2/src/test_lib/test_component_builder.ts index d212fb8b5d..a0d596e916 100644 --- a/modules/angular2/src/test_lib/test_component_builder.ts +++ b/modules/angular2/src/test_lib/test_component_builder.ts @@ -16,7 +16,7 @@ import { import {el} from './utils'; -import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/render'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {DebugElement} from 'angular2/src/debug/debug_element'; diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index ed357d9fb0..7836745848 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -15,10 +15,6 @@ import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; -import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import { - EmulatedUnscopedShadowDomStrategy -} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; import {XHR} from 'angular2/src/render/xhr'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {UrlResolver} from 'angular2/src/services/url_resolver'; @@ -54,9 +50,12 @@ import {RenderCompiler, Renderer} from 'angular2/src/render/api'; import { DomRenderer, DOCUMENT_TOKEN, - DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES -} from 'angular2/src/render/dom/dom_renderer'; -import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, + DefaultDomCompiler, + APP_ID_TOKEN, + SharedStylesHost, + DomSharedStylesHost +} from 'angular2/src/render/render'; import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {Log} from './utils'; @@ -94,13 +93,14 @@ function _getAppBindings() { return [ bind(DOCUMENT_TOKEN) .toValue(appDoc), - bind(ShadowDomStrategy) - .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]), DomRenderer, - DefaultDomCompiler, - bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), bind(Renderer).toAlias(DomRenderer), + bind(APP_ID_TOKEN).toValue('a'), + DefaultDomCompiler, bind(RenderCompiler).toAlias(DefaultDomCompiler), + DomSharedStylesHost, + bind(SharedStylesHost).toAlias(DomSharedStylesHost), + bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), ProtoViewFactory, AppViewPool, AppViewManager, diff --git a/modules/angular2/src/transform/template_compiler/generator.dart b/modules/angular2/src/transform/template_compiler/generator.dart index 07edd8596f..17e2c233b0 100644 --- a/modules/angular2/src/transform/template_compiler/generator.dart +++ b/modules/angular2/src/transform/template_compiler/generator.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:angular2/src/change_detection/parser/lexer.dart' as ng; import 'package:angular2/src/change_detection/parser/parser.dart' as ng; import 'package:angular2/src/core/compiler/proto_view_factory.dart'; +import 'package:angular2/src/dom/dom_adapter.dart'; import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart'; import 'package:angular2/src/render/dom/compiler/style_inliner.dart'; @@ -96,7 +97,7 @@ class _TemplateExtractor { // Check for "imperative views". if (viewDef.template == null && viewDef.templateAbsUrl == null) return null; - var templateEl = await _loader.load(viewDef); + var templateAndStyles = await _loader.load(viewDef); // NOTE(kegluneq): Since this is a global, we must not have any async // operations between saving and restoring it, otherwise we can get into @@ -108,7 +109,7 @@ class _TemplateExtractor { var pipeline = new CompilePipeline(_factory.createSteps(viewDef)); var compileElements = - pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId); + pipeline.processElements(DOM.createTemplate(templateAndStyles.template), ViewType.COMPONENT, viewDef); var protoViewDto = compileElements[0].inheritedProtoView.build(); reflector.reflectionCapabilities = savedReflectionCapabilities; diff --git a/modules/angular2/src/web-workers/shared/serializer.ts b/modules/angular2/src/web-workers/shared/serializer.ts index 8a84ad6886..5b2a03c0bb 100644 --- a/modules/angular2/src/web-workers/shared/serializer.ts +++ b/modules/angular2/src/web-workers/shared/serializer.ts @@ -213,7 +213,8 @@ export class Serializer { 'template': view.template, 'directives': this.serialize(view.directives, DirectiveMetadata), 'styleAbsUrls': view.styleAbsUrls, - 'styles': view.styles + 'styles': view.styles, + 'encapsulation': view.encapsulation }; } @@ -223,7 +224,8 @@ export class Serializer { templateAbsUrl: obj['templateAbsUrl'], template: obj['template'], directives: this.deserialize(obj['directives'], DirectiveMetadata), styleAbsUrls: obj['styleAbsUrls'], - styles: obj['styles'] + styles: obj['styles'], + encapsulation: obj['encapsulation'] }); } diff --git a/modules/angular2/src/web-workers/ui/di_bindings.ts b/modules/angular2/src/web-workers/ui/di_bindings.ts index f137278784..fd323829ea 100644 --- a/modules/angular2/src/web-workers/ui/di_bindings.ts +++ b/modules/angular2/src/web-workers/ui/di_bindings.ts @@ -24,15 +24,16 @@ import {Renderer, RenderCompiler} from 'angular2/src/render/api'; import { DomRenderer, DOCUMENT_TOKEN, - DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES -} from 'angular2/src/render/dom/dom_renderer'; -import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, + DefaultDomCompiler, + APP_ID_RANDOM_BINDING +} from 'angular2/src/render/render'; +import { + SharedStylesHost, + DomSharedStylesHost +} from 'angular2/src/render/dom/view/shared_styles_host'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; -import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import { - EmulatedUnscopedShadowDomStrategy -} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {AppViewListener} from 'angular2/src/core/compiler/view_listener'; @@ -89,14 +90,15 @@ function _injectorBindings(): List> { return new EventManager(plugins, ngZone); }, [NgZone]), - bind(ShadowDomStrategy) - .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]), bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), DomRenderer, - DefaultDomCompiler, - Serializer, bind(Renderer).toAlias(DomRenderer), + APP_ID_RANDOM_BINDING, + DefaultDomCompiler, bind(RenderCompiler).toAlias(DefaultDomCompiler), + DomSharedStylesHost, + bind(SharedStylesHost).toAlias(DomSharedStylesHost), + Serializer, bind(ON_WEBWORKER).toValue(false), RenderViewWithFragmentsStore, RenderProtoViewRefStore, diff --git a/modules/angular2/test/core/application_spec.ts b/modules/angular2/test/core/application_spec.ts index 56d982c8ca..f033b771aa 100644 --- a/modules/angular2/test/core/application_spec.ts +++ b/modules/angular2/test/core/application_spec.ts @@ -20,7 +20,7 @@ import {bind, Inject, Injector} from 'angular2/di'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; -import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/render'; @Component({selector: 'hello-app'}) @View({template: '{{greeting}} world!'}) diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts index d0cbeb9de7..76c23cac4e 100644 --- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts +++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts @@ -24,7 +24,7 @@ import {Component, View, LifecycleEvent} from 'angular2/annotations'; import * as viewAnn from 'angular2/src/core/annotations_impl/view'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; -import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/render'; import {DOM} from 'angular2/src/dom/dom_adapter'; export function main() { diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 6c5d076204..21f7dae156 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -1452,7 +1452,7 @@ class MyService { } @Component({selector: 'simple-imp-cmp'}) -@View({renderer: 'simple-imp-cmp-renderer', template: ''}) +@View({template: ''}) @Injectable() class SimpleImperativeViewComponent { done; diff --git a/modules/angular2/test/core/compiler/projection_integration_spec.ts b/modules/angular2/test/core/compiler/projection_integration_spec.ts index 186f2b8460..f1b0a01375 100644 --- a/modules/angular2/test/core/compiler/projection_integration_spec.ts +++ b/modules/angular2/test/core/compiler/projection_integration_spec.ts @@ -34,9 +34,9 @@ import { ViewContainerRef, ElementRef, TemplateRef, - bind + bind, + ViewEncapsulation } from 'angular2/angular2'; -import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render'; export function main() { describe('projection', () => { @@ -399,27 +399,21 @@ export function main() { })); if (DOM.supportsNativeShadowDOM()) { - describe('native shadow dom support', () => { - beforeEachBindings( - () => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; }); + it('should support native content projection', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MainComp, new viewAnn.View({ + template: '' + + '
A
' + + '
', + directives: [SimpleNative] + })) + .createAsync(MainComp) + .then((main) => { - it('should support native content projection', - inject([TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async) => { - tcb.overrideView(MainComp, new viewAnn.View({ - template: '' + - '
A
' + - '
', - directives: [SimpleNative] - })) - .createAsync(MainComp) - .then((main) => { - - expect(main.nativeElement).toHaveText('SIMPLE(A)'); - async.done(); - }); - })); - }); + expect(main.nativeElement).toHaveText('SIMPLE(A)'); + async.done(); + }); + })); } }); @@ -438,7 +432,11 @@ class Simple { } @Component({selector: 'simple-native'}) -@View({template: 'SIMPLE()', directives: []}) +@View({ + template: 'SIMPLE()', + directives: [], + encapsulation: ViewEncapsulation.NATIVE +}) class SimpleNative { } diff --git a/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts b/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts index 378dc12540..3cb0ea0862 100644 --- a/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts +++ b/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts @@ -17,26 +17,38 @@ import {Type, isBlank, stringify, isPresent, BaseException} from 'angular2/src/f import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler'; -import {ProtoViewDto, ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api'; -import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element'; +import { + ProtoViewDto, + ViewDefinition, + DirectiveMetadata, + ViewType, + ViewEncapsulation +} from 'angular2/src/render/api'; import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory'; -import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; -import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; +import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader'; import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; +import {SharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host'; + +import {MockStep} from './pipeline_spec'; export function runCompilerCommonTests() { describe('DomCompiler', function() { var mockStepFactory: MockStepFactory; + var sharedStylesHost: SharedStylesHost; - function createCompiler(processClosure, urlData = null) { + beforeEach(() => {sharedStylesHost = new SharedStylesHost()}); + + function createCompiler(processElementClosure = null, processStyleClosure = null, + urlData = null) { if (isBlank(urlData)) { urlData = new Map(); } var tplLoader = new FakeViewLoader(urlData); - mockStepFactory = new MockStepFactory([new MockStep(processClosure)]); - return new DomCompiler(mockStepFactory, tplLoader, false); + mockStepFactory = + new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]); + return new DomCompiler(mockStepFactory, tplLoader, sharedStylesHost); } describe('compile', () => { @@ -61,12 +73,13 @@ export function runCompilerCommonTests() { }); var dirMetadata = DirectiveMetadata.create( - {id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE}); + {id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE}); compiler.compileHost(dirMetadata) .then((protoView) => { expect(DOM.tagName(DOM.firstChild(DOM.content( - resolveInternalDomProtoView(protoView.render).rootElement)))) - .toEqual('CUSTOM'); + resolveInternalDomProtoView(protoView.render).rootElement))) + .toLowerCase()) + .toEqual('custom'); expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]); expect(protoView.variableBindings) .toEqual(MapWrapper.createFromStringMap({'a': 'b'})); @@ -88,7 +101,7 @@ export function runCompilerCommonTests() { it('should load url templates', inject([AsyncTestCompleter], (async) => { var urlData = MapWrapper.createFromStringMap({'someUrl': 'url component'}); - var compiler = createCompiler(EMPTY_STEP, urlData); + var compiler = createCompiler(EMPTY_STEP, null, urlData); compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})) .then((protoView) => { expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement)) @@ -98,7 +111,7 @@ export function runCompilerCommonTests() { })); it('should report loading errors', inject([AsyncTestCompleter], (async) => { - var compiler = createCompiler(EMPTY_STEP, new Map()); + var compiler = createCompiler(EMPTY_STEP, null, new Map()); PromiseWrapper.catchError( compiler.compile( new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})), @@ -137,6 +150,110 @@ export function runCompilerCommonTests() { }); + describe('compile styles', () => { + it('should run the steps', inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(null, (style) => { return style + 'b {};'; }); + compiler.compile(new ViewDefinition( + {componentId: 'someComponent', template: '', styles: ['a {};']})) + .then((protoViewDto) => { + expect(sharedStylesHost.getAllStyles()).toEqual(['a {};b {};']); + async.done(); + }); + })); + + it('should store the styles in the SharedStylesHost for ViewEncapsulation.NONE', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile(new ViewDefinition({ + componentId: 'someComponent', + template: '', + styles: ['a {};'], + encapsulation: ViewEncapsulation.NONE + })) + .then((protoViewDto) => { + var domProtoView = resolveInternalDomProtoView(protoViewDto.render); + expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual(''); + expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']); + async.done(); + }); + })); + + it('should store the styles in the SharedStylesHost for ViewEncapsulation.EMULATED', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile(new ViewDefinition({ + componentId: 'someComponent', + template: '', + styles: ['a {};'], + encapsulation: ViewEncapsulation.EMULATED + })) + .then((protoViewDto) => { + var domProtoView = resolveInternalDomProtoView(protoViewDto.render); + expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual(''); + expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']); + async.done(); + }); + })); + + if (DOM.supportsNativeShadowDOM()) { + it('should store the styles in the template for ViewEncapsulation.NATIVE', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile(new ViewDefinition({ + componentId: 'someComponent', + template: '', + styles: ['a {};'], + encapsulation: ViewEncapsulation.NATIVE + })) + .then((protoViewDto) => { + var domProtoView = resolveInternalDomProtoView(protoViewDto.render); + expect(DOM.getInnerHTML(domProtoView.rootElement)) + .toEqual(''); + expect(sharedStylesHost.getAllStyles()).toEqual([]); + async.done(); + }); + })); + } + + it('should default to ViewEncapsulation.NONE if no styles are specified', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile( + new ViewDefinition({componentId: 'someComponent', template: '', styles: []})) + .then((protoView) => { + expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.NONE); + async.done(); + }); + })); + + it('should default to ViewEncapsulation.EMULATED if styles are specified', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile(new ViewDefinition( + {componentId: 'someComponent', template: '', styles: ['a {};']})) + .then((protoView) => { + expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.EMULATED); + async.done(); + }); + })); + + }); + + describe('mergeProtoViews', () => { + it('should store the styles of the merged ProtoView in the SharedStylesHost', + inject([AsyncTestCompleter], (async) => { + var compiler = createCompiler(); + compiler.compile(new ViewDefinition( + {componentId: 'someComponent', template: '', styles: ['a {};']})) + .then(protoViewDto => compiler.mergeProtoViewsRecursively([protoViewDto.render])) + .then(_ => { + expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']); + async.done(); + }); + })); + + }); + }); } @@ -155,14 +272,6 @@ class MockStepFactory extends CompileStepFactory { } } -class MockStep implements CompileStep { - processClosure: Function; - constructor(process) { this.processClosure = process; } - process(parent: CompileElement, current: CompileElement, control: CompileControl) { - this.processClosure(parent, current, control); - } -} - var EMPTY_STEP = (parent, current, control) => { if (isPresent(parent)) { current.inheritedProtoView = parent.inheritedProtoView; @@ -176,16 +285,17 @@ class FakeViewLoader extends ViewLoader { this._urlData = urlData; } - load(view: ViewDefinition): Promise { - if (isPresent(view.template)) { - return PromiseWrapper.resolve(DOM.createTemplate(view.template)); + load(viewDef): Promise { + var styles = isPresent(viewDef.styles) ? viewDef.styles : []; + if (isPresent(viewDef.template)) { + return PromiseWrapper.resolve(new TemplateAndStyles(viewDef.template, styles)); } - if (isPresent(view.templateAbsUrl)) { - var content = this._urlData.get(view.templateAbsUrl); + if (isPresent(viewDef.templateAbsUrl)) { + var content = this._urlData.get(viewDef.templateAbsUrl); return isPresent(content) ? - PromiseWrapper.resolve(DOM.createTemplate(content)) : - PromiseWrapper.reject(`Failed to fetch url "${view.templateAbsUrl}"`, null); + PromiseWrapper.resolve(new TemplateAndStyles(content, styles)) : + PromiseWrapper.reject(`Failed to fetch url "${viewDef.templateAbsUrl}"`, null); } throw new BaseException('View should have either the templateUrl or template property set'); diff --git a/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts b/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts index 72e6d078ec..6c9c7b4935 100644 --- a/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts +++ b/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts @@ -4,12 +4,10 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/col import {DOM} from 'angular2/src/dom/dom_adapter'; import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; -import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; -import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element'; -import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; -import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api'; +import {ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api'; import {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; +import {MockStep} from './pipeline_spec'; export function main() { describe('DirectiveParser', () => { @@ -47,9 +45,15 @@ export function main() { ]); } + function createViewDefinition(): ViewDefinition { + return new ViewDefinition({componentId: 'someComponent'}); + } + function process(el, propertyBindings = null, directives = null): List { var pipeline = createPipeline(propertyBindings, directives); - return ListWrapper.map(pipeline.process(el), (ce) => ce.inheritedElementBinder); + return ListWrapper.map( + pipeline.processElements(el, ViewType.COMPONENT, createViewDefinition()), + (ce) => ce.inheritedElementBinder); } it('should not add directives if they are not used', () => { @@ -70,12 +74,14 @@ export function main() { }); it('should compile children by default', () => { - var results = createPipeline().process(el('
')); + var results = createPipeline().processElements(el('
'), + ViewType.COMPONENT, createViewDefinition()); expect(results[0].compileChildren).toEqual(true); }); it('should stop compiling children when specified in the directive config', () => { - var results = createPipeline().process(el('
')); + var results = createPipeline().processElements(el('
'), + ViewType.COMPONENT, createViewDefinition()); expect(results[0].compileChildren).toEqual(false); }); @@ -191,14 +197,6 @@ export function main() { }); } -class MockStep implements CompileStep { - processClosure: Function; - constructor(process) { this.processClosure = process; } - process(parent: CompileElement, current: CompileElement, control: CompileControl) { - this.processClosure(parent, current, control); - } -} - var someComponent = DirectiveMetadata.create( {selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE}); diff --git a/modules/angular2/test/render/dom/compiler/pipeline_spec.ts b/modules/angular2/test/render/dom/compiler/pipeline_spec.ts index fa2197f4be..9b88a27fe2 100644 --- a/modules/angular2/test/render/dom/compiler/pipeline_spec.ts +++ b/modules/angular2/test/render/dom/compiler/pipeline_spec.ts @@ -9,16 +9,21 @@ import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; -import {ProtoViewDto, ViewType} from 'angular2/src/render/api'; +import {ProtoViewDto, ViewType, ViewEncapsulation, ViewDefinition} from 'angular2/src/render/api'; export function main() { describe('compile_pipeline', () => { + function createViewDefinition(): ViewDefinition { + return new ViewDefinition({componentId: 'someComponent'}); + } + describe('children compilation', () => { it('should walk the tree in depth first order including template contents', () => { var element = el('
'); var step0Log = []; - var results = new CompilePipeline([createLoggerStep(step0Log)]).process(element); + var results = new CompilePipeline([createLoggerStep(step0Log)]) + .processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(step0Log).toEqual(['1', '1<2', '2<3']); expect(resultIdLog(results)).toEqual(['1', '2', '3']); @@ -30,7 +35,7 @@ export function main() { var step0Log = []; var pipeline = new CompilePipeline([new IgnoreChildrenStep(), createLoggerStep(step0Log)]); - var results = pipeline.process(element); + var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(step0Log).toEqual(['1', '1<2']); expect(resultIdLog(results)).toEqual(['1', '2']); @@ -42,11 +47,12 @@ export function main() { var pipeline = new CompilePipeline([ new MockStep((parent, current, control) => { if (isPresent(DOM.getAttribute(current.element, 'viewroot'))) { - current.inheritedProtoView = new ProtoViewBuilder(current.element, ViewType.EMBEDDED); + current.inheritedProtoView = + new ProtoViewBuilder(current.element, ViewType.EMBEDDED, ViewEncapsulation.NONE); } }) ]); - var results = pipeline.process(element); + var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(results[0].inheritedProtoView).toBe(results[1].inheritedProtoView); expect(results[2].inheritedProtoView).toBe(results[3].inheritedProtoView); }); @@ -60,14 +66,15 @@ export function main() { } }) ]); - var results = pipeline.process(element); + var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(results[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder); expect(results[2].inheritedElementBinder).toBe(results[3].inheritedElementBinder); }); it('should mark root elements as viewRoot', () => { var rootElement = el('
'); - var results = new CompilePipeline([]).process(rootElement); + var results = new CompilePipeline([]) + .processElements(rootElement, ViewType.COMPONENT, createViewDefinition()); expect(results[0].isViewRoot).toBe(true); }); @@ -80,7 +87,7 @@ export function main() { } }) ]); - var results = pipeline.process(element); + var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(results[0].inheritedElementBinder.distanceToParent).toBe(0); expect(results[1].inheritedElementBinder.distanceToParent).toBe(1); expect(results[3].inheritedElementBinder.distanceToParent).toBe(2); @@ -95,7 +102,7 @@ export function main() { new IgnoreCurrentElementStep(), createLoggerStep(logs), ]); - var results = pipeline.process(element); + var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(results.length).toBe(2); expect(logs).toEqual(['1', '1<3']) @@ -108,7 +115,7 @@ export function main() { var step1Log = []; var pipeline = new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]); - var result = pipeline.process(element); + var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition()); expect(step0Log).toEqual(['1', '1<2', '2<3']); expect(step1Log).toEqual(['1', '1 { + it('should call the steps for every style', () => { + var stepCalls = []; + var pipeline = new CompilePipeline([ + new MockStep(null, + (style) => { + stepCalls.push(style); + return style; + }) + ]); + var result = pipeline.processStyles(['a', 'b']); + expect(result[0]).toEqual('a'); + expect(result[1]).toEqual('b'); + expect(result).toEqual(stepCalls); + }); + }); + }); } -class MockStep implements CompileStep { - processClosure: Function; - constructor(process) { this.processClosure = process; } - process(parent: CompileElement, current: CompileElement, control: CompileControl) { - this.processClosure(parent, current, control); +export class MockStep implements CompileStep { + constructor(private processElementClosure: Function, + private processStyleClosure: Function = null) {} + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { + if (isPresent(this.processElementClosure)) { + this.processElementClosure(parent, current, control); + } + } + processStyle(style: string): string { + if (isPresent(this.processStyleClosure)) { + return this.processStyleClosure(style); + } else { + return style; + } } } export class IgnoreChildrenStep implements CompileStep { - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { var attributeMap = DOM.attributeMap(current.element); if (attributeMap.has('ignore-children')) { current.compileChildren = false; } } + processStyle(style: string): string { return style; } } class IgnoreCurrentElementStep implements CompileStep { - process(parent: CompileElement, current: CompileElement, control: CompileControl) { + processElement(parent: CompileElement, current: CompileElement, control: CompileControl) { var attributeMap = DOM.attributeMap(current.element); if (attributeMap.has('ignore-current')) { control.ignoreCurrentElement(); } } + processStyle(style: string): string { return style; } } function logEntry(log: string[], parent, current) { diff --git a/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.ts b/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.ts index 59124152b2..017018a5e3 100644 --- a/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.ts +++ b/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.ts @@ -3,11 +3,10 @@ import {IMPLEMENTS} from 'angular2/src/facade/lang'; import {PropertyBindingParser} from 'angular2/src/render/dom/compiler/property_binding_parser'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; -import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element'; -import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; -import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; import {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; +import {ViewDefinition, ViewType} from 'angular2/src/render/api'; +import {MockStep} from './pipeline_spec'; var EMPTY_MAP = new Map(); @@ -24,9 +23,15 @@ export function main() { ]); } + function createViewDefinition(): ViewDefinition { + return new ViewDefinition({componentId: 'someComponent'}); + } + function process(element, hasNestedProtoView = false): List { - return ListWrapper.map(createPipeline(hasNestedProtoView).process(element), - (compileElement) => compileElement.inheritedElementBinder); + return ListWrapper.map( + createPipeline(hasNestedProtoView) + .processElements(element, ViewType.COMPONENT, createViewDefinition()), + (compileElement) => compileElement.inheritedElementBinder); } it('should detect [] syntax', () => { @@ -174,13 +179,15 @@ export function main() { }); it('should store bound properties as temporal attributes', () => { - var results = createPipeline().process(el('
')); + var results = createPipeline().processElements(el('
'), + ViewType.COMPONENT, createViewDefinition()); expect(results[0].attrs().get('a')).toEqual('b'); expect(results[0].attrs().get('c')).toEqual('d'); }); it('should store variables as temporal attributes', () => { - var results = createPipeline().process(el('
')); + var results = createPipeline().processElements(el('
'), + ViewType.COMPONENT, createViewDefinition()); expect(results[0].attrs().get('a')).toEqual('b'); expect(results[0].attrs().get('c')).toEqual('d'); }); @@ -210,11 +217,3 @@ export function main() { }); }); } - -class MockStep implements CompileStep { - processClosure: Function; - constructor(process) { this.processClosure = process; } - process(parent: CompileElement, current: CompileElement, control: CompileControl) { - this.processClosure(parent, current, control); - } -} diff --git a/modules/angular2/test/render/dom/shadow_dom/shadow_css_spec.ts b/modules/angular2/test/render/dom/compiler/shadow_css_spec.ts similarity index 98% rename from modules/angular2/test/render/dom/shadow_dom/shadow_css_spec.ts rename to modules/angular2/test/render/dom/compiler/shadow_css_spec.ts index 463f6ffd5b..e64b2cd0e5 100644 --- a/modules/angular2/test/render/dom/shadow_dom/shadow_css_spec.ts +++ b/modules/angular2/test/render/dom/compiler/shadow_css_spec.ts @@ -9,7 +9,7 @@ import { el, normalizeCSS } from 'angular2/test_lib'; -import {ShadowCss} from 'angular2/src/render/dom/shadow_dom/shadow_css'; +import {ShadowCss} from 'angular2/src/render/dom/compiler/shadow_css'; import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang'; import {DOM} from 'angular2/src/dom/dom_adapter'; diff --git a/modules/angular2/test/render/dom/compiler/style_encapsulator_spec.ts b/modules/angular2/test/render/dom/compiler/style_encapsulator_spec.ts new file mode 100644 index 0000000000..c6e243bd4b --- /dev/null +++ b/modules/angular2/test/render/dom/compiler/style_encapsulator_spec.ts @@ -0,0 +1,135 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + el, + expect, + iit, + inject, + it, + xit, + SpyObject, +} from 'angular2/test_lib'; + +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; + +import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; +import { + ProtoViewBuilder, + ElementBinderBuilder +} from 'angular2/src/render/dom/view/proto_view_builder'; +import {ViewDefinition, ViewType, ViewEncapsulation} from 'angular2/src/render/api'; + +import {StyleEncapsulator} from 'angular2/src/render/dom/compiler/style_encapsulator'; +import {MockStep} from './pipeline_spec'; + +export function main() { + describe('StyleEncapsulator', () => { + var componentIdCache; + + beforeEach(() => { componentIdCache = new Map(); }); + + function createPipeline(viewDef: ViewDefinition) { + return new CompilePipeline([ + new MockStep((parent, current, control) => { + var tagName = DOM.tagName(current.element).toLowerCase(); + if (tagName.startsWith('comp-')) { + current.bindElement().setComponentId(tagName); + } + }), + new StyleEncapsulator('someapp', viewDef, componentIdCache) + ]); + } + + function createViewDefinition(encapsulation: ViewEncapsulation, componentId: string): + ViewDefinition { + return new ViewDefinition({encapsulation: encapsulation, componentId: componentId}); + } + + function processStyles(encapsulation: ViewEncapsulation, componentId: string, styles: string[]): + string[] { + var viewDef = createViewDefinition(encapsulation, componentId); + return createPipeline(viewDef).processStyles(styles); + } + + function processElements(encapsulation: ViewEncapsulation, componentId: string, + template: Element, viewType: ViewType = ViewType.COMPONENT): + ProtoViewBuilder { + var viewDef = createViewDefinition(encapsulation, componentId); + var compileElements = createPipeline(viewDef).processElements(template, viewType, viewDef); + return compileElements[0].inheritedProtoView; + } + + describe('ViewEncapsulation.NONE', () => { + it('should not change the styles', () => { + var cs = processStyles(ViewEncapsulation.NONE, 'someComponent', ['.one {}']); + expect(cs[0]).toEqual('.one {}'); + }); + }); + + describe('ViewEncapsulation.NATIVE', () => { + it('should not change the styles', () => { + var cs = processStyles(ViewEncapsulation.NATIVE, 'someComponent', ['.one {}']); + expect(cs[0]).toEqual('.one {}'); + }); + }); + + describe('ViewEncapsulation.EMULATED', () => { + it('should scope styles', () => { + var cs = processStyles(ViewEncapsulation.EMULATED, 'someComponent', ['.foo {} :host {}']); + expect(cs[0]).toEqual(".foo[_ngcontent-someapp-0] {\n\n}\n\n[_nghost-someapp-0] {\n\n}"); + }); + + it('should return the same style given the same component', () => { + var style = '.foo {} :host {}'; + var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]); + var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]); + + expect(cs1[0]).toEqual(cs2[0]); + }); + + it('should return different styles given different components', () => { + var style = '.foo {} :host {}'; + var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent1', [style]); + var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent2', [style]); + + expect(cs1[0]).not.toEqual(cs2[0]); + }); + + it('should add a host attribute to component proto views', () => { + var template = DOM.createTemplate('
'); + var protoViewBuilder = + processElements(ViewEncapsulation.EMULATED, 'someComponent', template); + expect(protoViewBuilder.hostAttributes.get('_nghost-someapp-0')).toEqual(''); + }); + + it('should not add a host attribute to embedded proto views', () => { + var template = DOM.createTemplate('
'); + var protoViewBuilder = processElements(ViewEncapsulation.EMULATED, 'someComponent', + template, ViewType.EMBEDDED); + expect(protoViewBuilder.hostAttributes.size).toBe(0); + }); + + it('should not add a host attribute to host proto views', () => { + var template = DOM.createTemplate('
'); + var protoViewBuilder = + processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST); + expect(protoViewBuilder.hostAttributes.size).toBe(0); + }); + + it('should add an attribute to the content elements', () => { + var template = DOM.createTemplate('
'); + processElements(ViewEncapsulation.EMULATED, 'someComponent', template); + expect(DOM.getInnerHTML(template)).toEqual('
'); + }); + + it('should not add an attribute to the content elements for host views', () => { + var template = DOM.createTemplate('
'); + processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST); + expect(DOM.getInnerHTML(template)).toEqual('
'); + }); + }); + }); +} diff --git a/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts b/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts index baa8a5e96d..d6a0d85190 100644 --- a/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts +++ b/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts @@ -9,6 +9,7 @@ import { ElementBinderBuilder } from 'angular2/src/render/dom/view/proto_view_builder'; import {DOM} from 'angular2/src/dom/dom_adapter'; +import {ViewDefinition, ViewType} from 'angular2/src/render/api'; export function main() { describe('TextInterpolationParser', () => { @@ -17,8 +18,13 @@ export function main() { [new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]); } + function createViewDefinition(): ViewDefinition { + return new ViewDefinition({componentId: 'someComponent'}); + } + function process(templateString: string): ProtoViewBuilder { - var compileElements = createPipeline().process(DOM.createTemplate(templateString)); + var compileElements = createPipeline().processElements( + DOM.createTemplate(templateString), ViewType.COMPONENT, createViewDefinition()); return compileElements[0].inheritedProtoView; } diff --git a/modules/angular2/test/render/dom/compiler/view_loader_spec.ts b/modules/angular2/test/render/dom/compiler/view_loader_spec.ts index 99e8b07f52..87a3f124e2 100644 --- a/modules/angular2/test/render/dom/compiler/view_loader_spec.ts +++ b/modules/angular2/test/render/dom/compiler/view_loader_spec.ts @@ -10,21 +10,21 @@ import { it, xit, } from 'angular2/test_lib'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; +import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver'; -import {ViewDefinition} from 'angular2/src/render/api'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {XHR} from 'angular2/src/render/xhr'; import {MockXHR} from 'angular2/src/render/xhr_mock'; +import {ViewDefinition} from 'angular2/src/render/api'; export function main() { describe('ViewLoader', () => { - var loader, xhr, styleUrlResolver, urlResolver; + var loader: ViewLoader; + var xhr, styleUrlResolver, urlResolver; beforeEach(() => { xhr = new MockXHR(); @@ -36,32 +36,33 @@ export function main() { describe('html', () => { it('should load inline templates', inject([AsyncTestCompleter], (async) => { - var view = new ViewDefinition({template: 'template template'}); - loader.load(view).then((el) => { - expect(DOM.content(el)).toHaveText('template template'); - async.done(); - }); + loader.load(new ViewDefinition({template: 'template template'})) + .then((el) => { + expect(el.template).toEqual('template template'); + async.done(); + }); })); it('should load templates through XHR', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/foo.html', 'xhr template'); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); - loader.load(view).then((el) => { - expect(DOM.content(el)).toHaveText('xhr template'); - async.done(); - }); + loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})) + .then((el) => { + expect(el.template).toEqual('xhr template'); + async.done(); + }); xhr.flush(); })); it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/foo.html', ''); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); - loader.load(view).then((el) => { - expect(DOM.content(el)) - .toHaveText(".foo { background-image: url('http://ng.io/double.jpg'); }"); - async.done(); - }); + loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})) + .then((el) => { + expect(el.template).toEqual(''); + expect(el.styles) + .toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]); + async.done(); + }); xhr.flush(); })); @@ -73,85 +74,66 @@ export function main() { let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)).toEqual(""); - async.done(); - }); - })); - - it('should return a new template element on each call', - inject([AsyncTestCompleter], (async) => { - var firstEl; - // we have only one xhr.expect, so there can only be one xhr call! - xhr.expect('http://ng.io/foo.html', 'xhr template'); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); - loader.load(view) + loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})) .then((el) => { - expect(DOM.content(el)).toHaveText('xhr template'); - firstEl = el; - return loader.load(view); - }) - .then((el) => { - expect(el).not.toBe(firstEl); - expect(DOM.content(el)).toHaveText('xhr template'); + expect(el.template).toEqual(''); + expect(el.styles).toEqual(["/* foo.css */\n"]); async.done(); }); - xhr.flush(); })); it('should throw when no template is defined', () => { - var view = new ViewDefinition({template: null, templateAbsUrl: null}); - expect(() => loader.load(view)) + expect(() => loader.load(new ViewDefinition({template: null, templateAbsUrl: null}))) .toThrowError('View should have either the templateUrl or template property set'); }); it('should return a rejected Promise when XHR loading fails', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/foo.html', null); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); - PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; }, - function(error) { - expect(error.message) - .toEqual('Failed to fetch url "http://ng.io/foo.html"'); - async.done(); - }); + PromiseWrapper.then( + loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})), + function(_) { throw 'Unexpected response'; }, + function(error) { + expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.html"'); + async.done(); + }); xhr.flush(); })); + it('should replace $baseUrl in attributes with the template base url', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/path/foo.html', ''); - var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'}); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)).toEqual(''); - async.done(); - }); + loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'})) + .then((el) => { + expect(el.template).toEqual(''); + async.done(); + }); xhr.flush(); })); }); describe('css', () => { it('should load inline styles', inject([AsyncTestCompleter], (async) => { - var view = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)) - .toEqual('html'); - async.done(); - }); + loader.load(new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']})) + .then((el) => { + expect(el.template).toEqual('html'); + expect(el.styles).toEqual(['style 1', 'style 2']); + async.done(); + }); })); it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/foo.html', 'html'); - var view = new ViewDefinition({ - templateAbsUrl: 'http://ng.io/foo.html', - styles: ['.foo { background-image: url("double.jpg"); }'] - }); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)) - .toEqual( - "html"); - async.done(); - }); + loader.load(new ViewDefinition({ + templateAbsUrl: 'http://ng.io/foo.html', + styles: ['.foo { background-image: url("double.jpg"); }'] + })) + .then((el) => { + expect(el.template).toEqual('html'); + expect(el.styles) + .toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]); + async.done(); + }); xhr.flush(); })); @@ -159,16 +141,16 @@ export function main() { xhr.expect('http://ng.io/foo.html', 'xhr template'); xhr.expect('http://ng.io/foo-1.css', '1'); xhr.expect('http://ng.io/foo-2.css', '2'); - var view = new ViewDefinition({ - templateAbsUrl: 'http://ng.io/foo.html', - styles: ['i1'], - styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css'] - }); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)) - .toEqual('xhr template'); - async.done(); - }); + loader.load(new ViewDefinition({ + templateAbsUrl: 'http://ng.io/foo.html', + styles: ['i1'], + styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css'] + })) + .then((el) => { + expect(el.template).toEqual('xhr template'); + expect(el.styles).toEqual(['i1', '1', '2']); + async.done(); + }); xhr.flush(); })); @@ -180,25 +162,27 @@ export function main() { let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); - var view = new ViewDefinition( - {templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']}); - loader.load(view).then((el) => { - expect(DOM.getInnerHTML(el)).toEqual("

template

"); - async.done(); - }); + loader.load( + new ViewDefinition( + {templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']})) + .then((el) => { + expect(el.template).toEqual("

template

"); + expect(el.styles).toEqual(["/* foo.css */\n"]); + async.done(); + }); })); - it('should return a rejected Promise when XHR loading fails', inject([AsyncTestCompleter], (async) => { xhr.expect('http://ng.io/foo.css', null); - var view = new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']}); - PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; }, - function(error) { - expect(error.message) - .toEqual('Failed to fetch url "http://ng.io/foo.css"'); - async.done(); - }); + PromiseWrapper.then( + loader.load( + new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']})), + function(_) { throw 'Unexpected response'; }, + function(error) { + expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.css"'); + async.done(); + }); xhr.flush(); })); }); diff --git a/modules/angular2/test/render/dom/compiler/view_splitter_spec.ts b/modules/angular2/test/render/dom/compiler/view_splitter_spec.ts index 37d33b1ae8..7ed60a9ae3 100644 --- a/modules/angular2/test/render/dom/compiler/view_splitter_spec.ts +++ b/modules/angular2/test/render/dom/compiler/view_splitter_spec.ts @@ -12,7 +12,8 @@ import {MapWrapper} from 'angular2/src/facade/collection'; import {ViewSplitter} from 'angular2/src/render/dom/compiler/view_splitter'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; -import {ProtoViewDto, ViewType} from 'angular2/src/render/api'; +import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element'; +import {ProtoViewDto, ViewType, ViewDefinition} from 'angular2/src/render/api'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; @@ -20,15 +21,23 @@ import {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; export function main() { describe('ViewSplitter', () => { + function createViewDefinition(): ViewDefinition { + return new ViewDefinition({componentId: 'someComponent'}); + } + function createPipeline() { return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]); } + function proceess(el): CompileElement[] { + return createPipeline().processElements(el, ViewType.COMPONENT, createViewDefinition()); + } + describe('