diff --git a/modules/angular2/src/change_detection/parser/parser.ts b/modules/angular2/src/change_detection/parser/parser.ts index 4624d5fbcf..ab9c5f7b50 100644 --- a/modules/angular2/src/change_detection/parser/parser.ts +++ b/modules/angular2/src/change_detection/parser/parser.ts @@ -106,7 +106,7 @@ export class Parser { } } -class _ParseAST { +export class _ParseAST { index: int = 0; constructor(public input: string, public location: any, public tokens: List, public reflector: Reflector, public parseAction: boolean) {} diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index 3ffd1aa3a3..edbc52bee8 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -45,6 +45,7 @@ import {HammerGesturesPlugin} from 'angular2/src/render/dom/events/hammer_gestur import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {UrlResolver} from 'angular2/src/services/url_resolver'; import {AppRootUrl} from 'angular2/src/services/app_root_url'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; import { ComponentRef, DynamicComponentLoader @@ -136,14 +137,14 @@ function _injectorBindings(appComponentType): List> { StyleInliner, DynamicComponentLoader, Testability, - AppRootUrl + AnchorBasedAppRootUrl, + bind(AppRootUrl).toAlias(AnchorBasedAppRootUrl) ]; } -function _createNgZone(): NgZone { +export function createNgZone(handler: ExceptionHandler): NgZone { // bootstrapErrorReporter is needed because we cannot use custom exception handler // configured via DI until the root Injector has been created. - var handler = new ExceptionHandler(); var bootstrapErrorReporter = (exception, stackTrace) => handler.call(exception, stackTrace); var zone = new NgZone({enableLongStackTrace: assertionsEnabled()}); zone.overrideOnErrorHandler(bootstrapErrorReporter); @@ -282,7 +283,7 @@ export function commonBootstrap( BrowserDomAdapter.makeCurrent(); var bootstrapProcess = PromiseWrapper.completer(); - var zone = _createNgZone(); + var zone = createNgZone(new ExceptionHandler()); zone.run(() => { // TODO(rado): prepopulate template cache, so applications with only // index.html and main.js are possible. diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index 7271a26ea1..b7941540b2 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -39,6 +39,19 @@ bool isDate(obj) => obj is DateTime; String stringify(obj) => obj.toString(); +int serializeEnum(val) { + return val.index; +} + +/** + * Deserializes an enum + * val should be the indexed value of the enum (sa returned from @Link{serializeEnum}) + * values should be a map from indexes to values for the enum that you want to deserialize. + */ +dynamic deserializeEnum(int val, Map values) { + return values[val]; +} + class StringWrapper { static String fromCharCode(int code) { return new String.fromCharCode(code); diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index 4dbe66cada..a061daef04 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -130,6 +130,17 @@ export function stringify(token): string { return (newLineIndex === -1) ? res : res.substring(0, newLineIndex); } +// serialize / deserialize enum exist only for consistency with dart API +// enums in typescript don't need to be serialized + +export function serializeEnum(val): int { + return val; +} + +export function deserializeEnum(val, values: Map): any { + return val; +} + export class StringWrapper { static fromCharCode(code: int): string { return String.fromCharCode(code); } diff --git a/modules/angular2/src/services/anchor_based_app_root_url.ts b/modules/angular2/src/services/anchor_based_app_root_url.ts new file mode 100644 index 0000000000..0742b01c0a --- /dev/null +++ b/modules/angular2/src/services/anchor_based_app_root_url.ts @@ -0,0 +1,21 @@ +import {AppRootUrl} from "angular2/src/services/app_root_url"; +import {DOM} from "angular2/src/dom/dom_adapter"; +import {Injectable} from "angular2/di"; + +/** + * Extension of {@link AppRootUrl} that uses a DOM anchor tag to set the root url to + * the current page's url. + */ +@Injectable() +export class AnchorBasedAppRootUrl extends AppRootUrl { + constructor() { + super(""); + // compute the root url to pass to AppRootUrl + var rootUrl: string; + var a = DOM.createElement('a'); + DOM.resolveAndSetHref(a, './', null); + rootUrl = DOM.getHref(a); + + this.value = rootUrl; + } +} diff --git a/modules/angular2/src/services/app_root_url.ts b/modules/angular2/src/services/app_root_url.ts index 4876c68247..9b543402b5 100644 --- a/modules/angular2/src/services/app_root_url.ts +++ b/modules/angular2/src/services/app_root_url.ts @@ -1,6 +1,5 @@ import {Injectable} from 'angular2/di'; import {isBlank} from 'angular2/src/facade/lang'; -import {DOM} from 'angular2/src/dom/dom_adapter'; /** * Specifies app root url for the application. @@ -15,16 +14,12 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; export class AppRootUrl { private _value: string; + constructor(value: string) { this._value = value; } + /** * Returns the base URL of the currently running application. */ - get value() { - if (isBlank(this._value)) { - var a = DOM.createElement('a'); - DOM.resolveAndSetHref(a, './', null); - this._value = DOM.getHref(a); - } + get value() { return this._value; } - return this._value; - } + set value(value: string) { this._value = value; } } diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index 2b41534f14..d185f01413 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -23,6 +23,7 @@ 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'; import {AppRootUrl} from 'angular2/src/services/app_root_url'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; @@ -56,6 +57,7 @@ import { DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES } from 'angular2/src/render/dom/dom_renderer'; import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; +import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {Log} from './utils'; /** @@ -88,6 +90,7 @@ function _getAppBindings() { } catch (e) { appDoc = null; } + return [ bind(DOCUMENT_TOKEN) .toValue(appDoc), @@ -102,6 +105,7 @@ function _getAppBindings() { AppViewPool, AppViewManager, AppViewManagerUtils, + Serializer, ELEMENT_PROBE_CONFIG, bind(APP_VIEW_POOL_CAPACITY).toValue(500), Compiler, @@ -120,7 +124,8 @@ function _getAppBindings() { bind(XHR).toClass(MockXHR), ComponentUrlMapper, UrlResolver, - AppRootUrl, + AnchorBasedAppRootUrl, + bind(AppRootUrl).toAlias(AnchorBasedAppRootUrl), StyleUrlResolver, StyleInliner, TestComponentBuilder, diff --git a/modules/angular2/src/web-workers/shared/api.ts b/modules/angular2/src/web-workers/shared/api.ts new file mode 100644 index 0000000000..0658e2d9ab --- /dev/null +++ b/modules/angular2/src/web-workers/shared/api.ts @@ -0,0 +1,9 @@ +import {CONST_EXPR} from "angular2/src/facade/lang"; +import {OpaqueToken} from "angular2/di"; +import {RenderElementRef, RenderViewRef} from "angular2/src/render/api"; + +export const ON_WEBWORKER = CONST_EXPR(new OpaqueToken('WebWorker.onWebWorker')); + +export class WorkerElementRef implements RenderElementRef { + constructor(public renderView: RenderViewRef, public renderBoundElementIndex: number) {} +} diff --git a/modules/angular2/src/web-workers/shared/message_bus.ts b/modules/angular2/src/web-workers/shared/message_bus.ts index 2648a803ff..e14278622e 100644 --- a/modules/angular2/src/web-workers/shared/message_bus.ts +++ b/modules/angular2/src/web-workers/shared/message_bus.ts @@ -13,6 +13,14 @@ export interface SourceListener { (data: any): void; // TODO: Replace this any type with the type of a real messaging protocol } -export interface MessageBusSource { listen(fn: SourceListener): void; } +export interface MessageBusSource { + /** + * Attaches the SourceListener to this source. + * The SourceListener will get called whenever the bus receives a message + * Returns a listener id that can be passed to {@link removeListener} + */ + addListener(fn: SourceListener): number; + removeListener(index: number); +} export interface MessageBusSink { send(message: Object): void; } diff --git a/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts b/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts new file mode 100644 index 0000000000..5accac9c7a --- /dev/null +++ b/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts @@ -0,0 +1,56 @@ +import {Injectable, Inject} from "angular2/di"; +import {RenderProtoViewRef} from "angular2/src/render/api"; +import {ON_WEBWORKER} from "angular2/src/web-workers/shared/api"; + +@Injectable() +export class RenderProtoViewRefStore { + private _lookupByIndex: Map = new Map(); + private _lookupByProtoView: Map = + new Map(); + private _nextIndex: number = 0; + private _onWebworker: boolean; + + constructor(@Inject(ON_WEBWORKER) onWebworker) { this._onWebworker = onWebworker; } + + storeRenderProtoViewRef(ref: RenderProtoViewRef): number { + if (this._lookupByProtoView.has(ref)) { + return this._lookupByProtoView.get(ref); + } else { + this._lookupByIndex.set(this._nextIndex, ref); + this._lookupByProtoView.set(ref, this._nextIndex); + return this._nextIndex++; + } + } + + retreiveRenderProtoViewRef(index: number): RenderProtoViewRef { + return this._lookupByIndex.get(index); + } + + deserialize(index: number): RenderProtoViewRef { + if (index == null) { + return null; + } + + if (this._onWebworker) { + return new WebworkerRenderProtoViewRef(index); + } else { + return this.retreiveRenderProtoViewRef(index); + } + } + + serialize(ref: RenderProtoViewRef): number { + if (ref == null) { + return null; + } + + if (this._onWebworker) { + return (ref).refNumber; + } else { + return this.storeRenderProtoViewRef(ref); + } + } +} + +export class WebworkerRenderProtoViewRef extends RenderProtoViewRef { + constructor(public refNumber: number) { super(); } +} diff --git a/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts b/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts new file mode 100644 index 0000000000..9acba48181 --- /dev/null +++ b/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts @@ -0,0 +1,149 @@ +import {Injectable, Inject} from "angular2/di"; +import {RenderViewRef, RenderFragmentRef, RenderViewWithFragments} from "angular2/src/render/api"; +import {ON_WEBWORKER} from "angular2/src/web-workers/shared/api"; +import {List, ListWrapper} from "angular2/src/facade/collection"; + +@Injectable() +export class RenderViewWithFragmentsStore { + private _nextIndex: number = 0; + private _onWebWorker: boolean; + private _lookupByIndex: Map; + private _lookupByView: Map; + + constructor(@Inject(ON_WEBWORKER) onWebWorker) { + this._onWebWorker = onWebWorker; + if (!onWebWorker) { + this._lookupByIndex = new Map(); + this._lookupByView = new Map(); + } + } + + allocate(fragmentCount: number): RenderViewWithFragments { + var viewRef = new WorkerRenderViewRef(this._nextIndex++); + var fragmentRefs = ListWrapper.createGrowableSize(fragmentCount); + + for (var i = 0; i < fragmentCount; i++) { + fragmentRefs[i] = new WorkerRenderFragmentRef(this._nextIndex++); + } + return new RenderViewWithFragments(viewRef, fragmentRefs); + } + + store(view: RenderViewWithFragments, startIndex: number) { + this._lookupByIndex.set(startIndex, view.viewRef); + this._lookupByView.set(view.viewRef, startIndex); + startIndex++; + + ListWrapper.forEach(view.fragmentRefs, (ref) => { + this._lookupByIndex.set(startIndex, ref); + this._lookupByView.set(ref, startIndex); + startIndex++; + }); + } + + retreive(ref: number): RenderViewRef | RenderFragmentRef { + if (ref == null) { + return null; + } + return this._lookupByIndex.get(ref); + } + + serializeRenderViewRef(viewRef: RenderViewRef): number { + return this._serializeRenderFragmentOrViewRef(viewRef); + } + + serializeRenderFragmentRef(fragmentRef: RenderFragmentRef): number { + return this._serializeRenderFragmentOrViewRef(fragmentRef); + } + + deserializeRenderViewRef(ref: number): RenderViewRef { + if (ref == null) { + return null; + } + + if (this._onWebWorker) { + return WorkerRenderViewRef.deserialize(ref); + } else { + return this.retreive(ref); + } + } + + deserializeRenderFragmentRef(ref: number): RenderFragmentRef { + if (ref == null) { + return null; + } + + if (this._onWebWorker) { + return WorkerRenderFragmentRef.deserialize(ref); + } else { + return this.retreive(ref); + } + } + + private _serializeRenderFragmentOrViewRef(ref: RenderViewRef | RenderFragmentRef): number { + if (ref == null) { + return null; + } + + if (this._onWebWorker) { + return (ref).serialize(); + } else { + return this._lookupByView.get(ref); + } + } + + serializeViewWithFragments(view: RenderViewWithFragments): StringMap { + if (view == null) { + return null; + } + + if (this._onWebWorker) { + return { + 'viewRef': (view.viewRef).serialize(), + 'fragmentRefs': ListWrapper.map(view.fragmentRefs, (val) => val.serialize()) + }; + } else { + return { + 'viewRef': this._lookupByView.get(view.viewRef), + 'fragmentRefs': ListWrapper.map(view.fragmentRefs, (val) => this._lookupByView.get(val)) + }; + } + } + + deserializeViewWithFragments(obj: StringMap): RenderViewWithFragments { + if (obj == null) { + return null; + } + + var viewRef: RenderViewRef | RenderFragmentRef; + var fragments: List; + if (this._onWebWorker) { + viewRef = WorkerRenderViewRef.deserialize(obj['viewRef']); + fragments = + ListWrapper.map(obj['fragmentRefs'], (val) => WorkerRenderFragmentRef.deserialize(val)); + + return new RenderViewWithFragments(viewRef, fragments); + } else { + viewRef = this.retreive(obj['viewRef']); + fragments = ListWrapper.map(obj['fragmentRefs'], (val) => this.retreive(val)); + + return new RenderViewWithFragments(viewRef, fragments); + } + } +} + +export class WorkerRenderViewRef extends RenderViewRef { + constructor(public refNumber: number) { super(); } + serialize(): number { return this.refNumber; } + + static deserialize(ref: number): WorkerRenderViewRef { return new WorkerRenderViewRef(ref); } +} + +export class WorkerRenderFragmentRef extends RenderFragmentRef { + constructor(public refNumber: number) { super(); } + + serialize(): number { return this.refNumber; } + + static deserialize(ref: number): WorkerRenderFragmentRef { + return new WorkerRenderFragmentRef(ref); + } +} diff --git a/modules/angular2/src/web-workers/shared/serializer.ts b/modules/angular2/src/web-workers/shared/serializer.ts index f7c6c4ae77..1fb9b0d24d 100644 --- a/modules/angular2/src/web-workers/shared/serializer.ts +++ b/modules/angular2/src/web-workers/shared/serializer.ts @@ -1,4 +1,4 @@ -import {Type, isArray, isPresent} from "angular2/src/facade/lang"; +import {Type, isArray, isPresent, serializeEnum, deserializeEnum} from "angular2/src/facade/lang"; import {List, ListWrapper, Map, StringMapWrapper, MapWrapper} from "angular2/src/facade/collection"; import { ProtoViewDto, @@ -7,75 +7,122 @@ import { DirectiveBinder, ElementPropertyBinding, EventBinding, - ViewDefinition + ViewDefinition, + RenderProtoViewRef, + RenderProtoViewMergeMapping, + RenderViewRef, + RenderFragmentRef, + RenderElementRef, + ViewType } from "angular2/src/render/api"; +import {WorkerElementRef} from 'angular2/src/web-workers/shared/api'; import {AST, ASTWithSource} from "angular2/change_detection"; import {Parser} from "angular2/src/change_detection/parser/parser"; +import {Injectable} from "angular2/di"; +import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store'; +import { + RenderViewWithFragmentsStore +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +@Injectable() export class Serializer { - static parser: Parser = null; + private _enumRegistry: Map>; + constructor(private _parser: Parser, private _protoViewStore: RenderProtoViewRefStore, + private _renderViewStore: RenderViewWithFragmentsStore) { + this._enumRegistry = new Map>(); + var viewTypeMap = new Map(); + viewTypeMap[0] = ViewType.HOST; + viewTypeMap[1] = ViewType.COMPONENT; + viewTypeMap[2] = ViewType.EMBEDDED; + this._enumRegistry.set(ViewType, viewTypeMap); + } - static serialize(obj: any, type: Type): Object { + serialize(obj: any, type: Type): Object { if (!isPresent(obj)) { return null; } if (isArray(obj)) { var serializedObj = []; - ListWrapper.forEach(obj, (val) => { serializedObj.push(Serializer.serialize(val, type)); }); + ListWrapper.forEach(obj, (val) => { serializedObj.push(this.serialize(val, type)); }); return serializedObj; } + if (type == String) { + return obj; + } if (type == ViewDefinition) { - return ViewDefinitionSerializer.serialize(obj); + return this._serializeViewDefinition(obj); } else if (type == DirectiveBinder) { - return DirectiveBinderSerializer.serialize(obj); + return this._serializeDirectiveBinder(obj); } else if (type == ProtoViewDto) { - return ProtoViewDtoSerializer.serialize(obj); + return this._serializeProtoViewDto(obj); } else if (type == ElementBinder) { - return ElementBinderSerializer.serialize(obj); + return this._serializeElementBinder(obj); } else if (type == DirectiveMetadata) { - return DirectiveMetadataSerializer.serialize(obj); + return this._serializeDirectiveMetadata(obj); } else if (type == ASTWithSource) { - return ASTWithSourceSerializer.serialize(obj); + return this._serializeASTWithSource(obj); + } else if (type == RenderProtoViewRef) { + return this._protoViewStore.serialize(obj); + } else if (type == RenderProtoViewMergeMapping) { + return this._serializeRenderProtoViewMergeMapping(obj); + } else if (type == RenderViewRef) { + return this._renderViewStore.serializeRenderViewRef(obj); + } else if (type == RenderFragmentRef) { + return this._renderViewStore.serializeRenderFragmentRef(obj); + } else if (type == WorkerElementRef) { + return this._serializeWorkerElementRef(obj); } else { throw "No serializer for " + type.toString(); } } - // TODO: template this to return the type that is passed if possible - static deserialize(map: List, type: Type, data?: any): any { + deserialize(map: any, type: Type, data?: any): any { if (!isPresent(map)) { return null; } if (isArray(map)) { var obj: List = new List(); - ListWrapper.forEach(map, (val) => { obj.push(Serializer.deserialize(val, type, data)); }); + ListWrapper.forEach(map, (val) => { obj.push(this.deserialize(val, type, data)); }); return obj; } + if (type == String) { + return map; + } if (type == ViewDefinition) { - return ViewDefinitionSerializer.deserialize(map); + return this._deserializeViewDefinition(map); } else if (type == DirectiveBinder) { - return DirectiveBinderSerializer.deserialize(map); + return this._deserializeDirectiveBinder(map); } else if (type == ProtoViewDto) { - return ProtoViewDtoSerializer.deserialize(map); + return this._deserializeProtoViewDto(map); } else if (type == DirectiveMetadata) { - return DirectiveMetadataSerializer.deserialize(map); + return this._deserializeDirectiveMetadata(map); } else if (type == ElementBinder) { - return ElementBinderSerializer.deserialize(map); + return this._deserializeElementBinder(map); } else if (type == ASTWithSource) { - return ASTWithSourceSerializer.deserialize(map, data); + return this._deserializeASTWithSource(map, data); + } else if (type == RenderProtoViewRef) { + return this._protoViewStore.deserialize(map); + } else if (type == RenderProtoViewMergeMapping) { + return this._deserializeRenderProtoViewMergeMapping(map); + } else if (type == RenderViewRef) { + return this._renderViewStore.deserializeRenderViewRef(map); + } else if (type == RenderFragmentRef) { + return this._renderViewStore.deserializeRenderFragmentRef(map); + } else if (type == WorkerElementRef) { + return this._deserializeWorkerElementRef(map); } else { throw "No deserializer for " + type.toString(); } } - static mapToObject(map: Map, type?: Type): Object { + mapToObject(map: Map, type?: Type): Object { var object = {}; var serialize = isPresent(type); MapWrapper.forEach(map, (value, key) => { if (serialize) { - object[key] = Serializer.serialize(value, type); + object[key] = this.serialize(value, type); } else { object[key] = value; } @@ -84,191 +131,213 @@ export class Serializer { } /* - * Transforms a Javascript object into a Map + * Transforms a Javascript object (StringMap) into a Map * If the values need to be deserialized pass in their type * and they will be deserialized before being placed in the map */ - static objectToMap(obj: Object, type?: Type, data?: any): Map { + objectToMap(obj: StringMap, type?: Type, data?: any): Map { if (isPresent(type)) { var map: Map = new Map(); - StringMapWrapper.forEach( - obj, (key, val) => { map.set(key, Serializer.deserialize(val, type, data)); }); + StringMapWrapper.forEach(obj, + (key, val) => { map.set(key, this.deserialize(val, type, data)); }); return map; } else { return MapWrapper.createFromStringMap(obj); } } -} -class ASTWithSourceSerializer { - static serialize(tree: ASTWithSource): Object { + allocateRenderViews(fragmentCount: number) { this._renderViewStore.allocate(fragmentCount); } + + private _serializeWorkerElementRef(elementRef: RenderElementRef): StringMap { + return { + 'renderView': this.serialize(elementRef.renderView, RenderViewRef), + 'renderBoundElementIndex': elementRef.renderBoundElementIndex + }; + } + + private _deserializeWorkerElementRef(map: StringMap): RenderElementRef { + return new WorkerElementRef(this.deserialize(map['renderView'], RenderViewRef), + map['renderBoundElementIndex']); + } + + private _serializeRenderProtoViewMergeMapping(mapping: RenderProtoViewMergeMapping): Object { + return { + 'mergedProtoViewRef': this._protoViewStore.serialize(mapping.mergedProtoViewRef), + 'fragmentCount': mapping.fragmentCount, + 'mappedElementIndices': mapping.mappedElementIndices, + 'mappedElementCount': mapping.mappedElementCount, + 'mappedTextIndices': mapping.mappedTextIndices, + 'hostElementIndicesByViewIndex': mapping.hostElementIndicesByViewIndex, + 'nestedViewCountByViewIndex': mapping.nestedViewCountByViewIndex + }; + } + + private _deserializeRenderProtoViewMergeMapping(obj: StringMap): + RenderProtoViewMergeMapping { + return new RenderProtoViewMergeMapping( + this._protoViewStore.deserialize(obj['mergedProtoViewRef']), obj['fragmentCount'], + obj['mappedElementIndices'], obj['mappedElementCount'], obj['mappedTextIndices'], + obj['hostElementIndicesByViewIndex'], obj['nestedViewCountByViewIndex']); + } + + private _serializeASTWithSource(tree: ASTWithSource): Object { return {'input': tree.source, 'location': tree.location}; } - static deserialize(obj: any, data: string): AST { + private _deserializeASTWithSource(obj: StringMap, data: string): AST { // TODO: make ASTs serializable var ast: AST; switch (data) { case "interpolation": - ast = Serializer.parser.parseInterpolation(obj.input, obj.location); + ast = this._parser.parseInterpolation(obj['input'], obj['location']); break; case "binding": - ast = Serializer.parser.parseBinding(obj.input, obj.location); + ast = this._parser.parseBinding(obj['input'], obj['location']); break; case "simpleBinding": - ast = Serializer.parser.parseSimpleBinding(obj.input, obj.location); + ast = this._parser.parseSimpleBinding(obj['input'], obj['location']); break; - /*case "templateBindings": - ast = Serializer.parser.parseTemplateBindings(obj.input, obj.location); - break;*/ case "interpolation": - ast = Serializer.parser.parseInterpolation(obj.input, obj.location); + ast = this._parser.parseInterpolation(obj['input'], obj['location']); break; default: throw "No AST deserializer for " + data; } return ast; } -} -class ViewDefinitionSerializer { - static serialize(view: ViewDefinition): Object { + private _serializeViewDefinition(view: ViewDefinition): Object { return { 'componentId': view.componentId, 'templateAbsUrl': view.templateAbsUrl, 'template': view.template, - 'directives': Serializer.serialize(view.directives, DirectiveMetadata), + 'directives': this.serialize(view.directives, DirectiveMetadata), 'styleAbsUrls': view.styleAbsUrls, 'styles': view.styles }; } - static deserialize(obj: any): ViewDefinition { + + private _deserializeViewDefinition(obj: StringMap): ViewDefinition { return new ViewDefinition({ - componentId: obj.componentId, - templateAbsUrl: obj.templateAbsUrl, template: obj.template, - directives: Serializer.deserialize(obj.directives, DirectiveMetadata), - styleAbsUrls: obj.styleAbsUrls, - styles: obj.styles + componentId: obj['componentId'], + templateAbsUrl: obj['templateAbsUrl'], template: obj['template'], + directives: this.deserialize(obj['directives'], DirectiveMetadata), + styleAbsUrls: obj['styleAbsUrls'], + styles: obj['styles'] }); } -} -class DirectiveBinderSerializer { - static serialize(binder: DirectiveBinder): Object { + private _serializeDirectiveBinder(binder: DirectiveBinder): Object { return { 'directiveIndex': binder.directiveIndex, - 'propertyBindings': Serializer.mapToObject(binder.propertyBindings, ASTWithSource), - 'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding), - 'hostPropertyBindings': - Serializer.serialize(binder.hostPropertyBindings, ElementPropertyBinding) + 'propertyBindings': this.mapToObject(binder.propertyBindings, ASTWithSource), + 'eventBindings': this.serialize(binder.eventBindings, EventBinding), + 'hostPropertyBindings': this.serialize(binder.hostPropertyBindings, ElementPropertyBinding) }; } - static deserialize(obj: any): DirectiveBinder { + private _deserializeDirectiveBinder(obj: StringMap): DirectiveBinder { return new DirectiveBinder({ - directiveIndex: obj.directiveIndex, - propertyBindings: Serializer.objectToMap(obj.propertyBindings, ASTWithSource, "binding"), - eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding), - hostPropertyBindings: Serializer.deserialize(obj.hostPropertyBindings, ElementPropertyBinding) + directiveIndex: obj['directiveIndex'], + propertyBindings: this.objectToMap(obj['propertyBindings'], ASTWithSource, "binding"), + eventBindings: this.deserialize(obj['eventBindings'], EventBinding), + hostPropertyBindings: this.deserialize(obj['hostPropertyBindings'], ElementPropertyBinding) }); } -} -class ElementBinderSerializer { - static serialize(binder: ElementBinder): Object { + private _serializeElementBinder(binder: ElementBinder): Object { return { 'index': binder.index, 'parentIndex': binder.parentIndex, 'distanceToParent': binder.distanceToParent, - 'directives': Serializer.serialize(binder.directives, DirectiveBinder), - 'nestedProtoView': Serializer.serialize(binder.nestedProtoView, ProtoViewDto), - 'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding), - 'variableBindings': Serializer.mapToObject(binder.variableBindings), - 'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding), - 'readAttributes': Serializer.mapToObject(binder.readAttributes) + 'directives': this.serialize(binder.directives, DirectiveBinder), + 'nestedProtoView': this.serialize(binder.nestedProtoView, ProtoViewDto), + 'propertyBindings': this.serialize(binder.propertyBindings, ElementPropertyBinding), + 'variableBindings': this.mapToObject(binder.variableBindings), + 'eventBindings': this.serialize(binder.eventBindings, EventBinding), + 'readAttributes': this.mapToObject(binder.readAttributes) }; } - static deserialize(obj: any): ElementBinder { + private _deserializeElementBinder(obj: StringMap): ElementBinder { return new ElementBinder({ - index: obj.index, - parentIndex: obj.parentIndex, - distanceToParent: obj.distanceToParent, - directives: Serializer.deserialize(obj.directives, DirectiveBinder), - nestedProtoView: Serializer.deserialize(obj.nestedProtoView, ProtoViewDto), - propertyBindings: Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding), - variableBindings: Serializer.objectToMap(obj.variableBindings), - eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding), - readAttributes: Serializer.objectToMap(obj.readAttributes) + index: obj['index'], + parentIndex: obj['parentIndex'], + distanceToParent: obj['distanceToParent'], + directives: this.deserialize(obj['directives'], DirectiveBinder), + nestedProtoView: this.deserialize(obj['nestedProtoView'], ProtoViewDto), + propertyBindings: this.deserialize(obj['propertyBindings'], ElementPropertyBinding), + variableBindings: this.objectToMap(obj['variableBindings']), + eventBindings: this.deserialize(obj['eventBindings'], EventBinding), + readAttributes: this.objectToMap(obj['readAttributes']) }); } -} -class ProtoViewDtoSerializer { - static serialize(view: ProtoViewDto): Object { - // TODO: fix render refs and write a serializer for them + private _serializeProtoViewDto(view: ProtoViewDto): Object { return { - 'render': null, - 'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder), - 'variableBindings': Serializer.mapToObject(view.variableBindings), - 'textBindings': Serializer.serialize(view.textBindings, ASTWithSource), - 'type': view.type + 'render': this._protoViewStore.serialize(view.render), + 'elementBinders': this.serialize(view.elementBinders, ElementBinder), + 'variableBindings': this.mapToObject(view.variableBindings), + 'type': serializeEnum(view.type), + 'textBindings': this.serialize(view.textBindings, ASTWithSource), + 'transitiveNgContentCount': view.transitiveNgContentCount }; } - static deserialize(obj: any): ProtoViewDto { + private _deserializeProtoViewDto(obj: StringMap): ProtoViewDto { return new ProtoViewDto({ - render: null, // TODO: fix render refs and write a serializer for them - elementBinders: Serializer.deserialize(obj.elementBinders, ElementBinder), - variableBindings: Serializer.objectToMap(obj.variableBindings), - textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"), - type: obj.type + render: this._protoViewStore.deserialize(obj["render"]), + elementBinders: this.deserialize(obj['elementBinders'], ElementBinder), + variableBindings: this.objectToMap(obj['variableBindings']), + textBindings: this.deserialize(obj['textBindings'], ASTWithSource, "interpolation"), + type: deserializeEnum(obj['type'], this._enumRegistry.get(ViewType)), + transitiveNgContentCount: obj['transitivengContentCount'] }); } -} -class DirectiveMetadataSerializer { - static serialize(meta: DirectiveMetadata): Object { + private _serializeDirectiveMetadata(meta: DirectiveMetadata): Object { var obj = { 'id': meta.id, 'selector': meta.selector, 'compileChildren': meta.compileChildren, - 'hostProperties': Serializer.mapToObject(meta.hostProperties), - 'hostListeners': Serializer.mapToObject(meta.hostListeners), - 'hostActions': Serializer.mapToObject(meta.hostActions), - 'hostAttributes': Serializer.mapToObject(meta.hostAttributes), + 'events': meta.events, 'properties': meta.properties, 'readAttributes': meta.readAttributes, 'type': meta.type, - 'exportAs': meta.exportAs, 'callOnDestroy': meta.callOnDestroy, + 'callOnChange': meta.callOnChange, 'callOnCheck': meta.callOnCheck, 'callOnInit': meta.callOnInit, 'callOnAllChangesDone': meta.callOnAllChangesDone, 'changeDetection': meta.changeDetection, - 'events': meta.events + 'exportAs': meta.exportAs, + 'hostProperties': this.mapToObject(meta.hostProperties), + 'hostListeners': this.mapToObject(meta.hostListeners), + 'hostActions': this.mapToObject(meta.hostActions), + 'hostAttributes': this.mapToObject(meta.hostAttributes) }; return obj; } - static deserialize(obj: any): DirectiveMetadata { + private _deserializeDirectiveMetadata(obj: StringMap): DirectiveMetadata { return new DirectiveMetadata({ - id: obj.id, - selector: obj.selector, - compileChildren: obj.compileChildren, - hostProperties: Serializer.objectToMap(obj.hostProperties), - hostListeners: Serializer.objectToMap(obj.hostListeners), - hostActions: Serializer.objectToMap(obj.hostActions), - hostAttributes: Serializer.objectToMap(obj.hostAttributes), - properties: obj.properties, - readAttributes: obj.readAttributes, - type: obj.type, - exportAs: obj.exportAs, - callOnDestroy: obj.callOnDestroy, - callOnCheck: obj.callOnCheck, - callOnInit: obj.callOnInit, - callOnAllChangesDone: obj.callOnAllChangesDone, - changeDetection: obj.changeDetection, - events: obj.events + id: obj['id'], + selector: obj['selector'], + compileChildren: obj['compileChildren'], + hostProperties: this.objectToMap(obj['hostProperties']), + hostListeners: this.objectToMap(obj['hostListeners']), + hostActions: this.objectToMap(obj['hostActions']), + hostAttributes: this.objectToMap(obj['hostAttributes']), + properties: obj['properties'], + readAttributes: obj['readAttributes'], + type: obj['type'], + exportAs: obj['exportAs'], + callOnDestroy: obj['callOnDestroy'], + callOnChange: obj['callOnChange'], + callOnCheck: obj['callOnCheck'], + callOnInit: obj['callOnInit'], + callOnAllChangesDone: obj['callOnAllChangesDone'], + changeDetection: obj['changeDetection'], + events: obj['events'] }); } } diff --git a/modules/angular2/src/web-workers/shared/serializer.ts.good b/modules/angular2/src/web-workers/shared/serializer.ts.good deleted file mode 100644 index c04de2d8b4..0000000000 --- a/modules/angular2/src/web-workers/shared/serializer.ts.good +++ /dev/null @@ -1,275 +0,0 @@ -import {Type, isArray, isPresent} from "angular2/src/facade/lang"; -import {List, ListWrapper, Map, StringMapWrapper, MapWrapper} from "angular2/src/facade/collection"; -import { - ProtoViewDto, - DirectiveMetadata, - ElementBinder, - DirectiveBinder, - ElementPropertyBinding, - EventBinding, - ViewDefinition -} from "angular2/src/render/api"; -import {AST, ASTWithSource} from "angular2/change_detection"; -import {Parser} from "angular2/src/change_detection/parser/parser"; - -export class Serializer { - static parser: Parser = null; - - static serialize(obj: any, type: Type): Object { - if (!isPresent(obj)) { - return null; - } - if (isArray(obj)) { - var serializedObj = []; - ListWrapper.forEach(obj, (val) => { serializedObj.push(Serializer.serialize(val, type)); }); - return serializedObj; - } - if (type == ViewDefinition) { - return ViewDefinitionSerializer.serialize(obj); - } else if (type == DirectiveBinder) { - return DirectiveBinderSerializer.serialize(obj); - } else if (type == ProtoViewDto) { - return ProtoViewDtoSerializer.serialize(obj); - } else if (type == ElementBinder) { - return ElementBinderSerializer.serialize(obj); - } else if (type == DirectiveMetadata) { - return DirectiveMetadataSerializer.serialize(obj); - } else if (type == ASTWithSource) { - return ASTWithSourceSerializer.serialize(obj); - } else { - throw "No serializer for " + type.toString(); - } - } - - // TODO: template this to return the type that is passed if possible - static deserialize(map, type: Type, data?): any { - if (!isPresent(map)) { - return null; - } - if (isArray(map)) { - var obj: List = new List(); - ListWrapper.forEach(map, (val) => { obj.push(Serializer.deserialize(val, type, data)); }); - return obj; - } - - if (type == ViewDefinition) { - return ViewDefinitionSerializer.deserialize(map); - } else if (type == DirectiveBinder) { - return DirectiveBinderSerializer.deserialize(map); - } else if (type == ProtoViewDto) { - return ProtoViewDtoSerializer.deserialize(map); - } else if (type == DirectiveMetadata) { - return DirectiveMetadataSerializer.deserialize(map); - } else if (type == ElementBinder) { - return ElementBinderSerializer.deserialize(map); - } else if (type == ASTWithSource) { - return ASTWithSourceSerializer.deserialize(map, data); - } else { - throw "No deserializer for " + type.toString(); - } - } - - static mapToObject(map, type?: Type): Object { - var object = {}; - var serialize = isPresent(type); - - MapWrapper.forEach(map, (value, key) => { - if (serialize) { - object[key] = Serializer.serialize(value, type); - } else { - object[key] = value; - } - }); - return object; - } - - /* - * Transforms a Javascript object into a Map - * If the values need to be deserialized pass in their type - * and they will be deserialized before being placed in the map - */ - static objectToMap(obj, type?: Type, data?): Map { - if (isPresent(type)) { - var map: Map = new Map(); - StringMapWrapper.forEach( - obj, (key, val) => { map.set(key, Serializer.deserialize(val, type, data)); }); - return map; - } else { - return MapWrapper.createFromStringMap(obj); - } - } -} - -class ASTWithSourceSerializer { - static serialize(tree: ASTWithSource): Object { - return { 'input': tree.source, 'location': tree.location } - } - - static deserialize(obj: any, data: string): AST { - // TODO: make ASTs serializable - var ast: AST; - switch (data) { - case "interpolation": - ast = Serializer.parser.parseInterpolation(obj.input, obj.location); - break; - case "binding": - ast = Serializer.parser.parseBinding(obj.input, obj.location); - break; - case "simpleBinding": - ast = Serializer.parser.parseSimpleBinding(obj.input, obj.location); - break; - /*case "templateBindings": - ast = Serializer.parser.parseTemplateBindings(obj.input, obj.location); - break;*/ - case "interpolation": - ast = Serializer.parser.parseInterpolation(obj.input, obj.location); - break; - default: - throw "No AST deserializer for " + data; - } - return ast; - } -} - -class ViewDefinitionSerializer { - static serialize(view: ViewDefinition): Object{ - return { - 'componentId': view.componentId, - 'templateAbsUrl': view.templateAbsUrl, - 'template': view.template, - 'directives': Serializer.serialize(view.directives, DirectiveMetadata), - 'styleAbsUrls': view.styleAbsUrls, - 'styles': view.styles - }; - } - static deserialize(obj): ViewDefinition { - return new ViewDefinition({ - 'componentId': obj.componentId, - 'templateAbsUrl': obj.templateAbsUrl, - 'template': obj.template, - 'directives': Serializer.deserialize(obj.directives, DirectiveMetadata), - 'styleAbsUrls': obj.styleAbsUrls, - 'styles': obj.styles - }); - } -} - -class DirectiveBinderSerializer { - static serialize(binder: DirectiveBinder): Object { - return { - 'directiveIndex': binder.directiveIndex, - 'propertyBindings': Serializer.mapToObject(binder.propertyBindings, ASTWithSource), - 'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding), - 'hostPropertyBindings': - Serializer.serialize(binder.hostPropertyBindings, ElementPropertyBinding) - }; - } - - static deserialize(obj): DirectiveBinder { - return new DirectiveBinder({ - 'directiveIndex': obj.directiveIndex, - 'propertyBindings': Serializer.objectToMap(obj.propertyBindings, ASTWithSource, "binding"), - 'eventBindings': Serializer.deserialize(obj.eventBindings, EventBinding), - 'hostPropertyBindings': - Serializer.deserialize(obj.hostPropertyBindings, ElementPropertyBinding) - }); - } -} - -class ElementBinderSerializer { - static serialize(binder: ElementBinder): Object { - return { - 'index': binder.index, - 'parentIndex': binder.parentIndex, - 'distanceToParent': binder.distanceToParent, - 'directives': Serializer.serialize(binder.directives, DirectiveBinder), - 'nestedProtoView': Serializer.serialize(binder.nestedProtoView, ProtoViewDto), - 'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding), - 'variableBindings': Serializer.mapToObject(binder.variableBindings), - 'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding), - 'textBindings': Serializer.serialize(binder.textBindings, ASTWithSource), - 'readAttributes': Serializer.mapToObject(binder.readAttributes) - } - } - - static deserialize(obj): ElementBinder { - return new ElementBinder({ - 'index': obj.index, - 'parentIndex': obj.parentIndex, - 'distanceToParent': obj.distanceToParent, - 'directives': Serializer.deserialize(obj.directives, DirectiveBinder), - 'nestedProtoView': Serializer.deserialize(obj.nestedProtoView, ProtoViewDto), - 'propertyBindings': Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding), - 'variableBindings': Serializer.objectToMap(obj.variableBindings), - 'eventBindings': Serializer.deserialize(obj.eventBindings, EventBinding), - 'textBindings': Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"), - 'readAttributes': Serializer.objectToMap(obj.readAttributes) - }); - } -} - -class ProtoViewDtoSerializer { - static serialize(view: ProtoViewDto): Object { - return { - 'render': null, // TODO: fix render refs and write a serializer for them - 'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder), - 'variableBindings': Serializer.mapToObject(view.variableBindings), - 'type': view.type - } - } - - static deserialize(obj): ProtoViewDto { - return new ProtoViewDto({ - 'render': null, // TODO: fix render refs and write a serializer for them - 'elementBinders': Serializer.deserialize(obj.elementBinders, ElementBinder), - 'variableBindings': Serializer.objectToMap(obj.variableBindings), - 'type': obj.type - }); - } -} - -class DirectiveMetadataSerializer { - static serialize(meta: DirectiveMetadata): Object { - var obj = { - 'id': meta.id, - 'selector': meta.selector, - 'compileChildren': meta.compileChildren, - 'hostProperties': Serializer.mapToObject(meta.hostProperties), - 'hostListeners': Serializer.mapToObject(meta.hostListeners), - 'hostActions': Serializer.mapToObject(meta.hostActions), - 'hostAttributes': Serializer.mapToObject(meta.hostAttributes), - 'properties': meta.properties, - 'readAttributes': meta.readAttributes, - 'type': meta.type, - 'exportAs': meta.exportAs, - 'callOnDestroy': meta.callOnDestroy, - 'callOnCheck': meta.callOnCheck, - 'callOnInit': meta.callOnInit, - 'callOnAllChangesDone': meta.callOnAllChangesDone, - 'changeDetection': meta.changeDetection, - 'events': meta.events - }; - return obj; - } - static deserialize(obj): DirectiveMetadata { - return new DirectiveMetadata({ - 'id': obj.id, - 'selector': obj.selector, - 'compileChildren': obj.compileChildren, - 'hostProperties': Serializer.objectToMap(obj.hostProperties), - 'hostListeners': Serializer.objectToMap(obj.hostListeners), - 'hostActions': Serializer.objectToMap(obj.hostActions), - 'hostAttributes': Serializer.objectToMap(obj.hostAttributes), - 'properties': obj.properties, - 'readAttributes': obj.readAttributes, - 'type': obj.type, - 'exportAs': obj.exportAs, - 'callOnDestroy': obj.callOnDestroy, - 'callOnCheck': obj.callOnCheck, - 'callOnInit': obj.callOnInit, - 'callOnAllChangesDone': obj.callOnAllChangesDone, - 'changeDetection': obj.changeDetection, - 'events': obj.events - }); - } -} diff --git a/modules/angular2/src/web-workers/ui/application.dart b/modules/angular2/src/web-workers/ui/application.dart index 03baeb8600..38b1d3bba7 100644 --- a/modules/angular2/src/web-workers/ui/application.dart +++ b/modules/angular2/src/web-workers/ui/application.dart @@ -2,23 +2,28 @@ library angular2.src.web_workers.ui; import 'dart:isolate'; import 'dart:async'; -import "package:angular2/src/web-workers/shared/message_bus.dart" +import 'dart:core'; +import 'package:angular2/src/web-workers/shared/message_bus.dart' show MessageBus, MessageBusSink, MessageBusSource; +import 'package:angular2/src/web-workers/ui/impl.dart' + show bootstrapUICommon; /** * Bootstrapping a WebWorker - * + * * You instantiate a WebWorker application by calling bootstrap with the URI of your worker's index script - * Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the bootstrapping process + * Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the bootstrapping process */ void bootstrap(String uri) { - throw "Not Implemented"; + spawnWorker(Uri.parse(uri)).then((bus) { + bootstrapUICommon(bus); + }); } /** * To be called from the main thread to spawn and communicate with the worker thread */ -Future spawnWorker(Uri uri) { +Future spawnWorker(Uri uri){ var receivePort = new ReceivePort(); var isolateEndSendPort = receivePort.sendPort; return Isolate.spawnUri(uri, const [], isolateEndSendPort).then((_) { @@ -52,6 +57,8 @@ class UIMessageBusSink extends MessageBusSink { class UIMessageBusSource extends MessageBusSource { final ReceivePort _port; final Stream rawDataStream; + Map _listenerStore = new Map(); + int _numListeners = 0; UIMessageBusSource(ReceivePort port) : _port = port, @@ -61,9 +68,17 @@ class UIMessageBusSource extends MessageBusSource { return message is SendPort; }); - void listen(Function fn) { - rawDataStream.listen((message) { + int addListener(Function fn){ + var subscription = rawDataStream.listen((message){ fn({"data": message}); }); + + _listenerStore[++_numListeners] = subscription; + return _numListeners; + } + + void removeListener(int index){ + _listenerStore[index].cancel(); + _listenerStore.remove(index); } } diff --git a/modules/angular2/src/web-workers/ui/application.ts b/modules/angular2/src/web-workers/ui/application.ts index 31b629db41..6d7cced8ae 100644 --- a/modules/angular2/src/web-workers/ui/application.ts +++ b/modules/angular2/src/web-workers/ui/application.ts @@ -5,6 +5,7 @@ import { SourceListener } from "angular2/src/web-workers/shared/message_bus"; import {BaseException} from "angular2/src/facade/lang"; +import {bootstrapUICommon} from "angular2/src/web-workers/ui/impl"; /** * Bootstrapping a WebWorker @@ -15,7 +16,8 @@ import {BaseException} from "angular2/src/facade/lang"; * bootstrapping process */ export function bootstrap(uri: string): void { - throw new BaseException("Not Implemented"); + var messageBus = spawnWorker(uri); + bootstrapUICommon(messageBus); } export function spawnWorker(uri: string): MessageBus { @@ -34,7 +36,19 @@ export class UIMessageBusSink implements MessageBusSink { } export class UIMessageBusSource implements MessageBusSource { + private _listenerStore: Map = new Map(); + private _numListeners: int = 0; + constructor(private _worker: Worker) {} - listen(fn: SourceListener): void { this._worker.addEventListener("message", fn); } + public addListener(fn: SourceListener): int { + this._worker.addEventListener("message", fn); + this._listenerStore[++this._numListeners] = fn; + return this._numListeners; + } + + public removeListener(index: int): void { + removeEventListener("message", this._listenerStore[index]); + this._listenerStore.delete(index); + } } diff --git a/modules/angular2/src/web-workers/ui/di_bindings.ts b/modules/angular2/src/web-workers/ui/di_bindings.ts new file mode 100644 index 0000000000..dd4b364f46 --- /dev/null +++ b/modules/angular2/src/web-workers/ui/di_bindings.ts @@ -0,0 +1,136 @@ +// TODO: This whole file is nearly identical to core/application.ts. +// There should be a way to refactor application so that this file is unnecessary +import {Injector, bind, Binding} from "angular2/di"; +import {Type, isBlank, isPresent} from "angular2/src/facade/lang"; +import {Reflector, reflector} from 'angular2/src/reflection/reflection'; +import {List, ListWrapper} from 'angular2/src/facade/collection'; +import { + Parser, + Lexer, + ChangeDetection, + DynamicChangeDetection, + JitChangeDetection, + PreGeneratedChangeDetection, + Pipes, + defaultPipes +} from 'angular2/change_detection'; +import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; +import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; +import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; +import {KeyEventsPlugin} from 'angular2/src/render/dom/events/key_events'; +import {HammerGesturesPlugin} from 'angular2/src/render/dom/events/hammer_gestures'; +import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; +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'; +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'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; +import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; +import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; +import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; +import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner'; +import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; +import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {Testability} from 'angular2/src/core/testability/testability'; +import {XHR} from 'angular2/src/render/xhr'; +import {XHRImpl} from 'angular2/src/render/xhr_impl'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import {ON_WEBWORKER} from 'angular2/src/web-workers/shared/api'; +import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store'; +import { + RenderViewWithFragmentsStore +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; +import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl'; + +var _rootInjector: Injector; + +// Contains everything that is safe to share between applications. +var _rootBindings = [bind(Reflector).toValue(reflector)]; + +// TODO: This code is nearly identitcal to core/application. There should be a way to only write it +// once +function _injectorBindings(): List> { + var bestChangeDetection: Type = DynamicChangeDetection; + if (PreGeneratedChangeDetection.isSupported()) { + bestChangeDetection = PreGeneratedChangeDetection; + } else if (JitChangeDetection.isSupported()) { + bestChangeDetection = JitChangeDetection; + } + // compute the root url to pass to AppRootUrl + /*var rootUrl: string; + var a = DOM.createElement('a'); + DOM.resolveAndSetHref(a, './', null); + rootUrl = DOM.getHref(a);*/ + + return [ + bind(DOCUMENT_TOKEN) + .toValue(DOM.defaultDoc()), + bind(EventManager) + .toFactory( + (ngZone) => { + var plugins = + [new HammerGesturesPlugin(), new KeyEventsPlugin(), new DomEventsPlugin()]; + 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), + bind(RenderCompiler).toAlias(DefaultDomCompiler), + bind(ON_WEBWORKER).toValue(false), + RenderViewWithFragmentsStore, + RenderProtoViewRefStore, + ProtoViewFactory, + AppViewPool, + bind(APP_VIEW_POOL_CAPACITY).toValue(10000), + AppViewManager, + AppViewManagerUtils, + AppViewListener, + Compiler, + CompilerCache, + ViewResolver, + bind(Pipes).toValue(defaultPipes), + bind(ChangeDetection).toClass(bestChangeDetection), + ViewLoader, + DirectiveResolver, + Parser, + Lexer, + ExceptionHandler, + bind(XHR).toValue(new XHRImpl()), + ComponentUrlMapper, + UrlResolver, + StyleUrlResolver, + StyleInliner, + DynamicComponentLoader, + Testability, + AnchorBasedAppRootUrl, + WebWorkerMain + ]; +} + +export function createInjector(zone: NgZone): Injector { + BrowserDomAdapter.makeCurrent(); + _rootBindings.push(bind(NgZone).toValue(zone)); + var injector: Injector = Injector.resolveAndCreate(_rootBindings); + return injector.resolveAndCreateChild(_injectorBindings()); +} diff --git a/modules/angular2/src/web-workers/ui/impl.ts b/modules/angular2/src/web-workers/ui/impl.ts new file mode 100644 index 0000000000..1e3c04b8d4 --- /dev/null +++ b/modules/angular2/src/web-workers/ui/impl.ts @@ -0,0 +1,234 @@ +/* + * This file is the entry point for the main thread + * It takes care of spawning the worker and sending it the initial init message + * It also acts and the messenger between the worker thread and the renderer running on the UI + * thread + * TODO: This class might need to be refactored to match application.ts... +*/ + +import {createInjector} from "./di_bindings"; +import { + Renderer, + RenderCompiler, + DirectiveMetadata, + ProtoViewDto, + ViewDefinition, + RenderProtoViewRef, + RenderProtoViewMergeMapping, + RenderViewRef, + RenderFragmentRef +} from "angular2/src/render/api"; +import {Type, print, BaseException} from "angular2/src/facade/lang"; +import {Promise, PromiseWrapper} from "angular2/src/facade/async"; +import {Serializer} from "angular2/src/web-workers/shared/serializer"; +import {MessageBus} from "angular2/src/web-workers/shared/message_bus"; +import { + RenderViewWithFragmentsStore +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {createNgZone} from 'angular2/src/core/application_common'; +import {WorkerElementRef} from 'angular2/src/web-workers/shared/api'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; +import {Injectable} from 'angular2/di'; +import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; + +/** + * Creates a zone, sets up the DI bindings + * And then creates a new WebWorkerMain object to handle messages from the worker + */ +export function bootstrapUICommon(bus: MessageBus) { + BrowserDomAdapter.makeCurrent(); + var zone = createNgZone(new ExceptionHandler()); + zone.run(() => { + var injector = createInjector(zone); + var webWorkerMain = injector.get(WebWorkerMain); + webWorkerMain.attachToWorker(bus); + }); +} + +@Injectable() +export class WebWorkerMain { + private _rootUrl: string; + private _bus: MessageBus; + + constructor(private _renderCompiler: RenderCompiler, private _renderer: Renderer, + private _renderViewWithFragmentsStore: RenderViewWithFragmentsStore, + private _serializer: Serializer, rootUrl: AnchorBasedAppRootUrl) { + this._rootUrl = rootUrl.value; + } + + /** + * Attach's this WebWorkerMain instance to the given MessageBus + * This instance will now listen for all messages from the worker and handle them appropriately + * Note: Don't attach more than one WebWorkerMain instance to the same MessageBus. + */ + attachToWorker(bus: MessageBus) { + this._bus = bus; + this._bus.source.addListener((message) => { this._handleWorkerMessage(message); }); + } + + private _sendInitMessage() { this._sendWorkerMessage("init", {"rootUrl": this._rootUrl}); } + + /* + * Sends an error back to the worker thread in response to an opeartion on the UI thread + */ + private _sendWorkerError(id: string, error: any) { + this._sendWorkerMessage("error", {"id": id, "error": error}); + } + + private _sendWorkerMessage(type: string, data: StringMap) { + this._bus.sink.send({'type': type, 'value': data}); + } + + // TODO: Transfer the types with the serialized data so this can be automated? + private _handleCompilerMessage(data: ReceivedMessage) { + var promise: Promise; + switch (data.method) { + case "compileHost": + var directiveMetadata = this._serializer.deserialize(data.args[0], DirectiveMetadata); + promise = this._renderCompiler.compileHost(directiveMetadata); + this._wrapWorkerPromise(data.id, promise, ProtoViewDto); + break; + case "compile": + var view = this._serializer.deserialize(data.args[0], ViewDefinition); + promise = this._renderCompiler.compile(view); + this._wrapWorkerPromise(data.id, promise, ProtoViewDto); + break; + case "mergeProtoViewsRecursively": + var views = this._serializer.deserialize(data.args[0], RenderProtoViewRef); + promise = this._renderCompiler.mergeProtoViewsRecursively(views); + this._wrapWorkerPromise(data.id, promise, RenderProtoViewMergeMapping); + break; + default: + throw new BaseException("not implemented"); + } + } + + private _createViewHelper(args: List, method) { + var hostProtoView = this._serializer.deserialize(args[0], RenderProtoViewRef); + var fragmentCount = args[1]; + var startIndex, renderViewWithFragments; + if (method == "createView") { + startIndex = args[2]; + renderViewWithFragments = this._renderer.createView(hostProtoView, fragmentCount); + } else { + var selector = args[2]; + startIndex = args[3]; + renderViewWithFragments = + this._renderer.createRootHostView(hostProtoView, fragmentCount, selector); + } + this._renderViewWithFragmentsStore.store(renderViewWithFragments, startIndex); + } + + private _handleRendererMessage(data: ReceivedMessage) { + var args = data.args; + switch (data.method) { + case "createRootHostView": + case "createView": + this._createViewHelper(args, data.method); + break; + case "destroyView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.destroyView(viewRef); + break; + case "attachFragmentAfterFragment": + var previousFragment = this._serializer.deserialize(args[0], RenderFragmentRef); + var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); + this._renderer.attachFragmentAfterFragment(previousFragment, fragment); + break; + case "attachFragmentAfterElement": + var element = this._serializer.deserialize(args[0], WorkerElementRef); + var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); + this._renderer.attachFragmentAfterElement(element, fragment); + break; + case "detachFragment": + var fragment = this._serializer.deserialize(args[0], RenderFragmentRef); + this._renderer.detachFragment(fragment); + break; + case "hydrateView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.hydrateView(viewRef); + break; + case "dehydrateView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.dehydrateView(viewRef); + break; + case "setText": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + var textNodeIndex = args[1]; + var text = args[2]; + this._renderer.setText(viewRef, textNodeIndex, text); + break; + case "setElementProperty": + var elementRef = this._serializer.deserialize(args[0], WorkerElementRef); + var propName = args[1]; + var propValue = args[2]; + this._renderer.setElementProperty(elementRef, propName, propValue); + break; + case "setElementAttribute": + var elementRef = this._serializer.deserialize(args[0], WorkerElementRef); + var attributeName = args[1]; + var attributeValue = args[2]; + this._renderer.setElementAttribute(elementRef, attributeName, attributeValue); + break; + case "setElementClass": + var elementRef = this._serializer.deserialize(args[0], WorkerElementRef); + var className = args[1]; + var isAdd = args[2]; + this._renderer.setElementClass(elementRef, className, isAdd); + break; + case "setElementStyle": + var elementRef = this._serializer.deserialize(args[0], WorkerElementRef); + var styleName = args[1]; + var styleValue = args[2]; + this._renderer.setElementStyle(elementRef, styleName, styleValue); + break; + case "invokeElementMethod": + var elementRef = this._serializer.deserialize(args[0], WorkerElementRef); + var methodName = args[1]; + var methodArgs = args[2]; + this._renderer.invokeElementMethod(elementRef, methodName, methodArgs); + break; + default: + throw new BaseException("Not Implemented"); + } + } + + // TODO: Create message type + private _handleWorkerMessage(message: StringMap) { + var data: ReceivedMessage = new ReceivedMessage(message['data']); + switch (data.type) { + case "ready": + return this._sendInitMessage(); + case "compiler": + return this._handleCompilerMessage(data); + case "renderer": + return this._handleRendererMessage(data); + } + } + + private _wrapWorkerPromise(id: string, promise: Promise, type: Type): void { + PromiseWrapper.then(promise, (result: any) => { + try { + this._sendWorkerMessage("result", + {"id": id, "value": this._serializer.serialize(result, type)}); + } catch (e) { + print(e); + } + }, (error: any) => { this._sendWorkerError(id, error); }); + } +} + +class ReceivedMessage { + method: string; + args: List; + id: string; + type: string; + + constructor(data: StringMap) { + this.method = data['method']; + this.args = data['args']; + this.id = data['id']; + this.type = data['type']; + } +} diff --git a/modules/angular2/src/web-workers/worker/application.dart b/modules/angular2/src/web-workers/worker/application.dart index 5fdcd14a95..47bb171f27 100644 --- a/modules/angular2/src/web-workers/worker/application.dart +++ b/modules/angular2/src/web-workers/worker/application.dart @@ -2,15 +2,18 @@ library angular2.src.web_workers.worker; import "package:angular2/src/web-workers/shared/message_bus.dart" show MessageBus, MessageBusSource, MessageBusSink; +import "package:angular2/src/web-workers/worker/application_common.dart" + show bootstrapWebworkerCommon; import "package:angular2/src/facade/async.dart" show Future; import "package:angular2/src/core/application.dart" show ApplicationRef; import "package:angular2/src/facade/lang.dart" show Type, BaseException; import "dart:isolate"; import "dart:async"; +import 'dart:core'; /** * Bootstrapping a Webworker Application - * + * * You instantiate the application side by calling bootstrapWebworker from your webworker index * script. * You must supply a SendPort for communicating with the UI side in order to instantiate @@ -21,9 +24,11 @@ import "dart:async"; */ Future bootstrapWebworker( SendPort replyTo, Type appComponentType, - [List componentInjectableBindings = null, - Function errorReporter = null]) { - throw new BaseException("Not implemented"); + [List componentInjectableBindings = null]) { + ReceivePort rPort = new ReceivePort(); + WorkerMessageBus bus = new WorkerMessageBus.fromPorts(replyTo, rPort); + return bootstrapWebworkerCommon(appComponentType, bus, + componentInjectableBindings); } class WorkerMessageBus extends MessageBus { @@ -52,14 +57,24 @@ class WorkerMessageBusSink extends MessageBusSink { class WorkerMessageBusSource extends MessageBusSource { final ReceivePort _port; final Stream rawDataStream; + Map _listenerStore = new Map(); + int _numListeners = 0; WorkerMessageBusSource(ReceivePort rPort) : _port = rPort, rawDataStream = rPort.asBroadcastStream(); - void listen(Function fn) { - rawDataStream.listen((message) { + int addListener(Function fn){ + var subscription = rawDataStream.listen((message){ fn({"data": message}); }); + + _listenerStore[++_numListeners] = subscription; + return _numListeners; + } + + void removeListener(int index){ + _listenerStore[index].cancel(); + _listenerStore.remove(index); } } diff --git a/modules/angular2/src/web-workers/worker/application.ts b/modules/angular2/src/web-workers/worker/application.ts index 60b46d5e17..bd8c20e0de 100644 --- a/modules/angular2/src/web-workers/worker/application.ts +++ b/modules/angular2/src/web-workers/worker/application.ts @@ -7,7 +7,9 @@ import { import {Type, BaseException} from "angular2/src/facade/lang"; import {Binding} from "angular2/di"; +import {bootstrapWebworkerCommon} from "angular2/src/web-workers/worker/application_common"; import {ApplicationRef} from "angular2/src/core/application"; +import {Injectable} from "angular2/di"; /** * Bootstrapping a Webworker Application @@ -19,11 +21,15 @@ import {ApplicationRef} from "angular2/src/core/application"; * See the bootstrap() docs for more details. */ export function bootstrapWebworker( - appComponentType: Type, componentInjectableBindings: List> = null, - errorReporter: Function = null): Promise { - throw new BaseException("Not Implemented"); + appComponentType: Type, componentInjectableBindings: List> = null): + Promise { + var bus: WorkerMessageBus = + new WorkerMessageBus(new WorkerMessageBusSink(), new WorkerMessageBusSource()); + + return bootstrapWebworkerCommon(appComponentType, bus, componentInjectableBindings); } +@Injectable() export class WorkerMessageBus implements MessageBus { sink: WorkerMessageBusSink; source: WorkerMessageBusSource; @@ -39,5 +45,22 @@ export class WorkerMessageBusSink implements MessageBusSink { } export class WorkerMessageBusSource implements MessageBusSource { - public listen(fn: SourceListener) { addEventListener("message", fn); } + private listenerStore: Map; + private numListeners: int; + + constructor() { + this.numListeners = 0; + this.listenerStore = new Map(); + } + + public addListener(fn: SourceListener): int { + addEventListener("message", fn); + this.listenerStore[++this.numListeners] = fn; + return this.numListeners; + } + + public removeListener(index: int): void { + removeEventListener("message", this.listenerStore[index]); + this.listenerStore.delete(index); + } } diff --git a/modules/angular2/src/web-workers/worker/application_common.ts b/modules/angular2/src/web-workers/worker/application_common.ts new file mode 100644 index 0000000000..c5d86e2830 --- /dev/null +++ b/modules/angular2/src/web-workers/worker/application_common.ts @@ -0,0 +1,195 @@ +import {Injector, bind, OpaqueToken, Binding} from 'angular2/di'; +import { + NumberWrapper, + Type, + isBlank, + isPresent, + BaseException, + assertionsEnabled, + print, + stringify +} from 'angular2/src/facade/lang'; +import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; +import {Reflector, reflector} from 'angular2/src/reflection/reflection'; +import { + Parser, + Lexer, + ChangeDetection, + DynamicChangeDetection, + JitChangeDetection, + Pipes, + defaultPipes, + PreGeneratedChangeDetection +} from 'angular2/change_detection'; +import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; +import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; +import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; +import {List, ListWrapper} from 'angular2/src/facade/collection'; +import {Promise, PromiseWrapper} 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 {XHR} from 'angular2/src/render/xhr'; +import {XHRImpl} from 'angular2/src/render/xhr_impl'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {AppRootUrl} from 'angular2/src/services/app_root_url'; +import { + ComponentRef, + DynamicComponentLoader +} from 'angular2/src/core/compiler/dynamic_component_loader'; +import {Testability} from 'angular2/src/core/testability/testability'; +import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; +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'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; +import {WorkerRenderer, WorkerCompiler} from './renderer'; +import {Renderer, RenderCompiler} from 'angular2/src/render/api'; +import {internalView} from 'angular2/src/core/compiler/view_ref'; + +import {MessageBroker} from 'angular2/src/web-workers/worker/broker'; +import {WorkerMessageBus} from 'angular2/src/web-workers/worker/application'; +import { + appComponentRefPromiseToken, + appComponentTypeToken +} from 'angular2/src/core/application_tokens'; +import {ApplicationRef} from 'angular2/src/core/application'; +import {createNgZone} from 'angular2/src/core/application_common'; +import {Serializer} from "angular2/src/web-workers/shared/serializer"; +import {ON_WEBWORKER} from "angular2/src/web-workers/shared/api"; +import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store'; +import { + RenderViewWithFragmentsStore +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {WorkerExceptionHandler} from 'angular2/src/web-workers/worker/exception_handler'; + +var _rootInjector: Injector; + +// Contains everything that is safe to share between applications. +var _rootBindings = [bind(Reflector).toValue(reflector)]; + +function _injectorBindings(appComponentType, bus: WorkerMessageBus, + initData: StringMap): List> { + var bestChangeDetection: Type = DynamicChangeDetection; + if (PreGeneratedChangeDetection.isSupported()) { + bestChangeDetection = PreGeneratedChangeDetection; + } else if (JitChangeDetection.isSupported()) { + bestChangeDetection = JitChangeDetection; + } + return [ + bind(appComponentTypeToken) + .toValue(appComponentType), + bind(appComponentRefPromiseToken) + .toFactory( + (dynamicComponentLoader, injector) => { + + // TODO(rado): investigate whether to support bindings on root component. + return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector) + .then((componentRef) => { return componentRef; }); + }, + [DynamicComponentLoader, Injector]), + + bind(appComponentType).toFactory((ref) => ref.instance, [appComponentRefPromiseToken]), + bind(LifeCycle).toFactory((exceptionHandler) => new LifeCycle(null, assertionsEnabled()), + [ExceptionHandler]), + Serializer, + bind(WorkerMessageBus).toValue(bus), + bind(MessageBroker) + .toFactory((a, b) => new MessageBroker(a, b), [WorkerMessageBus, Serializer]), + WorkerRenderer, + bind(Renderer).toAlias(WorkerRenderer), + WorkerCompiler, + bind(RenderCompiler).toAlias(WorkerCompiler), + bind(ON_WEBWORKER).toValue(true), + RenderViewWithFragmentsStore, + RenderProtoViewRefStore, + ProtoViewFactory, + AppViewPool, + bind(APP_VIEW_POOL_CAPACITY).toValue(10000), + AppViewManager, + AppViewManagerUtils, + AppViewListener, + Compiler, + CompilerCache, + ViewResolver, + bind(Pipes).toValue(defaultPipes), + bind(ChangeDetection).toClass(bestChangeDetection), + DirectiveResolver, + Parser, + Lexer, + WorkerExceptionHandler, + bind(ExceptionHandler).toAlias(WorkerExceptionHandler), + bind(XHR).toValue(new XHRImpl()), + ComponentUrlMapper, + UrlResolver, + StyleUrlResolver, + DynamicComponentLoader, + Testability, + bind(AppRootUrl).toValue(new AppRootUrl(initData['rootUrl'])) + ]; +} + +export function bootstrapWebworkerCommon( + appComponentType: Type, bus: WorkerMessageBus, + componentInjectableBindings: List> = null): Promise { + var bootstrapProcess = PromiseWrapper.completer(); + + var zone = createNgZone(new WorkerExceptionHandler()); + zone.run(() => { + // TODO(rado): prepopulate template cache, so applications with only + // index.html and main.js are possible. + // + + var listenerId: int; + listenerId = bus.source.addListener((message: StringMap) => { + if (message["data"]["type"] !== "init") { + return; + } + + var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, + message["data"]["value"]); + var compRefToken = PromiseWrapper.wrap(() => { + try { + return appInjector.get(appComponentRefPromiseToken); + } catch (e) { + throw e; + } + }); + var tick = (componentRef) => { + var appChangeDetector = internalView(componentRef.hostView).changeDetector; + // retrieve life cycle: may have already been created if injected in root component + var lc = appInjector.get(LifeCycle); + lc.registerWith(zone, appChangeDetector); + lc.tick(); // the first tick that will bootstrap the app + + bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector)); + }; + PromiseWrapper.then(compRefToken, tick, + (err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); }); + + PromiseWrapper.catchError(compRefToken, (err) => { + print(err); + bootstrapProcess.reject(err, err.stack); + }); + + bus.source.removeListener(listenerId); + }); + + bus.sink.send({'type': "ready"}); + }); + + return bootstrapProcess.promise; +} + +function _createAppInjector(appComponentType: Type, bindings: List>, + zone: NgZone, bus: WorkerMessageBus, initData: StringMap): + Injector { + if (isBlank(_rootInjector)) _rootInjector = Injector.resolveAndCreate(_rootBindings); + var mergedBindings: any[] = + isPresent(bindings) ? + ListWrapper.concat(_injectorBindings(appComponentType, bus, initData), bindings) : + _injectorBindings(appComponentType, bus, initData); + mergedBindings.push(bind(NgZone).toValue(zone)); + return _rootInjector.resolveAndCreateChild(mergedBindings); +} diff --git a/modules/angular2/src/web-workers/worker/broker.ts b/modules/angular2/src/web-workers/worker/broker.ts index dc1f37375e..496bd83ef7 100644 --- a/modules/angular2/src/web-workers/worker/broker.ts +++ b/modules/angular2/src/web-workers/worker/broker.ts @@ -4,12 +4,15 @@ import {print, isPresent, DateWrapper, stringify} from "../../facade/lang"; import {Promise, PromiseCompleter, PromiseWrapper} from "angular2/src/facade/async"; import {ListWrapper, StringMapWrapper, MapWrapper} from "../../facade/collection"; import {Serializer} from "angular2/src/web-workers/shared/serializer"; +import {Injectable} from "angular2/di"; +import {Type} from "angular2/src/facade/lang"; +@Injectable() export class MessageBroker { private _pending: Map = new Map(); - constructor(private _messageBus: MessageBus) { - this._messageBus.source.listen((data) => this._handleMessage(data['data'])); + constructor(private _messageBus: MessageBus, protected _serializer: Serializer) { + this._messageBus.source.addListener((data) => this._handleMessage(data['data'])); } private _generateMessageId(name: string): string { @@ -23,26 +26,48 @@ export class MessageBroker { return id; } - runOnUiThread(args: UiArguments): Promise { - var completer = PromiseWrapper.completer(); - var id: string = this._generateMessageId(args.type + args.method); - this._pending.set(id, completer.resolve); - PromiseWrapper.catchError(completer.promise, (err, stack?) => { - print(err); - completer.reject(err, stack); - }); - + runOnUiThread(args: UiArguments, returnType: Type): Promise { var fnArgs = []; if (isPresent(args.args)) { ListWrapper.forEach(args.args, (argument) => { - fnArgs.push(Serializer.serialize(argument.value, argument.type)); + if (argument.type != null) { + fnArgs.push(this._serializer.serialize(argument.value, argument.type)); + } else { + fnArgs.push(argument.value); + } }); } + var promise: Promise; + var id: string = null; + if (returnType != null) { + var completer = PromiseWrapper.completer(); + id = this._generateMessageId(args.type + args.method); + this._pending.set(id, completer.resolve); + PromiseWrapper.catchError(completer.promise, (err, stack?) => { + print(err); + completer.reject(err, stack); + }); + + promise = PromiseWrapper.then(completer.promise, (data: MessageResult) => { + if (this._serializer == null) { + return data.value; + } else { + return this._serializer.deserialize(data.value, returnType); + } + }); + } else { + promise = null; + } + // TODO(jteplitz602): Create a class for these messages so we don't keep using StringMap - var message = {'type': args.type, 'method': args.method, 'args': fnArgs, 'id': id}; + var message = {'type': args.type, 'method': args.method, 'args': fnArgs}; + if (id != null) { + message['id'] = id; + } this._messageBus.sink.send(message); - return completer.promise; + + return promise; } private _handleMessage(message: StringMap): void { diff --git a/modules/angular2/src/web-workers/worker/exception_handler.ts b/modules/angular2/src/web-workers/worker/exception_handler.ts new file mode 100644 index 0000000000..32e09c09a6 --- /dev/null +++ b/modules/angular2/src/web-workers/worker/exception_handler.ts @@ -0,0 +1,35 @@ +import {isPresent, print, BaseException} from 'angular2/src/facade/lang'; +import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; +import {Injectable} from 'angular2/di'; + +@Injectable() +export class WorkerExceptionHandler implements ExceptionHandler { + logError: Function = print; + + call(exception: Object, stackTrace: any = null, reason: string = null) { + var longStackTrace = isListLikeIterable(stackTrace) ? + (stackTrace).join("\n\n-----async gap-----\n") : + stackTrace; + + this.logError(`${exception}\n\n${longStackTrace}`); + + if (isPresent(reason)) { + this.logError(`Reason: ${reason}`); + } + + var context = this._findContext(exception); + if (isPresent(context)) { + this.logError("Error Context:"); + this.logError(context); + } + + throw exception; + } + + _findContext(exception: any): any { + if (!(exception instanceof BaseException)) return null; + return isPresent(exception.context) ? exception.context : + this._findContext(exception.originalException); + } +} diff --git a/modules/angular2/src/web-workers/worker/loader.js b/modules/angular2/src/web-workers/worker/loader.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/angular2/src/web-workers/worker/renderer.ts b/modules/angular2/src/web-workers/worker/renderer.ts new file mode 100644 index 0000000000..387e2dda2c --- /dev/null +++ b/modules/angular2/src/web-workers/worker/renderer.ts @@ -0,0 +1,260 @@ +import { + Renderer, + RenderCompiler, + DirectiveMetadata, + ProtoViewDto, + ViewDefinition, + RenderProtoViewRef, + RenderViewRef, + RenderElementRef, + RenderEventDispatcher, + RenderProtoViewMergeMapping, + RenderViewWithFragments, + RenderFragmentRef +} from 'angular2/src/render/api'; +import {Promise, PromiseWrapper} from "angular2/src/facade/async"; +import {MessageBroker, FnArg, UiArguments} from "angular2/src/web-workers/worker/broker"; +import {isPresent, print, BaseException} from "angular2/src/facade/lang"; +import {Injectable} from "angular2/di"; +import { + RenderViewWithFragmentsStore, + WorkerRenderViewRef +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {WorkerElementRef} from 'angular2/src/web-workers/shared/api'; + +@Injectable() +export class WorkerCompiler implements RenderCompiler { + constructor(private _messageBroker: MessageBroker) {} + /** + * Creats a ProtoViewDto that contains a single nested component with the given componentId. + */ + compileHost(directiveMetadata: DirectiveMetadata): Promise { + var fnArgs: List = [new FnArg(directiveMetadata, DirectiveMetadata)]; + var args: UiArguments = new UiArguments("compiler", "compileHost", fnArgs); + return this._messageBroker.runOnUiThread(args, ProtoViewDto); + } + + /** + * Compiles a single DomProtoView. Non recursive so that + * we don't need to serialize all possible components over the wire, + * but only the needed ones based on previous calls. + */ + compile(view: ViewDefinition): Promise { + var fnArgs: List = [new FnArg(view, ViewDefinition)]; + var args: UiArguments = new UiArguments("compiler", "compile", fnArgs); + return this._messageBroker.runOnUiThread(args, ProtoViewDto); + } + + /** + * Merges ProtoViews. + * The first entry of the array is the protoview into which all the other entries of the array + * should be merged. + * If the array contains other arrays, they will be merged before processing the parent array. + * The array must contain an entry for every component and embedded ProtoView of the first entry. + * @param protoViewRefs List of ProtoViewRefs or nested + * @return the merge result for every input array in depth first order. + */ + mergeProtoViewsRecursively( + protoViewRefs: List>): Promise { + var fnArgs: List = [new FnArg(protoViewRefs, RenderProtoViewRef)]; + var args: UiArguments = new UiArguments("compiler", "mergeProtoViewsRecursively", fnArgs); + return this._messageBroker.runOnUiThread(args, RenderProtoViewMergeMapping); + } +} + + +@Injectable() +export class WorkerRenderer implements Renderer { + constructor(private _messageBroker: MessageBroker, + private _renderViewStore: RenderViewWithFragmentsStore) {} + /** + * Creates a root host view that includes the given element. + * Note that the fragmentCount needs to be passed in so that we can create a result + * synchronously even when dealing with webworkers! + * + * @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type + * ProtoViewDto.HOST_VIEW_TYPE + * @param {any} hostElementSelector css selector for the host element (will be queried against the + * main document) + * @return {RenderViewRef} the created view + */ + createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number, + hostElementSelector: string): RenderViewWithFragments { + return this._createViewHelper(hostProtoViewRef, fragmentCount, hostElementSelector); + } + + /** + * Creates a regular view out of the given ProtoView + * Note that the fragmentCount needs to be passed in so that we can create a result + * synchronously even when dealing with webworkers! + */ + createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments { + return this._createViewHelper(protoViewRef, fragmentCount); + } + + private _createViewHelper(protoViewRef: RenderProtoViewRef, fragmentCount: number, + hostElementSelector?: string): RenderViewWithFragments { + var renderViewWithFragments = this._renderViewStore.allocate(fragmentCount); + + var startIndex = ((renderViewWithFragments.viewRef)).refNumber; + var fnArgs: List = [ + new FnArg(protoViewRef, RenderProtoViewRef), + new FnArg(fragmentCount, null), + ]; + var method = "createView"; + if (isPresent(hostElementSelector) && hostElementSelector != null) { + fnArgs.push(new FnArg(hostElementSelector, null)); + method = "createRootHostView"; + } + fnArgs.push(new FnArg(startIndex, null)); + + var args = new UiArguments("renderer", method, fnArgs); + this._messageBroker.runOnUiThread(args, null); + + return renderViewWithFragments; + } + + /** + * Destroys the given view after it has been dehydrated and detached + */ + destroyView(viewRef: RenderViewRef) { + var fnArgs = [new FnArg(viewRef, RenderViewRef)]; + var args = new UiArguments("renderer", "destroyView", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Attaches a fragment after another fragment. + */ + attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef, + fragmentRef: RenderFragmentRef) { + var fnArgs = [ + new FnArg(previousFragmentRef, RenderFragmentRef), + new FnArg(fragmentRef, RenderFragmentRef) + ]; + var args = new UiArguments("renderer", "attachFragmentAfterFragment", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Attaches a fragment after an element. + */ + attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) { + var fnArgs = + [new FnArg(elementRef, WorkerElementRef), new FnArg(fragmentRef, RenderFragmentRef)]; + var args = new UiArguments("renderer", "attachFragmentAfterElement", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Detaches a fragment. + */ + detachFragment(fragmentRef: RenderFragmentRef) { + var fnArgs = [new FnArg(fragmentRef, RenderFragmentRef)]; + var args = new UiArguments("renderer", "detachFragment", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views + * inside of the view pool. + */ + hydrateView(viewRef: RenderViewRef) { + var fnArgs = [new FnArg(viewRef, RenderViewRef)]; + var args = new UiArguments("renderer", "hydrateView", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Dehydrates a view after it has been attached. Hydration/dehydration is used for reusing views + * inside of the view pool. + */ + dehydrateView(viewRef: RenderViewRef) { + var fnArgs = [new FnArg(viewRef, RenderViewRef)]; + var args = new UiArguments("renderer", "deyhdrateView", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Returns the native element at the given location. + * Attention: In a WebWorker scenario, this should always return null! + */ + getNativeElementSync(location: RenderElementRef): any { return null; } + + /** + * Sets a property on an element. + */ + setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any) { + var fnArgs = [ + new FnArg(location, WorkerElementRef), + new FnArg(propertyName, null), + new FnArg(propertyValue, null) + ]; + var args = new UiArguments("renderer", "setElementProperty", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Sets an attribute on an element. + */ + setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string) { + var fnArgs = [ + new FnArg(location, WorkerElementRef), + new FnArg(attributeName, null), + new FnArg(attributeValue, null) + ]; + var args = new UiArguments("renderer", "setElementAttribute", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Sets a class on an element. + */ + setElementClass(location: RenderElementRef, className: string, isAdd: boolean) { + var fnArgs = + [new FnArg(location, WorkerElementRef), new FnArg(className, null), new FnArg(isAdd, null)]; + var args = new UiArguments("renderer", "setElementClass", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Sets a style on an element. + */ + setElementStyle(location: RenderElementRef, styleName: string, styleValue: string) { + var fnArgs = [ + new FnArg(location, WorkerElementRef), + new FnArg(styleName, null), + new FnArg(styleValue, null) + ]; + var args = new UiArguments("renderer", "setElementStyle", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Calls a method on an element. + * Note: For now we're assuming that everything in the args list are primitive + */ + invokeElementMethod(location: RenderElementRef, methodName: string, args: List) { + var fnArgs = + [new FnArg(location, WorkerElementRef), new FnArg(methodName, null), new FnArg(args, null)]; + var uiArgs = new UiArguments("renderer", "invokeElementMethod", fnArgs); + this._messageBroker.runOnUiThread(uiArgs, null); + } + + /** + * Sets the value of a text node. + */ + setText(viewRef: RenderViewRef, textNodeIndex: number, text: string) { + var fnArgs = + [new FnArg(viewRef, RenderViewRef), new FnArg(textNodeIndex, null), new FnArg(text, null)]; + var args = new UiArguments("renderer", "setText", fnArgs); + this._messageBroker.runOnUiThread(args, null); + } + + /** + * Sets the dispatcher for all events of the given view + */ + setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher) { + // TODO(jteplitz602) support dom events in web worker. See #3046 + } +} diff --git a/modules/angular2/test/core/compiler/compiler_spec.ts b/modules/angular2/test/core/compiler/compiler_spec.ts index 4f10d31e13..a69cfaaf57 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.ts +++ b/modules/angular2/test/core/compiler/compiler_spec.ts @@ -58,7 +58,8 @@ export function main() { protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults); return new Compiler(directiveResolver, new CompilerCache(), tplResolver, cmpUrlMapper, - urlResolver, renderCompiler, protoViewFactory, new FakeAppRootUrl()); + urlResolver, renderCompiler, protoViewFactory, + new AppRootUrl("http://www.app.com")); } beforeEach(() => { @@ -398,8 +399,9 @@ export function main() { var reader: any = new SpyDirectiveResolver(); // create the compiler - var compiler = new Compiler(reader, cache, tplResolver, cmpUrlMapper, new UrlResolver(), - renderCompiler, protoViewFactory, new FakeAppRootUrl()); + var compiler = + new Compiler(reader, cache, tplResolver, cmpUrlMapper, new UrlResolver(), + renderCompiler, protoViewFactory, new AppRootUrl("http://www.app.com")); compiler.compileInHost(MainComponent) .then((protoViewRef) => { // the test should have failed if the resolver was called, so we're good @@ -669,10 +671,6 @@ class SpyDirectiveResolver extends SpyObject { noSuchMethod(m) { return super.noSuchMethod(m) } } -class FakeAppRootUrl extends AppRootUrl { - get value() { return 'http://www.app.com'; } -} - class FakeViewResolver extends ViewResolver { _cmpViews: Map = new Map(); diff --git a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts index e31b28b9b4..0faef271da 100644 --- a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts +++ b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts @@ -276,5 +276,5 @@ export function main() { }); } -var someComponent = DirectiveMetadata.create( +export var someComponent = DirectiveMetadata.create( {id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'some-comp'}); diff --git a/modules/angular2/test/render/dom/dom_testbed.ts b/modules/angular2/test/render/dom/dom_testbed.ts index 25d3aede26..85535e3d83 100644 --- a/modules/angular2/test/render/dom/dom_testbed.ts +++ b/modules/angular2/test/render/dom/dom_testbed.ts @@ -68,7 +68,7 @@ export class DomTestbed { @Inject(DOCUMENT_TOKEN) document) { this.renderer = renderer; this.compiler = compiler; - this.rootEl = el('
'); + this.rootEl = el('
'); var oldRoots = DOM.querySelectorAll(document, '#root'); for (var i = 0; i < oldRoots.length; i++) { DOM.remove(oldRoots[i]); diff --git a/modules/angular2/test/web-workers/shared/render_proto_view_ref_store_spec.ts b/modules/angular2/test/web-workers/shared/render_proto_view_ref_store_spec.ts new file mode 100644 index 0000000000..c4c9278eaf --- /dev/null +++ b/modules/angular2/test/web-workers/shared/render_proto_view_ref_store_spec.ts @@ -0,0 +1,24 @@ +import {AsyncTestCompleter, inject, describe, it, expect} from "angular2/test_lib"; +import {RenderProtoViewRef} from "angular2/src/render/api"; +import {RenderProtoViewRefStore} from "angular2/src/web-workers/shared/render_proto_view_ref_store"; + +export function main() { + describe("RenderProtoViewRefStore", () => { + it("should store and return the correct reference", () => { + var store = new RenderProtoViewRefStore(true); + var ref1 = new RenderProtoViewRef(); + var index1 = store.storeRenderProtoViewRef(ref1); + expect(store.retreiveRenderProtoViewRef(index1)).toBe(ref1); + var ref2 = new RenderProtoViewRef(); + var index2 = store.storeRenderProtoViewRef(ref2); + expect(store.retreiveRenderProtoViewRef(index2)).toBe(ref2); + }); + + it("should cache index numbers", () => { + var store = new RenderProtoViewRefStore(true); + var ref = new RenderProtoViewRef(); + var index = store.storeRenderProtoViewRef(ref); + expect(store.storeRenderProtoViewRef(ref)).toEqual(index); + }); + }); +} diff --git a/modules/angular2/test/web-workers/shared/render_view_with_fragments_store_spec.ts b/modules/angular2/test/web-workers/shared/render_view_with_fragments_store_spec.ts new file mode 100644 index 0000000000..d6c680b719 --- /dev/null +++ b/modules/angular2/test/web-workers/shared/render_view_with_fragments_store_spec.ts @@ -0,0 +1,120 @@ +import {AsyncTestCompleter, beforeEach, inject, describe, it, expect} from "angular2/test_lib"; +import {RenderViewWithFragments, RenderViewRef, RenderFragmentRef} from "angular2/src/render/api"; +import { + RenderViewWithFragmentsStore, + WorkerRenderViewRef, + WorkerRenderFragmentRef +} from "angular2/src/web-workers/shared/render_view_with_fragments_store"; +import {List, ListWrapper} from "angular2/src/facade/collection"; + +export function main() { + describe("RenderViewWithFragmentsStore", () => { + describe("on WebWorker", () => { + var store; + beforeEach(() => { store = new RenderViewWithFragmentsStore(true); }); + + it("should allocate fragmentCount + 1 refs", () => { + var view: RenderViewWithFragments = store.allocate(10); + + var viewRef: WorkerRenderViewRef = view.viewRef; + expect(viewRef.refNumber).toEqual(0); + + var fragmentRefs: List = + >view.fragmentRefs; + expect(fragmentRefs.length).toEqual(10); + + for (var i = 0; i < fragmentRefs.length; i++) { + expect(fragmentRefs[i].refNumber).toEqual(i + 1); + } + }); + + it("should not reuse a reference", () => { + store.allocate(10); + var view = store.allocate(0); + var viewRef = view.viewRef; + expect(viewRef.refNumber).toEqual(11); + }); + + it("should be serializable", () => { + var view = store.allocate(1); + expect(store.deserializeViewWithFragments(store.serializeViewWithFragments(view))) + .toEqual(view); + }); + }); + + describe("on UI", () => { + var store; + beforeEach(() => { store = new RenderViewWithFragmentsStore(false); }); + function createMockRenderViewWithFragments(): RenderViewWithFragments { + var view = new MockRenderViewRef(); + var fragments = ListWrapper.createGrowableSize(20); + for (var i = 0; i < 20; i++) { + fragments[i] = new MockRenderFragmentRef(); + } + + return new RenderViewWithFragments(view, fragments); + } + it("should associate views with the correct references", () => { + var renderViewWithFragments = createMockRenderViewWithFragments(); + + store.store(renderViewWithFragments, 100); + expect(store.retreive(100)).toBe(renderViewWithFragments.viewRef); + + for (var i = 0; i < renderViewWithFragments.fragmentRefs.length; i++) { + expect(store.retreive(101 + i)).toBe(renderViewWithFragments.fragmentRefs[i]); + } + }); + + describe("RenderViewWithFragments", () => { + it("should be serializable", () => { + var renderViewWithFragments = createMockRenderViewWithFragments(); + store.store(renderViewWithFragments, 0); + + var deserialized = store.deserializeViewWithFragments( + store.serializeViewWithFragments(renderViewWithFragments)); + expect(deserialized.viewRef).toBe(renderViewWithFragments.viewRef); + + expect(deserialized.fragmentRefs.length) + .toEqual(renderViewWithFragments.fragmentRefs.length); + + for (var i = 0; i < deserialized.fragmentRefs.length; i++) { + var val = deserialized.fragmentRefs[i]; + expect(val).toBe(renderViewWithFragments.fragmentRefs[i]); + }; + }); + }); + + describe("RenderViewRef", () => { + it("should be serializable", () => { + var renderViewWithFragments = createMockRenderViewWithFragments(); + store.store(renderViewWithFragments, 0); + + var deserialized = store.deserializeRenderViewRef( + store.serializeRenderViewRef(renderViewWithFragments.viewRef)); + expect(deserialized).toBe(renderViewWithFragments.viewRef); + }); + }); + + describe("RenderFragmentRef", () => { + it("should be serializable", () => { + var renderViewWithFragments = createMockRenderViewWithFragments(); + store.store(renderViewWithFragments, 0); + + var serialized = + store.serializeRenderFragmentRef(renderViewWithFragments.fragmentRefs[0]); + var deserialized = store.deserializeRenderFragmentRef(serialized); + + expect(deserialized).toBe(renderViewWithFragments.fragmentRefs[0]); + }); + }); + }); + }); +} + +class MockRenderViewRef extends RenderViewRef { + constructor() { super(); } +} + +class MockRenderFragmentRef extends RenderFragmentRef { + constructor() { super(); } +} diff --git a/modules/angular2/test/web-workers/worker/renderer_spec.ts b/modules/angular2/test/web-workers/worker/renderer_spec.ts new file mode 100644 index 0000000000..6ea8f4ef2b --- /dev/null +++ b/modules/angular2/test/web-workers/worker/renderer_spec.ts @@ -0,0 +1,343 @@ +import { + AsyncTestCompleter, + inject, + describe, + it, + expect, + beforeEach, + createTestInjector, + beforeEachBindings +} from "angular2/test_lib"; +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {DomTestbed, TestRootView, elRef} from '../../render/dom/dom_testbed'; +import {bind} from 'angular2/di'; +import {WorkerCompiler, WorkerRenderer} from "angular2/src/web-workers/worker/renderer"; +import {MessageBroker, UiArguments, FnArg} from "angular2/src/web-workers/worker/broker"; +import {Serializer} from "angular2/src/web-workers/shared/serializer"; +import {isPresent, isBlank, BaseException, Type} from "angular2/src/facade/lang"; +import {MapWrapper, ListWrapper} from "angular2/src/facade/collection"; +import { + DirectiveMetadata, + ProtoViewDto, + RenderProtoViewRef, + RenderViewWithFragments, + ViewDefinition, + RenderProtoViewMergeMapping, + RenderViewRef, + RenderFragmentRef +} from "angular2/src/render/api"; +import { + MessageBus, + MessageBusSource, + MessageBusSink, + SourceListener +} from "angular2/src/web-workers/shared/message_bus"; +import { + RenderProtoViewRefStore, + WebworkerRenderProtoViewRef +} from "angular2/src/web-workers/shared/render_proto_view_ref_store"; +import { + RenderViewWithFragmentsStore, + WorkerRenderViewRef +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; +import {someComponent} from '../../render/dom/dom_renderer_integration_spec'; +import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; + +export function main() { + function createBroker(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed, + uiRenderViewStore: RenderViewWithFragmentsStore, + workerRenderViewStore: RenderViewWithFragmentsStore): MessageBroker { + // set up the two message buses to pass messages to each other + var uiMessageBus = new MockMessageBus(new MockMessageBusSink(), new MockMessageBusSource()); + var workerMessageBus = new MockMessageBus(new MockMessageBusSink(), new MockMessageBusSource()); + uiMessageBus.attachToBus(workerMessageBus); + workerMessageBus.attachToBus(uiMessageBus); + + // set up the worker side + var broker = new MessageBroker(workerMessageBus, workerSerializer); + + // set up the ui side + var webWorkerMain = new WebWorkerMain(tb.compiler, tb.renderer, uiRenderViewStore, uiSerializer, + new AnchorBasedAppRootUrl()); + webWorkerMain.attachToWorker(uiMessageBus); + return broker; + } + + function createWorkerRenderer(workerSerializer: Serializer, uiSerializer: Serializer, + tb: DomTestbed, uiRenderViewStore: RenderViewWithFragmentsStore, + workerRenderViewStore: RenderViewWithFragmentsStore): + WorkerRenderer { + var broker = + createBroker(workerSerializer, uiSerializer, tb, uiRenderViewStore, workerRenderViewStore); + return new WorkerRenderer(broker, workerRenderViewStore); + } + + function createWorkerCompiler(workerSerializer: Serializer, uiSerializer: Serializer, + tb: DomTestbed): WorkerCompiler { + var broker = createBroker(workerSerializer, uiSerializer, tb, null, null); + return new WorkerCompiler(broker); + } + + describe("Web Worker Compiler", function() { + var workerSerializer: Serializer; + var uiSerializer: Serializer; + var workerRenderProtoViewRefStore: RenderProtoViewRefStore; + var uiRenderProtoViewRefStore: RenderProtoViewRefStore; + var tb: DomTestbed; + + beforeEach(() => { + workerRenderProtoViewRefStore = new RenderProtoViewRefStore(true); + uiRenderProtoViewRefStore = new RenderProtoViewRefStore(false); + workerSerializer = createSerializer(workerRenderProtoViewRefStore, null); + uiSerializer = createSerializer(uiRenderProtoViewRefStore, null); + tb = createTestInjector([DomTestbed]).get(DomTestbed); + }); + + function resolveWebWorkerRef(ref: RenderProtoViewRef) { + var refNumber = (ref).refNumber; + return resolveInternalDomProtoView(uiRenderProtoViewRefStore.deserialize(refNumber)); + } + + it('should build the proto view', inject([AsyncTestCompleter], (async) => { + var compiler: WorkerCompiler = createWorkerCompiler(workerSerializer, uiSerializer, tb); + + var dirMetadata = DirectiveMetadata.create( + {id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE}); + compiler.compileHost(dirMetadata) + .then((protoView) => { + expect(DOM.tagName(DOM.firstChild( + DOM.content(resolveWebWorkerRef(protoView.render).rootElement)))) + .toEqual('CUSTOM'); + expect(protoView).not.toBeNull(); + async.done(); + }); + })); + }); + + describe("Web Worker Renderer", () => { + beforeEachBindings(() => [DomTestbed]); + var renderer: WorkerRenderer; + var workerSerializer: Serializer; + var workerRenderViewStore: RenderViewWithFragmentsStore; + var uiRenderViewStore: RenderViewWithFragmentsStore; + var uiSerializer: Serializer; + var tb: DomTestbed; + + /** + * Seriliazes the given obj with the uiSerializer and then returns the version that + * the worker would deserialize + */ + function serialize(obj: any, type: Type): any { + var serialized = uiSerializer.serialize(obj, type); + return workerSerializer.deserialize(serialized, type); + } + + beforeEach(() => { + workerRenderViewStore = new RenderViewWithFragmentsStore(true); + tb = createTestInjector([DomTestbed]).get(DomTestbed); + uiRenderViewStore = new RenderViewWithFragmentsStore(false); + workerSerializer = createSerializer(new RenderProtoViewRefStore(true), workerRenderViewStore); + uiSerializer = createSerializer(new RenderProtoViewRefStore(false), uiRenderViewStore); + renderer = createWorkerRenderer(workerSerializer, uiSerializer, tb, uiRenderViewStore, + workerRenderViewStore); + }); + + + it('should create and destroy root host views while using the given elements in place', + inject([AsyncTestCompleter], (async) => { + tb.compiler.compileHost(someComponent) + .then((hostProtoViewDto: any) => { + hostProtoViewDto = serialize(hostProtoViewDto, ProtoViewDto); + var viewWithFragments = + renderer.createRootHostView(hostProtoViewDto.render, 1, '#root'); + var view = new WorkerTestRootView(viewWithFragments, uiRenderViewStore); + + expect(tb.rootEl.parentNode).toBeTruthy(); + expect(view.hostElement).toBe(tb.rootEl); + + renderer.detachFragment(viewWithFragments.fragmentRefs[0]); + renderer.destroyView(viewWithFragments.viewRef); + expect(tb.rootEl.parentNode).toBeFalsy(); + + async.done(); + }); + })); + + it('should update text nodes', inject([AsyncTestCompleter], (async) => { + tb.compileAndMerge( + someComponent, + [ + new ViewDefinition( + {componentId: 'someComponent', template: '{{a}}', directives: []}) + ]) + .then((protoViewMergeMappings) => { + protoViewMergeMappings = + serialize(protoViewMergeMappings, RenderProtoViewMergeMapping); + var rootView = renderer.createView(protoViewMergeMappings.mergedProtoViewRef, 1); + renderer.hydrateView(rootView.viewRef); + + renderer.setText(rootView.viewRef, 0, 'hello'); + var view = new WorkerTestRootView(rootView, uiRenderViewStore); + expect(view.hostElement).toHaveText('hello'); + async.done(); + }); + })); + + + it('should update any element property/attributes/class/style independent of the compilation on the root element and other elements', + inject([AsyncTestCompleter], (async) => { + tb.compileAndMerge(someComponent, + [ + new ViewDefinition({ + componentId: 'someComponent', + template: '', + directives: [] + }) + ]) + .then((protoViewMergeMappings) => { + protoViewMergeMappings = + serialize(protoViewMergeMappings, RenderProtoViewMergeMapping); + + var checkSetters = (elr, el) => { + renderer.setElementProperty(elr, 'tabIndex', 1); + expect((el).tabIndex).toEqual(1); + + renderer.setElementClass(elr, 'a', true); + expect(DOM.hasClass(el, 'a')).toBe(true); + renderer.setElementClass(elr, 'a', false); + expect(DOM.hasClass(el, 'a')).toBe(false); + + renderer.setElementStyle(elr, 'width', '10px'); + expect(DOM.getStyle(el, 'width')).toEqual('10px'); + renderer.setElementStyle(elr, 'width', null); + expect(DOM.getStyle(el, 'width')).toEqual(''); + + renderer.setElementAttribute(elr, 'someAttr', 'someValue'); + expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue'); + }; + + var rootViewWithFragments = + renderer.createView(protoViewMergeMappings.mergedProtoViewRef, 1); + renderer.hydrateView(rootViewWithFragments.viewRef); + + var rootView = new WorkerTestRootView(rootViewWithFragments, uiRenderViewStore); + // root element + checkSetters(elRef(rootViewWithFragments.viewRef, 0), rootView.hostElement); + // nested elements + checkSetters(elRef(rootViewWithFragments.viewRef, 1), + DOM.firstChild(rootView.hostElement)); + + async.done(); + }); + })); + + it('should add and remove empty fragments', inject([AsyncTestCompleter], (async) => { + tb.compileAndMerge(someComponent, + [ + new ViewDefinition({ + componentId: 'someComponent', + template: '', + directives: [] + }) + ]) + .then((protoViewMergeMappings) => { + protoViewMergeMappings = + serialize(protoViewMergeMappings, RenderProtoViewMergeMapping); + var rootViewWithFragments = + renderer.createView(protoViewMergeMappings.mergedProtoViewRef, 3); + + var elr = elRef(rootViewWithFragments.viewRef, 1); + var rootView = new WorkerTestRootView(rootViewWithFragments, uiRenderViewStore); + expect(rootView.hostElement).toHaveText(''); + var fragment = rootViewWithFragments.fragmentRefs[1]; + var fragment2 = rootViewWithFragments.fragmentRefs[2]; + renderer.attachFragmentAfterElement(elr, fragment); + renderer.attachFragmentAfterFragment(fragment, fragment2); + renderer.detachFragment(fragment); + renderer.detachFragment(fragment2); + expect(rootView.hostElement).toHaveText(''); + + async.done(); + }); + })); + + if (DOM.supportsDOMEvents()) { + it('should call actions on the element independent of the compilation', + inject([AsyncTestCompleter], (async) => { + tb.compileAndMerge(someComponent, + [ + new ViewDefinition({ + componentId: 'someComponent', + template: '', + directives: [] + }) + ]) + .then((protoViewMergeMappings) => { + protoViewMergeMappings = + serialize(protoViewMergeMappings, RenderProtoViewMergeMapping); + var rootViewWithFragments = + renderer.createView(protoViewMergeMappings.mergedProtoViewRef, 1); + var rootView = new WorkerTestRootView(rootViewWithFragments, uiRenderViewStore); + + renderer.invokeElementMethod(elRef(rootViewWithFragments.viewRef, 1), + 'setAttribute', ['a', 'b']); + + expect(DOM.getAttribute(DOM.childNodes(rootView.hostElement)[0], 'a')) + .toEqual('b'); + async.done(); + }); + })); + } + }); +} + +class WorkerTestRootView extends TestRootView { + constructor(workerViewWithFragments: RenderViewWithFragments, uiRenderViewStore) { + super(new RenderViewWithFragments( + uiRenderViewStore.retreive( + (workerViewWithFragments.viewRef).refNumber), + ListWrapper.map(workerViewWithFragments.fragmentRefs, + (val) => { return uiRenderViewStore.retreive(val.refNumber); }))); + } +} + +function createSerializer(protoViewRefStore: RenderProtoViewRefStore, + renderViewStore: RenderViewWithFragmentsStore): Serializer { + var injector = createTestInjector([ + bind(RenderProtoViewRefStore) + .toValue(protoViewRefStore), + bind(RenderViewWithFragmentsStore).toValue(renderViewStore) + ]); + return injector.get(Serializer); +} + +class MockMessageBusSource implements MessageBusSource { + private _listenerStore: Map = new Map(); + private _numListeners: number = 0; + + addListener(fn: SourceListener): int { + this._listenerStore.set(++this._numListeners, fn); + return this._numListeners; + } + + removeListener(index: int): void { MapWrapper.delete(this._listenerStore, index); } + + receive(message: Object): void { + MapWrapper.forEach(this._listenerStore, (fn: SourceListener, key: int) => { fn(message); }); + } +} + +class MockMessageBusSink implements MessageBusSink { + private _sendTo: MockMessageBusSource; + + send(message: Object): void { this._sendTo.receive({'data': message}); } + + attachToSource(source: MockMessageBusSource) { this._sendTo = source; } +} + +class MockMessageBus implements MessageBus { + constructor(public sink: MockMessageBusSink, public source: MockMessageBusSource) {} + attachToBus(bus: MockMessageBus) { this.sink.attachToSource(bus.source); } +} diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.ts b/modules/benchmarks/src/compiler/compiler_benchmark.ts index 02e476b4d0..542befef5e 100644 --- a/modules/benchmarks/src/compiler/compiler_benchmark.ts +++ b/modules/benchmarks/src/compiler/compiler_benchmark.ts @@ -41,7 +41,7 @@ export function main() { new ViewLoader(null, null, null)); var compiler = new Compiler(reader, cache, viewResolver, new ComponentUrlMapper(), urlResolver, renderCompiler, new ProtoViewFactory(new DynamicChangeDetection()), - new FakeAppRootUrl()); + new AppRootUrl("")); function measureWrapper(func, desc) { return function() { @@ -161,7 +161,3 @@ class BenchmarkComponentNoBindings { }) class BenchmarkComponentWithBindings { } - -class FakeAppRootUrl extends AppRootUrl { - get value() { return ''; } -} diff --git a/modules/examples/src/assets/zone-microtask-web-workers.js b/modules/examples/src/assets/zone-microtask-web-workers.js new file mode 100644 index 0000000000..e914a30395 --- /dev/null +++ b/modules/examples/src/assets/zone-microtask-web-workers.js @@ -0,0 +1,1773 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1; + +// TODO(vicb): remove '!isFirefox' when the bug gets fixed: +// https://bugzilla.mozilla.org/show_bug.cgi?id=1162013 +if (hasNativePromise && !isFirefox) { + // When available use a native Promise to schedule microtasks. + // When not available, es6-promise fallback will be used + var resolvedPromise = Promise.resolve(); + es6Promise._setScheduler(function(fn) { + resolvedPromise.then(fn); + }); +} + + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"es6-promise":11}],4:[function(require,module,exports){ +'use strict'; + +// might need similar for object.freeze +// i regret nothing + +var _defineProperty = Object.defineProperty; +var _getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +var _create = Object.create; + +function apply() { + Object.defineProperty = function (obj, prop, desc) { + if (isUnconfigurable(obj, prop)) { + throw new TypeError('Cannot assign to read only property \'' + prop + '\' of ' + obj); + } + if (prop !== 'prototype') { + desc = rewriteDescriptor(obj, prop, desc); + } + return _defineProperty(obj, prop, desc); + }; + + Object.defineProperties = function (obj, props) { + Object.keys(props).forEach(function (prop) { + Object.defineProperty(obj, prop, props[prop]); + }); + return obj; + }; + + Object.create = function (obj, proto) { + if (typeof proto === 'object') { + Object.keys(proto).forEach(function (prop) { + proto[prop] = rewriteDescriptor(obj, prop, proto[prop]); + }); + } + return _create(obj, proto); + }; + + Object.getOwnPropertyDescriptor = function (obj, prop) { + var desc = _getOwnPropertyDescriptor(obj, prop); + if (isUnconfigurable(obj, prop)) { + desc.configurable = false; + } + return desc; + }; +}; + +function _redefineProperty(obj, prop, desc) { + desc = rewriteDescriptor(obj, prop, desc); + return _defineProperty(obj, prop, desc); +}; + +function isUnconfigurable (obj, prop) { + return obj && obj.__unconfigurables && obj.__unconfigurables[prop]; +} + +function rewriteDescriptor (obj, prop, desc) { + desc.configurable = true; + if (!desc.configurable) { + if (!obj.__unconfigurables) { + _defineProperty(obj, '__unconfigurables', { writable: true, value: {} }); + } + obj.__unconfigurables[prop] = true; + } + return desc; +} + +module.exports = { + apply: apply, + _redefineProperty: _redefineProperty +}; + + + +},{}],5:[function(require,module,exports){ +(function (global){ +'use strict'; + +var utils = require('../utils'); + +function apply() { + // patched properties depend on addEventListener, so this needs to come first + if (global.EventTarget) { + utils.patchEventTargetMethods(global.EventTarget.prototype); + + // Note: EventTarget is not available in all browsers, + // if it's not available, we instead patch the APIs in the IDL that inherit from EventTarget + } else { + var apis = [ 'ApplicationCache', + 'EventSource', + 'FileReader', + 'InputMethodContext', + 'MediaController', + 'MessagePort', + 'Node', + 'Performance', + 'SVGElementInstance', + 'SharedWorker', + 'TextTrack', + 'TextTrackCue', + 'TextTrackList', + 'WebKitNamedFlow', + 'Window', + 'Worker', + 'WorkerGlobalScope', + 'XMLHttpRequest', + 'XMLHttpRequestEventTarget', + 'XMLHttpRequestUpload' + ]; + + apis.forEach(function(thing) { + global[thing] && utils.patchEventTargetMethods(global[thing].prototype); + }); + } +} + +module.exports = { + apply: apply +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../utils":10}],6:[function(require,module,exports){ +(function (global){ +'use strict'; + +var utils = require('../utils'); + +function patchSetClearFunction(obj, fnNames) { + fnNames.map(function (name) { + return name[0].toUpperCase() + name.substr(1); + }).forEach(function (name) { + var setName = 'set' + name; + var delegate = obj[setName]; + + if (delegate) { + var clearName = 'clear' + name; + var ids = {}; + + var bindArgs = setName === 'setInterval' ? utils.bindArguments : utils.bindArgumentsOnce; + + global.zone[setName] = function (fn) { + var id, fnRef = fn; + arguments[0] = function () { + delete ids[id]; + return fnRef.apply(this, arguments); + }; + var args = bindArgs(arguments); + id = delegate.apply(obj, args); + ids[id] = true; + return id; + }; + + obj[setName] = function () { + return global.zone[setName].apply(this, arguments); + }; + + var clearDelegate = obj[clearName]; + + global.zone[clearName] = function (id) { + if (ids[id]) { + delete ids[id]; + global.zone.dequeueTask(); + } + return clearDelegate.apply(this, arguments); + }; + + obj[clearName] = function () { + return global.zone[clearName].apply(this, arguments); + }; + } + }); +}; + +function patchSetFunction(obj, fnNames) { + fnNames.forEach(function (name) { + var delegate = obj[name]; + + if (delegate) { + global.zone[name] = function (fn) { + var fnRef = fn; + arguments[0] = function () { + return fnRef.apply(this, arguments); + }; + var args = utils.bindArgumentsOnce(arguments); + return delegate.apply(obj, args); + }; + + obj[name] = function () { + return zone[name].apply(this, arguments); + }; + } + }); +}; + +function patchFunction(obj, fnNames) { + fnNames.forEach(function (name) { + var delegate = obj[name]; + global.zone[name] = function () { + return delegate.apply(obj, arguments); + }; + + obj[name] = function () { + return global.zone[name].apply(this, arguments); + }; + }); +}; + + +module.exports = { + patchSetClearFunction: patchSetClearFunction, + patchSetFunction: patchSetFunction, + patchFunction: patchFunction +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../utils":10}],7:[function(require,module,exports){ +(function (global){ +'use strict'; + +var utils = require('../utils'); + +/* + * Patches a function that returns a Promise-like instance. + * + * This function must be used when either: + * - Native Promises are not available, + * - The function returns a Promise-like object. + * + * This is required because zones rely on a Promise monkey patch that could not be applied when + * Promise is not natively available or when the returned object is not an instance of Promise. + * + * Note that calling `bindPromiseFn` on a function that returns a native Promise will also work + * with minimal overhead. + * + * ``` + * var boundFunction = bindPromiseFn(FunctionReturningAPromise); + * + * boundFunction.then(successHandler, errorHandler); + * ``` + */ +var bindPromiseFn; + +if (global.Promise) { + bindPromiseFn = function (delegate) { + return function() { + var delegatePromise = delegate.apply(this, arguments); + + // if the delegate returned an instance of Promise, forward it. + if (delegatePromise instanceof Promise) { + return delegatePromise; + } + + // Otherwise wrap the Promise-like in a global Promise + return new Promise(function(resolve, reject) { + delegatePromise.then(resolve, reject); + }); + }; + }; +} else { + bindPromiseFn = function (delegate) { + return function () { + return _patchThenable(delegate.apply(this, arguments)); + }; + }; +} + + +function _patchPromiseFnsOnObject(objectPath, fnNames) { + var obj = global; + + var exists = objectPath.every(function (segment) { + obj = obj[segment]; + return obj; + }); + + if (!exists) { + return; + } + + fnNames.forEach(function (name) { + var fn = obj[name]; + if (fn) { + obj[name] = bindPromiseFn(fn); + } + }); +} + +function _patchThenable(thenable) { + var then = thenable.then; + thenable.then = function () { + var args = utils.bindArguments(arguments); + var nextThenable = then.apply(thenable, args); + return _patchThenable(nextThenable); + }; + + var ocatch = thenable.catch; + thenable.catch = function () { + var args = utils.bindArguments(arguments); + var nextThenable = ocatch.apply(thenable, args); + return _patchThenable(nextThenable); + }; + + return thenable; +} + + +function apply() { + // Patch .then() and .catch() on native Promises to execute callbacks in the zone where + // those functions are called. + if (global.Promise) { + utils.patchPrototype(Promise.prototype, [ + 'then', + 'catch' + ]); + + // Patch browser APIs that return a Promise + var patchFns = [ + // fetch + [[], ['fetch']], + [['Response', 'prototype'], ['arrayBuffer', 'blob', 'json', 'text']] + ]; + + patchFns.forEach(function(objPathAndFns) { + _patchPromiseFnsOnObject(objPathAndFns[0], objPathAndFns[1]); + }); + } +} + +module.exports = { + apply: apply, + bindPromiseFn: bindPromiseFn +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../utils":10}],8:[function(require,module,exports){ +(function (global){ +'use strict'; + +var fnPatch = require('./functions'); +var promisePatch = require('./promise'); +var definePropertyPatch = require('./define-property'); +var webSocketPatch = require('./websocket'); +var eventTargetPatch = require('./event-target'); + +function apply() { + fnPatch.patchSetClearFunction(global, [ + 'timeout', + 'interval', + 'immediate' + ]); + + eventTargetPatch.apply(); + + promisePatch.apply(); + + definePropertyPatch.apply(); +} + +module.exports = { + apply: apply +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./define-property":4,"./event-target":5,"./functions":6,"./promise":7,"./websocket":9}],9:[function(require,module,exports){ +(function (global){ +'use strict'; + +var utils = require('../utils'); + +// we have to patch the instance since the proto is non-configurable +function apply() { + var WS = global.WebSocket; + utils.patchEventTargetMethods(WS.prototype); + global.WebSocket = function(a, b) { + var socket = arguments.length > 1 ? new WS(a, b) : new WS(a); + var proxySocket; + + // Safari 7.0 has non-configurable own 'onmessage' and friends properties on the socket instance + var onmessageDesc = Object.getOwnPropertyDescriptor(socket, 'onmessage'); + if (onmessageDesc && onmessageDesc.configurable === false) { + proxySocket = Object.create(socket); + ['addEventListener', 'removeEventListener', 'send', 'close'].forEach(function(propName) { + proxySocket[propName] = function() { + return socket[propName].apply(socket, arguments); + }; + }); + } else { + // we can patch the real socket + proxySocket = socket; + } + + utils.patchProperties(proxySocket, ['onclose', 'onerror', 'onmessage', 'onopen']); + + return proxySocket; + }; +} + +module.exports = { + apply: apply +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../utils":10}],10:[function(require,module,exports){ +(function (global){ +'use strict'; + +function bindArguments(args) { + for (var i = args.length - 1; i >= 0; i--) { + if (typeof args[i] === 'function') { + args[i] = global.zone.bind(args[i]); + } + } + return args; +}; + +function bindArgumentsOnce(args) { + for (var i = args.length - 1; i >= 0; i--) { + if (typeof args[i] === 'function') { + args[i] = global.zone.bindOnce(args[i]); + } + } + return args; +}; + +function patchPrototype(obj, fnNames) { + fnNames.forEach(function (name) { + var delegate = obj[name]; + if (delegate) { + obj[name] = function () { + return delegate.apply(this, bindArguments(arguments)); + }; + } + }); +}; + +function patchProperty(obj, prop) { + var desc = Object.getOwnPropertyDescriptor(obj, prop) || { + enumerable: true, + configurable: true + }; + + // A property descriptor cannot have getter/setter and be writable + // deleting the writable and value properties avoids this error: + // + // TypeError: property descriptors must not specify a value or be writable when a + // getter or setter has been specified + delete desc.writable; + delete desc.value; + + // substr(2) cuz 'onclick' -> 'click', etc + var eventName = prop.substr(2); + var _prop = '_' + prop; + + desc.set = function (fn) { + if (this[_prop]) { + this.removeEventListener(eventName, this[_prop]); + } + + if (typeof fn === 'function') { + this[_prop] = fn; + this.addEventListener(eventName, fn, false); + } else { + this[_prop] = null; + } + }; + + desc.get = function () { + return this[_prop]; + }; + + Object.defineProperty(obj, prop, desc); +}; + +function patchProperties(obj, properties) { + + (properties || (function () { + var props = []; + for (var prop in obj) { + props.push(prop); + } + return props; + }()). + filter(function (propertyName) { + return propertyName.substr(0,2) === 'on'; + })). + forEach(function (eventName) { + patchProperty(obj, eventName); + }); +}; + +function patchEventTargetMethods(obj) { + var addDelegate = obj.addEventListener; + obj.addEventListener = function (eventName, handler) { + var fn; + + if (handler.handleEvent) { + // Have to pass in 'handler' reference as an argument here, otherwise it gets clobbered in + // IE9 by the arguments[1] assignment at end of this function. + fn = (function(handler) { + return function() { + handler.handleEvent.apply(handler, arguments); + }; + })(handler); + } else { + fn = handler; + } + + handler._fn = fn; + handler._bound = handler._bound || {}; + arguments[1] = handler._bound[eventName] = zone.bind(fn); + return addDelegate.apply(this, arguments); + }; + + var removeDelegate = obj.removeEventListener; + obj.removeEventListener = function (eventName, handler) { + if(handler._bound && handler._bound[eventName]) { + var _bound = handler._bound; + + arguments[1] = _bound[eventName]; + delete _bound[eventName]; + } + var result = removeDelegate.apply(this, arguments); + global.zone.dequeueTask(handler._fn); + return result; + }; +}; + +// wrap some native API on `window` +function patchClass(className) { + var OriginalClass = global[className]; + if (!OriginalClass) return; + + global[className] = function () { + var a = bindArguments(arguments); + switch (a.length) { + case 0: this._o = new OriginalClass(); break; + case 1: this._o = new OriginalClass(a[0]); break; + case 2: this._o = new OriginalClass(a[0], a[1]); break; + case 3: this._o = new OriginalClass(a[0], a[1], a[2]); break; + case 4: this._o = new OriginalClass(a[0], a[1], a[2], a[3]); break; + default: throw new Error('what are you even doing?'); + } + }; + + var instance = new OriginalClass(); + + var prop; + for (prop in instance) { + (function (prop) { + if (typeof instance[prop] === 'function') { + global[className].prototype[prop] = function () { + return this._o[prop].apply(this._o, arguments); + }; + } else { + Object.defineProperty(global[className].prototype, prop, { + set: function (fn) { + if (typeof fn === 'function') { + this._o[prop] = global.zone.bind(fn); + } else { + this._o[prop] = fn; + } + }, + get: function () { + return this._o[prop]; + } + }); + } + }(prop)); + } + + for (prop in OriginalClass) { + if (prop !== 'prototype' && OriginalClass.hasOwnProperty(prop)) { + global[className][prop] = OriginalClass[prop]; + } + } +}; + +module.exports = { + bindArguments: bindArguments, + bindArgumentsOnce: bindArgumentsOnce, + patchPrototype: patchPrototype, + patchProperty: patchProperty, + patchProperties: patchProperties, + patchEventTargetMethods: patchEventTargetMethods, + patchClass: patchClass +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(require,module,exports){ +(function (process,global){ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version 2.3.0 + */ + +(function() { + "use strict"; + function lib$es6$promise$utils$$objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); + } + + function lib$es6$promise$utils$$isFunction(x) { + return typeof x === 'function'; + } + + function lib$es6$promise$utils$$isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + + var lib$es6$promise$utils$$_isArray; + if (!Array.isArray) { + lib$es6$promise$utils$$_isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + lib$es6$promise$utils$$_isArray = Array.isArray; + } + + var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; + var lib$es6$promise$asap$$len = 0; + var lib$es6$promise$asap$$toString = {}.toString; + var lib$es6$promise$asap$$vertxNext; + var lib$es6$promise$asap$$customSchedulerFn; + + var lib$es6$promise$asap$$asap = function asap(callback, arg) { + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; + lib$es6$promise$asap$$len += 2; + if (lib$es6$promise$asap$$len === 2) { + // If len is 2, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + if (lib$es6$promise$asap$$customSchedulerFn) { + lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); + } else { + lib$es6$promise$asap$$scheduleFlush(); + } + } + } + + function lib$es6$promise$asap$$setScheduler(scheduleFn) { + lib$es6$promise$asap$$customSchedulerFn = scheduleFn; + } + + function lib$es6$promise$asap$$setAsap(asapFn) { + lib$es6$promise$asap$$asap = asapFn; + } + + var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; + var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; + var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; + var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; + + // test for web worker but not in IE10 + var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; + + // node + function lib$es6$promise$asap$$useNextTick() { + var nextTick = process.nextTick; + // node version 0.10.x displays a deprecation warning when nextTick is used recursively + // setImmediate should be used instead instead + var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/); + if (Array.isArray(version) && version[1] === '0' && version[2] === '10') { + nextTick = setImmediate; + } + return function() { + nextTick(lib$es6$promise$asap$$flush); + }; + } + + // vertx + function lib$es6$promise$asap$$useVertxTimer() { + return function() { + lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); + }; + } + + function lib$es6$promise$asap$$useMutationObserver() { + var iterations = 0; + var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function() { + node.data = (iterations = ++iterations % 2); + }; + } + + // web worker + function lib$es6$promise$asap$$useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = lib$es6$promise$asap$$flush; + return function () { + channel.port2.postMessage(0); + }; + } + + function lib$es6$promise$asap$$useSetTimeout() { + return function() { + setTimeout(lib$es6$promise$asap$$flush, 1); + }; + } + + var lib$es6$promise$asap$$queue = new Array(1000); + function lib$es6$promise$asap$$flush() { + for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { + var callback = lib$es6$promise$asap$$queue[i]; + var arg = lib$es6$promise$asap$$queue[i+1]; + + callback(arg); + + lib$es6$promise$asap$$queue[i] = undefined; + lib$es6$promise$asap$$queue[i+1] = undefined; + } + + lib$es6$promise$asap$$len = 0; + } + + function lib$es6$promise$asap$$attemptVertex() { + try { + var r = require; + var vertx = r('vertx'); + lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; + return lib$es6$promise$asap$$useVertxTimer(); + } catch(e) { + return lib$es6$promise$asap$$useSetTimeout(); + } + } + + var lib$es6$promise$asap$$scheduleFlush; + // Decide what async method to use to triggering processing of queued callbacks: + if (lib$es6$promise$asap$$isNode) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); + } else if (lib$es6$promise$asap$$BrowserMutationObserver) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); + } else if (lib$es6$promise$asap$$isWorker) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); + } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertex(); + } else { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); + } + + function lib$es6$promise$$internal$$noop() {} + + var lib$es6$promise$$internal$$PENDING = void 0; + var lib$es6$promise$$internal$$FULFILLED = 1; + var lib$es6$promise$$internal$$REJECTED = 2; + + var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$selfFullfillment() { + return new TypeError("You cannot resolve a promise with itself"); + } + + function lib$es6$promise$$internal$$cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.'); + } + + function lib$es6$promise$$internal$$getThen(promise) { + try { + return promise.then; + } catch(error) { + lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; + return lib$es6$promise$$internal$$GET_THEN_ERROR; + } + } + + function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } + } + + function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { + lib$es6$promise$asap$$asap(function(promise) { + var sealed = false; + var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + lib$es6$promise$$internal$$resolve(promise, value); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + lib$es6$promise$$internal$$reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + lib$es6$promise$$internal$$reject(promise, error); + } + }, promise); + } + + function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { + if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, thenable._result); + } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, thenable._result); + } else { + lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { + lib$es6$promise$$internal$$resolve(promise, value); + }, function(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } + } + + function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) { + if (maybeThenable.constructor === promise.constructor) { + lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); + } else { + var then = lib$es6$promise$$internal$$getThen(maybeThenable); + + if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); + } else if (then === undefined) { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } else if (lib$es6$promise$utils$$isFunction(then)) { + lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); + } else { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } + } + } + + function lib$es6$promise$$internal$$resolve(promise, value) { + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFullfillment()); + } else if (lib$es6$promise$utils$$objectOrFunction(value)) { + lib$es6$promise$$internal$$handleMaybeThenable(promise, value); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + } + + function lib$es6$promise$$internal$$publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + lib$es6$promise$$internal$$publish(promise); + } + + function lib$es6$promise$$internal$$fulfill(promise, value) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + + promise._result = value; + promise._state = lib$es6$promise$$internal$$FULFILLED; + + if (promise._subscribers.length !== 0) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); + } + } + + function lib$es6$promise$$internal$$reject(promise, reason) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + promise._state = lib$es6$promise$$internal$$REJECTED; + promise._result = reason; + + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); + } + + function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; + subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; + + if (length === 0 && parent._state) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); + } + } + + function lib$es6$promise$$internal$$publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; + } + + function lib$es6$promise$$internal$$ErrorObject() { + this.error = null; + } + + var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; + return lib$es6$promise$$internal$$TRY_CATCH_ERROR; + } + } + + function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { + var hasCallback = lib$es6$promise$utils$$isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = lib$es6$promise$$internal$$tryCatch(callback, detail); + + if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); + return; + } + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== lib$es6$promise$$internal$$PENDING) { + // noop + } else if (hasCallback && succeeded) { + lib$es6$promise$$internal$$resolve(promise, value); + } else if (failed) { + lib$es6$promise$$internal$$reject(promise, error); + } else if (settled === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, value); + } else if (settled === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } + } + + function lib$es6$promise$$internal$$initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + lib$es6$promise$$internal$$resolve(promise, value); + }, function rejectPromise(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } catch(e) { + lib$es6$promise$$internal$$reject(promise, e); + } + } + + function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { + var enumerator = this; + + enumerator._instanceConstructor = Constructor; + enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop); + + if (enumerator._validateInput(input)) { + enumerator._input = input; + enumerator.length = input.length; + enumerator._remaining = input.length; + + enumerator._init(); + + if (enumerator.length === 0) { + lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); + } else { + enumerator.length = enumerator.length || 0; + enumerator._enumerate(); + if (enumerator._remaining === 0) { + lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); + } + } + } else { + lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError()); + } + } + + lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) { + return lib$es6$promise$utils$$isArray(input); + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() { + return new Error('Array Methods must be provided an Array'); + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._init = function() { + this._result = new Array(this.length); + }; + + var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; + + lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { + var enumerator = this; + + var length = enumerator.length; + var promise = enumerator.promise; + var input = enumerator._input; + + for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { + enumerator._eachEntry(input[i], i); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { + var enumerator = this; + var c = enumerator._instanceConstructor; + + if (lib$es6$promise$utils$$isMaybeThenable(entry)) { + if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) { + entry._onerror = null; + enumerator._settledAt(entry._state, i, entry._result); + } else { + enumerator._willSettleAt(c.resolve(entry), i); + } + } else { + enumerator._remaining--; + enumerator._result[i] = entry; + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { + var enumerator = this; + var promise = enumerator.promise; + + if (promise._state === lib$es6$promise$$internal$$PENDING) { + enumerator._remaining--; + + if (state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } else { + enumerator._result[i] = value; + } + } + + if (enumerator._remaining === 0) { + lib$es6$promise$$internal$$fulfill(promise, enumerator._result); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { + enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); + }); + }; + function lib$es6$promise$promise$all$$all(entries) { + return new lib$es6$promise$enumerator$$default(this, entries).promise; + } + var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; + function lib$es6$promise$promise$race$$race(entries) { + /*jshint validthis:true */ + var Constructor = this; + + var promise = new Constructor(lib$es6$promise$$internal$$noop); + + if (!lib$es6$promise$utils$$isArray(entries)) { + lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + + var length = entries.length; + + function onFulfillment(value) { + lib$es6$promise$$internal$$resolve(promise, value); + } + + function onRejection(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + } + + for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { + lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + + return promise; + } + var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; + function lib$es6$promise$promise$resolve$$resolve(object) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$resolve(promise, object); + return promise; + } + var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; + function lib$es6$promise$promise$reject$$reject(reason) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$reject(promise, reason); + return promise; + } + var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; + + var lib$es6$promise$promise$$counter = 0; + + function lib$es6$promise$promise$$needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + + function lib$es6$promise$promise$$needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise's eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor + */ + function lib$es6$promise$promise$$Promise(resolver) { + this._id = lib$es6$promise$promise$$counter++; + this._state = undefined; + this._result = undefined; + this._subscribers = []; + + if (lib$es6$promise$$internal$$noop !== resolver) { + if (!lib$es6$promise$utils$$isFunction(resolver)) { + lib$es6$promise$promise$$needsResolver(); + } + + if (!(this instanceof lib$es6$promise$promise$$Promise)) { + lib$es6$promise$promise$$needsNew(); + } + + lib$es6$promise$$internal$$initializePromise(this, resolver); + } + } + + lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; + lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; + lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; + lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; + lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; + lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; + lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; + + lib$es6$promise$promise$$Promise.prototype = { + constructor: lib$es6$promise$promise$$Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: function(onFulfillment, onRejection) { + var parent = this; + var state = parent._state; + + if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) { + return this; + } + + var child = new this.constructor(lib$es6$promise$$internal$$noop); + var result = parent._result; + + if (state) { + var callback = arguments[state - 1]; + lib$es6$promise$asap$$asap(function(){ + lib$es6$promise$$internal$$invokeCallback(state, child, callback, result); + }); + } else { + lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + }, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection) { + return this.then(null, onRejection); + } + }; + function lib$es6$promise$polyfill$$polyfill() { + var local; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { + return; + } + + local.Promise = lib$es6$promise$promise$$default; + } + var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; + + var lib$es6$promise$umd$$ES6Promise = { + 'Promise': lib$es6$promise$promise$$default, + 'polyfill': lib$es6$promise$polyfill$$default + }; + + /* global define:true module:true window: true */ + if (typeof define === 'function' && define['amd']) { + define(function() { return lib$es6$promise$umd$$ES6Promise; }); + } else if (typeof module !== 'undefined' && module['exports']) { + module['exports'] = lib$es6$promise$umd$$ES6Promise; + } else if (typeof this !== 'undefined') { + this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; + } + + lib$es6$promise$polyfill$$default(); +}).call(this); + + +}).call(this,{},typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}]},{},[1]); diff --git a/modules/examples/src/message_broker/background_index.dart b/modules/examples/src/message_broker/background_index.dart index ad165d5f50..a476f48583 100644 --- a/modules/examples/src/message_broker/background_index.dart +++ b/modules/examples/src/message_broker/background_index.dart @@ -10,16 +10,16 @@ import "dart:isolate"; main(List args, SendPort replyTo) { ReceivePort rPort = new ReceivePort(); WorkerMessageBus bus = new WorkerMessageBus.fromPorts(replyTo, rPort); - bus.source.listen((message) { + bus.source.addListener((message) { if (identical(message['data']['type'], "echo")) { bus.sink .send({"type": "echo_response", "value": message['data']['value']}); } }); - MessageBroker broker = new MessageBroker(bus); + MessageBroker broker = new MessageBroker(bus, null); var args = new UiArguments("test", "tester"); - broker.runOnUiThread(args).then((data) { - bus.sink.send({"type": "result", "value": data.value}); + broker.runOnUiThread(args, String).then((data) { + bus.sink.send({"type": "result", "value": data}); }); } diff --git a/modules/examples/src/message_broker/background_index.ts b/modules/examples/src/message_broker/background_index.ts index 4b4e6e0f13..d31c318ca9 100644 --- a/modules/examples/src/message_broker/background_index.ts +++ b/modules/examples/src/message_broker/background_index.ts @@ -4,17 +4,18 @@ import { WorkerMessageBusSink } from "angular2/src/web-workers/worker/application"; import {MessageBroker, UiArguments} from "angular2/src/web-workers/worker/broker"; +import {Serializer} from "angular2/src/web-workers/shared/serializer"; export function main() { var bus = new WorkerMessageBus(new WorkerMessageBusSink(), new WorkerMessageBusSource()); - bus.source.listen((message) => { + bus.source.addListener((message) => { if (message.data.type === "echo") { bus.sink.send({type: "echo_response", 'value': message.data.value}); } }); - var broker = new MessageBroker(bus); + var broker = new MessageBroker(bus, new Serializer(null)); var args = new UiArguments("test", "tester"); - broker.runOnUiThread(args) - .then((data) => { bus.sink.send({type: "result", value: data.value}); }); + broker.runOnUiThread(args, String) + .then((data: string) => { bus.sink.send({type: "result", value: data}); }); } diff --git a/modules/examples/src/message_broker/index.dart b/modules/examples/src/message_broker/index.dart index 12d7a661b0..b180edc114 100644 --- a/modules/examples/src/message_broker/index.dart +++ b/modules/examples/src/message_broker/index.dart @@ -12,7 +12,7 @@ main() { var val = (querySelector("#echo_input") as InputElement).value; bus.sink.send({'type': 'echo', 'value': val}); }); - bus.source.listen((message) { + bus.source.addListener((message) { var data = message['data']; if (identical(data['type'], "echo_response")) { querySelector("#echo_result") diff --git a/modules/examples/src/message_broker/index.ts b/modules/examples/src/message_broker/index.ts index ddd336056e..62383e3adc 100644 --- a/modules/examples/src/message_broker/index.ts +++ b/modules/examples/src/message_broker/index.ts @@ -13,7 +13,8 @@ document.getElementById("send_echo") var val = (document.getElementById("echo_input")).value; bus.sink.send({type: "echo", value: val}); }); -bus.source.listen((message) => { + +bus.source.addListener((message) => { if (message.data.type === "echo_response") { document.getElementById("echo_result").innerHTML = `${message.data.value}`; diff --git a/modules/examples/src/web_workers/background_index.dart b/modules/examples/src/web_workers/background_index.dart new file mode 100644 index 0000000000..94b3a8e9da --- /dev/null +++ b/modules/examples/src/web_workers/background_index.dart @@ -0,0 +1,12 @@ +library examples.src.web_workers.index; + +import "index_common.dart" show HelloCmp; +import "dart:isolate"; +import "package:angular2/src/web-workers/worker/application.dart" show bootstrapWebworker; +import "package:angular2/src/reflection/reflection_capabilities.dart"; +import "package:angular2/src/reflection/reflection.dart"; + +main(List args, SendPort replyTo){ + reflector.reflectionCapabilities = new ReflectionCapabilities(); + bootstrapWebworker(replyTo, HelloCmp); +} diff --git a/modules/examples/src/web_workers/background_index.ts b/modules/examples/src/web_workers/background_index.ts new file mode 100644 index 0000000000..40b09d9348 --- /dev/null +++ b/modules/examples/src/web_workers/background_index.ts @@ -0,0 +1,6 @@ +import {HelloCmp} from "./index_common"; +import {bootstrapWebworker} from "angular2/src/web-workers/worker/application"; + +export function main() { + bootstrapWebworker(HelloCmp); +} diff --git a/modules/examples/src/web_workers/example.dart b/modules/examples/src/web_workers/example.dart new file mode 100644 index 0000000000..781b90adc1 --- /dev/null +++ b/modules/examples/src/web_workers/example.dart @@ -0,0 +1,10 @@ +library angular2.examples.web_workers; + +import "package:angular2/src/web-workers/ui/application.dart" show bootstrap; +import "package:angular2/src/reflection/reflection_capabilities.dart"; +import "package:angular2/src/reflection/reflection.dart"; + +main(){ + reflector.reflectionCapabilities = new ReflectionCapabilities(); + bootstrap("background_index.dart"); +} diff --git a/modules/examples/src/web_workers/example.html b/modules/examples/src/web_workers/example.html new file mode 100644 index 0000000000..1e6008f42b --- /dev/null +++ b/modules/examples/src/web_workers/example.html @@ -0,0 +1,11 @@ + + + Hello Angular 2.0 + + + Loading... + + + $SCRIPTS$ + + diff --git a/modules/examples/src/web_workers/example.ts b/modules/examples/src/web_workers/example.ts new file mode 100644 index 0000000000..5b67e20633 --- /dev/null +++ b/modules/examples/src/web_workers/example.ts @@ -0,0 +1,2 @@ +import {bootstrap} from "angular2/src/web-workers/ui/application"; +bootstrap("loader.js"); \ No newline at end of file diff --git a/modules/examples/src/web_workers/index_common.ts b/modules/examples/src/web_workers/index_common.ts new file mode 100644 index 0000000000..4e948150cc --- /dev/null +++ b/modules/examples/src/web_workers/index_common.ts @@ -0,0 +1,53 @@ +import {ElementRef, Component, Directive, View, Injectable, Renderer} from 'angular2/angular2'; + +// A service available to the Injector, used by the HelloCmp component. +@Injectable() +class GreetingService { + greeting: string = 'hello'; +} + +// Directives are light-weight. They don't allow new +// expression contexts (use @Component for those needs). +@Directive({selector: '[red]'}) +class RedDec { + // ElementRef is always injectable and it wraps the element on which the + // directive was found by the compiler. + constructor(el: ElementRef, renderer: Renderer) { renderer.setElementStyle(el, 'color', 'red'); } + // constructor(renderer: Renderer) {} +} + +// Angular 2.0 supports 2 basic types of directives: +// - Component - the basic building blocks of Angular 2.0 apps. Backed by +// ShadowDom.(http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/) +// - Directive - add behavior to existing elements. + +// @Component is AtScript syntax to annotate the HelloCmp class as an Angular +// 2.0 component. +@Component({ + // The Selector prop tells Angular on which elements to instantiate this + // class. The syntax supported is a basic subset of CSS selectors, for example + // 'element', '[attr]', [attr=foo]', etc. + selector: 'hello-app', + // These are services that would be created if a class in the component's + // template tries to inject them. + viewInjector: [GreetingService] +}) +// The template for the component. +@View({ + // Expressions in the template (like {{greeting}}) are evaluated in the + // context of the HelloCmp class below. + template: `
{{greeting}} world!
+ `, + // All directives used in the template need to be specified. This allows for + // modularity (RedDec can only be used in this template) + // and better tooling (the template can be invalidated if the attribute is + // misspelled). + directives: [RedDec] +}) +export class HelloCmp { + greeting: string; + + constructor(service: GreetingService) { this.greeting = service.greeting; } + + changeGreeting(): void { this.greeting = 'howdy'; } +} diff --git a/modules/examples/src/web_workers/loader.js b/modules/examples/src/web_workers/loader.js new file mode 100644 index 0000000000..3af4738512 --- /dev/null +++ b/modules/examples/src/web_workers/loader.js @@ -0,0 +1,27 @@ +$SCRIPTS$ + + // TODO (jteplitz) Monkey patch this from within angular (#3207) + window = { + setTimeout: setTimeout, + Map: Map, + Set: Set, + Array: Array, + Reflect: Reflect, + RegExp: RegExp, + Promise: Promise, + Date: Date + }; +assert = function() {} + + + System.import("examples/src/web_workers/background_index") + .then( + function(m) { + console.log("running main"); + try { + m.main(); + } catch (e) { + console.error(e); + } + }, + function(error) { console.error("error loading background", error); }); diff --git a/tools/broccoli/js-replace/SCRIPTS.js b/tools/broccoli/js-replace/SCRIPTS.js index 71fe642e4d..13ccb2b98c 100644 --- a/tools/broccoli/js-replace/SCRIPTS.js +++ b/tools/broccoli/js-replace/SCRIPTS.js @@ -1,7 +1,3 @@ -importScripts("traceur-runtime.js", - "es6-module-loader-sans-promises.src.js", - "system.src.js", - "extension-register.js", - "extension-cjs.js", - "Reflect.js", - "runtime_paths.js"); +importScripts("/examples/src/assets/zone-microtask-web-workers.js", "long-stack-trace-zone.js", + "traceur-runtime.js", "es6-module-loader-sans-promises.src.js", "system.src.js", + "extension-register.js", "extension-cjs.js", "Reflect.js", "runtime_paths.js"); diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts index d90a37b6aa..5f98e34d0e 100644 --- a/tools/broccoli/trees/browser_tree.ts +++ b/tools/broccoli/trees/browser_tree.ts @@ -61,7 +61,8 @@ const kServedPaths = [ 'examples/src/material/progress-linear', 'examples/src/material/radio', 'examples/src/material/switcher', - 'examples/src/message_broker' + 'examples/src/message_broker', + 'examples/src/web_workers' ];