feat(platform-webworker): renderer v2 integration

This commit is contained in:
Victor Berchet 2017-02-16 22:36:21 -08:00
parent f38dbfbd64
commit 3517f28609
23 changed files with 784 additions and 218 deletions

View File

@ -7,7 +7,7 @@
*/
// This file is not used to build this module. It is only used during editing
// by the TypeScript language serivce and during build for verifcation. `ngc`
// by the TypeScript language service and during build for verification. `ngc`
// replaces this file with production index.ts when it rewrites private symbol
// names.

View File

@ -344,23 +344,6 @@ export function splitNamespace(name: string): string[] {
}
let attrCache: Map<string, Attr>;
function createAttributeNode(name: string): Attr {
if (!attrCache) {
attrCache = new Map<string, Attr>();
}
if (attrCache.has(name)) {
return attrCache.get(name);
}
const div = document.createElement('div');
div.innerHTML = `<div ${name}>`;
const attr: Attr = div.firstChild.attributes[0];
attrCache.set(name, attr);
return attr;
}
@Injectable()
export class DomRendererFactoryV2 implements RendererFactoryV2 {
private rendererByCompId = new Map<string, RendererV2>();

View File

@ -33,8 +33,8 @@ export const __platform_browser_private__: {
setRootDomAdapter: typeof dom_adapter.setRootDomAdapter,
_DomRootRenderer?: dom_renderer.DomRootRenderer,
DomRootRenderer: typeof dom_renderer.DomRootRenderer,
_DomRootRenderer_?: dom_renderer.DomRootRenderer,
DomRootRenderer_: typeof dom_renderer.DomRootRenderer_,
DomRendererFactoryV2: typeof dom_renderer.DomRendererFactoryV2,
NAMESPACE_URIS: typeof dom_renderer.NAMESPACE_URIS,
shimContentAttribute: typeof dom_renderer.shimContentAttribute,
shimHostAttribute: typeof dom_renderer.shimHostAttribute,
@ -64,6 +64,7 @@ export const __platform_browser_private__: {
setRootDomAdapter: dom_adapter.setRootDomAdapter,
DomRootRenderer_: dom_renderer.DomRootRenderer_,
DomRootRenderer: dom_renderer.DomRootRenderer,
DomRendererFactoryV2: dom_renderer.DomRendererFactoryV2,
NAMESPACE_URIS: dom_renderer.NAMESPACE_URIS,
shimContentAttribute: dom_renderer.shimContentAttribute,
shimHostAttribute: dom_renderer.shimHostAttribute,

View File

@ -74,8 +74,7 @@ export class BrowserDetection {
BrowserDetection.setup();
export function dispatchEvent(
element: any /** TODO #9100 */, eventType: any /** TODO #9100 */): void {
export function dispatchEvent(element: any, eventType: any): void {
getDOM().dispatchEvent(element, getDOM().createEvent(eventType));
}

View File

@ -13,6 +13,7 @@ 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 {SerializerTypes} from './web_workers/shared/serialized_types';
export {PRIMITIVE} 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';
@ -21,6 +22,7 @@ export {WorkerAppModule, platformWorkerApp} from './worker_app';
export {platformWorkerUi} from './worker_render';
/**
* Bootstraps the worker ui.
*

View File

@ -17,6 +17,8 @@ export const BrowserDomAdapter: typeof _.BrowserDomAdapter = _.BrowserDomAdapter
export const BrowserGetTestability: typeof _.BrowserGetTestability = _.BrowserGetTestability;
export const DomRootRenderer: typeof _.DomRootRenderer = _.DomRootRenderer;
export const DomRootRenderer_: typeof _.DomRootRenderer_ = _.DomRootRenderer_;
export const DomRendererFactoryV2: typeof _.DomRendererFactoryV2 = _.DomRendererFactoryV2;
export const DomEventsPlugin: typeof _.DomEventsPlugin = _.DomEventsPlugin;
export const DomSharedStylesHost: typeof _.DomSharedStylesHost = _.DomSharedStylesHost;
export const SharedStylesHost: typeof _.SharedStylesHost = _.SharedStylesHost;

View File

@ -12,8 +12,10 @@ import {EventEmitter} from '../../facade/async';
import {stringify} from '../../facade/lang';
import {MessageBus} from './message_bus';
import {SerializerTypes} from './serialized_types';
import {Serializer} from './serializer';
/**
* @experimental WebWorker support in Angular is experimental.
*/
@ -165,7 +167,7 @@ class MessageData {
* @experimental WebWorker support in Angular is experimental.
*/
export class FnArg {
constructor(public value: any, public type: Type<any>) {}
constructor(public value: any, public type: Type<any>|SerializerTypes = null) {}
}
/**

View File

@ -12,4 +12,8 @@
*/
export const RENDERER_CHANNEL = 'ng-Renderer';
export const EVENT_CHANNEL = 'ng-Events';
export const RENDERER_V2_CHANNEL = 'v2.ng-Renderer';
export const EVENT_V2_CHANNEL = 'v2.ng-Events';
export const ROUTER_CHANNEL = 'ng-Router';

View File

@ -14,3 +14,11 @@ export class LocationType {
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 {
// RendererTypeV2
RENDERER_TYPE_V2,
}

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, RenderComponentType, Type, ViewEncapsulation} from '@angular/core';
import {Injectable, RenderComponentType, RendererTypeV2, Type, ViewEncapsulation} from '@angular/core';
import {stringify} from '../../facade/lang';
import {RenderStore} from './render_store';
import {LocationType} from './serialized_types';
import {LocationType, SerializerTypes} from './serialized_types';
// PRIMITIVE is any type that does not need to be serialized (string, number, boolean)
// We set it to String so that it is considered a Type.
@ -40,45 +41,44 @@ export class Serializer {
if (type === RenderComponentType) {
return this._serializeRenderComponentType(obj);
}
if (type === SerializerTypes.RENDERER_TYPE_V2) {
return this._serializeRendererTypeV2(obj);
}
if (type === ViewEncapsulation) {
return obj;
}
if (type === LocationType) {
return this._serializeLocation(obj);
}
throw new Error(`No serializer for type ${stringify}`);
throw new Error(`No serializer for type ${stringify(type)}`);
}
deserialize(map: any, type: any, data?: any): any {
if (map == null) {
return null;
}
if (Array.isArray(map)) {
return map.map(val => this.deserialize(val, type, data));
}
if (type === PRIMITIVE) {
return map;
}
if (type === RenderStoreObject) {
return this._renderStore.deserialize(map);
}
if (type === RenderComponentType) {
return this._deserializeRenderComponentType(map);
}
if (type === SerializerTypes.RENDERER_TYPE_V2) {
return this._deserializeRendererTypeV2(map);
}
if (type === ViewEncapsulation) {
return map as ViewEncapsulation;
}
if (type === LocationType) {
return this._deserializeLocation(map);
}
throw new Error('No deserializer for ' + type.toString());
throw new Error(`No deserializer for type ${stringify(type)}`);
}
private _serializeLocation(loc: LocationType): Object {
@ -101,21 +101,39 @@ export class Serializer {
loc['search'], loc['hash'], loc['origin']);
}
private _serializeRenderComponentType(obj: RenderComponentType): Object {
private _serializeRenderComponentType(type: RenderComponentType): Object {
return {
'id': obj.id,
'templateUrl': obj.templateUrl,
'slotCount': obj.slotCount,
'encapsulation': this.serialize(obj.encapsulation, ViewEncapsulation),
'styles': this.serialize(obj.styles, PRIMITIVE),
'id': type.id,
'templateUrl': type.templateUrl,
'slotCount': type.slotCount,
'encapsulation': this.serialize(type.encapsulation, ViewEncapsulation),
'styles': this.serialize(type.styles, PRIMITIVE),
};
}
private _deserializeRenderComponentType(map: {[key: string]: any}): RenderComponentType {
private _deserializeRenderComponentType(props: {[key: string]: any}): RenderComponentType {
return new RenderComponentType(
map['id'], map['templateUrl'], map['slotCount'],
this.deserialize(map['encapsulation'], ViewEncapsulation),
this.deserialize(map['styles'], PRIMITIVE), {});
props['id'], props['templateUrl'], props['slotCount'],
this.deserialize(props['encapsulation'], ViewEncapsulation),
this.deserialize(props['styles'], PRIMITIVE), {});
}
private _serializeRendererTypeV2(type: RendererTypeV2): {[key: string]: any} {
return {
'id': type.id,
'encapsulation': this.serialize(type.encapsulation, ViewEncapsulation),
'styles': this.serialize(type.styles, PRIMITIVE),
'data': this.serialize(type.data, PRIMITIVE),
};
}
private _deserializeRendererTypeV2(props: {[key: string]: any}): RendererTypeV2 {
return {
id: props['id'],
encapsulation: props['encapsulation'],
styles: this.deserialize(props['styles'], PRIMITIVE),
data: this.deserialize(props['data'], PRIMITIVE)
};
}
}

View File

@ -105,6 +105,7 @@ export class EventDispatcher {
default:
throw new Error(eventName + ' not supported on WebWorkers');
}
this._sink.emit({
'element': this._serializer.serialize(element, RenderStoreObject),
'eventName': eventName,

View File

@ -6,10 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AnimationPlayer, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
import {AnimationPlayer, Injectable, RenderComponentType, Renderer, RendererFactoryV2, RendererTypeV2, RendererV2, RootRenderer} from '@angular/core';
import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {EVENT_CHANNEL, EVENT_V2_CHANNEL, RENDERER_CHANNEL, RENDERER_V2_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {SerializerTypes} from '../shared/serialized_types';
import {ANIMATION_WORKER_PLAYER_PREFIX, PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
import {ServiceMessageBroker, ServiceMessageBrokerFactory} from '../shared/service_message_broker';
import {EventDispatcher} from '../ui/event_dispatcher';
@ -25,73 +27,39 @@ export class MessageBasedRenderer {
start(): void {
const broker = this._brokerFactory.createMessageBroker(RENDERER_CHANNEL);
this._bus.initChannel(EVENT_CHANNEL);
this._eventDispatcher = new EventDispatcher(this._bus.to(EVENT_CHANNEL), this._serializer);
broker.registerMethod(
'renderComponent', [RenderComponentType, PRIMITIVE], this._renderComponent.bind(this));
const [RCT, RSO, P] = [RenderComponentType, RenderStoreObject, PRIMITIVE];
broker.registerMethod(
'selectRootElement', [RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._selectRootElement.bind(this));
broker.registerMethod(
'createElement', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._createElement.bind(this));
broker.registerMethod(
'createViewRoot', [RenderStoreObject, RenderStoreObject, PRIMITIVE],
this._createViewRoot.bind(this));
broker.registerMethod(
'createTemplateAnchor', [RenderStoreObject, RenderStoreObject, PRIMITIVE],
this._createTemplateAnchor.bind(this));
broker.registerMethod(
'createText', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._createText.bind(this));
broker.registerMethod(
'projectNodes', [RenderStoreObject, RenderStoreObject, RenderStoreObject],
this._projectNodes.bind(this));
broker.registerMethod(
'attachViewAfter', [RenderStoreObject, RenderStoreObject, RenderStoreObject],
this._attachViewAfter.bind(this));
broker.registerMethod(
'detachView', [RenderStoreObject, RenderStoreObject], this._detachView.bind(this));
broker.registerMethod(
'destroyView', [RenderStoreObject, RenderStoreObject, RenderStoreObject],
this._destroyView.bind(this));
broker.registerMethod(
'setElementProperty', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._setElementProperty.bind(this));
broker.registerMethod(
'setElementAttribute', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._setElementAttribute.bind(this));
broker.registerMethod(
'setBindingDebugInfo', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._setBindingDebugInfo.bind(this));
broker.registerMethod(
'setElementClass', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._setElementClass.bind(this));
broker.registerMethod(
'setElementStyle', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._setElementStyle.bind(this));
broker.registerMethod(
'invokeElementMethod', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._invokeElementMethod.bind(this));
broker.registerMethod(
'setText', [RenderStoreObject, RenderStoreObject, PRIMITIVE], this._setText.bind(this));
broker.registerMethod(
'listen', [RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE],
this._listen.bind(this));
broker.registerMethod(
'listenGlobal', [RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE],
this._listenGlobal.bind(this));
broker.registerMethod(
'listenDone', [RenderStoreObject, RenderStoreObject], this._listenDone.bind(this));
broker.registerMethod(
'animate',
[
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
PRIMITIVE, PRIMITIVE, PRIMITIVE
],
this._animate.bind(this));
const methods: any[][] = [
['renderComponent', this._renderComponent, RCT, P],
['selectRootElement', this._selectRootElement, RSO, P, P],
['createElement', this._createElement, RSO, RSO, P, P],
['createViewRoot', this._createViewRoot, RSO, RSO, P],
['createTemplateAnchor', this._createTemplateAnchor, RSO, RSO, P],
['createText', this._createText, RSO, RSO, P, P],
['projectNodes', this._projectNodes, RSO, RSO, RSO],
['attachViewAfter', this._attachViewAfter, RSO, RSO, RSO],
['detachView', this._detachView, RSO, RSO],
['destroyView', this._destroyView, RSO, RSO, RSO],
['setElementProperty', this._setElementProperty, RSO, RSO, P, P],
['setElementAttribute', this._setElementAttribute, RSO, RSO, P, P],
['setBindingDebugInfo', this._setBindingDebugInfo, RSO, RSO, P, P],
['setElementClass', this._setElementClass, RSO, RSO, P, P],
['setElementStyle', this._setElementStyle, RSO, RSO, P, P],
['invokeElementMethod', this._invokeElementMethod, RSO, RSO, P, P],
['setText', this._setText, RSO, RSO, P],
['listen', this._listen, RSO, RSO, P, P],
['listenGlobal', this._listenGlobal, RSO, P, P, P],
['listenDone', this._listenDone, RSO, RSO],
['animate', this._animate, RSO, RSO, P, P, P, P, P, P, P],
];
methods.forEach(([name, method, ...argTypes]: any[]) => {
broker.registerMethod(name, argTypes, method.bind(this));
});
this._bindAnimationPlayerMethods(broker);
}
@ -270,3 +238,124 @@ export class MessageBasedRenderer {
}
}
}
@Injectable()
export class MessageBasedRendererV2 {
private _eventDispatcher: EventDispatcher;
constructor(
private _brokerFactory: ServiceMessageBrokerFactory, private _bus: MessageBus,
private _serializer: Serializer, private _renderStore: RenderStore,
private _rendererFactory: RendererFactoryV2) {}
start(): void {
const broker = this._brokerFactory.createMessageBroker(RENDERER_V2_CHANNEL);
this._bus.initChannel(EVENT_V2_CHANNEL);
this._eventDispatcher = new EventDispatcher(this._bus.to(EVENT_V2_CHANNEL), this._serializer);
const [RSO, P, CRT] = [RenderStoreObject, PRIMITIVE, SerializerTypes.RENDERER_TYPE_V2];
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],
];
methods.forEach(([name, method, ...argTypes]: any[]) => {
broker.registerMethod(name, argTypes, method.bind(this));
});
}
private createRenderer(el: any, type: RendererTypeV2, id: number) {
this._renderStore.store(this._rendererFactory.createRenderer(el, type), id);
}
private createElement(r: RendererV2, name: string, namespace: string, id: number) {
this._renderStore.store(r.createElement(name, namespace), id);
}
private createComment(r: RendererV2, value: string, id: number) {
this._renderStore.store(r.createComment(value), id);
}
private createText(r: RendererV2, value: string, id: number) {
this._renderStore.store(r.createText(value), id);
}
private appendChild(r: RendererV2, parent: any, child: any) { r.appendChild(parent, child); }
private insertBefore(r: RendererV2, parent: any, child: any, ref: any) {
r.insertBefore(parent, child, ref);
}
private removeChild(r: RendererV2, parent: any, child: any) { r.removeChild(parent, child); }
private selectRootElement(r: RendererV2, selector: string, id: number) {
this._renderStore.store(r.selectRootElement(selector), id);
}
private parentNode(r: RendererV2, node: any, id: number) {
this._renderStore.store(r.parentNode(node), id);
}
private nextSibling(r: RendererV2, node: any, id: number) {
this._renderStore.store(r.nextSibling(node), id);
}
private setAttribute(r: RendererV2, el: any, name: string, value: string, namespace: string) {
r.setAttribute(el, name, value, namespace);
}
private removeAttribute(r: RendererV2, el: any, name: string, namespace: string) {
r.removeAttribute(el, name, namespace);
}
private addClass(r: RendererV2, el: any, name: string) { r.addClass(el, name); }
private removeClass(r: RendererV2, el: any, name: string) { r.removeClass(el, name); }
private setStyle(
r: RendererV2, el: any, style: string, value: any, hasVendorPrefix: boolean,
hasImportant: boolean) {
r.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
private removeStyle(r: RendererV2, el: any, style: string, hasVendorPrefix: boolean) {
r.removeStyle(el, style, hasVendorPrefix);
}
private setProperty(r: RendererV2, el: any, name: string, value: any) {
r.setProperty(el, name, value);
}
private setValue(r: RendererV2, node: any, value: string) { r.setValue(node, value); }
private listen(r: RendererV2, 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: RendererV2, unlisten: () => boolean) { unlisten(); }
}

View File

@ -1,13 +0,0 @@
/**
* @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
*/
// no deserialization is necessary in TS.
// This is only here to match dart interface
export function deserializeGenericEvent(serializedEvent: {[key: string]: any}):
{[key: string]: any} {
return serializedEvent;
}

View File

@ -7,10 +7,11 @@
*/
import {PlatformLocation} from '@angular/common';
import {APP_INITIALIZER, NgZone} from '@angular/core';
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.
@ -22,7 +23,7 @@ export const WORKER_APP_LOCATION_PROVIDERS = [
provide: APP_INITIALIZER,
useFactory: appInitFnFactory,
multi: true,
deps: [PlatformLocation, NgZone]
deps: [PlatformLocation, NgZone],
},
];

View File

@ -16,8 +16,6 @@ import {ROUTER_CHANNEL} from '../shared/messaging_api';
import {LocationType} from '../shared/serialized_types';
import {PRIMITIVE, Serializer} from '../shared/serializer';
import {deserializeGenericEvent} from './event_deserializer';
@Injectable()
export class WebWorkerPlatformLocation extends PlatformLocation {
private _broker: ClientMessageBroker;
@ -44,10 +42,9 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
}
if (listeners) {
const e = deserializeGenericEvent(msg['event']);
// 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(e));
listeners.forEach((fn: Function) => fn(msg['event']));
}
}
}

View File

@ -6,24 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';
import {Injectable, RenderComponentType, Renderer, RendererFactoryV2, RendererTypeV2, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
import {ListWrapper} from '../../facade/collection';
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from '../../private_import_core';
import {ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_message_broker';
import {ClientMessageBroker, ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_message_broker';
import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {EVENT_CHANNEL, EVENT_V2_CHANNEL, RENDERER_CHANNEL, RENDERER_V2_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {SerializerTypes} from '../shared/serialized_types';
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
import {deserializeGenericEvent} from './event_deserializer';
@Injectable()
export class WebWorkerRootRenderer implements RootRenderer {
globalEvents = new NamedEventEmitter();
private _messageBroker: ClientMessageBroker;
public globalEvents: NamedEventEmitter = new NamedEventEmitter();
private _componentRenderers: Map<string, WebWorkerRenderer> =
new Map<string, WebWorkerRenderer>();
private _componentRenderers = new Map<string, WebWorkerRenderer>();
constructor(
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
@ -46,7 +45,7 @@ export class WebWorkerRootRenderer implements RootRenderer {
} else {
const eventName = message['eventName'];
const target = message['eventTarget'];
const event = deserializeGenericEvent(message['event']);
const event = message['event'];
if (target) {
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
} else {
@ -259,21 +258,13 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
}
}
function eventNameWithTarget(target: string, eventName: string): string {
return `${target}:${eventName}`;
}
export class NamedEventEmitter {
private _listeners: Map<string, Function[]>;
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;
}
listen(eventName: string, callback: Function) { this._getListeners(eventName).push(callback); }
unlisten(eventName: string, callback: Function) {
@ -286,6 +277,271 @@ export class NamedEventEmitter {
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;
}
}
const globalEvents = new NamedEventEmitter();
@Injectable()
export class WebWorkerRendererFactoryV2 implements RendererFactoryV2 {
private _messageBroker: ClientMessageBroker;
constructor(
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
private _serializer: Serializer, public renderStore: RenderStore) {
this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_V2_CHANNEL);
bus.initChannel(EVENT_V2_CHANNEL);
const source = bus.from(EVENT_V2_CHANNEL);
source.subscribe({next: (message: any) => this._dispatchEvent(message)});
}
createRenderer(element: any, type: RendererTypeV2): RendererV2 {
const renderer = new WebWorkerRendererV2(this);
const id = this.renderStore.allocateId();
this.renderStore.store(renderer, id);
this.callUI('createRenderer', [
new FnArg(element, RenderStoreObject),
new FnArg(type, SerializerTypes.RENDERER_TYPE_V2),
new FnArg(renderer, RenderStoreObject),
]);
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;
}
allocateId(): number { return this.renderStore.allocateId(); }
private _dispatchEvent(message: {[key: string]: any}): void {
const element: WebWorkerRenderNode =
this._serializer.deserialize(message['element'], RenderStoreObject);
const eventName = message['eventName'];
const target = message['eventTarget'];
const event = message['event'];
if (target) {
globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
} else {
element.events.dispatchEvent(eventName, event);
}
}
}
export class WebWorkerRendererV2 implements RendererV2 {
constructor(private _rendererFactory: WebWorkerRendererFactoryV2) {}
destroyNode: (node: any) => void | null = null;
private asFnArg = new FnArg(this, RenderStoreObject);
// TODO(vicb): destroy the allocated nodes
destroy(): void { this.callUIWithRenderer('destroy'); }
createElement(name: string, namespace?: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createElement', [
new FnArg(name),
new FnArg(namespace),
new FnArg(node, RenderStoreObject),
]);
return node;
}
createComment(value: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createComment', [
new FnArg(value),
new FnArg(node, RenderStoreObject),
]);
return node;
}
createText(value: string): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('createText', [
new FnArg(value),
new FnArg(node, RenderStoreObject),
]);
return node;
}
appendChild(parent: any, newChild: any): void {
this.callUIWithRenderer('appendChild', [
new FnArg(parent, RenderStoreObject),
new FnArg(newChild, RenderStoreObject),
]);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
if (!parent) {
return;
}
this.callUIWithRenderer('insertBefore', [
new FnArg(parent, RenderStoreObject),
new FnArg(newChild, RenderStoreObject),
new FnArg(refChild, RenderStoreObject),
]);
}
removeChild(parent: any, oldChild: any): void {
this.callUIWithRenderer('removeChild', [
new FnArg(parent, RenderStoreObject),
new FnArg(oldChild, RenderStoreObject),
]);
}
selectRootElement(selectorOrNode: string|any): any {
const node = this._rendererFactory.allocateNode();
this.callUIWithRenderer('selectRootElement', [
new FnArg(selectorOrNode),
new FnArg(node, RenderStoreObject),
]);
return node;
}
parentNode(node: any): any {
const res = this._rendererFactory.allocateNode();
this.callUIWithRenderer('parentNode', [
new FnArg(node, RenderStoreObject),
new FnArg(res, RenderStoreObject),
]);
return res;
}
nextSibling(node: any): any {
const res = this._rendererFactory.allocateNode();
this.callUIWithRenderer('nextSibling', [
new FnArg(node, RenderStoreObject),
new FnArg(res, RenderStoreObject),
]);
return res;
}
setAttribute(el: any, name: string, value: string, namespace?: string): void {
this.callUIWithRenderer('setAttribute', [
new FnArg(el, RenderStoreObject),
new FnArg(name),
new FnArg(value),
new FnArg(namespace),
]);
}
removeAttribute(el: any, name: string, namespace?: string): void {
this.callUIWithRenderer('removeAttribute', [
new FnArg(el, RenderStoreObject),
new FnArg(name),
new FnArg(namespace),
]);
}
addClass(el: any, name: string): void {
this.callUIWithRenderer('addClass', [
new FnArg(el, RenderStoreObject),
new FnArg(name),
]);
}
removeClass(el: any, name: string): void {
this.callUIWithRenderer('removeClass', [
new FnArg(el, RenderStoreObject),
new FnArg(name),
]);
}
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
this.callUIWithRenderer('setStyle', [
new FnArg(el, RenderStoreObject),
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, RenderStoreObject),
new FnArg(style),
new FnArg(hasVendorPrefix),
]);
}
setProperty(el: any, name: string, value: any): void {
this.callUIWithRenderer('setProperty', [
new FnArg(el, RenderStoreObject),
new FnArg(name),
new FnArg(value),
]);
}
setValue(node: any, value: string): void {
this.callUIWithRenderer('setValue', [
new FnArg(node, RenderStoreObject),
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) {
globalEvents.listen(fullName, listener);
} else {
targetEl.events.listen(eventName, listener);
}
this.callUIWithRenderer('listen', [
new FnArg(targetEl, RenderStoreObject),
new FnArg(targetName),
new FnArg(eventName),
new FnArg(unlistenId),
]);
return () => {
if (fullName) {
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 AnimationPlayerEmitter {
@ -320,10 +576,6 @@ export class AnimationPlayerEmitter {
}
}
function eventNameWithTarget(target: string, eventName: string): string {
return `${target}:${eventName}`;
}
export class WebWorkerRenderNode {
events = new NamedEventEmitter();
animationPlayerEvents = new AnimationPlayerEmitter();

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PlatformRef, Provider, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PlatformRef, Provider, RendererFactoryV2, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';
import {BROWSER_SANITIZATION_PROVIDERS} from './private_import_platform-browser';
@ -18,7 +18,7 @@ import {PostMessageBus, PostMessageBusSink, PostMessageBusSource} from './web_wo
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 {WebWorkerRootRenderer} from './web_workers/worker/renderer';
import {WebWorkerRendererFactoryV2, WebWorkerRootRenderer} from './web_workers/worker/renderer';
import {WorkerDomAdapter} from './web_workers/worker/worker_adapter';
@ -40,7 +40,6 @@ const _postMessage = {
}
};
export function createMessageBus(zone: NgZone): MessageBus {
const sink = new PostMessageBusSink(_postMessage);
const source = new PostMessageBusSource();
@ -49,7 +48,6 @@ export function createMessageBus(zone: NgZone): MessageBus {
return bus;
}
export function setupWebWorker(): void {
WorkerDomAdapter.makeCurrent();
}
@ -68,6 +66,8 @@ export function setupWebWorker(): void {
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
WebWorkerRootRenderer,
{provide: RootRenderer, useExisting: WebWorkerRootRenderer},
WebWorkerRendererFactoryV2,
{provide: RendererFactoryV2, useExisting: WebWorkerRendererFactoryV2},
{provide: ON_WEB_WORKER, useValue: true},
RenderStore,
{provide: ErrorHandler, useFactory: errorHandler, deps: []},

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_INITIALIZER, PlatformRef, Provider, RootRenderer, Testability, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactoryV2, RootRenderer, Testability, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationDriver, DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser';
import {APP_ID_RANDOM_PROVIDER} from './private_import_core';
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, WebAnimationsDriver, getDOM} from './private_import_platform-browser';
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRendererFactoryV2, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, WebAnimationsDriver, getDOM} from './private_import_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';
@ -20,8 +20,6 @@ import {Serializer} from './web_workers/shared/serializer';
import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_workers/shared/service_message_broker';
import {MessageBasedRenderer} from './web_workers/ui/renderer';
/**
* Wrapper class that exposes the Worker
* and underlying {@link MessageBus} for lower level message passing.
@ -71,6 +69,8 @@ export const _WORKER_UI_PLATFORM_PROVIDERS: Provider[] = [
APP_ID_RANDOM_PROVIDER,
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer},
DomRendererFactoryV2,
{provide: RendererFactoryV2, useExisting: DomRendererFactoryV2},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},

View File

@ -42,7 +42,7 @@ export function createPairedMessageBuses(): PairedMessageBuses {
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>) => {
broker.spy('callUI').and.callFake((args: UiArguments, returnType: Type<any>) => {
expect(args.method).toEqual(methodName);
if (isPresent(vals)) {
expect(args.args.length).toEqual(vals.length);

View File

@ -7,7 +7,6 @@
*/
import {Type} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
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} from '@angular/platform-webworker/src/web_workers/shared/serialized_types';
@ -75,16 +74,15 @@ export function main() {
expect(() => platformLocation.pathname = 'TEST').toThrowError();
});
it('should send pathname to render thread',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const platformLocation = createWebWorkerPlatformLocation(TEST_LOCATION);
platformLocation.init().then((_) => {
const PATHNAME = '/test';
expectBrokerCall(broker, 'setPathname', [PATHNAME]);
platformLocation.pathname = PATHNAME;
async.done();
});
}));
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); });

View File

@ -6,23 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ComponentRef, Injectable} from '@angular/core';
import {Component, ComponentRef} from '@angular/core';
import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer';
import {RootRenderer} from '@angular/core/src/render/api';
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 {DomRootRenderer, DomRootRenderer_} 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 '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
import {RenderStore} from '@angular/platform-webworker/src/web_workers/shared/render_store';
import {Serializer} from '@angular/platform-webworker/src/web_workers/shared/serializer';
import {ServiceMessageBrokerFactory_} from '@angular/platform-webworker/src/web_workers/shared/service_message_broker';
import {MessageBasedRenderer} from '@angular/platform-webworker/src/web_workers/ui/renderer';
import {WebWorkerRootRenderer} from '@angular/platform-webworker/src/web_workers/worker/renderer';
import {platformBrowserDynamicTesting} from '../../../../platform-browser-dynamic/testing';
import {dispatchEvent} from '../../../../platform-browser/testing/browser_util';
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 {MessageBasedRenderer} from '../../../src/web_workers/ui/renderer';
import {WebWorkerRootRenderer} from '../../../src/web_workers/worker/renderer';
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
export function main() {
@ -70,19 +70,23 @@ export function main() {
testUiInjector.ngModule = BrowserTestingModule;
testUiInjector.configureTestingModule({
providers: [
Serializer, {provide: RenderStore, useValue: uiRenderStore},
Serializer,
{provide: RenderStore, useValue: uiRenderStore},
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer}
{provide: RootRenderer, useExisting: DomRootRenderer},
]
});
const uiSerializer = testUiInjector.get(Serializer);
const domRootRenderer = testUiInjector.get(DomRootRenderer);
workerRenderStore = new RenderStore();
TestBed.configureTestingModule({
declarations: [MyComp2],
providers: [
Serializer, {provide: RenderStore, useValue: workerRenderStore}, {
Serializer,
{provide: RenderStore, useValue: workerRenderStore},
{
provide: RootRenderer,
useFactory: (workerSerializer: Serializer) => {
return createWorkerRenderer(
@ -90,12 +94,12 @@ export function main() {
workerRenderStore);
},
deps: [Serializer]
}
},
]
});
});
function getRenderElement(workerEl: any) {
function getRenderElement(workerEl: any): any {
const id = workerRenderStore.serialize(workerEl);
return uiRenderStore.deserialize(id);
}
@ -122,26 +126,25 @@ export function main() {
MyComp2, {set: {template: '<input [title]="y" style="position:absolute">'}});
const fixture = TestBed.createComponent(MyComp2);
const checkSetters =
(componentRef: any /** TODO #9100 */, workerEl: any /** TODO #9100 */) => {
const renderer = getRenderer(componentRef);
const el = getRenderElement(workerEl);
renderer.setElementProperty(workerEl, 'tabIndex', 1);
expect((<HTMLInputElement>el).tabIndex).toEqual(1);
const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => {
const renderer = getRenderer(componentRef);
const el = getRenderElement(workerEl);
renderer.setElementProperty(workerEl, 'tabIndex', 1);
expect(el.tabIndex).toEqual(1);
renderer.setElementClass(workerEl, 'a', true);
expect(getDOM().hasClass(el, 'a')).toBe(true);
renderer.setElementClass(workerEl, 'a', false);
expect(getDOM().hasClass(el, 'a')).toBe(false);
renderer.setElementClass(workerEl, 'a', true);
expect(getDOM().hasClass(el, 'a')).toBe(true);
renderer.setElementClass(workerEl, 'a', false);
expect(getDOM().hasClass(el, 'a')).toBe(false);
renderer.setElementStyle(workerEl, 'width', '10px');
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
renderer.setElementStyle(workerEl, 'width', null);
expect(getDOM().getStyle(el, 'width')).toEqual('');
renderer.setElementStyle(workerEl, 'width', '10px');
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
renderer.setElementStyle(workerEl, 'width', null);
expect(getDOM().getStyle(el, 'width')).toEqual('');
renderer.setElementAttribute(workerEl, 'someattr', 'someValue');
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
};
renderer.setElementAttribute(workerEl, 'someattr', 'someValue');
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
};
// root element
checkSetters(fixture.componentRef, fixture.nativeElement);
@ -150,12 +153,11 @@ export function main() {
});
it('should update any template comment property/attributes', () => {
TestBed.overrideComponent(
MyComp2, {set: {template: '<ng-container *ngIf="ctxBoolProp"></ng-container>'}});
const fixture = TestBed.createComponent(MyComp2);
(<MyComp2>fixture.componentInstance).ctxBoolProp = true;
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
const el = getRenderElement(fixture.nativeElement);
expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
@ -183,6 +185,7 @@ export function main() {
TestBed.overrideComponent(MyComp2, {set: {template: '<input [title]="y">'}});
const fixture = TestBed.createComponent(MyComp2);
const el = fixture.debugElement.children[0];
getRenderer(fixture.componentRef).invokeElementMethod(el.nativeElement, 'setAttribute', [
'a', 'b'
]);
@ -207,16 +210,8 @@ export function main() {
@Component({selector: 'my-comp'})
@Injectable()
class MyComp2 {
ctxProp: string;
ctxNumProp: any /** TODO #9100 */;
ctxBoolProp: boolean;
constructor() {
this.ctxProp = 'initial value';
this.ctxNumProp = 0;
this.ctxBoolProp = false;
}
throwError() { throw 'boom'; }
ctxProp = 'initial value';
ctxNumProp = 0;
ctxBoolProp = false;
}

View File

@ -0,0 +1,222 @@
/**
* @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 {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {Component, ComponentRef, RendererFactoryV2, RendererTypeV2, RendererV2, 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 {DomRendererFactoryV2, DomRootRenderer, DomRootRenderer_} 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 {MessageBasedRendererV2} from '../../../src/web_workers/ui/renderer';
import {WebWorkerRendererFactoryV2} from '../../../src/web_workers/worker/renderer';
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
let lastCreatedRenderer: RendererV2;
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: [
{provide: USE_VIEW_ENGINE, useValue: true}, Serializer,
{provide: RenderStore, useValue: uiRenderStore}, DomRendererFactoryV2,
{provide: RendererFactoryV2, useExisting: DomRendererFactoryV2},
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer}
]
});
const uiSerializer = uiInjector.get(Serializer);
const domRendererFactory = uiInjector.get(RendererFactoryV2);
// Worker side
lastCreatedRenderer = null;
wwRenderStore = new RenderStore();
TestBed
.configureCompiler({
useJit: true,
providers: [
{provide: USE_VIEW_ENGINE, useValue: true},
],
})
.configureTestingModule({
declarations: [MyComp2],
providers: [
Serializer,
{provide: RenderStore, useValue: wwRenderStore},
{
provide: RendererFactoryV2,
useFactory: (wwSerializer: Serializer) => createWebWorkerRendererFactoryV2(
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: DomRendererFactoryV2,
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 MessageBasedRendererV2(
uiBrokerFactory, uiMessageBus, uiSerializer, uiRenderStore, domRendererFactory);
renderer.start();
return wwBrokerFactory;
}
function createWebWorkerRendererFactoryV2(
workerSerializer: Serializer, uiSerializer: Serializer,
domRendererFactory: DomRendererFactoryV2, uiRenderStore: RenderStore,
workerRenderStore: RenderStore): RendererFactoryV2 {
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 WebWorkerRendererFactoryV2 {
createRenderer(element: any, type: RendererTypeV2): RendererV2 {
lastCreatedRenderer = super.createRenderer(element, type);
return lastCreatedRenderer;
}
}

View File

@ -13,9 +13,9 @@ export declare abstract class ClientMessageBrokerFactory {
/** @experimental */
export declare class FnArg {
type: Type<any>;
type: Type<any> | SerializerTypes;
value: any;
constructor(value: any, type: Type<any>);
constructor(value: any, type?: Type<any> | SerializerTypes);
}
/** @experimental */
@ -60,6 +60,11 @@ export declare class ReceivedMessage {
});
}
/** @experimental */
export declare const enum SerializerTypes {
RENDERER_TYPE_V2 = 0,
}
/** @experimental */
export declare abstract class ServiceMessageBroker {
abstract registerMethod(methodName: string, signature: Type<any>[], method: Function, returnType?: Type<any>): void;