refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,16 @@
{
"presets": ["es2015"],
"plugins": [["transform-es2015-modules-umd", {
"globals": {
"@angular/core": "ng.core",
"@angular/common": "ng.common",
"@angular/platform-browser": "ng.platformBrowser",
"@angular/platform-webworker": "ng.platformWebworker",
"rxjs/Observable": "Rx",
"rxjs/Subject": "Rx"
},
"exactGlobals": true
}]],
"moduleId": "@angular/platform-webworker"
}

View File

@ -0,0 +1,14 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// This file is not used to build this module. It is only used during editing
// by the TypeScript language service and during build for verification. `ngc`
// replaces this file with production index.ts when it rewrites private symbol
// names.
export * from './public_api';

View File

@ -0,0 +1,20 @@
{
"name": "@angular/platform-webworker",
"version": "0.0.0-PLACEHOLDER",
"description": "Angular - library for using Angular in a web browser with web workers",
"main": "./bundles/platform-webworker.umd.js",
"module": "./@angular/platform-webworker.es5.js",
"es2015": "./@angular/platform-webworker.js",
"typings": "./typings/platform-webworker.d.ts",
"author": "angular",
"license": "MIT",
"peerDependencies": {
"@angular/common": "0.0.0-PLACEHOLDER",
"@angular/core": "0.0.0-PLACEHOLDER",
"@angular/platform-browser": "0.0.0-PLACEHOLDER"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
}
}

View File

@ -0,0 +1,15 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Entry point for all public APIs of the platform-browser package.
*/
export * from './src/platform-webworker';
// This file only reexports content of the `src` folder. Keep it that way.

View File

@ -0,0 +1,37 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {PlatformRef, Provider} from '@angular/core';
import {WORKER_SCRIPT, platformWorkerUi} from './worker_render';
export {VERSION} from './version';
export {ClientMessageBroker, ClientMessageBrokerFactory, FnArg, UiArguments} from './web_workers/shared/client_message_broker';
export {MessageBus, MessageBusSink, MessageBusSource} from './web_workers/shared/message_bus';
export {PRIMITIVE, SerializerTypes} from './web_workers/shared/serializer';
export {ReceivedMessage, ServiceMessageBroker, ServiceMessageBrokerFactory} from './web_workers/shared/service_message_broker';
export {WORKER_UI_LOCATION_PROVIDERS} from './web_workers/ui/location_providers';
export {WORKER_APP_LOCATION_PROVIDERS} from './web_workers/worker/location_providers';
export {WorkerAppModule, platformWorkerApp} from './worker_app';
export {platformWorkerUi} from './worker_render';
/**
* Bootstraps the worker ui.
*
* @experimental
*/
export function bootstrapWorkerUi(
workerScriptUri: string, customProviders: Provider[] = []): Promise<PlatformRef> {
// For now, just creates the worker ui platform...
const platform = platformWorkerUi([
{provide: WORKER_SCRIPT, useValue: workerScriptUri},
...customProviders,
]);
return Promise.resolve(platform);
}

View File

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {InjectionToken} from '@angular/core';
export const ON_WEB_WORKER = new InjectionToken<boolean>('WebWorker.onWebWorker');

View File

@ -0,0 +1,168 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, Injectable, Type, ɵstringify as stringify} from '@angular/core';
import {MessageBus} from './message_bus';
import {Serializer, SerializerTypes} from './serializer';
/**
* @experimental WebWorker support in Angular is experimental.
*/
export abstract class ClientMessageBrokerFactory {
/**
* Initializes the given channel and attaches a new {@link ClientMessageBroker} to it.
*/
abstract createMessageBroker(channel: string, runInZone?: boolean): ClientMessageBroker;
}
@Injectable()
export class ClientMessageBrokerFactory_ extends ClientMessageBrokerFactory {
/** @internal */
_serializer: Serializer;
constructor(private _messageBus: MessageBus, _serializer: Serializer) {
super();
this._serializer = _serializer;
}
/**
* Initializes the given channel and attaches a new {@link ClientMessageBroker} to it.
*/
createMessageBroker(channel: string, runInZone: boolean = true): ClientMessageBroker {
this._messageBus.initChannel(channel, runInZone);
return new ClientMessageBroker_(this._messageBus, this._serializer, channel);
}
}
/**
* @experimental WebWorker support in Angular is experimental.
*/
export abstract class ClientMessageBroker {
abstract runOnService(args: UiArguments, returnType: Type<any>|SerializerTypes): Promise<any>;
}
interface PromiseCompleter {
resolve: (result: any) => void;
reject: (err: any) => void;
}
export class ClientMessageBroker_ extends ClientMessageBroker {
private _pending = new Map<string, PromiseCompleter>();
private _sink: EventEmitter<any>;
/** @internal */
public _serializer: Serializer;
constructor(messageBus: MessageBus, _serializer: Serializer, public channel: any) {
super();
this._sink = messageBus.to(channel);
this._serializer = _serializer;
const source = messageBus.from(channel);
source.subscribe({next: (message: ResponseMessageData) => this._handleMessage(message)});
}
private _generateMessageId(name: string): string {
const time: string = stringify(new Date().getTime());
let iteration: number = 0;
let id: string = name + time + stringify(iteration);
while (this._pending.has(id)) {
id = `${name}${time}${iteration}`;
iteration++;
}
return id;
}
runOnService(args: UiArguments, returnType: Type<any>|SerializerTypes): Promise<any> {
const fnArgs: any[] = [];
if (args.args) {
args.args.forEach(argument => {
if (argument.type != null) {
fnArgs.push(this._serializer.serialize(argument.value, argument.type));
} else {
fnArgs.push(argument.value);
}
});
}
let promise: Promise<any>;
let id: string = null;
if (returnType != null) {
let completer: PromiseCompleter;
promise = new Promise((resolve, reject) => { completer = {resolve, reject}; });
id = this._generateMessageId(args.method);
this._pending.set(id, completer);
promise.catch((err) => {
if (console && console.error) {
// tslint:disable-next-line:no-console
console.error(err);
}
completer.reject(err);
});
promise = promise.then(
(v: any) => this._serializer ? this._serializer.deserialize(v, returnType) : v);
} else {
promise = null;
}
const message: RequestMessageData = {
'method': args.method,
'args': fnArgs,
};
if (id != null) {
message['id'] = id;
}
this._sink.emit(message);
return promise;
}
private _handleMessage(message: ResponseMessageData): void {
if (message.type === 'result' || message.type === 'error') {
const id = message.id;
if (this._pending.has(id)) {
if (message.type === 'result') {
this._pending.get(id).resolve(message.value);
} else {
this._pending.get(id).reject(message.value);
}
this._pending.delete(id);
}
}
}
}
interface RequestMessageData {
method: string;
args?: any[];
id?: string;
}
interface ResponseMessageData {
type: 'result'|'error';
value?: any;
id?: string;
}
/**
* @experimental WebWorker support in Angular is experimental.
*/
export class FnArg {
constructor(
public value: any, public type: Type<any>|SerializerTypes = SerializerTypes.PRIMITIVE) {}
}
/**
* @experimental WebWorker support in Angular is experimental.
*/
export class UiArguments {
constructor(public method: string, public args?: FnArg[]) {}
}

View File

@ -0,0 +1,103 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, NgZone} from '@angular/core';
/**
* Message Bus is a low level API used to communicate between the UI and the background.
* Communication is based on a channel abstraction. Messages published in a
* given channel to one MessageBusSink are received on the same channel
* by the corresponding MessageBusSource.
*
* @experimental WebWorker support in Angular is currenlty experimental.
*/
export abstract class MessageBus implements MessageBusSource, MessageBusSink {
/**
* Sets up a new channel on the MessageBus.
* MUST be called before calling from or to on the channel.
* If runInZone is true then the source will emit events inside the angular zone
* and the sink will buffer messages and send only once the zone exits.
* if runInZone is false then the source will emit events inside the global zone
* and the sink will send messages immediately.
*/
abstract initChannel(channel: string, runInZone?: boolean): void;
/**
* Assigns this bus to the given zone.
* Any callbacks attached to channels where runInZone was set to true on initialization
* will be executed in the given zone.
*/
abstract attachToZone(zone: NgZone): void;
/**
* Returns an {@link EventEmitter} that emits every time a message
* is received on the given channel.
*/
abstract from(channel: string): EventEmitter<any>;
/**
* Returns an {@link EventEmitter} for the given channel
* To publish methods to that channel just call next on the returned emitter
*/
abstract to(channel: string): EventEmitter<any>;
}
/**
* @experimental WebWorker support in Angular is currenlty experimental.
*/
export interface MessageBusSource {
/**
* Sets up a new channel on the MessageBusSource.
* MUST be called before calling from on the channel.
* If runInZone is true then the source will emit events inside the angular zone.
* if runInZone is false then the source will emit events inside the global zone.
*/
initChannel(channel: string, runInZone: boolean): void;
/**
* Assigns this source to the given zone.
* Any channels which are initialized with runInZone set to true will emit events that will be
* executed within the given zone.
*/
attachToZone(zone: NgZone): void;
/**
* Returns an {@link EventEmitter} that emits every time a message
* is received on the given channel.
*/
from(channel: string): EventEmitter<any>;
}
/**
* @experimental WebWorker support in Angular is currenlty experimental.
*/
export interface MessageBusSink {
/**
* Sets up a new channel on the MessageBusSink.
* MUST be called before calling to on the channel.
* If runInZone is true the sink will buffer messages and send only once the zone exits.
* if runInZone is false the sink will send messages immediatly.
*/
initChannel(channel: string, runInZone: boolean): void;
/**
* Assigns this sink to the given zone.
* Any channels which are initialized with runInZone set to true will wait for the given zone
* to exit before sending messages.
*/
attachToZone(zone: NgZone): void;
/**
* Returns an {@link EventEmitter} for the given channel
* To publish methods to that channel just call next on the returned emitter
*/
to(channel: string): EventEmitter<any>;
}

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* All channels used by angular's WebWorker components are listed here.
* You should not use these channels in your application code.
*/
export const RENDERER_2_CHANNEL = 'v2.ng-Renderer';
export const EVENT_2_CHANNEL = 'v2.ng-Events';
export const ROUTER_CHANNEL = 'ng-Router';

View File

