refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
16
packages/platform-webworker/.babelrc
Normal file
16
packages/platform-webworker/.babelrc
Normal 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"
|
||||
}
|
14
packages/platform-webworker/index.ts
Normal file
14
packages/platform-webworker/index.ts
Normal 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';
|
20
packages/platform-webworker/package.json
Normal file
20
packages/platform-webworker/package.json
Normal 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"
|
||||
}
|
||||
}
|
15
packages/platform-webworker/public_api.ts
Normal file
15
packages/platform-webworker/public_api.ts
Normal 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.
|
37
packages/platform-webworker/src/platform-webworker.ts
Normal file
37
packages/platform-webworker/src/platform-webworker.ts
Normal 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);
|
||||
}
|
19
packages/platform-webworker/src/version.ts
Normal file
19
packages/platform-webworker/src/version.ts
Normal 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');
|
11
packages/platform-webworker/src/web_workers/shared/api.ts
Normal file
11
packages/platform-webworker/src/web_workers/shared/api.ts
Normal 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');
|
@ -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[]) {}
|
||||
}
|
@ -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>;
|
||||
}
|
@ -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';
|
@ -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) {}
|
||||
}
|
@ -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); }
|
||||
}
|
142
packages/platform-webworker/src/web_workers/shared/serializer.ts
Normal file
142
packages/platform-webworker/src/web_workers/shared/serializer.ts
Normal 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'])
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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());
|
||||
};
|
||||
}
|
@ -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; }
|
||||
}
|
147
packages/platform-webworker/src/web_workers/ui/renderer.ts
Normal file
147
packages/platform-webworker/src/web_workers/ui/renderer.ts
Normal 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(); }
|
||||
}
|
@ -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());
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
315
packages/platform-webworker/src/web_workers/worker/renderer.ts
Normal file
315
packages/platform-webworker/src/web_workers/worker/renderer.ts
Normal 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(); }
|
@ -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'; }
|
||||
}
|
80
packages/platform-webworker/src/worker_app.ts
Normal file
80
packages/platform-webworker/src/worker_app.ts
Normal 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 {
|
||||
}
|
152
packages/platform-webworker/src/worker_render.ts
Normal file
152
packages/platform-webworker/src/worker_render.ts
Normal 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);
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
@ -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}); }
|
||||
}
|
@ -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 {}
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -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);
|
||||
},
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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); });
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
14
packages/platform-webworker/test/web_workers/worker/spies.ts
Normal file
14
packages/platform-webworker/test/web_workers/worker/spies.ts
Normal 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); }
|
||||
}
|
34
packages/platform-webworker/tsconfig-build.json
Normal file
34
packages/platform-webworker/tsconfig-build.json
Normal 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"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user