feat(web-workers) Add WebWorker Renderer
Allows angular apps to be rendered from the webworker! Closes #3052, #3053, and #3097
This commit is contained in:
@@ -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<any>,
|
||||
public reflector: Reflector, public parseAction: boolean) {}
|
||||
|
||||
@@ -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<Type | Binding | List<any>> {
|
||||
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.
|
||||
|
||||
@@ -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<int, dynamic> values) {
|
||||
return values[val];
|
||||
}
|
||||
|
||||
class StringWrapper {
|
||||
static String fromCharCode(int code) {
|
||||
return new String.fromCharCode(code);
|
||||
|
||||
@@ -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<int, any>): any {
|
||||
return val;
|
||||
}
|
||||
|
||||
export class StringWrapper {
|
||||
static fromCharCode(code: int): string { return String.fromCharCode(code); }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<number, RenderProtoViewRef> = new Map<number, RenderProtoViewRef>();
|
||||
private _lookupByProtoView: Map<RenderProtoViewRef, number> =
|
||||
new Map<RenderProtoViewRef, number>();
|
||||
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 (<WebworkerRenderProtoViewRef>ref).refNumber;
|
||||
} else {
|
||||
return this.storeRenderProtoViewRef(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class WebworkerRenderProtoViewRef extends RenderProtoViewRef {
|
||||
constructor(public refNumber: number) { super(); }
|
||||
}
|
||||
@@ -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<number, RenderViewRef | RenderFragmentRef>;
|
||||
private _lookupByView: Map<RenderViewRef | RenderFragmentRef, number>;
|
||||
|
||||
constructor(@Inject(ON_WEBWORKER) onWebWorker) {
|
||||
this._onWebWorker = onWebWorker;
|
||||
if (!onWebWorker) {
|
||||
this._lookupByIndex = new Map<number, RenderViewRef | RenderFragmentRef>();
|
||||
this._lookupByView = new Map<RenderViewRef | RenderFragmentRef, number>();
|
||||
}
|
||||
}
|
||||
|
||||
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 (<WorkerRenderFragmentRef | WorkerRenderViewRef>ref).serialize();
|
||||
} else {
|
||||
return this._lookupByView.get(ref);
|
||||
}
|
||||
}
|
||||
|
||||
serializeViewWithFragments(view: RenderViewWithFragments): StringMap<string, any> {
|
||||
if (view == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._onWebWorker) {
|
||||
return {
|
||||
'viewRef': (<WorkerRenderViewRef>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<string, any>): RenderViewWithFragments {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var viewRef: RenderViewRef | RenderFragmentRef;
|
||||
var fragments: List<RenderViewRef | RenderFragmentRef>;
|
||||
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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<any> = new List<any>();
|
||||
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<string, V>
|
||||
* 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<string, any> {
|
||||
if (isPresent(type)) {
|
||||
var map: Map<string, any> = 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<UIMessageBus> spawnWorker(Uri uri) {
|
||||
Future<UIMessageBus> 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<int, StreamSubscription> _listenerStore = new Map<int, StreamSubscription>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<int, SourceListener> = new Map<int, SourceListener>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Type | Binding | List<any>> {
|
||||
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());
|
||||
}
|
||||
@@ -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<string, any>) {
|
||||
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<any>;
|
||||
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<any>, 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<string, any>) {
|
||||
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<any>, 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<any>;
|
||||
id: string;
|
||||
type: string;
|
||||
|
||||
constructor(data: StringMap<string, any>) {
|
||||
this.method = data['method'];
|
||||
this.args = data['args'];
|
||||
this.id = data['id'];
|
||||
this.type = data['type'];
|
||||
}
|
||||
}
|
||||
@@ -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<ApplicationRef> bootstrapWebworker(
|
||||
SendPort replyTo, Type appComponentType,
|
||||
[List<dynamic> componentInjectableBindings = null,
|
||||
Function errorReporter = null]) {
|
||||
throw new BaseException("Not implemented");
|
||||
[List<dynamic> 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<int, StreamSubscription> _listenerStore = new Map<int, StreamSubscription>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Type | Binding | List<any>> = null,
|
||||
errorReporter: Function = null): Promise<ApplicationRef> {
|
||||
throw new BaseException("Not Implemented");
|
||||
appComponentType: Type, componentInjectableBindings: List<Type | Binding | List<any>> = null):
|
||||
Promise<ApplicationRef> {
|
||||
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<int, SourceListener>;
|
||||
private numListeners: int;
|
||||
|
||||
constructor() {
|
||||
this.numListeners = 0;
|
||||
this.listenerStore = new Map<int, SourceListener>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, any>): List<Type | Binding | List<any>> {
|
||||
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<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
||||
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<string, any>) => {
|
||||
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<Type | Binding | List<any>>,
|
||||
zone: NgZone, bus: WorkerMessageBus, initData: StringMap<string, any>):
|
||||
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);
|
||||
}
|
||||
@@ -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<string, Function> = new Map<string, Function>();
|
||||
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<any>;
|
||||
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<string, any>): void {
|
||||
|
||||
@@ -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) ?
|
||||
(<any>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);
|
||||
}
|
||||
}
|
||||
@@ -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<ProtoViewDto> {
|
||||
var fnArgs: List<FnArg> = [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<ProtoViewDto> {
|
||||
var fnArgs: List<FnArg> = [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<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping> {
|
||||
var fnArgs: List<FnArg> = [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 = (<WorkerRenderViewRef>(renderViewWithFragments.viewRef)).refNumber;
|
||||
var fnArgs: List<FnArg> = [
|
||||
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<any>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<Type, viewAnn.View> = new Map();
|
||||
|
||||
|
||||
@@ -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'});
|
||||
|
||||
@@ -68,7 +68,7 @@ export class DomTestbed {
|
||||
@Inject(DOCUMENT_TOKEN) document) {
|
||||
this.renderer = renderer;
|
||||
this.compiler = compiler;
|
||||
this.rootEl = el('<div id="root"></div>');
|
||||
this.rootEl = el('<div id="root" class="rootElem"></div>');
|
||||
var oldRoots = DOM.querySelectorAll(document, '#root');
|
||||
for (var i = 0; i < oldRoots.length; i++) {
|
||||
DOM.remove(oldRoots[i]);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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 = <WorkerRenderViewRef>view.viewRef;
|
||||
expect(viewRef.refNumber).toEqual(0);
|
||||
|
||||
var fragmentRefs: List<WorkerRenderFragmentRef> =
|
||||
<List<WorkerRenderFragmentRef>>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 = <WorkerRenderViewRef>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(); }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user