@ -0,0 +1,152 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, Injectable, NgZone} from '@angular/core';
import {MessageBus, MessageBusSink, MessageBusSource} from './message_bus';
// TODO(jteplitz602) Replace this with the definition in lib.webworker.d.ts(#3492)
export interface PostMessageTarget {
postMessage: (message: any, transfer?: [ArrayBuffer]) => void;
}
export class PostMessageBusSink implements MessageBusSink {
private _zone: NgZone;
private _channels: {[key: string]: _Channel} = {};
private _messageBuffer: Array<Object> = [];
constructor(private _postMessageTarget: PostMessageTarget) {}
attachToZone(zone: NgZone): void {
this._zone = zone;
this._zone.runOutsideAngular(
() => { this._zone.onStable.subscribe({next: () => { this._handleOnEventDone(); }}); });
}
initChannel(channel: string, runInZone: boolean = true): void {
if (this._channels.hasOwnProperty(channel)) {
throw new Error(`${channel} has already been initialized`);
}
const emitter = new EventEmitter(false);
const channelInfo = new _Channel(emitter, runInZone);
this._channels[channel] = channelInfo;
emitter.subscribe((data: Object) => {
const message = {channel: channel, message: data};
if (runInZone) {
this._messageBuffer.push(message);
} else {
this._sendMessages([message]);
}
});
}
to(channel: string): EventEmitter<any> {
if (this._channels.hasOwnProperty(channel)) {
return this._channels[channel].emitter;
} else {
throw new Error(`${channel} is not set up. Did you forget to call initChannel?`);
}
}
private _handleOnEventDone() {
if (this._messageBuffer.length > 0) {
this._sendMessages(this._messageBuffer);
this._messageBuffer = [];
}
}
private _sendMessages(messages: Array<Object>) { this._postMessageTarget.postMessage(messages); }
}
export class PostMessageBusSource implements MessageBusSource {
private _zone: NgZone;
private _channels: {[key: string]: _Channel} = {};
constructor(eventTarget?: EventTarget) {
if (eventTarget) {
eventTarget.addEventListener('message', (ev: MessageEvent) => this._handleMessages(ev));
} else {
// if no eventTarget is given we assume we're in a WebWorker and listen on the global scope
const workerScope = <EventTarget>self;
workerScope.addEventListener('message', (ev: MessageEvent) => this._handleMessages(ev));
}
}
attachToZone(zone: NgZone) { this._zone = zone; }
initChannel(channel: string, runInZone: boolean = true) {
if (this._channels.hasOwnProperty(channel)) {
throw new Error(`${channel} has already been initialized`);
}
const emitter = new EventEmitter(false);
const channelInfo = new _Channel(emitter, runInZone);
this._channels[channel] = channelInfo;
}
from(channel: string): EventEmitter<any> {
if (this._channels.hasOwnProperty(channel)) {
return this._channels[channel].emitter;
} else {
throw new Error(`${channel} is not set up. Did you forget to call initChannel?`);
}
}
private _handleMessages(ev: MessageEvent): void {
const messages = ev.data;
for (let i = 0; i < messages.length; i++) {
this._handleMessage(messages[i]);
}
}
private _handleMessage(data: any): void {
const channel = data.channel;
if (this._channels.hasOwnProperty(channel)) {
const channelInfo = this._channels[channel];
if (channelInfo.runInZone) {
this._zone.run(() => { channelInfo.emitter.emit(data.message); });
} else {
channelInfo.emitter.emit(data.message);
}
}
}
}
/**
* A TypeScript implementation of {@link MessageBus} for communicating via JavaScript's
* postMessage API.
*/
@Injectable()
export class PostMessageBus implements MessageBus {
constructor(public sink: PostMessageBusSink, public source: PostMessageBusSource) {}
attachToZone(zone: NgZone): void {
this.source.attachToZone(zone);
this.sink.attachToZone(zone);
}
initChannel(channel: string, runInZone: boolean = true): void {
this.source.initChannel(channel, runInZone);
this.sink.initChannel(channel, runInZone);
}
from(channel: string): EventEmitter<any> { return this.source.from(channel); }
to(channel: string): EventEmitter<any> { return this.sink.to(channel); }
}
/**
* Helper class that wraps a channel's {@link EventEmitter} and
* keeps track of if it should run in the zone.
*/
class _Channel {
constructor(public emitter: EventEmitter<any>, public runInZone: boolean) {}
}

View File

@ -0,0 +1,38 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
@Injectable()
export class RenderStore {
private _nextIndex = 0;
private _lookupById = new Map<number, any>();
private _lookupByObject = new Map<any, number>();
allocateId(): number { return this._nextIndex++; }
store(obj: any, id: number): void {
if (id == null) return;
this._lookupById.set(id, obj);
this._lookupByObject.set(obj, id);
}
remove(obj: any): void {
const index = this._lookupByObject.get(obj);
if (index != null) {
this._lookupByObject.delete(obj);
this._lookupById.delete(index);
}
}
deserialize(id: number): any {
return this._lookupById.has(id) ? this._lookupById.get(id) : null;
}
serialize(obj: any): number { return obj == null ? null : this._lookupByObject.get(obj); }
}

View File

@ -0,0 +1,142 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, RenderComponentType, RendererType2, Type, ɵstringify as stringify} from '@angular/core';
import {RenderStore} from './render_store';
/**
* Any type that does not need to be serialized (string, number, boolean)
*
* @experimental WebWorker support in Angular is currently experimental.
* @deprecated in v4. Use SerializerTypes.PRIMITIVE instead
*/
export const PRIMITIVE = SerializerTypes.PRIMITIVE;
export class LocationType {
constructor(
public href: string, public protocol: string, public host: string, public hostname: string,
public port: string, public pathname: string, public search: string, public hash: string,
public origin: string) {}
}
/**
* @experimental WebWorker support in Angular is currently experimental.
*/
export const enum SerializerTypes {
// RendererType2
RENDERER_TYPE_2,
// Primitive types
PRIMITIVE,
// An object stored in a RenderStore
RENDER_STORE_OBJECT,
}
@Injectable()
export class Serializer {
constructor(private _renderStore: RenderStore) {}
serialize(obj: any, type: Type<any>|SerializerTypes = SerializerTypes.PRIMITIVE): Object {
if (obj == null || type === SerializerTypes.PRIMITIVE) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(v => this.serialize(v, type));
}
if (type === SerializerTypes.RENDER_STORE_OBJECT) {
return this._renderStore.serialize(obj);
}
if (type === RenderComponentType) {
return this._serializeRenderComponentType(obj);
}
if (type === SerializerTypes.RENDERER_TYPE_2) {
return this._serializeRendererType2(obj);
}
if (type === LocationType) {
return this._serializeLocation(obj);
}
throw new Error(`No serializer for type ${stringify(type)}`);
}
deserialize(map: any, type: Type<any>|SerializerTypes = SerializerTypes.PRIMITIVE, data?: any):
any {
if (map == null || type === SerializerTypes.PRIMITIVE) {
return map;
}
if (Array.isArray(map)) {
return map.map(val => this.deserialize(val, type, data));
}
if (type === SerializerTypes.RENDER_STORE_OBJECT) {
return this._renderStore.deserialize(map);
}
if (type === RenderComponentType) {
return this._deserializeRenderComponentType(map);
}
if (type === SerializerTypes.RENDERER_TYPE_2) {
return this._deserializeRendererType2(map);
}
if (type === LocationType) {
return this._deserializeLocation(map);
}
throw new Error(`No deserializer for type ${stringify(type)}`);
}
private _serializeLocation(loc: LocationType): Object {
return {
'href': loc.href,
'protocol': loc.protocol,
'host': loc.host,
'hostname': loc.hostname,
'port': loc.port,
'pathname': loc.pathname,
'search': loc.search,
'hash': loc.hash,
'origin': loc.origin,
};
}
private _deserializeLocation(loc: {[key: string]: any}): LocationType {
return new LocationType(
loc['href'], loc['protocol'], loc['host'], loc['hostname'], loc['port'], loc['pathname'],
loc['search'], loc['hash'], loc['origin']);
}
private _serializeRenderComponentType(type: RenderComponentType): Object {
return {
'id': type.id,
'templateUrl': type.templateUrl,
'slotCount': type.slotCount,
'encapsulation': this.serialize(type.encapsulation),
'styles': this.serialize(type.styles),
};
}
private _deserializeRenderComponentType(props: {[key: string]: any}): RenderComponentType {
return new RenderComponentType(
props['id'], props['templateUrl'], props['slotCount'],
this.deserialize(props['encapsulation']), this.deserialize(props['styles']), {});
}
private _serializeRendererType2(type: RendererType2): {[key: string]: any} {
return {
'id': type.id,
'encapsulation': this.serialize(type.encapsulation),
'styles': this.serialize(type.styles),
'data': this.serialize(type.data),
};
}
private _deserializeRendererType2(props: {[key: string]: any}): RendererType2 {
return {
id: props['id'],
encapsulation: props['encapsulation'],
styles: this.deserialize(props['styles']),
data: this.deserialize(props['data'])
};
}
}

View File

@ -0,0 +1,110 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, Injectable, Type} from '@angular/core';
import {MessageBus} from '../shared/message_bus';
import {Serializer, SerializerTypes} from '../shared/serializer';
/**
* @experimental WebWorker support in Angular is currently experimental.
*/
export abstract class ServiceMessageBrokerFactory {
/**
* Initializes the given channel and attaches a new {@link ServiceMessageBroker} to it.
*/
abstract createMessageBroker(channel: string, runInZone?: boolean): ServiceMessageBroker;
}
@Injectable()
export class ServiceMessageBrokerFactory_ extends ServiceMessageBrokerFactory {
/** @internal */
_serializer: Serializer;
constructor(private _messageBus: MessageBus, _serializer: Serializer) {
super();
this._serializer = _serializer;
}
createMessageBroker(channel: string, runInZone: boolean = true): ServiceMessageBroker {
this._messageBus.initChannel(channel, runInZone);
return new ServiceMessageBroker_(this._messageBus, this._serializer, channel);
}
}
/**
* Helper class for UIComponents that allows components to register methods.
* If a registered method message is received from the broker on the worker,
* the UIMessageBroker deserializes its arguments and calls the registered method.
* If that method returns a promise, the UIMessageBroker returns the result to the worker.
*
* @experimental WebWorker support in Angular is currently experimental.
*/
export abstract class ServiceMessageBroker {
abstract registerMethod(
methodName: string, signature: Array<Type<any>|SerializerTypes>, method: Function,
returnType?: Type<any>|SerializerTypes): void;
}
export class ServiceMessageBroker_ extends ServiceMessageBroker {
private _sink: EventEmitter<any>;
private _methods = new Map<string, Function>();
constructor(messageBus: MessageBus, private _serializer: Serializer, public channel: string) {
super();
this._sink = messageBus.to(channel);
const source = messageBus.from(channel);
source.subscribe({next: (message: any) => this._handleMessage(message)});
}
registerMethod(
methodName: string, signature: Array<Type<any>|SerializerTypes>,
method: (..._: any[]) => Promise<any>| void, returnType?: Type<any>|SerializerTypes): void {
this._methods.set(methodName, (message: ReceivedMessage) => {
const serializedArgs = message.args;
const numArgs = signature ? signature.length : 0;
const deserializedArgs = new Array(numArgs);
for (let i = 0; i < numArgs; i++) {
const serializedArg = serializedArgs[i];
deserializedArgs[i] = this._serializer.deserialize(serializedArg, signature[i]);
}
const promise = method(...deserializedArgs);
if (returnType && promise) {
this._wrapWebWorkerPromise(message.id, promise, returnType);
}
});
}
private _handleMessage(message: ReceivedMessage): void {
if (this._methods.has(message.method)) {
this._methods.get(message.method)(message);
}
}
private _wrapWebWorkerPromise(id: string, promise: Promise<any>, type: Type<any>|SerializerTypes):
void {
promise.then((result: any) => {
this._sink.emit({
'type': 'result',
'value': this._serializer.serialize(result, type),
'id': id,
});
});
}
}
/**
* @experimental WebWorker support in Angular is currently experimental.
*/
export interface ReceivedMessage {
method: string;
args: any[];
id: string;
type: string;
}

