diff --git a/modules/@angular/core/index.ts b/modules/@angular/core/index.ts index 05e6b19532..e727e2e8a7 100644 --- a/modules/@angular/core/index.ts +++ b/modules/@angular/core/index.ts @@ -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. diff --git a/modules/@angular/platform-browser/src/dom/dom_renderer.ts b/modules/@angular/platform-browser/src/dom/dom_renderer.ts index 434b396a13..b140c3c00d 100644 --- a/modules/@angular/platform-browser/src/dom/dom_renderer.ts +++ b/modules/@angular/platform-browser/src/dom/dom_renderer.ts @@ -344,23 +344,6 @@ export function splitNamespace(name: string): string[] { } -let attrCache: Map; - -function createAttributeNode(name: string): Attr { - if (!attrCache) { - attrCache = new Map(); - } - if (attrCache.has(name)) { - return attrCache.get(name); - } - - const div = document.createElement('div'); - div.innerHTML = `
`; - const attr: Attr = div.firstChild.attributes[0]; - attrCache.set(name, attr); - return attr; -} - @Injectable() export class DomRendererFactoryV2 implements RendererFactoryV2 { private rendererByCompId = new Map(); diff --git a/modules/@angular/platform-browser/src/private_export.ts b/modules/@angular/platform-browser/src/private_export.ts index 9f990d4d0d..67dea5adf2 100644 --- a/modules/@angular/platform-browser/src/private_export.ts +++ b/modules/@angular/platform-browser/src/private_export.ts @@ -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, diff --git a/modules/@angular/platform-browser/testing/browser_util.ts b/modules/@angular/platform-browser/testing/browser_util.ts index b555cf159c..ca62d1b6b2 100644 --- a/modules/@angular/platform-browser/testing/browser_util.ts +++ b/modules/@angular/platform-browser/testing/browser_util.ts @@ -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)); } diff --git a/modules/@angular/platform-webworker/src/platform-webworker.ts b/modules/@angular/platform-webworker/src/platform-webworker.ts index a8ecccba9b..14ce817e9d 100644 --- a/modules/@angular/platform-webworker/src/platform-webworker.ts +++ b/modules/@angular/platform-webworker/src/platform-webworker.ts @@ -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. * diff --git a/modules/@angular/platform-webworker/src/private_import_platform-browser.ts b/modules/@angular/platform-webworker/src/private_import_platform-browser.ts index c592b7f0cd..99c9a930d2 100644 --- a/modules/@angular/platform-webworker/src/private_import_platform-browser.ts +++ b/modules/@angular/platform-webworker/src/private_import_platform-browser.ts @@ -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; diff --git a/modules/@angular/platform-webworker/src/web_workers/shared/client_message_broker.ts b/modules/@angular/platform-webworker/src/web_workers/shared/client_message_broker.ts index 419876fe2e..57eeed0da3 100644 --- a/modules/@angular/platform-webworker/src/web_workers/shared/client_message_broker.ts +++ b/modules/@angular/platform-webworker/src/web_workers/shared/client_message_broker.ts @@ -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) {} + constructor(public value: any, public type: Type|SerializerTypes = null) {} } /** diff --git a/modules/@angular/platform-webworker/src/web_workers/shared/messaging_api.ts b/modules/@angular/platform-webworker/src/web_workers/shared/messaging_api.ts index 716339a188..3c7439413a 100644 --- a/modules/@angular/platform-webworker/src/web_workers/shared/messaging_api.ts +++ b/modules/@angular/platform-webworker/src/web_workers/shared/messaging_api.ts @@ -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'; diff --git a/modules/@angular/platform-webworker/src/web_workers/shared/serialized_types.ts b/modules/@angular/platform-webworker/src/web_workers/shared/serialized_types.ts index 3a28a155e9..21222e9267 100644 --- a/modules/@angular/platform-webworker/src/web_workers/shared/serialized_types.ts +++ b/modules/@angular/platform-webworker/src/web_workers/shared/serialized_types.ts @@ -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, +} diff --git a/modules/@angular/platform-webworker/src/web_workers/shared/serializer.ts b/modules/@angular/platform-webworker/src/web_workers/shared/serializer.ts index 331d4b27af..b99af6c911 100644 --- a/modules/@angular/platform-webworker/src/web_workers/shared/serializer.ts +++ b/modules/@angular/platform-webworker/src/web_workers/shared/serializer.ts @@ -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) + }; } } diff --git a/modules/@angular/platform-webworker/src/web_workers/ui/event_dispatcher.ts b/modules/@angular/platform-webworker/src/web_workers/ui/event_dispatcher.ts index 2a0bdbf3eb..f1fdbe7781 100644 --- a/modules/@angular/platform-webworker/src/web_workers/ui/event_dispatcher.ts +++ b/modules/@angular/platform-webworker/src/web_workers/ui/event_dispatcher.ts @@ -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, diff --git a/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts b/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts index 2f0d8c7939..a203c067b9 100644 --- a/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts +++ b/modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts @@ -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(); } +} diff --git a/modules/@angular/platform-webworker/src/web_workers/worker/event_deserializer.ts b/modules/@angular/platform-webworker/src/web_workers/worker/event_deserializer.ts deleted file mode 100644 index 93486c4018..0000000000 --- a/modules/@angular/platform-webworker/src/web_workers/worker/event_deserializer.ts +++ /dev/null @@ -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; -} diff --git a/modules/@angular/platform-webworker/src/web_workers/worker/location_providers.ts b/modules/@angular/platform-webworker/src/web_workers/worker/location_providers.ts index 0cab3b52c3..5eabfd11c4 100644 --- a/modules/@angular/platform-webworker/src/web_workers/worker/location_providers.ts +++ b/modules/@angular/platform-webworker/src/web_workers/worker/location_providers.ts @@ -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], }, ]; diff --git a/modules/@angular/platform-webworker/src/web_workers/worker/platform_location.ts b/modules/@angular/platform-webworker/src/web_workers/worker/platform_location.ts index c2d601a130..98607d122f 100644 --- a/modules/@angular/platform-webworker/src/web_workers/worker/platform_location.ts +++ b/modules/@angular/platform-webworker/src/web_workers/worker/platform_location.ts @@ -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'])); } } } diff --git a/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts b/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts index af014c47ab..3d6f826e19 100644 --- a/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts +++ b/modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts @@ -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 = - new Map(); + private _componentRenderers = new Map(); 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; - private _getListeners(eventName: string): Function[] { - if (!this._listeners) { - this._listeners = new Map(); - } - 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(); + } + 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(); diff --git a/modules/@angular/platform-webworker/src/worker_app.ts b/modules/@angular/platform-webworker/src/worker_app.ts index 3aa89d54e1..89f5ef04a5 100644 --- a/modules/@angular/platform-webworker/src/worker_app.ts +++ b/modules/@angular/platform-webworker/src/worker_app.ts @@ -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: []}, diff --git a/modules/@angular/platform-webworker/src/worker_render.ts b/modules/@angular/platform-webworker/src/worker_render.ts index 459c017f16..1b81a94d9d 100644 --- a/modules/@angular/platform-webworker/src/worker_render.ts +++ b/modules/@angular/platform-webworker/src/worker_render.ts @@ -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_}, diff --git a/modules/@angular/platform-webworker/test/web_workers/shared/web_worker_test_util.ts b/modules/@angular/platform-webworker/test/web_workers/shared/web_worker_test_util.ts index 4db1667453..03f9e8b7e1 100644 --- a/modules/@angular/platform-webworker/test/web_workers/shared/web_worker_test_util.ts +++ b/modules/@angular/platform-webworker/test/web_workers/shared/web_worker_test_util.ts @@ -42,7 +42,7 @@ export function createPairedMessageBuses(): PairedMessageBuses { export function expectBrokerCall( broker: SpyMessageBroker, methodName: string, vals?: Array, handler?: (..._: any[]) => Promise| void): void { - broker.spy('runOnService').and.callFake((args: UiArguments, returnType: Type) => { + broker.spy('callUI').and.callFake((args: UiArguments, returnType: Type) => { expect(args.method).toEqual(methodName); if (isPresent(vals)) { expect(args.args.length).toEqual(vals.length); diff --git a/modules/@angular/platform-webworker/test/web_workers/worker/platform_location_spec.ts b/modules/@angular/platform-webworker/test/web_workers/worker/platform_location_spec.ts index 34d7252ee4..5d9a30a419 100644 --- a/modules/@angular/platform-webworker/test/web_workers/worker/platform_location_spec.ts +++ b/modules/@angular/platform-webworker/test/web_workers/worker/platform_location_spec.ts @@ -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); }); diff --git a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_integration_spec.ts b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_integration_spec.ts index 5dc4bb383c..bfe03783c7 100644 --- a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_integration_spec.ts +++ b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_integration_spec.ts @@ -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: ''}}); 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((el).tabIndex).toEqual(1); + const checkSetters = (componentRef: ComponentRef, 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: ''}}); const fixture = TestBed.createComponent(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: ''}}); 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; } diff --git a/modules/@angular/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts new file mode 100644 index 0000000000..477d4ce742 --- /dev/null +++ b/modules/@angular/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts @@ -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, '
{{ctxProp}}
').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, '') + .createComponent(MyComp2); + + const checkSetters = (componentRef: ComponentRef, 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, '') + .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, 'hello') + .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, '') + .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; + } +} diff --git a/tools/public_api_guard/platform-webworker/index.d.ts b/tools/public_api_guard/platform-webworker/index.d.ts index 0eb94f897a..e07a48c843 100644 --- a/tools/public_api_guard/platform-webworker/index.d.ts +++ b/tools/public_api_guard/platform-webworker/index.d.ts @@ -13,9 +13,9 @@ export declare abstract class ClientMessageBrokerFactory { /** @experimental */ export declare class FnArg { - type: Type; + type: Type | SerializerTypes; value: any; - constructor(value: any, type: Type); + constructor(value: any, type?: Type | 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[], method: Function, returnType?: Type): void;