View File

@ -0,0 +1,120 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter} from '@angular/core';
import {Serializer, SerializerTypes} from '../shared/serializer';
import {serializeEventWithTarget, serializeGenericEvent, serializeKeyboardEvent, serializeMouseEvent, serializeTransitionEvent} from './event_serializer';
export class EventDispatcher {
constructor(private _sink: EventEmitter<any>, private _serializer: Serializer) {}
dispatchAnimationEvent(player: any, phaseName: string, element: any): boolean {
this._sink.emit({
'element': this._serializer.serialize(element, SerializerTypes.RENDER_STORE_OBJECT),
'animationPlayer': this._serializer.serialize(player, SerializerTypes.RENDER_STORE_OBJECT),
'phaseName': phaseName,
});
return true;
}
dispatchRenderEvent(element: any, eventTarget: string, eventName: string, event: any): boolean {
let serializedEvent: any;
// TODO (jteplitz602): support custom events #3350
switch (event.type) {
case 'click':
case 'mouseup':
case 'mousedown':
case 'dblclick':
case 'contextmenu':
case 'mouseenter':
case 'mouseleave':
case 'mousemove':
case 'mouseout':
case 'mouseover':
case 'show':
serializedEvent = serializeMouseEvent(event);
break;
case 'keydown':
case 'keypress':
case 'keyup':
serializedEvent = serializeKeyboardEvent(event);
break;
case 'input':
case 'change':
case 'blur':
serializedEvent = serializeEventWithTarget(event);
break;
case 'abort':
case 'afterprint':
case 'beforeprint':
case 'cached':
case 'canplay':
case 'canplaythrough':
case 'chargingchange':
case 'chargingtimechange':
case 'close':
case 'dischargingtimechange':
case 'DOMContentLoaded':
case 'downloading':
case 'durationchange':
case 'emptied':
case 'ended':
case 'error':
case 'fullscreenchange':
case 'fullscreenerror':
case 'invalid':
case 'languagechange':
case 'levelfchange':
case 'loadeddata':
case 'loadedmetadata':
case 'obsolete':
case 'offline':
case 'online':
case 'open':
case 'orientatoinchange':
case 'pause':
case 'pointerlockchange':
case 'pointerlockerror':
case 'play':
case 'playing':
case 'ratechange':
case 'readystatechange':
case 'reset':
case 'scroll':
case 'seeked':
case 'seeking':
case 'stalled':
case 'submit':
case 'success':
case 'suspend':
case 'timeupdate':
case 'updateready':
case 'visibilitychange':
case 'volumechange':
case 'waiting':
serializedEvent = serializeGenericEvent(event);
break;
case 'transitionend':
serializedEvent = serializeTransitionEvent(event);
break;
default:
throw new Error(eventName + ' not supported on WebWorkers');
}
this._sink.emit({
'element': this._serializer.serialize(element, SerializerTypes.RENDER_STORE_OBJECT),
'eventName': eventName,
'eventTarget': eventTarget,
'event': serializedEvent,
});
// TODO(kegluneq): Eventually, we want the user to indicate from the UI side whether the event
// should be canceled, but for now just call `preventDefault` on the original DOM event.
return false;
}
}

View File

@ -0,0 +1,70 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const MOUSE_EVENT_PROPERTIES = [
'altKey', 'button', 'clientX', 'clientY', 'metaKey', 'movementX', 'movementY', 'offsetX',
'offsetY', 'region', 'screenX', 'screenY', 'shiftKey'
];
const KEYBOARD_EVENT_PROPERTIES = [
'altkey', 'charCode', 'code', 'ctrlKey', 'isComposing', 'key', 'keyCode', 'location', 'metaKey',
'repeat', 'shiftKey', 'which'
];
const TRANSITION_EVENT_PROPERTIES = ['propertyName', 'elapsedTime', 'pseudoElement'];
const EVENT_PROPERTIES = ['type', 'bubbles', 'cancelable'];
const NODES_WITH_VALUE = new Set(
['input', 'select', 'option', 'button', 'li', 'meter', 'progress', 'param', 'textarea']);
export function serializeGenericEvent(e: Event): {[key: string]: any} {
return serializeEvent(e, EVENT_PROPERTIES);
}
// TODO(jteplitz602): Allow users to specify the properties they need rather than always
// adding value and files #3374
export function serializeEventWithTarget(e: Event): {[key: string]: any} {
const serializedEvent = serializeEvent(e, EVENT_PROPERTIES);
return addTarget(e, serializedEvent);
}
export function serializeMouseEvent(e: MouseEvent): {[key: string]: any} {
return serializeEvent(e, MOUSE_EVENT_PROPERTIES);
}
export function serializeKeyboardEvent(e: KeyboardEvent): {[key: string]: any} {
const serializedEvent = serializeEvent(e, KEYBOARD_EVENT_PROPERTIES);
return addTarget(e, serializedEvent);
}
export function serializeTransitionEvent(e: TransitionEvent): {[key: string]: any} {
const serializedEvent = serializeEvent(e, TRANSITION_EVENT_PROPERTIES);
return addTarget(e, serializedEvent);
}
// TODO(jteplitz602): #3374. See above.
function addTarget(e: Event, serializedEvent: {[key: string]: any}): {[key: string]: any} {
if (NODES_WITH_VALUE.has((<HTMLElement>e.target).tagName.toLowerCase())) {
const target = <HTMLInputElement>e.target;
serializedEvent['target'] = {'value': target.value};
if (target.files) {
serializedEvent['target']['files'] = target.files;
}
}
return serializedEvent;
}
function serializeEvent(e: any, properties: string[]): {[key: string]: any} {
const serialized: {[k: string]: any} = {};
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
serialized[prop] = e[prop];
}
return serialized;
}

View File

@ -0,0 +1,32 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector, NgZone, PLATFORM_INITIALIZER, Provider} from '@angular/core';
import {ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/platform-browser';
import {MessageBasedPlatformLocation} from './platform_location';
/**
* A list of {@link Provider}s. To use the router in a Worker enabled application you must
* include these providers when setting up the render thread.
* @experimental
*/
export const WORKER_UI_LOCATION_PROVIDERS: Provider[] = [
MessageBasedPlatformLocation, BrowserPlatformLocation,
{provide: PLATFORM_INITIALIZER, useFactory: initUiLocation, multi: true, deps: [Injector]}
];
function initUiLocation(injector: Injector): () => void {
return () => {
const zone = injector.get(NgZone);
zone.runGuarded(() => injector.get(MessageBasedPlatformLocation).start());
};
}

View File

@ -0,0 +1,61 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LocationChangeListener} from '@angular/common';
import {EventEmitter, Injectable} from '@angular/core';
import {ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/platform-browser';
import {MessageBus} from '../shared/message_bus';
import {ROUTER_CHANNEL} from '../shared/messaging_api';
import {LocationType, Serializer, SerializerTypes} from '../shared/serializer';
import {ServiceMessageBroker, ServiceMessageBrokerFactory} from '../shared/service_message_broker';
@Injectable()
export class MessageBasedPlatformLocation {
private _channelSink: EventEmitter<Object>;
private _broker: ServiceMessageBroker;
constructor(
private _brokerFactory: ServiceMessageBrokerFactory,
private _platformLocation: BrowserPlatformLocation, bus: MessageBus,
private _serializer: Serializer) {
this._platformLocation.onPopState(<LocationChangeListener>this._sendUrlChangeEvent.bind(this));
this._platformLocation.onHashChange(
<LocationChangeListener>this._sendUrlChangeEvent.bind(this));
this._broker = this._brokerFactory.createMessageBroker(ROUTER_CHANNEL);
this._channelSink = bus.to(ROUTER_CHANNEL);
}
start(): void {
const P = SerializerTypes.PRIMITIVE;
this._broker.registerMethod('getLocation', null, this._getLocation.bind(this), LocationType);
this._broker.registerMethod('setPathname', [P], this._setPathname.bind(this));
this._broker.registerMethod(
'pushState', [P, P, P], this._platformLocation.pushState.bind(this._platformLocation));
this._broker.registerMethod(
'replaceState', [P, P, P],
this._platformLocation.replaceState.bind(this._platformLocation));
this._broker.registerMethod(
'forward', null, this._platformLocation.forward.bind(this._platformLocation));
this._broker.registerMethod(
'back', null, this._platformLocation.back.bind(this._platformLocation));
}
private _getLocation(): Promise<Location> {
return Promise.resolve(this._platformLocation.location);
}
private _sendUrlChangeEvent(e: Event): void {
this._channelSink.emit({
'event': {'type': e.type},
'location': this._serializer.serialize(this._platformLocation.location, LocationType),
});
}
private _setPathname(pathname: string): void { this._platformLocation.pathname = pathname; }
}

View File

@ -0,0 +1,147 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererType2, RootRenderer} from '@angular/core';
import {MessageBus} from '../shared/message_bus';
import {EVENT_2_CHANNEL, RENDERER_2_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {Serializer, SerializerTypes} from '../shared/serializer';
import {ServiceMessageBroker, ServiceMessageBrokerFactory} from '../shared/service_message_broker';
import {EventDispatcher} from '../ui/event_dispatcher';
@Injectable()
export class MessageBasedRenderer2 {
private _eventDispatcher: EventDispatcher;
constructor(
private _brokerFactory: ServiceMessageBrokerFactory, private _bus: MessageBus,
private _serializer: Serializer, private _renderStore: RenderStore,
private _rendererFactory: RendererFactory2) {}
start(): void {
const broker = this._brokerFactory.createMessageBroker(RENDERER_2_CHANNEL);
this._bus.initChannel(EVENT_2_CHANNEL);
this._eventDispatcher = new EventDispatcher(this._bus.to(EVENT_2_CHANNEL), this._serializer);
const [RSO, P, CRT] = [
SerializerTypes.RENDER_STORE_OBJECT,
SerializerTypes.PRIMITIVE,
SerializerTypes.RENDERER_TYPE_2,
];
const methods: any[][] = [
['createRenderer', this.createRenderer, RSO, CRT, P],
['createElement', this.createElement, RSO, P, P, P],
['createComment', this.createComment, RSO, P, P], ['createText', this.createText, RSO, P, P],
['appendChild', this.appendChild, RSO, RSO, RSO],
['insertBefore', this.insertBefore, RSO, RSO, RSO, RSO],
['removeChild', this.removeChild, RSO, RSO, RSO],
['selectRootElement', this.selectRootElement, RSO, P, P],
['parentNode', this.parentNode, RSO, RSO, P], ['nextSibling', this.nextSibling, RSO, RSO, P],
['setAttribute', this.setAttribute, RSO, RSO, P, P, P],
['removeAttribute', this.removeAttribute, RSO, RSO, P, P],
['addClass', this.addClass, RSO, RSO, P], ['removeClass', this.removeClass, RSO, RSO, P],
['setStyle', this.setStyle, RSO, RSO, P, P, P, P],
['removeStyle', this.removeStyle, RSO, RSO, P, P],
['setProperty', this.setProperty, RSO, RSO, P, P], ['setValue', this.setValue, RSO, RSO, P],
['listen', this.listen, RSO, RSO, P, P, P], ['unlisten', this.unlisten, RSO, RSO],
['destroy', this.destroy, RSO], ['destroyNode', this.destroyNode, RSO, P]
];
methods.forEach(([name, method, ...argTypes]: any[]) => {
broker.registerMethod(name, argTypes, method.bind(this));
});
}
private destroy(r: Renderer2) { r.destroy(); }
private destroyNode(r: Renderer2, node: any) {
if (r.destroyNode) {
r.destroyNode(node);
}
this._renderStore.remove(node);
}
private createRenderer(el: any, type: RendererType2, id: number) {
this._renderStore.store(this._rendererFactory.createRenderer(el, type), id);
}
private createElement(r: Renderer2, name: string, namespace: string, id: number) {
this._renderStore.store(r.createElement(name, namespace), id);
}
private createComment(r: Renderer2, value: string, id: number) {
this._renderStore.store(r.createComment(value), id);
}
private createText(r: Renderer2, value: string, id: number) {
this._renderStore.store(r.createText(value), id);
}
private appendChild(r: Renderer2, parent: any, child: any) { r.appendChild(parent, child); }
private insertBefore(r: Renderer2, parent: any, child: any, ref: any) {
r.insertBefore(parent, child, ref);
}
private removeChild(r: Renderer2, parent: any, child: any) { r.removeChild(parent, child); }
private selectRootElement(r: Renderer2, selector: string, id: number) {
this._renderStore.store(r.selectRootElement(selector), id);
}
private parentNode(r: Renderer2, node: any, id: number) {
this._renderStore.store(r.parentNode(node), id);
}
private nextSibling(r: Renderer2, node: any, id: number) {
this._renderStore.store(r.nextSibling(node), id);
}
private setAttribute(r: Renderer2, el: any, name: string, value: string, namespace: string) {
r.setAttribute(el, name, value, namespace);
}
private removeAttribute(r: Renderer2, el: any, name: string, namespace: string) {
r.removeAttribute(el, name, namespace);
}
private addClass(r: Renderer2, el: any, name: string) { r.addClass(el, name); }
private removeClass(r: Renderer2, el: any, name: string) { r.removeClass(el, name); }
private setStyle(
r: Renderer2, el: any, style: string, value: any, hasVendorPrefix: boolean,
hasImportant: boolean) {
r.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
private removeStyle(r: Renderer2, el: any, style: string, hasVendorPrefix: boolean) {
r.removeStyle(el, style, hasVendorPrefix);
}
private setProperty(r: Renderer2, el: any, name: string, value: any) {
r.setProperty(el, name, value);
}
private setValue(r: Renderer2, node: any, value: string) { r.setValue(node, value); }
private listen(r: Renderer2, el: any, elName: string, eventName: string, unlistenId: number) {
const listener = (event: any) => {
return this._eventDispatcher.dispatchRenderEvent(el, elName, eventName, event);
};
const unlisten = r.listen(el || elName, eventName, listener);
this._renderStore.store(unlisten, unlistenId);
}
private unlisten(r: Renderer2, unlisten: () => boolean) { unlisten(); }
}

View File

@ -0,0 +1,37 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LOCATION_INITIALIZED, PlatformLocation} from '@angular/common';
import {APP_INITIALIZER, InjectionToken, NgZone} from '@angular/core';
import {WebWorkerPlatformLocation} from './platform_location';
/**
* Those providers should be added when the router is used in a worker context in addition to the
* {@link ROUTER_PROVIDERS} and after them.
* @experimental
*/
export const WORKER_APP_LOCATION_PROVIDERS = [
{provide: PlatformLocation, useClass: WebWorkerPlatformLocation}, {
provide: APP_INITIALIZER,
useFactory: appInitFnFactory,
multi: true,
deps: [PlatformLocation, NgZone]
},
{provide: LOCATION_INITIALIZED, useFactory: locationInitialized, deps: [PlatformLocation]}
];
export function locationInitialized(platformLocation: WebWorkerPlatformLocation) {
return platformLocation.initialized;
}
export function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () =>
Promise<boolean> {
return () => zone.runGuarded(() => platformLocation.init());
}

View File

@ -0,0 +1,124 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LocationChangeListener, PlatformLocation} from '@angular/common';
import {EventEmitter, Injectable} from '@angular/core';
import {ClientMessageBroker, ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_message_broker';
import {MessageBus} from '../shared/message_bus';
import {ROUTER_CHANNEL} from '../shared/messaging_api';
import {LocationType, Serializer, SerializerTypes} from '../shared/serializer';
@Injectable()
export class WebWorkerPlatformLocation extends PlatformLocation {
private _broker: ClientMessageBroker;
private _popStateListeners: Array<Function> = [];
private _hashChangeListeners: Array<Function> = [];
private _location: LocationType = null;
private _channelSource: EventEmitter<Object>;
public initialized: Promise<any>;
private initializedResolve: () => void;
constructor(
brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) {
super();
this._broker = brokerFactory.createMessageBroker(ROUTER_CHANNEL);
this._channelSource = bus.from(ROUTER_CHANNEL);
this._channelSource.subscribe({
next: (msg: {[key: string]: any}) => {
let listeners: Array<Function> = null;
if (msg.hasOwnProperty('event')) {
const type: string = msg['event']['type'];
if (type === 'popstate') {
listeners = this._popStateListeners;
} else if (type === 'hashchange') {
listeners = this._hashChangeListeners;
}
if (listeners) {
// There was a popState or hashChange event, so the location object thas been updated
this._location = this._serializer.deserialize(msg['location'], LocationType);
listeners.forEach((fn: Function) => fn(msg['event']));
}
}
}
});
this.initialized = new Promise(res => this.initializedResolve = res);
}
/** @internal **/
init(): Promise<boolean> {
const args: UiArguments = new UiArguments('getLocation');
return this._broker.runOnService(args, LocationType)
.then(
(val: LocationType) => {
this._location = val;
this.initializedResolve();
return true;
},
err => { throw new Error(err); });
}
getBaseHrefFromDOM(): string {
throw new Error(
'Attempt to get base href from DOM from WebWorker. You must either provide a value for the APP_BASE_HREF token through DI or use the hash location strategy.');
}
onPopState(fn: LocationChangeListener): void { this._popStateListeners.push(fn); }
onHashChange(fn: LocationChangeListener): void { this._hashChangeListeners.push(fn); }
get pathname(): string { return this._location ? this._location.pathname : null; }
get search(): string { return this._location ? this._location.search : null; }
get hash(): string { return this._location ? this._location.hash : null; }
set pathname(newPath: string) {
if (this._location === null) {
throw new Error('Attempt to set pathname before value is obtained from UI');
}
this._location.pathname = newPath;
const fnArgs = [new FnArg(newPath, SerializerTypes.PRIMITIVE)];
const args = new UiArguments('setPathname', fnArgs);
this._broker.runOnService(args, null);
}
pushState(state: any, title: string, url: string): void {
const fnArgs = [
new FnArg(state, SerializerTypes.PRIMITIVE),
new FnArg(title, SerializerTypes.PRIMITIVE),
new FnArg(url, SerializerTypes.PRIMITIVE),
];
const args = new UiArguments('pushState', fnArgs);
this._broker.runOnService(args, null);
}
replaceState(state: any, title: string, url: string): void {
const fnArgs = [
new FnArg(state, SerializerTypes.PRIMITIVE),
new FnArg(title, SerializerTypes.PRIMITIVE),
new FnArg(url, SerializerTypes.PRIMITIVE),
];
const args = new UiArguments('replaceState', fnArgs);
this._broker.runOnService(args, null);
}
forward(): void {
const args = new UiArguments('forward');
this._broker.runOnService(args, null);
}
back(): void {
const args = new UiArguments('back');
this._broker.runOnService(args, null);
}
}

View File

@ -0,0 +1,315 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererType2, RootRenderer, ViewEncapsulation} from '@angular/core';
import {ClientMessageBroker, ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_message_broker';
import {MessageBus} from '../shared/message_bus';
import {EVENT_2_CHANNEL, RENDERER_2_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {Serializer, SerializerTypes} from '../shared/serializer';
export class NamedEventEmitter {
private _listeners: Map<string, Function[]>;
listen(eventName: string, callback: Function) { this._getListeners(eventName).push(callback); }
unlisten(eventName: string, listener: Function) {
const listeners = this._getListeners(eventName);
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
dispatchEvent(eventName: string, event: any) {
const listeners = this._getListeners(eventName);
for (let i = 0; i < listeners.length; i++) {
listeners[i](event);
}
}
private _getListeners(eventName: string): Function[] {
if (!this._listeners) {
this._listeners = new Map<string, Function[]>();
}
let listeners = this._listeners.get(eventName);
if (!listeners) {
listeners = [];
this._listeners.set(eventName, listeners);
}
return listeners;
}
}
function eventNameWithTarget(target: string, eventName: string): string {
return `${target}:${eventName}`;
}
@Injectable()
export class WebWorkerRendererFactory2 implements RendererFactory2 {
globalEvents = new NamedEventEmitter();
private _messageBroker: ClientMessageBroker;
constructor(
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
private _serializer: Serializer, public renderStore: RenderStore) {
this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_2_CHANNEL);
bus.initChannel(EVENT_2_CHANNEL);
const source = bus.from(EVENT_2_CHANNEL);
source.subscribe({next: (message: any) => this._dispatchEvent(message)});
}
createRenderer(element: any, type: RendererType2): Renderer2 {
const renderer = new WebWorkerRenderer2(this);
const id = this.renderStore.allocateId();
this.renderStore.store(renderer, id);
this.callUI('createRenderer', [
new FnArg(element, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(type, SerializerTypes.RENDERER_TYPE_2),
new FnArg(renderer, SerializerTypes.RENDER_STORE_OBJECT),
]);
return renderer;
}
callUI(fnName: string, fnArgs: FnArg[]) {
const args = new UiArguments(fnName, fnArgs);
this._messageBroker.runOnService(args, null);
}
allocateNode(): WebWorkerRenderNode {
const result = new WebWorkerRenderNode();
const id = this.renderStore.allocateId();
this.renderStore.store(result, id);
return result;
}
freeNode(node: any) { this.renderStore.remove(node); }
allocateId(): number { return this.renderStore.allocateId(); }
private _dispatchEvent(message: {[key: string]: any}): void {
const element: WebWorkerRenderNode =
this._serializer.deserialize(message['element'], SerializerTypes.RENDER_STORE_OBJECT);
const eventName = message['eventName'];
const target = message['eventTarget'];
const event = message['event'];
if (target) {
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
} else {
element.events.dispatchEvent(eventName, event);
}
}
}
export class WebWorkerRenderer2 implements Renderer2 {
data: {[key: string]: any} = Object.create(null);
constructor(private _rendererFactory: WebWorkerRendererFactory2) {}
private asFnArg = new FnArg(this, SerializerTypes.RENDER_STORE_OBJECT);
destroy(): void { this.callUIWithRenderer('destroy'); }
destroyNode(node: any) {
this.callUIWithRenderer('destroyNode', [new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT)]);
this._rendererFactory.freeNode(node);
}
createElement(name: string, namespace?: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createElement', [
new FnArg(name),
new FnArg(namespace),
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
]);
return node;
}
createComment(value: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createComment', [
new FnArg(value),
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
]);
return node;
}
createText(value: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createText', [
new FnArg(value),
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
]);
return node;
}
appendChild(parent: any, newChild: any): void {
this.callUIWithRenderer('appendChild', [
new FnArg(parent, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(newChild, SerializerTypes.RENDER_STORE_OBJECT),
]);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
if (!parent) {
return;
}
this.callUIWithRenderer('insertBefore', [
new FnArg(parent, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(newChild, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(refChild, SerializerTypes.RENDER_STORE_OBJECT),
]);
}
removeChild(parent: any, oldChild: any): void {
this.callUIWithRenderer('removeChild', [
new FnArg(parent, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(oldChild, SerializerTypes.RENDER_STORE_OBJECT),
]);
}
selectRootElement(selectorOrNode: string|any): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('selectRootElement', [
new FnArg(selectorOrNode),
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
]);
return node;
}
parentNode(node: any): any {
const res = this._rendererFactory.allocateNode();
this.callUIWithRenderer('parentNode', [
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(res, SerializerTypes.RENDER_STORE_OBJECT),
]);
return res;
}
nextSibling(node: any): any {
const res = this._rendererFactory.allocateNode();
this.callUIWithRenderer('nextSibling', [
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(res, SerializerTypes.RENDER_STORE_OBJECT),
]);
return res;
}
setAttribute(el: any, name: string, value: string, namespace?: string): void {
this.callUIWithRenderer('setAttribute', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(name),
new FnArg(value),
new FnArg(namespace),
]);
}
removeAttribute(el: any, name: string, namespace?: string): void {
this.callUIWithRenderer('removeAttribute', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(name),
new FnArg(namespace),
]);
}
addClass(el: any, name: string): void {
this.callUIWithRenderer('addClass', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(name),
]);
}
removeClass(el: any, name: string): void {
this.callUIWithRenderer('removeClass', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(name),
]);
}
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
this.callUIWithRenderer('setStyle', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(style),
new FnArg(value),
new FnArg(hasVendorPrefix),
new FnArg(hasImportant),
]);
}
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
this.callUIWithRenderer('removeStyle', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(style),
new FnArg(hasVendorPrefix),
]);
}
setProperty(el: any, name: string, value: any): void {
this.callUIWithRenderer('setProperty', [
new FnArg(el, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(name),
new FnArg(value),
]);
}
setValue(node: any, value: string): void {
this.callUIWithRenderer('setValue', [
new FnArg(node, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(value),
]);
}
listen(
target: 'window'|'document'|'body'|any, eventName: string,
listener: (event: any) => boolean): () => void {
const unlistenId = this._rendererFactory.allocateId();
const [targetEl, targetName, fullName]: [any, string, string] = typeof target === 'string' ?
[null, target, `${target}:${eventName}`] :
[target, null, null];
if (fullName) {
this._rendererFactory.globalEvents.listen(fullName, listener);
} else {
targetEl.events.listen(eventName, listener);
}
this.callUIWithRenderer('listen', [
new FnArg(targetEl, SerializerTypes.RENDER_STORE_OBJECT),
new FnArg(targetName),
new FnArg(eventName),
new FnArg(unlistenId),
]);
return () => {
if (fullName) {
this._rendererFactory.globalEvents.unlisten(fullName, listener);
} else {
targetEl.events.unlisten(eventName, listener);
}
this.callUIWithRenderer('unlisten', [new FnArg(unlistenId)]);
};
}
private callUIWithRenderer(fnName: string, fnArgs: FnArg[] = []) {
// always pass the renderer as the first arg
this._rendererFactory.callUI(fnName, [this.asFnArg, ...fnArgs]);
}
}
export class WebWorkerRenderNode { events = new NamedEventEmitter(); }

View File

@ -0,0 +1,166 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ɵDomAdapter as DomAdapter, ɵsetRootDomAdapter as setRootDomAdapter} from '@angular/platform-browser';
/**
* This adapter is required to log error messages.
*
* Note: other methods all throw as the DOM is not accessible directly in web worker context.
*/
export class WorkerDomAdapter extends DomAdapter {
static makeCurrent() { setRootDomAdapter(new WorkerDomAdapter()); }
logError(error: any) {
if (console.error) {
console.error(error);
} else {
// tslint:disable-next-line:no-console
console.log(error);
}
}
// tslint:disable-next-line:no-console
log(error: any) { console.log(error); }
logGroup(error: any) {
if (console.group) {
console.group(error);
this.logError(error);
} else {
// tslint:disable-next-line:no-console
console.log(error);
}
}
logGroupEnd() {
if (console.groupEnd) {
console.groupEnd();
}
}
hasProperty(element: any, name: string): boolean { throw 'not implemented'; }
setProperty(el: Element, name: string, value: any) { throw 'not implemented'; }
getProperty(el: Element, name: string): any { throw 'not implemented'; }
invoke(el: Element, methodName: string, args: any[]): any { throw 'not implemented'; }
get attrToPropMap(): {[key: string]: string} { throw 'not implemented'; }
set attrToPropMap(value: {[key: string]: string}) { throw 'not implemented'; }
parse(templateHtml: string) { throw 'not implemented'; }
querySelector(el: any, selector: string): HTMLElement { throw 'not implemented'; }
querySelectorAll(el: any, selector: string): any[] { throw 'not implemented'; }
on(el: any, evt: any, listener: any) { throw 'not implemented'; }
onAndCancel(el: any, evt: any, listener: any): Function { throw 'not implemented'; }
dispatchEvent(el: any, evt: any) { throw 'not implemented'; }
createMouseEvent(eventType: any): any { throw 'not implemented'; }
createEvent(eventType: string): any { throw 'not implemented'; }
preventDefault(evt: any) { throw 'not implemented'; }
isPrevented(evt: any): boolean { throw 'not implemented'; }
getInnerHTML(el: any): string { throw 'not implemented'; }
getTemplateContent(el: any): any { throw 'not implemented'; }
getOuterHTML(el: any): string { throw 'not implemented'; }
nodeName(node: any): string { throw 'not implemented'; }
nodeValue(node: any): string { throw 'not implemented'; }
type(node: any): string { throw 'not implemented'; }
content(node: any): any { throw 'not implemented'; }
firstChild(el: any): Node { throw 'not implemented'; }
nextSibling(el: any): Node { throw 'not implemented'; }
parentElement(el: any): Node { throw 'not implemented'; }
childNodes(el: any): Node[] { throw 'not implemented'; }
childNodesAsList(el: any): Node[] { throw 'not implemented'; }
clearNodes(el: any) { throw 'not implemented'; }
appendChild(el: any, node: any) { throw 'not implemented'; }
removeChild(el: any, node: any) { throw 'not implemented'; }
replaceChild(el: any, newNode: any, oldNode: any) { throw 'not implemented'; }
remove(el: any): Node { throw 'not implemented'; }
insertBefore(parent: any, el: any, node: any) { throw 'not implemented'; }
insertAllBefore(parent: any, el: any, nodes: any) { throw 'not implemented'; }
insertAfter(parent: any, el: any, node: any) { throw 'not implemented'; }
setInnerHTML(el: any, value: any) { throw 'not implemented'; }
getText(el: any): string { throw 'not implemented'; }
setText(el: any, value: string) { throw 'not implemented'; }
getValue(el: any): string { throw 'not implemented'; }
setValue(el: any, value: string) { throw 'not implemented'; }
getChecked(el: any): boolean { throw 'not implemented'; }
setChecked(el: any, value: boolean) { throw 'not implemented'; }
createComment(text: string): any { throw 'not implemented'; }
createTemplate(html: any): HTMLElement { throw 'not implemented'; }
createElement(tagName: any, doc?: any): HTMLElement { throw 'not implemented'; }
createElementNS(ns: string, tagName: string, doc?: any): Element { throw 'not implemented'; }
createTextNode(text: string, doc?: any): Text { throw 'not implemented'; }
createScriptTag(attrName: string, attrValue: string, doc?: any): HTMLElement {
throw 'not implemented';
}
createStyleElement(css: string, doc?: any): HTMLStyleElement { throw 'not implemented'; }
createShadowRoot(el: any): any { throw 'not implemented'; }
getShadowRoot(el: any): any { throw 'not implemented'; }
getHost(el: any): any { throw 'not implemented'; }
getDistributedNodes(el: any): Node[] { throw 'not implemented'; }
clone(node: Node): Node { throw 'not implemented'; }
getElementsByClassName(element: any, name: string): HTMLElement[] { throw 'not implemented'; }
getElementsByTagName(element: any, name: string): HTMLElement[] { throw 'not implemented'; }
classList(element: any): any[] { throw 'not implemented'; }
addClass(element: any, className: string) { throw 'not implemented'; }
removeClass(element: any, className: string) { throw 'not implemented'; }
hasClass(element: any, className: string): boolean { throw 'not implemented'; }
setStyle(element: any, styleName: string, styleValue: string) { throw 'not implemented'; }
removeStyle(element: any, styleName: string) { throw 'not implemented'; }
getStyle(element: any, styleName: string): string { throw 'not implemented'; }
hasStyle(element: any, styleName: string, styleValue?: string): boolean {
throw 'not implemented';
}
tagName(element: any): string { throw 'not implemented'; }
attributeMap(element: any): Map<string, string> { throw 'not implemented'; }
hasAttribute(element: any, attribute: string): boolean { throw 'not implemented'; }
hasAttributeNS(element: any, ns: string, attribute: string): boolean { throw 'not implemented'; }
getAttribute(element: any, attribute: string): string { throw 'not implemented'; }
getAttributeNS(element: any, ns: string, attribute: string): string { throw 'not implemented'; }
setAttribute(element: any, name: string, value: string) { throw 'not implemented'; }
setAttributeNS(element: any, ns: string, name: string, value: string) { throw 'not implemented'; }
removeAttribute(element: any, attribute: string) { throw 'not implemented'; }
removeAttributeNS(element: any, ns: string, attribute: string) { throw 'not implemented'; }
templateAwareRoot(el: any) { throw 'not implemented'; }
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
getBoundingClientRect(el: any) { throw 'not implemented'; }
getTitle(doc: Document): string { throw 'not implemented'; }
setTitle(doc: Document, newTitle: string) { throw 'not implemented'; }
elementMatches(n: any, selector: string): boolean { throw 'not implemented'; }
isTemplateElement(el: any): boolean { throw 'not implemented'; }
isTextNode(node: any): boolean { throw 'not implemented'; }
isCommentNode(node: any): boolean { throw 'not implemented'; }
isElementNode(node: any): boolean { throw 'not implemented'; }
hasShadowRoot(node: any): boolean { throw 'not implemented'; }
isShadowRoot(node: any): boolean { throw 'not implemented'; }
importIntoDoc(node: Node): Node { throw 'not implemented'; }
adoptNode(node: Node): Node { throw 'not implemented'; }
getHref(element: any): string { throw 'not implemented'; }
getEventKey(event: any): string { throw 'not implemented'; }
resolveAndSetHref(element: any, baseUrl: string, href: string) { throw 'not implemented'; }
supportsDOMEvents(): boolean { throw 'not implemented'; }
supportsNativeShadowDOM(): boolean { throw 'not implemented'; }
getGlobalEventTarget(doc: Document, target: string): any { throw 'not implemented'; }
getHistory(): History { throw 'not implemented'; }
getLocation(): Location { throw 'not implemented'; }
getBaseHref(doc: Document): string { throw 'not implemented'; }
resetBaseElement(): void { throw 'not implemented'; }
getUserAgent(): string { throw 'not implemented'; }
setData(element: any, name: string, value: string) { throw 'not implemented'; }
getComputedStyle(element: any): any { throw 'not implemented'; }
getData(element: any, name: string): string { throw 'not implemented'; }
setGlobalVar(name: string, value: any) { throw 'not implemented'; }
performanceNow(): number { throw 'not implemented'; }
getAnimationPrefix(): string { throw 'not implemented'; }
getTransitionEnd(): string { throw 'not implemented'; }
supportsAnimation(): boolean { throw 'not implemented'; }
supportsWebAnimation(): boolean { throw 'not implemented'; }
supportsCookies(): boolean { return false; }
getCookie(name: string): string { throw 'not implemented'; }
setCookie(name: string, value: string) { throw 'not implemented'; }
}

View File

@ -0,0 +1,80 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CommonModule, ɵPLATFORM_WORKER_APP_ID as PLATFORM_WORKER_APP_ID} from '@angular/common';
import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PLATFORM_ID, PlatformRef, Provider, RendererFactory2, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
import {DOCUMENT, ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS} from '@angular/platform-browser';
import {ON_WEB_WORKER} from './web_workers/shared/api';
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
import {MessageBus} from './web_workers/shared/message_bus';
import {PostMessageBus, PostMessageBusSink, PostMessageBusSource} from './web_workers/shared/post_message_bus';
import {RenderStore} from './web_workers/shared/render_store';
import {Serializer} from './web_workers/shared/serializer';
import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_workers/shared/service_message_broker';
import {WebWorkerRendererFactory2} from './web_workers/worker/renderer';
import {WorkerDomAdapter} from './web_workers/worker/worker_adapter';
/**
* @experimental
*/
export const platformWorkerApp = createPlatformFactory(
platformCore, 'workerApp', [{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_APP_ID}]);
export function errorHandler(): ErrorHandler {
return new ErrorHandler();
}
// TODO(jteplitz602) remove this and compile with lib.webworker.d.ts (#3492)
const _postMessage = {
postMessage: (message: any, transferrables?: [ArrayBuffer]) => {
(<any>postMessage)(message, transferrables);
}
};
export function createMessageBus(zone: NgZone): MessageBus {
const sink = new PostMessageBusSink(_postMessage);
const source = new PostMessageBusSource();
const bus = new PostMessageBus(sink, source);
bus.attachToZone(zone);
return bus;
}
export function setupWebWorker(): void {
WorkerDomAdapter.makeCurrent();
}
/**
* The ng module for the worker app side.
*
* @experimental
*/
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS,
Serializer,
{provide: DOCUMENT, useValue: null},
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
WebWorkerRendererFactory2,
{provide: RendererFactory2, useExisting: WebWorkerRendererFactory2},
{provide: ON_WEB_WORKER, useValue: true},
RenderStore,
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{provide: MessageBus, useFactory: createMessageBus, deps: [NgZone]},
{provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true},
],
exports: [
CommonModule,
ApplicationModule,
]
})
export class WorkerAppModule {
}

View File

@ -0,0 +1,152 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CommonModule, ɵPLATFORM_WORKER_UI_ID as PLATFORM_WORKER_UI_ID} from '@angular/common';
import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, Testability, createPlatformFactory, isDevMode, platformCore, ɵAPP_ID_RANDOM_PROVIDER as APP_ID_RANDOM_PROVIDER} from '@angular/core';
import {DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig, ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS, ɵBrowserDomAdapter as BrowserDomAdapter, ɵBrowserGetTestability as BrowserGetTestability, ɵDomEventsPlugin as DomEventsPlugin, ɵDomRendererFactory2 as DomRendererFactory2, ɵDomSharedStylesHost as DomSharedStylesHost, ɵHammerGesturesPlugin as HammerGesturesPlugin, ɵKeyEventsPlugin as KeyEventsPlugin, ɵSharedStylesHost as SharedStylesHost, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {ON_WEB_WORKER} from './web_workers/shared/api';
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
import {MessageBus} from './web_workers/shared/message_bus';
import {PostMessageBus, PostMessageBusSink, PostMessageBusSource} from './web_workers/shared/post_message_bus';
import {RenderStore} from './web_workers/shared/render_store';
import {Serializer} from './web_workers/shared/serializer';
import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_workers/shared/service_message_broker';
import {MessageBasedRenderer2} from './web_workers/ui/renderer';
/**
* Wrapper class that exposes the Worker
* and underlying {@link MessageBus} for lower level message passing.
*
* @experimental WebWorker support is currently experimental.
*/
@Injectable()
export class WebWorkerInstance {
public worker: Worker;
public bus: MessageBus;
/** @internal */
public init(worker: Worker, bus: MessageBus) {
this.worker = worker;
this.bus = bus;
}
}
/**
* @experimental WebWorker support is currently experimental.
*/
export const WORKER_SCRIPT = new InjectionToken<string>('WebWorkerScript');
/**
* A multi-provider used to automatically call the `start()` method after the service is
* created.
*
* @experimental WebWorker support is currently experimental.
*/
export const WORKER_UI_STARTABLE_MESSAGING_SERVICE =
new InjectionToken<({start: () => void})[]>('WorkerRenderStartableMsgService');
export const _WORKER_UI_PLATFORM_PROVIDERS: Provider[] = [
{provide: NgZone, useFactory: createNgZone, deps: []},
MessageBasedRenderer2,
{provide: WORKER_UI_STARTABLE_MESSAGING_SERVICE, useExisting: MessageBasedRenderer2, multi: true},
BROWSER_SANITIZATION_PROVIDERS,
{provide: ErrorHandler, useFactory: _exceptionHandler, deps: []},
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(jteplitz602): Investigate if we definitely need EVENT_MANAGER on the render thread
// #5298
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
APP_ID_RANDOM_PROVIDER,
DomRendererFactory2,
{provide: RendererFactory2, useExisting: DomRendererFactory2},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
Serializer,
{provide: ON_WEB_WORKER, useValue: false},
RenderStore,
DomSharedStylesHost,
Testability,
EventManager,
WebWorkerInstance,
{
provide: PLATFORM_INITIALIZER,
useFactory: initWebWorkerRenderPlatform,
multi: true,
deps: [Injector]
},
{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_UI_ID},
{provide: MessageBus, useFactory: messageBusFactory, deps: [WebWorkerInstance]},
];
function initializeGenericWorkerRenderer(injector: Injector) {
const bus = injector.get(MessageBus);
const zone = injector.get(NgZone);
bus.attachToZone(zone);
// initialize message services after the bus has been created
const services = injector.get(WORKER_UI_STARTABLE_MESSAGING_SERVICE);
zone.runGuarded(() => { services.forEach((svc: any) => { svc.start(); }); });
}
function messageBusFactory(instance: WebWorkerInstance): MessageBus {
return instance.bus;
}
function initWebWorkerRenderPlatform(injector: Injector): () => void {
return () => {
BrowserDomAdapter.makeCurrent();
BrowserGetTestability.init();
let scriptUri: string;
try {
scriptUri = injector.get(WORKER_SCRIPT);
} catch (e) {
throw new Error(
'You must provide your WebWorker\'s initialization script with the WORKER_SCRIPT token');
}
const instance = injector.get(WebWorkerInstance);
spawnWebWorker(scriptUri, instance);
initializeGenericWorkerRenderer(injector);
};
}
/**
* @experimental WebWorker support is currently experimental.
*/
export const platformWorkerUi =
createPlatformFactory(platformCore, 'workerUi', _WORKER_UI_PLATFORM_PROVIDERS);
function _exceptionHandler(): ErrorHandler {
return new ErrorHandler();
}
function _document(): any {
return document;
}
function createNgZone(): NgZone {
return new NgZone({enableLongStackTrace: isDevMode()});
}
/**
* Spawns a new class and initializes the WebWorkerInstance
*/
function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
const webWorker: Worker = new Worker(uri);
const sink = new PostMessageBusSink(webWorker);
const source = new PostMessageBusSource(webWorker);
const bus = new PostMessageBus(sink, source);
instance.init(webWorker, bus);
}

View File

@ -0,0 +1,160 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgZone} from '@angular/core';
import {withModule} from '@angular/core/testing/test_bed';
import {AsyncTestCompleter, MockNgZone, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {MessageBus} from '@angular/platform-webworker/src/web_workers/shared/message_bus';
import {createConnectedMessageBus} from './message_bus_util';
export function main() {
/**
* Tests the PostMessageBus
*/
describe('MessageBus', () => {
let bus: MessageBus;
beforeEach(() => { bus = createConnectedMessageBus(); });
it('should pass messages in the same channel from sink to source',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const CHANNEL = 'CHANNEL 1';
const MESSAGE = 'Test message';
bus.initChannel(CHANNEL, false);
const fromEmitter = bus.from(CHANNEL);
fromEmitter.subscribe({
next: (message: any) => {
expect(message).toEqual(MESSAGE);
async.done();
}
});
const toEmitter = bus.to(CHANNEL);
toEmitter.emit(MESSAGE);
}));
it('should broadcast', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const CHANNEL = 'CHANNEL 1';
const MESSAGE = 'TESTING';
const NUM_LISTENERS = 2;
bus.initChannel(CHANNEL, false);
let callCount = 0;
const emitHandler = (message: any) => {
expect(message).toEqual(MESSAGE);
callCount++;
if (callCount == NUM_LISTENERS) {
async.done();
}
};
for (let i = 0; i < NUM_LISTENERS; i++) {
const emitter = bus.from(CHANNEL);
emitter.subscribe({next: emitHandler});
}
const toEmitter = bus.to(CHANNEL);
toEmitter.emit(MESSAGE);
}));
it('should keep channels independent',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const CHANNEL_ONE = 'CHANNEL 1';
const CHANNEL_TWO = 'CHANNEL 2';
const MESSAGE_ONE = 'This is a message on CHANNEL 1';
const MESSAGE_TWO = 'This is a message on CHANNEL 2';
let callCount = 0;
bus.initChannel(CHANNEL_ONE, false);
bus.initChannel(CHANNEL_TWO, false);
const firstFromEmitter = bus.from(CHANNEL_ONE);
firstFromEmitter.subscribe({
next: (message: any) => {
expect(message).toEqual(MESSAGE_ONE);
callCount++;
if (callCount == 2) {
async.done();
}
}
});
const secondFromEmitter = bus.from(CHANNEL_TWO);
secondFromEmitter.subscribe({
next: (message: any) => {
expect(message).toEqual(MESSAGE_TWO);
callCount++;
if (callCount == 2) {
async.done();
}
}
});
const firstToEmitter = bus.to(CHANNEL_ONE);
firstToEmitter.emit(MESSAGE_ONE);
const secondToEmitter = bus.to(CHANNEL_TWO);
secondToEmitter.emit(MESSAGE_TWO);
}));
});
describe('PostMessageBusSink', () => {
let bus: MessageBus;
const CHANNEL = 'Test Channel';
function setup(runInZone: boolean, zone: NgZone) {
bus.attachToZone(zone);
bus.initChannel(CHANNEL, runInZone);
}
/**
* Flushes pending messages and then runs the given function.
*/
// TODO(mlaval): timeout is fragile, test to be rewritten
function flushMessages(fn: () => void) { setTimeout(fn, 50); }
it('should buffer messages and wait for the zone to exit before sending',
withModule({providers: [{provide: NgZone, useClass: MockNgZone}]})
.inject(
[AsyncTestCompleter, NgZone],
(async: AsyncTestCompleter, zone: MockNgZone) => {
bus = createConnectedMessageBus();
setup(true, zone);
let wasCalled = false;
bus.from(CHANNEL).subscribe({next: (message: any) => { wasCalled = true; }});
bus.to(CHANNEL).emit('hi');
flushMessages(() => {
expect(wasCalled).toBeFalsy();
zone.simulateZoneExit();
flushMessages(() => {
expect(wasCalled).toBeTruthy();
async.done();
});
});
}),
500);
it('should send messages immediatly when run outside the zone',
inject([AsyncTestCompleter, NgZone], (async: AsyncTestCompleter, zone: MockNgZone) => {
bus = createConnectedMessageBus();
setup(false, zone);
let wasCalled = false;
bus.from(CHANNEL).subscribe({next: (message: any) => { wasCalled = true; }});
bus.to(CHANNEL).emit('hi');
flushMessages(() => {
expect(wasCalled).toBeTruthy();
async.done();
});
}), 10000);
});
}

View File

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MessageBus} from '@angular/platform-webworker/src/web_workers/shared/message_bus';
import {PostMessageBus, PostMessageBusSink, PostMessageBusSource} from '@angular/platform-webworker/src/web_workers/shared/post_message_bus';
/*
* Returns a PostMessageBus thats sink is connected to its own source.
* Useful for testing the sink and source.
*/
export function createConnectedMessageBus(): MessageBus {
const mockPostMessage = new MockPostMessage();
const source = new PostMessageBusSource(<any>mockPostMessage);
const sink = new PostMessageBusSink(mockPostMessage);
return new PostMessageBus(sink, source);
}
class MockPostMessage {
private _listener: EventListener;
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void {
if (type === 'message') {
this._listener = listener;
}
}
postMessage(data: any, transfer?: [ArrayBuffer]): void { this._listener(<any>{data: data}); }
}

View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter} from '@angular/core';
export class MockEventEmitter<T> extends EventEmitter<T> {
private _nextFns: Function[] = [];
constructor() { super(); }
subscribe(generator: any): any {
this._nextFns.push(generator.next);
return new MockDisposable();
}
emit(value: any) { this._nextFns.forEach(fn => fn(value)); }
}
class MockDisposable {
isUnsubscribed: boolean = false;
unsubscribe(): void {}
}

View File

@ -0,0 +1,36 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {RenderStore} from '@angular/platform-webworker/src/web_workers/shared/render_store';
export function main() {
describe('RenderStoreSpec', () => {
let store: RenderStore;
beforeEach(() => { store = new RenderStore(); });
it('should allocate ids', () => {
expect(store.allocateId()).toBe(0);
expect(store.allocateId()).toBe(1);
});
it('should serialize objects', () => {
const id = store.allocateId();
const obj = 'testObject';
store.store(obj, id);
expect(store.serialize(obj)).toBe(id);
});
it('should deserialize objects', () => {
const id = store.allocateId();
const obj = 'testObject';
store.store(obj, id);
expect(store.deserialize(id)).toBe(obj);
});
});
}

View File

@ -0,0 +1,72 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {ON_WEB_WORKER} from '@angular/platform-webworker/src/web_workers/shared/api';
import {RenderStore} from '@angular/platform-webworker/src/web_workers/shared/render_store';
import {Serializer, SerializerTypes} from '@angular/platform-webworker/src/web_workers/shared/serializer';
import {ServiceMessageBroker_} from '@angular/platform-webworker/src/web_workers/shared/service_message_broker';
import {createPairedMessageBuses} from './web_worker_test_util';
export function main() {
const CHANNEL = 'UIMessageBroker Test Channel';
const TEST_METHOD = 'TEST_METHOD';
const PASSED_ARG_1 = 5;
const PASSED_ARG_2 = 'TEST';
const RESULT = 20;
const ID = 'methodId';
beforeEachProviders(() => [Serializer, {provide: ON_WEB_WORKER, useValue: true}, RenderStore]);
describe('UIMessageBroker', () => {
let messageBuses: any /** TODO #9100 */;
beforeEach(() => {
messageBuses = createPairedMessageBuses();
messageBuses.ui.initChannel(CHANNEL);
messageBuses.worker.initChannel(CHANNEL);
});
it('should call registered method with correct arguments',
inject([Serializer], (serializer: Serializer) => {
const broker = new ServiceMessageBroker_(messageBuses.ui, serializer, CHANNEL);
broker.registerMethod(
TEST_METHOD, [SerializerTypes.PRIMITIVE, SerializerTypes.PRIMITIVE], (arg1, arg2) => {
expect(arg1).toEqual(PASSED_ARG_1);
expect(arg2).toEqual(PASSED_ARG_2);
});
messageBuses.worker.to(CHANNEL).emit({
'method': TEST_METHOD,
'args': [PASSED_ARG_1, PASSED_ARG_2],
});
}));
it('should return promises to the worker', inject([Serializer], (serializer: Serializer) => {
const broker = new ServiceMessageBroker_(messageBuses.ui, serializer, CHANNEL);
broker.registerMethod(TEST_METHOD, [SerializerTypes.PRIMITIVE], (arg1) => {
expect(arg1).toEqual(PASSED_ARG_1);
return new Promise((res, rej) => {
try {
res(RESULT);
} catch (e) {
rej(e);
}
});
});
messageBuses.worker.to(CHANNEL).emit(
{'method': TEST_METHOD, 'id': ID, 'args': [PASSED_ARG_1]});
messageBuses.worker.from(CHANNEL).subscribe({
next: (data: any) => {
expect(data.type).toEqual('result');
expect(data.id).toEqual(ID);
expect(data.value).toEqual(RESULT);
},
});
}));
});
}

View File

@ -0,0 +1,136 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '@angular/core';
import {NgZone} from '@angular/core/src/zone/ng_zone';
import {ClientMessageBroker, ClientMessageBrokerFactory_, UiArguments} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
import {MessageBus, MessageBusSink, MessageBusSource} from '@angular/platform-webworker/src/web_workers/shared/message_bus';
import {SpyMessageBroker} from '../worker/spies';
import {MockEventEmitter} from './mock_event_emitter';
/**
* Returns two MessageBus instances that are attached to each other.
* Such that whatever goes into one's sink comes out the others source.
*/
export function createPairedMessageBuses(): PairedMessageBuses {
const firstChannels: {[key: string]: MockEventEmitter<any>} = {};
const workerMessageBusSink = new MockMessageBusSink(firstChannels);
const uiMessageBusSource = new MockMessageBusSource(firstChannels);
const secondChannels: {[key: string]: MockEventEmitter<any>} = {};
const uiMessageBusSink = new MockMessageBusSink(secondChannels);
const workerMessageBusSource = new MockMessageBusSource(secondChannels);
return new PairedMessageBuses(
new MockMessageBus(uiMessageBusSink, uiMessageBusSource),
new MockMessageBus(workerMessageBusSink, workerMessageBusSource));
}
/**
* Spies on the given {@link SpyMessageBroker} and expects a call with the given methodName
* andvalues.
* If a handler is provided it will be called to handle the request.
* Only intended to be called on a given broker instance once.
*/
export function expectBrokerCall(
broker: SpyMessageBroker, methodName: string, vals?: Array<any>,
handler?: (..._: any[]) => Promise<any>| void): void {
broker.spy('runOnService').and.callFake((args: UiArguments, returnType: Type<any>) => {
expect(args.method).toEqual(methodName);
if (vals != null) {
expect(args.args.length).toEqual(vals.length);
vals.forEach((v, i) => { expect(v).toEqual(args.args[i].value); });
}
let promise: Promise<any>|void = null;
if (handler != null) {
const givenValues = args.args.map((arg) => arg.value);
if (givenValues.length > 0) {
promise = handler(givenValues);
} else {
promise = handler();
}
}
if (promise == null) {
promise = new Promise((res, rej) => {
try {
res();
} catch (e) {
rej(e);
}
});
}
return promise;
});
}
export class PairedMessageBuses {
constructor(public ui: MessageBus, public worker: MessageBus) {}
}
export class MockMessageBusSource implements MessageBusSource {
constructor(private _channels: {[key: string]: MockEventEmitter<any>}) {}
initChannel(channel: string, runInZone = true) {
if (!this._channels.hasOwnProperty(channel)) {
this._channels[channel] = new MockEventEmitter();
}
}
from(channel: string): MockEventEmitter<any> {
if (!this._channels.hasOwnProperty(channel)) {
throw new Error(`${channel} is not set up. Did you forget to call initChannel?`);
}
return this._channels[channel];
}
attachToZone(zone: NgZone) {}
}
export class MockMessageBusSink implements MessageBusSink {
constructor(private _channels: {[key: string]: MockEventEmitter<any>}) {}
initChannel(channel: string, runInZone = true) {
if (!this._channels.hasOwnProperty(channel)) {
this._channels[channel] = new MockEventEmitter();
}
}
to(channel: string): MockEventEmitter<any> {
if (!this._channels.hasOwnProperty(channel)) {
this._channels[channel] = new MockEventEmitter();
}
return this._channels[channel];
}
attachToZone(zone: NgZone) {}
}
/**
* Mock implementation of the {@link MessageBus} for tests.
* Runs syncronously, and does not support running within the zone.
*/
export class MockMessageBus extends MessageBus {
constructor(public sink: MockMessageBusSink, public source: MockMessageBusSource) { super(); }
initChannel(channel: string, runInZone = true) {
this.sink.initChannel(channel, runInZone);
this.source.initChannel(channel, runInZone);
}
to(channel: string): MockEventEmitter<any> { return this.sink.to(channel); }
from(channel: string): MockEventEmitter<any> { return this.source.from(channel); }
attachToZone(zone: NgZone) {}
}
export class MockMessageBrokerFactory extends ClientMessageBrokerFactory_ {
constructor(private _messageBroker: ClientMessageBroker) { super(null, null); }
createMessageBroker(channel: string, runInZone = true) { return this._messageBroker; }
}

View File

@ -0,0 +1,93 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '@angular/core';
import {UiArguments} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
import {MessageBus} from '@angular/platform-webworker/src/web_workers/shared/message_bus';
import {LocationType, SerializerTypes} from '@angular/platform-webworker/src/web_workers/shared/serializer';
import {WebWorkerPlatformLocation} from '@angular/platform-webworker/src/web_workers/worker/platform_location';
import {MockMessageBrokerFactory, createPairedMessageBuses, expectBrokerCall} from '../shared/web_worker_test_util';
import {SpyMessageBroker} from './spies';
export function main() {
describe('WebWorkerPlatformLocation', () => {
let uiBus: MessageBus = null;
let workerBus: MessageBus = null;
let broker: any = null;
const TEST_LOCATION = new LocationType(
'http://www.example.com', 'http', 'example.com', 'example.com', '80', '/', '', '',
'http://www.example.com');
function createWebWorkerPlatformLocation(loc: LocationType): WebWorkerPlatformLocation {
broker.spy('runOnService')
.and.callFake((args: UiArguments, returnType: Type<any>| SerializerTypes) => {
if (args.method === 'getLocation') {
return Promise.resolve(loc);
}
});
const factory = new MockMessageBrokerFactory(broker);
return new WebWorkerPlatformLocation(factory, workerBus, null);
}
function testPushOrReplaceState(pushState: boolean) {
const platformLocation = createWebWorkerPlatformLocation(null);
const TITLE = 'foo';
const URL = 'http://www.example.com/foo';
expectBrokerCall(broker, pushState ? 'pushState' : 'replaceState', [null, TITLE, URL]);
if (pushState) {
platformLocation.pushState(null, TITLE, URL);
} else {
platformLocation.replaceState(null, TITLE, URL);
}
}
beforeEach(() => {
const buses = createPairedMessageBuses();
uiBus = buses.ui;
workerBus = buses.worker;
workerBus.initChannel('ng-Router');
uiBus.initChannel('ng-Router');
broker = new SpyMessageBroker();
});
it('should throw if getBaseHrefFromDOM is called', () => {
const platformLocation = createWebWorkerPlatformLocation(null);
expect(() => platformLocation.getBaseHrefFromDOM()).toThrowError();
});
it('should get location on init', () => {
const platformLocation = createWebWorkerPlatformLocation(null);
expectBrokerCall(broker, 'getLocation');
platformLocation.init();
});
it('should throw if set pathname is called before init finishes', () => {
const platformLocation = createWebWorkerPlatformLocation(null);
platformLocation.init();
expect(() => platformLocation.pathname = 'TEST').toThrowError();
});
it('should send pathname to render thread', done => {
const platformLocation = createWebWorkerPlatformLocation(TEST_LOCATION);
platformLocation.init().then((_) => {
const PATHNAME = '/test';
expectBrokerCall(broker, 'setPathname', [PATHNAME]);
platformLocation.pathname = PATHNAME;
done();
});
});
it('should send pushState to render thread', () => { testPushOrReplaceState(true); });
it('should send replaceState to render thread', () => { testPushOrReplaceState(false); });
});
}

View File

@ -0,0 +1,212 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ComponentRef, Renderer2, RendererFactory2, RendererType2, RootRenderer} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
import {BrowserTestingModule} from '@angular/platform-browser/testing';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
import {expect} from '@angular/platform-browser/testing/matchers';
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from '../../../src/web_workers/shared/client_message_broker';
import {RenderStore} from '../../../src/web_workers/shared/render_store';
import {Serializer} from '../../../src/web_workers/shared/serializer';
import {ServiceMessageBrokerFactory_} from '../../../src/web_workers/shared/service_message_broker';
import {MessageBasedRenderer2} from '../../../src/web_workers/ui/renderer';
import {WebWorkerRendererFactory2} from '../../../src/web_workers/worker/renderer';
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
let lastCreatedRenderer: Renderer2;
export function main() {
describe('Web Worker Renderer v2', () => {
// Don't run on server...
if (!getDOM().supportsDOMEvents()) return;
let uiRenderStore: RenderStore;
let wwRenderStore: RenderStore;
beforeEach(() => {
// UI side
uiRenderStore = new RenderStore();
const uiInjector = new TestBed();
uiInjector.platform = platformBrowserDynamicTesting();
uiInjector.ngModule = BrowserTestingModule;
uiInjector.configureTestingModule({
providers: [
Serializer,
{provide: RenderStore, useValue: uiRenderStore},
DomRendererFactory2,
{provide: RendererFactory2, useExisting: DomRendererFactory2},
]
});
const uiSerializer = uiInjector.get(Serializer);
const domRendererFactory = uiInjector.get(RendererFactory2);
// Worker side
lastCreatedRenderer = null;
wwRenderStore = new RenderStore();
TestBed.configureTestingModule({
declarations: [MyComp2],
providers: [
Serializer,
{provide: RenderStore, useValue: wwRenderStore},
{
provide: RendererFactory2,
useFactory:
(wwSerializer: Serializer) => createWebWorkerRendererFactory2(
wwSerializer, uiSerializer, domRendererFactory, uiRenderStore, wwRenderStore),
deps: [Serializer],
},
],
});
});
function getRenderElement(workerEl: any): any {
const id = wwRenderStore.serialize(workerEl);
return uiRenderStore.deserialize(id);
}
it('should update text nodes', () => {
const fixture =
TestBed.overrideTemplate(MyComp2, '<div>{{ctxProp}}</div>').createComponent(MyComp2);
const renderEl = getRenderElement(fixture.nativeElement);
expect(renderEl).toHaveText('');
fixture.componentInstance.ctxProp = 'Hello World!';
fixture.detectChanges();
expect(renderEl).toHaveText('Hello World!');
});
it('should update any element property/attributes/class/style(s) independent of the compilation on the root element and other elements',
() => {
const fixture =
TestBed.overrideTemplate(MyComp2, '<input [title]="y" style="position:absolute">')
.createComponent(MyComp2);
const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => {
expect(lastCreatedRenderer).not.toEqual(null);
const el = getRenderElement(workerEl);
lastCreatedRenderer.setProperty(workerEl, 'tabIndex', 1);
expect(el.tabIndex).toEqual(1);
lastCreatedRenderer.addClass(workerEl, 'a');
expect(getDOM().hasClass(el, 'a')).toBe(true);
lastCreatedRenderer.removeClass(workerEl, 'a');
expect(getDOM().hasClass(el, 'a')).toBe(false);
lastCreatedRenderer.setStyle(workerEl, 'width', '10px', false, false);
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
lastCreatedRenderer.removeStyle(workerEl, 'width', false);
expect(getDOM().getStyle(el, 'width')).toEqual('');
lastCreatedRenderer.setAttribute(workerEl, 'someattr', 'someValue');
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
};
// root element
checkSetters(fixture.componentRef, fixture.nativeElement);
// nested elements
checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement);
});
it('should update any template comment property/attributes', () => {
const fixture =
TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>')
.createComponent(MyComp2);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
const el = getRenderElement(fixture.nativeElement);
expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
});
it('should add and remove fragments', () => {
const fixture =
TestBed
.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp">hello</ng-container>')
.createComponent(MyComp2);
const rootEl = getRenderElement(fixture.nativeElement);
expect(rootEl).toHaveText('');
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
expect(rootEl).toHaveText('hello');
fixture.componentInstance.ctxBoolProp = false;
fixture.detectChanges();
expect(rootEl).toHaveText('');
});
if (getDOM().supportsDOMEvents()) {
it('should listen to events', () => {
const fixture = TestBed.overrideTemplate(MyComp2, '<input (change)="ctxNumProp = 1">')
.createComponent(MyComp2);
const el = fixture.debugElement.children[0];
dispatchEvent(getRenderElement(el.nativeElement), 'change');
expect(fixture.componentInstance.ctxNumProp).toBe(1);
fixture.destroy();
});
}
});
}
@Component({selector: 'my-comp'})
class MyComp2 {
ctxProp = 'initial value';
ctxNumProp = 0;
ctxBoolProp = false;
}
function createWebWorkerBrokerFactory(
messageBuses: PairedMessageBuses, wwSerializer: Serializer, uiSerializer: Serializer,
domRendererFactory: DomRendererFactory2,
uiRenderStore: RenderStore): ClientMessageBrokerFactory {
const uiMessageBus = messageBuses.ui;
const wwMessageBus = messageBuses.worker;
// set up the worker side
const wwBrokerFactory = new ClientMessageBrokerFactory_(wwMessageBus, wwSerializer);
// set up the ui side
const uiBrokerFactory = new ServiceMessageBrokerFactory_(uiMessageBus, uiSerializer);
const renderer = new MessageBasedRenderer2(
uiBrokerFactory, uiMessageBus, uiSerializer, uiRenderStore, domRendererFactory);
renderer.start();
return wwBrokerFactory;
}
function createWebWorkerRendererFactory2(
workerSerializer: Serializer, uiSerializer: Serializer, domRendererFactory: DomRendererFactory2,
uiRenderStore: RenderStore, workerRenderStore: RenderStore): RendererFactory2 {
const messageBuses = createPairedMessageBuses();
const brokerFactory = createWebWorkerBrokerFactory(
messageBuses, workerSerializer, uiSerializer, domRendererFactory, uiRenderStore);
const rendererFactory =
new RenderFactory(brokerFactory, messageBuses.worker, workerSerializer, workerRenderStore);
return rendererFactory;
}
class RenderFactory extends WebWorkerRendererFactory2 {
createRenderer(element: any, type: RendererType2): Renderer2 {
lastCreatedRenderer = super.createRenderer(element, type);
return lastCreatedRenderer;
}
}

View File

@ -0,0 +1,14 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SpyObject} from '@angular/core/testing/testing_internal';
import {ClientMessageBroker} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
export class SpyMessageBroker extends SpyObject {
constructor() { super(ClientMessageBroker); }
}

View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"stripInternal": true,
"experimentalDecorators": true,
"module": "es2015",
"moduleResolution": "node",
"outDir": "../../../dist/packages-dist/platform-webworker",
"paths": {
"@angular/core": ["../../../dist/packages-dist/core"],
"@angular/common": ["../../../dist/packages-dist/common"],
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"]
},
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
"target": "es2015",
"skipLibCheck": true,
"lib": ["es2015", "dom"],
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
"types": []
},
"files": [
"public_api.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@angular/platform-webworker"
}
}