From 0b59e664ec0daa32ba6ed61af46b205fc3663d4b Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Mon, 17 Aug 2015 10:28:47 -0700 Subject: [PATCH] feat(WebWorker) Add channel support to MessageBus closes #3661 and #3686 --- modules/angular2/src/facade/async.dart | 4 +- .../shared/isolate_message_bus.dart | 76 ++++ .../src/web-workers/shared/message_bus.ts | 60 +++- .../src/web-workers/shared/messaging_api.ts | 9 + .../web-workers/shared/post_message_bus.dart | 3 + .../web-workers/shared/post_message_bus.ts | 75 ++++ .../src/web-workers/ui/application.dart | 54 +-- .../src/web-workers/ui/application.ts | 42 +-- .../src/web-workers/ui/di_bindings.ts | 14 +- .../src/web-workers/ui/event_dispatcher.ts | 110 ++++++ modules/angular2/src/web-workers/ui/impl.ts | 331 +----------------- .../src/web-workers/ui/render_compiler.ts | 63 ++++ .../angular2/src/web-workers/ui/renderer.ts | 127 +++++++ modules/angular2/src/web-workers/ui/setup.ts | 20 ++ .../angular2/src/web-workers/ui/xhr_impl.ts | 44 +++ .../src/web-workers/worker/application.dart | 56 +-- .../src/web-workers/worker/application.ts | 61 +--- .../web-workers/worker/application_common.ts | 40 +-- .../angular2/src/web-workers/worker/broker.ts | 71 ++-- .../web-workers/worker/event_dispatcher.ts | 49 +++ .../src/web-workers/worker/renderer.ts | 61 ++-- .../src/web-workers/worker/xhr_impl.ts | 17 +- .../web-workers/shared/message_bus_spec.ts | 94 +++++ .../web-workers/shared/message_bus_util.dart | 20 ++ .../web-workers/shared/message_bus_util.ts | 30 ++ ...roker_spec.ts => event_dispatcher_spec.ts} | 60 ++-- .../worker/mock_event_emitter.dart | 21 ++ .../web-workers/worker/mock_event_emitter.ts | 18 + ...r_spec.ts => renderer_integration_spec.ts} | 49 +-- .../web-workers/worker/worker_test_util.ts | 70 ++-- .../test/web-workers/worker/xhr_impl_spec.ts | 14 +- .../src/message_broker/background_index.dart | 25 +- .../src/message_broker/background_index.ts | 41 ++- .../examples/src/message_broker/index.dart | 32 +- modules/examples/src/message_broker/index.ts | 39 +-- .../web_workers/images/services/bitmap.dart | 8 +- 36 files changed, 1155 insertions(+), 753 deletions(-) create mode 100644 modules/angular2/src/web-workers/shared/isolate_message_bus.dart create mode 100644 modules/angular2/src/web-workers/shared/messaging_api.ts create mode 100644 modules/angular2/src/web-workers/shared/post_message_bus.dart create mode 100644 modules/angular2/src/web-workers/shared/post_message_bus.ts create mode 100644 modules/angular2/src/web-workers/ui/event_dispatcher.ts create mode 100644 modules/angular2/src/web-workers/ui/render_compiler.ts create mode 100644 modules/angular2/src/web-workers/ui/renderer.ts create mode 100644 modules/angular2/src/web-workers/ui/setup.ts create mode 100644 modules/angular2/src/web-workers/ui/xhr_impl.ts create mode 100644 modules/angular2/src/web-workers/worker/event_dispatcher.ts create mode 100644 modules/angular2/test/web-workers/shared/message_bus_spec.ts create mode 100644 modules/angular2/test/web-workers/shared/message_bus_util.dart create mode 100644 modules/angular2/test/web-workers/shared/message_bus_util.ts rename modules/angular2/test/web-workers/worker/{broker_spec.ts => event_dispatcher_spec.ts} (53%) create mode 100644 modules/angular2/test/web-workers/worker/mock_event_emitter.dart create mode 100644 modules/angular2/test/web-workers/worker/mock_event_emitter.ts rename modules/angular2/test/web-workers/worker/{renderer_spec.ts => renderer_integration_spec.ts} (88%) diff --git a/modules/angular2/src/facade/async.dart b/modules/angular2/src/facade/async.dart index 89f577b4ae..eee67827ec 100644 --- a/modules/angular2/src/facade/async.dart +++ b/modules/angular2/src/facade/async.dart @@ -81,13 +81,13 @@ class ObservableWrapper { } class EventEmitter extends Stream { - StreamController _controller; + StreamController _controller; EventEmitter() { _controller = new StreamController.broadcast(); } - StreamSubscription listen(void onData(String line), + StreamSubscription listen(void onData(dynamic line), {void onError(Error error), void onDone(), bool cancelOnError}) { return _controller.stream.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); diff --git a/modules/angular2/src/web-workers/shared/isolate_message_bus.dart b/modules/angular2/src/web-workers/shared/isolate_message_bus.dart new file mode 100644 index 0000000000..7d60e5882a --- /dev/null +++ b/modules/angular2/src/web-workers/shared/isolate_message_bus.dart @@ -0,0 +1,76 @@ +library angular2.src.web_workers.shared.isolate_message_bus; + +import 'dart:isolate'; +import 'dart:async'; +import 'dart:core'; +import 'package:angular2/src/web-workers/shared/message_bus.dart' + show MessageBus, MessageBusSink, MessageBusSource; +import 'package:angular2/src/facade/async.dart'; + +class IsolateMessageBus implements MessageBus { + final IsolateMessageBusSink sink; + final IsolateMessageBusSource source; + + IsolateMessageBus(IsolateMessageBusSink sink, IsolateMessageBusSource source) + : sink = sink, + source = source; + + EventEmitter from(String channel) { + return source.from(channel); + } + + EventEmitter to(String channel) { + return sink.to(channel); + } +} + +class IsolateMessageBusSink implements MessageBusSink { + final SendPort _port; + final Map _channels = new Map(); + + IsolateMessageBusSink(SendPort port) : _port = port; + + EventEmitter to(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + emitter.listen((message) { + _port.send({'channel': channel, 'message': message}); + }); + _channels[channel] = emitter; + return emitter; + } + } +} + +class IsolateMessageBusSource extends MessageBusSource { + final Stream rawDataStream; + final Map _channels = new Map(); + + IsolateMessageBusSource(ReceivePort port) + : rawDataStream = port.asBroadcastStream() { + rawDataStream.listen((message) { + if (message is SendPort){ + return; + } + + if (message.containsKey("channel")) { + var channel = message['channel']; + if (_channels.containsKey(channel)) { + _channels[channel].add(message['message']); + } + } + }); + } + + EventEmitter from(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + _channels[channel] = emitter; + return emitter; + } + } +} diff --git a/modules/angular2/src/web-workers/shared/message_bus.ts b/modules/angular2/src/web-workers/shared/message_bus.ts index 8f40e76732..65e6e5bfe1 100644 --- a/modules/angular2/src/web-workers/shared/message_bus.ts +++ b/modules/angular2/src/web-workers/shared/message_bus.ts @@ -1,26 +1,52 @@ -// TODO(jteplitz602) to be idiomatic these should be releated to Observable's or Streams -/** - * Message Bus is a low level API used to communicate between the UI and the worker. - * It smooths out the differences between Javascript's postMessage and Dart's Isolate - * allowing you to work with one consistent API. - */ -export interface MessageBus { - sink: MessageBusSink; - source: MessageBusSource; +import {EventEmitter} from 'angular2/src/facade/async'; +import {BaseException} from 'angular2/src/facade/lang'; +// TODO(jteplitz602): Replace both the interface and the exported class with an abstract class #3683 + +function _abstract() { + throw new BaseException("This method is abstract"); } -export interface SourceListener { - (data: any): void; // TODO: Replace this any type with the type of a real messaging protocol +/** + * Message Bus is a low level API used to communicate between the UI and the background. + * Communication is based on a channel abstraction. Messages published in a + * given channel to one MessageBusSink are received on the same channel + * by the corresponding MessageBusSource. + * TODO(jteplitz602): This should just extend both the source and the sink once + * https://github.com/angular/ts2dart/issues/263 is closed. + */ +export interface MessageBusInterface { + /** + * Returns an {@link EventEmitter} that emits every time a messsage + * is received on the given channel. + */ + from(channel: string): EventEmitter; + + /** + * Returns an {@link EventEmitter} for the given channel + * To publish methods to that channel just call next (or add in dart) on the returned emitter + */ + to(channel: string): EventEmitter; } export interface MessageBusSource { /** - * Attaches the SourceListener to this source. - * The SourceListener will get called whenever the bus receives a message - * Returns a listener id that can be passed to {removeListener} + * Returns an {@link EventEmitter} that emits every time a messsage + * is received on the given channel. */ - addListener(fn: SourceListener): number; - removeListener(index: number); + from(channel: string): EventEmitter; } -export interface MessageBusSink { send(message: Object): void; } +export interface MessageBusSink { + /** + * Returns an {@link EventEmitter} for the given channel + * To publish methods to that channel just call next (or add in dart) on the returned emitter + */ + to(channel: string): EventEmitter; +} + +// TODO(jteplitz602): Remove this class once we have abstract classes #3683 +export class MessageBus implements MessageBusInterface { + from(channel: string): EventEmitter { throw _abstract(); } + + to(channel: string): EventEmitter { throw _abstract(); } +} diff --git a/modules/angular2/src/web-workers/shared/messaging_api.ts b/modules/angular2/src/web-workers/shared/messaging_api.ts new file mode 100644 index 0000000000..95e5114372 --- /dev/null +++ b/modules/angular2/src/web-workers/shared/messaging_api.ts @@ -0,0 +1,9 @@ +/** + * All channels used by angular's WebWorker components are listed here. + * You should not use these channels in your application code. + */ +export const SETUP_CHANNEL = "ng-WebWorkerSetup"; +export const RENDER_COMPILER_CHANNEL = "ng-RenderCompiler"; +export const RENDERER_CHANNEL = "ng-Renderer"; +export const XHR_CHANNEL = "ng-XHR"; +export const EVENT_CHANNEL = "ng-events"; diff --git a/modules/angular2/src/web-workers/shared/post_message_bus.dart b/modules/angular2/src/web-workers/shared/post_message_bus.dart new file mode 100644 index 0000000000..51b057013d --- /dev/null +++ b/modules/angular2/src/web-workers/shared/post_message_bus.dart @@ -0,0 +1,3 @@ +// PostMessageBus can't be implemented in dart since dart doesn't use postMessage +// This file is only here to prevent ts2dart from trying to transpile the PostMessageBus +library angular2.src.web_workers.shared.post_message_bus; diff --git a/modules/angular2/src/web-workers/shared/post_message_bus.ts b/modules/angular2/src/web-workers/shared/post_message_bus.ts new file mode 100644 index 0000000000..cdd807efe2 --- /dev/null +++ b/modules/angular2/src/web-workers/shared/post_message_bus.ts @@ -0,0 +1,75 @@ +import { + MessageBusInterface, + MessageBusSource, + MessageBusSink +} from "angular2/src/web-workers/shared/message_bus"; +import {EventEmitter} from 'angular2/src/facade/async'; +import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; +import {Injectable} from "angular2/di"; + +/** + * A TypeScript implementation of {@link MessageBus} for communicating via JavaScript's + * postMessage API. + */ +@Injectable() +export class PostMessageBus implements MessageBusInterface { + constructor(private _sink: PostMessageBusSink, private _source: PostMessageBusSource) {} + + from(channel: string): EventEmitter { return this._source.from(channel); } + + to(channel: string): EventEmitter { return this._sink.to(channel); } +} + +export class PostMessageBusSink implements MessageBusSink { + private _channels: StringMap = StringMapWrapper.create(); + + constructor(private _postMessageTarget: PostMessageTarget) {} + + public to(channel: string): EventEmitter { + if (StringMapWrapper.contains(this._channels, channel)) { + return this._channels[channel]; + } else { + var emitter = new EventEmitter(); + emitter.observer({ + next: (message: Object) => { + this._postMessageTarget.postMessage({channel: channel, message: message}); + } + }); + return emitter; + } + } +} + +export class PostMessageBusSource implements MessageBusSource { + private _channels: StringMap = StringMapWrapper.create(); + + constructor(eventTarget?: EventTarget) { + if (eventTarget) { + eventTarget.addEventListener("message", (ev: MessageEvent) => this._handleMessage(ev)); + } else { + // if no eventTarget is given we assume we're in a WebWorker and listen on the global scope + addEventListener("message", (ev: MessageEvent) => this._handleMessage(ev)); + } + } + + private _handleMessage(ev: MessageEvent) { + var data = ev.data; + var channel = data.channel; + if (StringMapWrapper.contains(this._channels, channel)) { + this._channels[channel].next(data.message); + } + } + + public from(channel: string): EventEmitter { + if (StringMapWrapper.contains(this._channels, channel)) { + return this._channels[channel]; + } else { + var emitter = new EventEmitter(); + this._channels[channel] = emitter; + return emitter; + } + } +} + +// TODO(jteplitz602) Replace this with the definition in lib.webworker.d.ts(#3492) +export interface PostMessageTarget { postMessage: (message: any, transfer?:[ArrayBuffer]) => void; } diff --git a/modules/angular2/src/web-workers/ui/application.dart b/modules/angular2/src/web-workers/ui/application.dart index 595540e22d..fc51fc8641 100644 --- a/modules/angular2/src/web-workers/ui/application.dart +++ b/modules/angular2/src/web-workers/ui/application.dart @@ -4,8 +4,9 @@ import 'dart:isolate'; import 'dart:async'; import 'dart:core'; import 'package:angular2/src/web-workers/shared/message_bus.dart' - show MessageBus, MessageBusSink, MessageBusSource; + show MessageBus; import 'package:angular2/src/web-workers/ui/impl.dart' show bootstrapUICommon; +import 'package:angular2/src/web-workers/shared/isolate_message_bus.dart'; /** * Bootstrapping a WebWorker @@ -23,63 +24,22 @@ Future bootstrap(String uri) { /** * To be called from the main thread to spawn and communicate with the worker thread */ -Future spawnWebWorker(Uri uri) { +Future spawnWebWorker(Uri uri) { var receivePort = new ReceivePort(); var isolateEndSendPort = receivePort.sendPort; return Isolate.spawnUri(uri, const [], isolateEndSendPort).then((_) { var source = new UIMessageBusSource(receivePort); return source.sink.then((sendPort) { - var sink = new UIMessageBusSink(sendPort); - return new UIMessageBus(sink, source); + var sink = new IsolateMessageBusSink(sendPort); + return new IsolateMessageBus(sink, source); }); }); } -class UIMessageBus extends MessageBus { - final UIMessageBusSink sink; - final UIMessageBusSource source; - - UIMessageBus(UIMessageBusSink sink, UIMessageBusSource source) - : sink = sink, - source = source; -} - -class UIMessageBusSink extends MessageBusSink { - final SendPort _port; - - UIMessageBusSink(SendPort port) : _port = port; - - void send(message) { - _port.send(message); - } -} - -class UIMessageBusSource extends MessageBusSource { - final ReceivePort _port; - final Stream rawDataStream; - Map _listenerStore = - new Map(); - int _numListeners = 0; - - UIMessageBusSource(ReceivePort port) - : _port = port, - rawDataStream = port.asBroadcastStream(); +class UIMessageBusSource extends IsolateMessageBusSource { + UIMessageBusSource(ReceivePort port) : super(port); Future get sink => rawDataStream.firstWhere((message) { return message is SendPort; }); - - int addListener(Function fn) { - var subscription = rawDataStream.listen((message) { - fn({"data": message}); - }); - - _listenerStore[++_numListeners] = subscription; - return _numListeners; - } - - void removeListener(int index) { - _listenerStore[index].cancel(); - _listenerStore.remove(index); - } } diff --git a/modules/angular2/src/web-workers/ui/application.ts b/modules/angular2/src/web-workers/ui/application.ts index 8dcfa1b30b..4d898c0a03 100644 --- a/modules/angular2/src/web-workers/ui/application.ts +++ b/modules/angular2/src/web-workers/ui/application.ts @@ -1,9 +1,9 @@ import { - MessageBus, - MessageBusSource, - MessageBusSink, - SourceListener -} from "angular2/src/web-workers/shared/message_bus"; + PostMessageBus, + PostMessageBusSink, + PostMessageBusSource +} from 'angular2/src/web-workers/shared/post_message_bus'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; import {BaseException} from "angular2/src/facade/lang"; import {bootstrapUICommon} from "angular2/src/web-workers/ui/impl"; @@ -23,33 +23,7 @@ export function bootstrap(uri: string): MessageBus { export function spawnWebWorker(uri: string): MessageBus { var webWorker: Worker = new Worker(uri); - return new UIMessageBus(new UIMessageBusSink(webWorker), new UIMessageBusSource(webWorker)); -} - -export class UIMessageBus implements MessageBus { - constructor(public sink: UIMessageBusSink, public source: UIMessageBusSource) {} -} - -export class UIMessageBusSink implements MessageBusSink { - constructor(private _webWorker: Worker) {} - - send(message: Object): void { this._webWorker.postMessage(message); } -} - -export class UIMessageBusSource implements MessageBusSource { - private _listenerStore: Map = new Map(); - private _numListeners: int = 0; - - constructor(private _webWorker: Worker) {} - - public addListener(fn: SourceListener): int { - this._webWorker.addEventListener("message", fn); - this._listenerStore[++this._numListeners] = fn; - return this._numListeners; - } - - public removeListener(index: int): void { - removeEventListener("message", this._listenerStore[index]); - this._listenerStore.delete(index); - } + var sink = new PostMessageBusSink(webWorker); + var source = new PostMessageBusSource(webWorker); + return new PostMessageBus(sink, source); } diff --git a/modules/angular2/src/web-workers/ui/di_bindings.ts b/modules/angular2/src/web-workers/ui/di_bindings.ts index 985e1ec402..accaeb88a4 100644 --- a/modules/angular2/src/web-workers/ui/di_bindings.ts +++ b/modules/angular2/src/web-workers/ui/di_bindings.ts @@ -62,6 +62,11 @@ import { } from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl'; +import {MessageBus, MessageBusInterface} from 'angular2/src/web-workers/shared/message_bus'; +import {MessageBasedRenderCompiler} from 'angular2/src/web-workers/ui/render_compiler'; +import {MessageBasedRenderer} from 'angular2/src/web-workers/ui/renderer'; +import {MessageBasedXHRImpl} from 'angular2/src/web-workers/ui/xhr_impl'; +import {WebWorkerSetup} from 'angular2/src/web-workers/ui/setup'; var _rootInjector: Injector; @@ -129,13 +134,18 @@ function _injectorBindings(): List> { Testability, AnchorBasedAppRootUrl, bind(AppRootUrl).toAlias(AnchorBasedAppRootUrl), - WebWorkerMain + WebWorkerMain, + WebWorkerSetup, + MessageBasedRenderCompiler, + MessageBasedXHRImpl, + MessageBasedRenderer ]; } -export function createInjector(zone: NgZone): Injector { +export function createInjector(zone: NgZone, bus: MessageBusInterface): Injector { BrowserDomAdapter.makeCurrent(); _rootBindings.push(bind(NgZone).toValue(zone)); + _rootBindings.push(bind(MessageBus).toValue(bus)); var injector: Injector = Injector.resolveAndCreate(_rootBindings); return injector.resolveAndCreateChild(_injectorBindings()); } diff --git a/modules/angular2/src/web-workers/ui/event_dispatcher.ts b/modules/angular2/src/web-workers/ui/event_dispatcher.ts new file mode 100644 index 0000000000..bdbad5b54a --- /dev/null +++ b/modules/angular2/src/web-workers/ui/event_dispatcher.ts @@ -0,0 +1,110 @@ +import { + RenderViewRef, + RenderEventDispatcher, +} from 'angular2/src/render/api'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import { + serializeMouseEvent, + serializeKeyboardEvent, + serializeGenericEvent, + serializeEventWithTarget +} from 'angular2/src/web-workers/ui/event_serializer'; +import {BaseException} from "angular2/src/facade/lang"; +import {StringMapWrapper} from 'angular2/src/facade/collection'; +import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; + +export class EventDispatcher implements RenderEventDispatcher { + constructor(private _viewRef: RenderViewRef, private _sink: EventEmitter, + private _serializer: Serializer) {} + + dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map) { + var e = locals.get('$event'); + var serializedEvent; + // TODO (jteplitz602): support custom events #3350 + switch (e.type) { + case "click": + case "mouseup": + case "mousedown": + case "dblclick": + case "contextmenu": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "show": + serializedEvent = serializeMouseEvent(e); + break; + case "keydown": + case "keypress": + case "keyup": + serializedEvent = serializeKeyboardEvent(e); + break; + case "input": + case "change": + case "blur": + serializedEvent = serializeEventWithTarget(e); + break; + case "abort": + case "afterprint": + case "beforeprint": + case "cached": + case "canplay": + case "canplaythrough": + case "chargingchange": + case "chargingtimechange": + case "close": + case "dischargingtimechange": + case "DOMContentLoaded": + case "downloading": + case "durationchange": + case "emptied": + case "ended": + case "error": + case "fullscreenchange": + case "fullscreenerror": + case "invalid": + case "languagechange": + case "levelfchange": + case "loadeddata": + case "loadedmetadata": + case "obsolete": + case "offline": + case "online": + case "open": + case "orientatoinchange": + case "pause": + case "pointerlockchange": + case "pointerlockerror": + case "play": + case "playing": + case "ratechange": + case "readystatechange": + case "reset": + case "seeked": + case "seeking": + case "stalled": + case "submit": + case "success": + case "suspend": + case "timeupdate": + case "updateready": + case "visibilitychange": + case "volumechange": + case "waiting": + serializedEvent = serializeGenericEvent(e); + break; + default: + throw new BaseException(eventName + " not supported on WebWorkers"); + } + var serializedLocals = StringMapWrapper.create(); + StringMapWrapper.set(serializedLocals, '$event', serializedEvent); + + ObservableWrapper.callNext(this._sink, { + "viewRef": this._serializer.serialize(this._viewRef, RenderViewRef), + "elementIndex": elementIndex, + "eventName": eventName, + "locals": serializedLocals + }); + } +} diff --git a/modules/angular2/src/web-workers/ui/impl.ts b/modules/angular2/src/web-workers/ui/impl.ts index 0421d3c103..ffa1e4893c 100644 --- a/modules/angular2/src/web-workers/ui/impl.ts +++ b/modules/angular2/src/web-workers/ui/impl.ts @@ -6,39 +6,15 @@ */ import {createInjector} from "./di_bindings"; -import { - Renderer, - RenderCompiler, - RenderDirectiveMetadata, - ProtoViewDto, - ViewDefinition, - RenderProtoViewRef, - RenderProtoViewMergeMapping, - RenderViewRef, - RenderEventDispatcher, - RenderFragmentRef -} from "angular2/src/render/api"; -import {Type, print, BaseException, isFunction} from "angular2/src/facade/lang"; -import {Promise, PromiseWrapper} from "angular2/src/facade/async"; -import {StringMapWrapper, SetWrapper} from 'angular2/src/facade/collection'; -import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {MessageBus, MessageBusSink} from "angular2/src/web-workers/shared/message_bus"; -import { - RenderViewWithFragmentsStore -} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; import {createNgZone} from 'angular2/src/core/application_common'; -import {WebWorkerElementRef} from 'angular2/src/web-workers/shared/api'; -import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; import {Injectable} from 'angular2/di'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; -import {XHR} from 'angular2/src/render/xhr'; -import { - serializeMouseEvent, - serializeKeyboardEvent, - serializeGenericEvent, - serializeEventWithTarget -} from 'angular2/src/web-workers/ui/event_serializer'; import {wtfInit} from 'angular2/src/profile/wtf_init'; +import {WebWorkerSetup} from 'angular2/src/web-workers/ui/setup'; +import {MessageBasedRenderCompiler} from 'angular2/src/web-workers/ui/render_compiler'; +import {MessageBasedRenderer} from 'angular2/src/web-workers/ui/renderer'; +import {MessageBasedXHRImpl} from 'angular2/src/web-workers/ui/xhr_impl'; /** * Creates a zone, sets up the DI bindings @@ -49,305 +25,20 @@ export function bootstrapUICommon(bus: MessageBus) { var zone = createNgZone(); wtfInit(); zone.run(() => { - var injector = createInjector(zone); - var webWorkerMain = injector.get(WebWorkerMain); - webWorkerMain.attachToWebWorker(bus); + var injector = createInjector(zone, bus); + // necessary to kick off all the message based components + injector.get(WebWorkerMain); }); } @Injectable() export class WebWorkerMain { - private _rootUrl: string; - private _bus: MessageBus; - - constructor(private _renderCompiler: RenderCompiler, private _renderer: Renderer, - private _renderViewWithFragmentsStore: RenderViewWithFragmentsStore, - private _serializer: Serializer, rootUrl: AnchorBasedAppRootUrl, private _xhr: XHR) { - this._rootUrl = rootUrl.value; - } - - /** - * Attach's this WebWorkerMain instance to the given MessageBus - * This instance will now listen for all messages from the worker and handle them appropriately - * Note: Don't attach more than one WebWorkerMain instance to the same MessageBus. - */ - attachToWebWorker(bus: MessageBus) { - this._bus = bus; - this._bus.source.addListener((message) => { this._handleWebWorkerMessage(message); }); - } - - private _sendInitMessage() { this._sendWebWorkerMessage("init", {"rootUrl": this._rootUrl}); } - - /* - * Sends an error back to the worker thread in response to an opeartion on the UI thread - */ - private _sendWebWorkerError(id: string, error: any) { - this._sendWebWorkerMessage("error", {"error": error}, id); - } - - private _sendWebWorkerMessage(type: string, value: StringMap, id?: string) { - this._bus.sink.send({'type': type, 'id': id, 'value': value}); - } - - // TODO: Transfer the types with the serialized data so this can be automated? - private _handleCompilerMessage(data: ReceivedMessage) { - var promise: Promise; - switch (data.method) { - case "compileHost": - var directiveMetadata = this._serializer.deserialize(data.args[0], RenderDirectiveMetadata); - promise = this._renderCompiler.compileHost(directiveMetadata); - this._wrapWebWorkerPromise(data.id, promise, ProtoViewDto); - break; - case "compile": - var view = this._serializer.deserialize(data.args[0], ViewDefinition); - promise = this._renderCompiler.compile(view); - this._wrapWebWorkerPromise(data.id, promise, ProtoViewDto); - break; - case "mergeProtoViewsRecursively": - var views = this._serializer.deserialize(data.args[0], RenderProtoViewRef); - promise = this._renderCompiler.mergeProtoViewsRecursively(views); - this._wrapWebWorkerPromise(data.id, promise, RenderProtoViewMergeMapping); - break; - default: - throw new BaseException("not implemented"); - } - } - - private _createViewHelper(args: List, method) { - var hostProtoView = this._serializer.deserialize(args[0], RenderProtoViewRef); - var fragmentCount = args[1]; - var startIndex, renderViewWithFragments; - if (method == "createView") { - startIndex = args[2]; - renderViewWithFragments = this._renderer.createView(hostProtoView, fragmentCount); - } else { - var selector = args[2]; - startIndex = args[3]; - renderViewWithFragments = - this._renderer.createRootHostView(hostProtoView, fragmentCount, selector); - } - this._renderViewWithFragmentsStore.store(renderViewWithFragments, startIndex); - } - - private _handleRendererMessage(data: ReceivedMessage) { - var args = data.args; - switch (data.method) { - case "createRootHostView": - case "createView": - this._createViewHelper(args, data.method); - break; - case "destroyView": - var viewRef = this._serializer.deserialize(args[0], RenderViewRef); - this._renderer.destroyView(viewRef); - break; - case "attachFragmentAfterFragment": - var previousFragment = this._serializer.deserialize(args[0], RenderFragmentRef); - var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); - this._renderer.attachFragmentAfterFragment(previousFragment, fragment); - break; - case "attachFragmentAfterElement": - var element = this._serializer.deserialize(args[0], WebWorkerElementRef); - var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); - this._renderer.attachFragmentAfterElement(element, fragment); - break; - case "detachFragment": - var fragment = this._serializer.deserialize(args[0], RenderFragmentRef); - this._renderer.detachFragment(fragment); - break; - case "hydrateView": - var viewRef = this._serializer.deserialize(args[0], RenderViewRef); - this._renderer.hydrateView(viewRef); - break; - case "dehydrateView": - var viewRef = this._serializer.deserialize(args[0], RenderViewRef); - this._renderer.dehydrateView(viewRef); - break; - case "setText": - var viewRef = this._serializer.deserialize(args[0], RenderViewRef); - var textNodeIndex = args[1]; - var text = args[2]; - this._renderer.setText(viewRef, textNodeIndex, text); - break; - case "setElementProperty": - var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); - var propName = args[1]; - var propValue = args[2]; - this._renderer.setElementProperty(elementRef, propName, propValue); - break; - case "setElementAttribute": - var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); - var attributeName = args[1]; - var attributeValue = args[2]; - this._renderer.setElementAttribute(elementRef, attributeName, attributeValue); - break; - case "setElementClass": - var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); - var className = args[1]; - var isAdd = args[2]; - this._renderer.setElementClass(elementRef, className, isAdd); - break; - case "setElementStyle": - var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); - var styleName = args[1]; - var styleValue = args[2]; - this._renderer.setElementStyle(elementRef, styleName, styleValue); - break; - case "invokeElementMethod": - var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); - var methodName = args[1]; - var methodArgs = args[2]; - this._renderer.invokeElementMethod(elementRef, methodName, methodArgs); - break; - case "setEventDispatcher": - var viewRef = this._serializer.deserialize(args[0], RenderViewRef); - var dispatcher = new EventDispatcher(viewRef, this._bus.sink, this._serializer); - this._renderer.setEventDispatcher(viewRef, dispatcher); - break; - default: - throw new BaseException("Not Implemented"); - } - } - - private _handleXhrMessage(data: ReceivedMessage) { - var args = data.args; - switch (data.method) { - case "get": - var url = args[0]; - var promise = this._xhr.get(url); - this._wrapWebWorkerPromise(data.id, promise, String); - break; - default: - throw new BaseException(data.method + " Not Implemented"); - } - } - - // TODO(jteplitz602): Create message type enum #3044 - private _handleWebWorkerMessage(message: StringMap) { - var data: ReceivedMessage = new ReceivedMessage(message['data']); - // TODO(jteplitz602): Replace these with MessageBUs channels #3661 - switch (data.type) { - case "ready": - return this._sendInitMessage(); - case "compiler": - return this._handleCompilerMessage(data); - case "renderer": - return this._handleRendererMessage(data); - case "xhr": - return this._handleXhrMessage(data); - } - } - - private _wrapWebWorkerPromise(id: string, promise: Promise, type: Type): void { - PromiseWrapper.then(promise, (result: any) => { - try { - this._sendWebWorkerMessage("result", this._serializer.serialize(result, type), id); - } catch (e) { - print(e); - } - }, (error: any) => { this._sendWebWorkerError(id, error); }); - } + constructor(public renderCompiler: MessageBasedRenderCompiler, + public renderer: MessageBasedRenderer, public xhr: MessageBasedXHRImpl, + public setup: WebWorkerSetup) {} } -class EventDispatcher implements RenderEventDispatcher { - constructor(private _viewRef: RenderViewRef, private _sink: MessageBusSink, - private _serializer: Serializer) {} - - dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map) { - var e = locals.get('$event'); - var serializedEvent; - // TODO (jteplitz602): support custom events #3350 - switch (e.type) { - case "click": - case "mouseup": - case "mousedown": - case "dblclick": - case "contextmenu": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "show": - serializedEvent = serializeMouseEvent(e); - break; - case "keydown": - case "keypress": - case "keyup": - serializedEvent = serializeKeyboardEvent(e); - break; - case "input": - case "change": - case "blur": - serializedEvent = serializeEventWithTarget(e); - break; - case "abort": - case "afterprint": - case "beforeprint": - case "cached": - case "canplay": - case "canplaythrough": - case "chargingchange": - case "chargingtimechange": - case "close": - case "dischargingtimechange": - case "DOMContentLoaded": - case "downloading": - case "durationchange": - case "emptied": - case "ended": - case "error": - case "fullscreenchange": - case "fullscreenerror": - case "invalid": - case "languagechange": - case "levelfchange": - case "loadeddata": - case "loadedmetadata": - case "obsolete": - case "offline": - case "online": - case "open": - case "orientatoinchange": - case "pause": - case "pointerlockchange": - case "pointerlockerror": - case "play": - case "playing": - case "ratechange": - case "readystatechange": - case "reset": - case "seeked": - case "seeking": - case "stalled": - case "submit": - case "success": - case "suspend": - case "timeupdate": - case "updateready": - case "visibilitychange": - case "volumechange": - case "waiting": - serializedEvent = serializeGenericEvent(e); - break; - default: - throw new BaseException(eventName + " not supported on WebWorkers"); - } - var serializedLocals = StringMapWrapper.create(); - StringMapWrapper.set(serializedLocals, '$event', serializedEvent); - - this._sink.send({ - "type": "event", - "value": { - "viewRef": this._serializer.serialize(this._viewRef, RenderViewRef), - "elementIndex": elementIndex, - "eventName": eventName, - "locals": serializedLocals - } - }); - } -} - -class ReceivedMessage { +export class ReceivedMessage { method: string; args: List; id: string; diff --git a/modules/angular2/src/web-workers/ui/render_compiler.ts b/modules/angular2/src/web-workers/ui/render_compiler.ts new file mode 100644 index 0000000000..1efbfef675 --- /dev/null +++ b/modules/angular2/src/web-workers/ui/render_compiler.ts @@ -0,0 +1,63 @@ +import {Injectable} from 'angular2/di'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import { + RenderDirectiveMetadata, + ProtoViewDto, + ViewDefinition, + RenderProtoViewRef, + RenderProtoViewMergeMapping, + RenderCompiler +} from 'angular2/src/render/api'; +import {EventEmitter, ObservableWrapper, PromiseWrapper, Promise} from 'angular2/src/facade/async'; +import {RENDER_COMPILER_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {ReceivedMessage} from 'angular2/src/web-workers/ui/impl'; +import {BaseException, Type} from 'angular2/src/facade/lang'; + +// TODO(jteplitz602): Create parent UIComponent class #3703 +@Injectable() +export class MessageBasedRenderCompiler { + private _sink: EventEmitter; + private _source: EventEmitter; + + constructor(bus: MessageBus, private _serializer: Serializer, + private _renderCompiler: RenderCompiler) { + this._sink = bus.to(RENDER_COMPILER_CHANNEL); + this._source = bus.from(RENDER_COMPILER_CHANNEL); + ObservableWrapper.subscribe(this._source, + (message: StringMap) => this._handleMessage(message)); + } + + private _handleMessage(map: StringMap): void { + var message = new ReceivedMessage(map); + var args = message.args; + var promise: Promise; + switch (message.method) { + case "compileHost": + var directiveMetadata = this._serializer.deserialize(args[0], RenderDirectiveMetadata); + promise = this._renderCompiler.compileHost(directiveMetadata); + this._wrapWebWorkerPromise(message.id, promise, ProtoViewDto); + break; + case "compile": + var view = this._serializer.deserialize(args[0], ViewDefinition); + promise = this._renderCompiler.compile(view); + this._wrapWebWorkerPromise(message.id, promise, ProtoViewDto); + break; + case "mergeProtoViewsRecursively": + var views = this._serializer.deserialize(args[0], RenderProtoViewRef); + promise = this._renderCompiler.mergeProtoViewsRecursively(views); + this._wrapWebWorkerPromise(message.id, promise, RenderProtoViewMergeMapping); + break; + default: + throw new BaseException("not implemented"); + } + } + + private _wrapWebWorkerPromise(id: string, promise: Promise, type: Type): void { + PromiseWrapper.then(promise, (result: any) => { + ObservableWrapper.callNext( + this._sink, + {'type': 'result', 'value': this._serializer.serialize(result, type), 'id': id}); + }); + } +} diff --git a/modules/angular2/src/web-workers/ui/renderer.ts b/modules/angular2/src/web-workers/ui/renderer.ts new file mode 100644 index 0000000000..2450f5fd3c --- /dev/null +++ b/modules/angular2/src/web-workers/ui/renderer.ts @@ -0,0 +1,127 @@ +import {Injectable} from 'angular2/di'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import { + RenderViewRef, + RenderFragmentRef, + RenderProtoViewRef, + Renderer +} from 'angular2/src/render/api'; +import {WebWorkerElementRef} from 'angular2/src/web-workers/shared/api'; +import {EventEmitter, ObservableWrapper, PromiseWrapper, Promise} from 'angular2/src/facade/async'; +import {EVENT_CHANNEL, RENDERER_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {ReceivedMessage} from 'angular2/src/web-workers/ui/impl'; +import {BaseException, Type} from 'angular2/src/facade/lang'; +import {EventDispatcher} from 'angular2/src/web-workers/ui/event_dispatcher'; +import { + RenderViewWithFragmentsStore +} from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; + +@Injectable() +export class MessageBasedRenderer { + constructor(private _bus: MessageBus, private _serializer: Serializer, + private _renderViewWithFragmentsStore: RenderViewWithFragmentsStore, + private _renderer: Renderer) { + var source = _bus.from(RENDERER_CHANNEL); + ObservableWrapper.subscribe(source, + (message: StringMap) => this._handleMessage(message)); + } + + private _createViewHelper(args: List, method) { + var hostProtoView = this._serializer.deserialize(args[0], RenderProtoViewRef); + var fragmentCount = args[1]; + var startIndex, renderViewWithFragments; + if (method == "createView") { + startIndex = args[2]; + renderViewWithFragments = this._renderer.createView(hostProtoView, fragmentCount); + } else { + var selector = args[2]; + startIndex = args[3]; + renderViewWithFragments = + this._renderer.createRootHostView(hostProtoView, fragmentCount, selector); + } + this._renderViewWithFragmentsStore.store(renderViewWithFragments, startIndex); + } + + + private _handleMessage(map: StringMap): void { + var data = new ReceivedMessage(map); + var args = data.args; + switch (data.method) { + case "createRootHostView": + case "createView": + this._createViewHelper(args, data.method); + break; + case "destroyView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.destroyView(viewRef); + break; + case "attachFragmentAfterFragment": + var previousFragment = this._serializer.deserialize(args[0], RenderFragmentRef); + var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); + this._renderer.attachFragmentAfterFragment(previousFragment, fragment); + break; + case "attachFragmentAfterElement": + var element = this._serializer.deserialize(args[0], WebWorkerElementRef); + var fragment = this._serializer.deserialize(args[1], RenderFragmentRef); + this._renderer.attachFragmentAfterElement(element, fragment); + break; + case "detachFragment": + var fragment = this._serializer.deserialize(args[0], RenderFragmentRef); + this._renderer.detachFragment(fragment); + break; + case "hydrateView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.hydrateView(viewRef); + break; + case "dehydrateView": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + this._renderer.dehydrateView(viewRef); + break; + case "setText": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + var textNodeIndex = args[1]; + var text = args[2]; + this._renderer.setText(viewRef, textNodeIndex, text); + break; + case "setElementProperty": + var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); + var propName = args[1]; + var propValue = args[2]; + this._renderer.setElementProperty(elementRef, propName, propValue); + break; + case "setElementAttribute": + var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); + var attributeName = args[1]; + var attributeValue = args[2]; + this._renderer.setElementAttribute(elementRef, attributeName, attributeValue); + break; + case "setElementClass": + var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); + var className = args[1]; + var isAdd = args[2]; + this._renderer.setElementClass(elementRef, className, isAdd); + break; + case "setElementStyle": + var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); + var styleName = args[1]; + var styleValue = args[2]; + this._renderer.setElementStyle(elementRef, styleName, styleValue); + break; + case "invokeElementMethod": + var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef); + var methodName = args[1]; + var methodArgs = args[2]; + this._renderer.invokeElementMethod(elementRef, methodName, methodArgs); + break; + case "setEventDispatcher": + var viewRef = this._serializer.deserialize(args[0], RenderViewRef); + var dispatcher = + new EventDispatcher(viewRef, this._bus.to(EVENT_CHANNEL), this._serializer); + this._renderer.setEventDispatcher(viewRef, dispatcher); + break; + default: + throw new BaseException("Not Implemented"); + } + } +} diff --git a/modules/angular2/src/web-workers/ui/setup.ts b/modules/angular2/src/web-workers/ui/setup.ts new file mode 100644 index 0000000000..89b1a0b410 --- /dev/null +++ b/modules/angular2/src/web-workers/ui/setup.ts @@ -0,0 +1,20 @@ +import {SETUP_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; +import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; +import {Injectable} from 'angular2/di'; + +@Injectable() +export class WebWorkerSetup { + constructor(bus: MessageBus, anchorBasedAppRootUrl: AnchorBasedAppRootUrl) { + var rootUrl = anchorBasedAppRootUrl.value; + var sink = bus.to(SETUP_CHANNEL); + var source = bus.from(SETUP_CHANNEL); + + ObservableWrapper.subscribe(source, (message: string) => { + if (message === "ready") { + ObservableWrapper.callNext(sink, {"rootUrl": rootUrl}); + } + }); + } +} \ No newline at end of file diff --git a/modules/angular2/src/web-workers/ui/xhr_impl.ts b/modules/angular2/src/web-workers/ui/xhr_impl.ts new file mode 100644 index 0000000000..683762d45f --- /dev/null +++ b/modules/angular2/src/web-workers/ui/xhr_impl.ts @@ -0,0 +1,44 @@ +import {Injectable} from 'angular2/di'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import {EventEmitter, ObservableWrapper, PromiseWrapper, Promise} from 'angular2/src/facade/async'; +import {XHR_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {ReceivedMessage} from 'angular2/src/web-workers/ui/impl'; +import {BaseException, Type} from 'angular2/src/facade/lang'; +import {XHR} from 'angular2/src/render/xhr'; + +// TODO(jteplitz602): Create parent UIComponent class #3703 +@Injectable() +export class MessageBasedXHRImpl { + private _sink: EventEmitter; + private _source: EventEmitter; + + constructor(bus: MessageBus, private _serializer: Serializer, private _xhr: XHR) { + this._sink = bus.to(XHR_CHANNEL); + this._source = bus.from(XHR_CHANNEL); + ObservableWrapper.subscribe(this._source, + (message: StringMap) => this._handleMessage(message)); + } + + private _handleMessage(map: StringMap) { + var message = new ReceivedMessage(map); + var args = message.args; + switch (message.method) { + case "get": + var url = args[0]; + var promise = this._xhr.get(url); + this._wrapWebWorkerPromise(message.id, promise, String); + break; + default: + throw new BaseException(message.method + " Not Implemented"); + } + } + + private _wrapWebWorkerPromise(id: string, promise: Promise, type: Type): void { + PromiseWrapper.then(promise, (result: any) => { + ObservableWrapper.callNext( + this._sink, + {'type': 'result', 'value': this._serializer.serialize(result, type), 'id': id}); + }); + } +} diff --git a/modules/angular2/src/web-workers/worker/application.dart b/modules/angular2/src/web-workers/worker/application.dart index e34bbc7150..5bb666cb84 100644 --- a/modules/angular2/src/web-workers/worker/application.dart +++ b/modules/angular2/src/web-workers/worker/application.dart @@ -1,7 +1,6 @@ library angular2.src.web_workers.worker; -import "package:angular2/src/web-workers/shared/message_bus.dart" - show MessageBus, MessageBusSource, MessageBusSink; +import "package:angular2/src/web-workers/shared/isolate_message_bus.dart"; import "package:angular2/src/web-workers/worker/application_common.dart" show bootstrapWebWorkerCommon; import "package:angular2/src/facade/async.dart" show Future; @@ -26,56 +25,15 @@ Future bootstrapWebWorker( SendPort replyTo, Type appComponentType, [List componentInjectableBindings = null]) { ReceivePort rPort = new ReceivePort(); - WebWorkerMessageBus bus = new WebWorkerMessageBus.fromPorts(replyTo, rPort); + var sink = new WebWorkerMessageBusSink(replyTo, rPort); + var source = new IsolateMessageBusSource(rPort); + IsolateMessageBus bus = new IsolateMessageBus(sink, source); return bootstrapWebWorkerCommon( appComponentType, bus, componentInjectableBindings); } -class WebWorkerMessageBus extends MessageBus { - final WebWorkerMessageBusSink sink; - final WebWorkerMessageBusSource source; - - WebWorkerMessageBus(this.sink, this.source); - - WebWorkerMessageBus.fromPorts(SendPort sPort, ReceivePort rPort) - : sink = new WebWorkerMessageBusSink(sPort, rPort), - source = new WebWorkerMessageBusSource(rPort); -} - -class WebWorkerMessageBusSink extends MessageBusSink { - final SendPort _port; - - WebWorkerMessageBusSink(SendPort sPort, ReceivePort rPort) : _port = sPort { - this.send(rPort.sendPort); - } - - void send(dynamic message) { - this._port.send(message); - } -} - -class WebWorkerMessageBusSource extends MessageBusSource { - final ReceivePort _port; - final Stream rawDataStream; - Map _listenerStore = - new Map(); - int _numListeners = 0; - - WebWorkerMessageBusSource(ReceivePort rPort) - : _port = rPort, - rawDataStream = rPort.asBroadcastStream(); - - int addListener(Function fn) { - var subscription = rawDataStream.listen((message) { - fn({"data": message}); - }); - - _listenerStore[++_numListeners] = subscription; - return _numListeners; - } - - void removeListener(int index) { - _listenerStore[index].cancel(); - _listenerStore.remove(index); +class WebWorkerMessageBusSink extends IsolateMessageBusSink { + WebWorkerMessageBusSink(SendPort sPort, ReceivePort rPort) : super(sPort) { + sPort.send(rPort.sendPort); } } diff --git a/modules/angular2/src/web-workers/worker/application.ts b/modules/angular2/src/web-workers/worker/application.ts index e0e5c302b2..50c34e0b3f 100644 --- a/modules/angular2/src/web-workers/worker/application.ts +++ b/modules/angular2/src/web-workers/worker/application.ts @@ -1,18 +1,21 @@ import { - MessageBus, - MessageBusSource, - MessageBusSink, - SourceListener -} from "angular2/src/web-workers/shared/message_bus"; + PostMessageBus, + PostMessageBusSink, + PostMessageBusSource +} from 'angular2/src/web-workers/shared/post_message_bus'; import {Type, BaseException} from "angular2/src/facade/lang"; import {Binding} from "angular2/di"; - +import {Map} from 'angular2/src/facade/collection'; +import {Promise} from 'angular2/src/facade/async'; import {bootstrapWebWorkerCommon} from "angular2/src/web-workers/worker/application_common"; import {ApplicationRef} from "angular2/src/core/application"; import {Injectable} from "angular2/di"; // TODO(jteplitz602) remove this and compile with lib.webworker.d.ts (#3492) -var _postMessage: (message: any, transferrables?:[ArrayBuffer]) => void = postMessage; +interface PostMessageInterface { + (message: any, transferrables?:[ArrayBuffer]): void; +} +var _postMessage: PostMessageInterface = postMessage; /** * Bootstrapping a Webworker Application @@ -26,44 +29,12 @@ var _postMessage: (message: any, transferrables?:[ArrayBuffer]) => void = p export function bootstrapWebWorker( appComponentType: Type, componentInjectableBindings: List> = null): Promise { - var bus: WebWorkerMessageBus = - new WebWorkerMessageBus(new WebWorkerMessageBusSink(), new WebWorkerMessageBusSource()); + var sink = new PostMessageBusSink({ + postMessage: + (message: any, transferrables?:[ArrayBuffer]) => { _postMessage(message, transferrables); } + }); + var source = new PostMessageBusSource(); + var bus = new PostMessageBus(sink, source); return bootstrapWebWorkerCommon(appComponentType, bus, componentInjectableBindings); } - -@Injectable() -export class WebWorkerMessageBus implements MessageBus { - sink: WebWorkerMessageBusSink; - source: WebWorkerMessageBusSource; - - constructor(sink: WebWorkerMessageBusSink, source: WebWorkerMessageBusSource) { - this.sink = sink; - this.source = source; - } -} - -export class WebWorkerMessageBusSink implements MessageBusSink { - public send(message: Object) { _postMessage(message); } -} - -export class WebWorkerMessageBusSource implements MessageBusSource { - private listenerStore: Map; - private numListeners: int; - - constructor() { - this.numListeners = 0; - this.listenerStore = new Map(); - } - - public addListener(fn: SourceListener): int { - addEventListener("message", fn); - this.listenerStore[++this.numListeners] = fn; - return this.numListeners; - } - - public removeListener(index: int): void { - removeEventListener("message", this.listenerStore[index]); - this.listenerStore.delete(index); - } -} diff --git a/modules/angular2/src/web-workers/worker/application_common.ts b/modules/angular2/src/web-workers/worker/application_common.ts index 839bba6015..c52f27d716 100644 --- a/modules/angular2/src/web-workers/worker/application_common.ts +++ b/modules/angular2/src/web-workers/worker/application_common.ts @@ -52,8 +52,8 @@ import {WebWorkerRenderer, WebWorkerCompiler} from './renderer'; import {Renderer, RenderCompiler} from 'angular2/src/render/api'; import {internalView} from 'angular2/src/core/compiler/view_ref'; -import {MessageBroker} from 'angular2/src/web-workers/worker/broker'; -import {WebWorkerMessageBus} from 'angular2/src/web-workers/worker/application'; +import {MessageBrokerFactory} from 'angular2/src/web-workers/worker/broker'; +import {MessageBus, MessageBusInterface} from 'angular2/src/web-workers/shared/message_bus'; import {APP_COMPONENT_REF_PROMISE, APP_COMPONENT} from 'angular2/src/core/application_tokens'; import {ApplicationRef} from 'angular2/src/core/application'; import {createNgZone} from 'angular2/src/core/application_common'; @@ -63,6 +63,9 @@ import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_pr import { RenderViewWithFragmentsStore } from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; +import {ObservableWrapper} from 'angular2/src/facade/async'; +import {SETUP_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {WebWorkerEventDispatcher} from 'angular2/src/web-workers/worker/event_dispatcher'; var _rootInjector: Injector; @@ -75,7 +78,7 @@ class PrintLogger { logGroupEnd() {} } -function _injectorBindings(appComponentType, bus: WebWorkerMessageBus, +function _injectorBindings(appComponentType, bus: MessageBusInterface, initData: StringMap): List> { var bestChangeDetection: Type = DynamicChangeDetection; if (PreGeneratedChangeDetection.isSupported()) { @@ -100,10 +103,8 @@ function _injectorBindings(appComponentType, bus: WebWorkerMessageBus, bind(LifeCycle).toFactory((exceptionHandler) => new LifeCycle(null, assertionsEnabled()), [ExceptionHandler]), Serializer, - bind(WebWorkerMessageBus).toValue(bus), - bind(MessageBroker) - .toFactory((a, b, c) => new MessageBroker(a, b, c), - [WebWorkerMessageBus, Serializer, NgZone]), + bind(MessageBus).toValue(bus), + MessageBrokerFactory, WebWorkerRenderer, bind(Renderer).toAlias(WebWorkerRenderer), WebWorkerCompiler, @@ -136,12 +137,13 @@ function _injectorBindings(appComponentType, bus: WebWorkerMessageBus, StyleUrlResolver, DynamicComponentLoader, Testability, - bind(AppRootUrl).toValue(new AppRootUrl(initData['rootUrl'])) + bind(AppRootUrl).toValue(new AppRootUrl(initData['rootUrl'])), + WebWorkerEventDispatcher ]; } export function bootstrapWebWorkerCommon( - appComponentType: Type, bus: WebWorkerMessageBus, + appComponentType: Type, bus: MessageBusInterface, componentInjectableBindings: List> = null): Promise { var bootstrapProcess: PromiseCompleter = PromiseWrapper.completer(); @@ -151,14 +153,12 @@ export function bootstrapWebWorkerCommon( // index.html and main.js are possible. // - var listenerId: int; - listenerId = bus.source.addListener((message: StringMap) => { - if (message["data"]["type"] !== "init") { - return; - } - var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, - message["data"]["value"]); + var subscription: any; + var emitter = bus.from(SETUP_CHANNEL); + subscription = ObservableWrapper.subscribe(emitter, (message: StringMap) => { + var appInjector = + _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, message); var compRefToken = PromiseWrapper.wrap(() => { try { return appInjector.get(APP_COMPONENT_REF_PROMISE); @@ -178,17 +178,17 @@ export function bootstrapWebWorkerCommon( PromiseWrapper.then(compRefToken, tick, (err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); }); - bus.source.removeListener(listenerId); + ObservableWrapper.dispose(subscription); }); - bus.sink.send({'type': "ready"}); + ObservableWrapper.callNext(bus.to(SETUP_CHANNEL), "ready"); }); return bootstrapProcess.promise; } function _createAppInjector(appComponentType: Type, bindings: List>, - zone: NgZone, bus: WebWorkerMessageBus, + zone: NgZone, bus: MessageBusInterface, initData: StringMap): Injector { if (isBlank(_rootInjector)) _rootInjector = Injector.resolveAndCreate(_rootBindings); var mergedBindings: any[] = @@ -197,4 +197,4 @@ function _createAppInjector(appComponentType: Type, bindings: List import {MessageBus} from "angular2/src/web-workers/shared/message_bus"; import {print, isPresent, DateWrapper, stringify} from "../../facade/lang"; -import {Promise, PromiseCompleter, PromiseWrapper} from "angular2/src/facade/async"; +import { + Promise, + PromiseCompleter, + PromiseWrapper, + ObservableWrapper, + EventEmitter +} from "angular2/src/facade/async"; import {ListWrapper, StringMapWrapper, MapWrapper} from "../../facade/collection"; import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {Injectable} from "angular2/di"; import {Type} from "angular2/src/facade/lang"; -import {RenderViewRef, RenderEventDispatcher} from 'angular2/src/render/api'; -import {NgZone} from 'angular2/src/core/zone/ng_zone'; -import {deserializeGenericEvent} from './event_deserializer'; @Injectable() +export class MessageBrokerFactory { + constructor(private _messageBus: MessageBus, protected _serializer: Serializer) {} + + createMessageBroker(channel: string): MessageBroker { + return new MessageBroker(this._messageBus, this._serializer, channel); + } +} + export class MessageBroker { private _pending: Map> = new Map>(); - private _eventDispatchRegistry: Map = - new Map(); + private _sink: EventEmitter; - constructor(private _messageBus: MessageBus, protected _serializer: Serializer, - private _zone: NgZone) { - this._messageBus.source.addListener((data) => this._handleMessage(data['data'])); + constructor(messageBus: MessageBus, protected _serializer: Serializer, public channel) { + this._sink = messageBus.to(channel); + var source = messageBus.from(channel); + ObservableWrapper.subscribe(source, (message) => this._handleMessage(message)); } private _generateMessageId(name: string): string { @@ -48,7 +59,7 @@ export class MessageBroker { var id: string = null; if (returnType != null) { var completer: PromiseCompleter = PromiseWrapper.completer(); - id = this._generateMessageId(args.type + args.method); + id = this._generateMessageId(args.method); this._pending.set(id, completer); PromiseWrapper.catchError(completer.promise, (err, stack?) => { print(err); @@ -66,22 +77,20 @@ export class MessageBroker { promise = null; } - // TODO(jteplitz602): Create a class for these messages so we don't keep using StringMap - var message = {'type': args.type, 'method': args.method, 'args': fnArgs}; + // TODO(jteplitz602): Create a class for these messages so we don't keep using StringMap #3685 + var message = {'method': args.method, 'args': fnArgs}; if (id != null) { message['id'] = id; } - this._messageBus.sink.send(message); + ObservableWrapper.callNext(this._sink, message); return promise; } private _handleMessage(message: StringMap): void { var data = new MessageData(message); - // TODO(jteplitz602): replace these strings with messaging constants - if (data.type === "event") { - this._dispatchEvent(new RenderEventData(data.value, this._serializer)); - } else if (data.type === "result" || data.type === "error") { + // TODO(jteplitz602): replace these strings with messaging constants #3685 + if (data.type === "result" || data.type === "error") { var id = data.id; if (this._pending.has(id)) { if (data.type === "result") { @@ -93,32 +102,6 @@ export class MessageBroker { } } } - - private _dispatchEvent(eventData: RenderEventData): void { - var dispatcher = this._eventDispatchRegistry.get(eventData.viewRef); - this._zone.run(() => { - eventData.locals['$event'] = deserializeGenericEvent(eventData.locals['$event']); - dispatcher.dispatchRenderEvent(eventData.elementIndex, eventData.eventName, eventData.locals); - }); - } - - registerEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher): void { - this._eventDispatchRegistry.set(viewRef, dispatcher); - } -} - -class RenderEventData { - viewRef: RenderViewRef; - elementIndex: number; - eventName: string; - locals: Map; - - constructor(message: StringMap, serializer: Serializer) { - this.viewRef = serializer.deserialize(message['viewRef'], RenderViewRef); - this.elementIndex = message['elementIndex']; - this.eventName = message['eventName']; - this.locals = MapWrapper.createFromStringMap(message['locals']); - } } class MessageData { @@ -149,5 +132,5 @@ export class FnArg { } export class UiArguments { - constructor(public type: string, public method: string, public args?: List) {} + constructor(public method: string, public args?: List) {} } diff --git a/modules/angular2/src/web-workers/worker/event_dispatcher.ts b/modules/angular2/src/web-workers/worker/event_dispatcher.ts new file mode 100644 index 0000000000..61d4fcb99b --- /dev/null +++ b/modules/angular2/src/web-workers/worker/event_dispatcher.ts @@ -0,0 +1,49 @@ +import {Injectable} from 'angular2/di'; +import {Map, MapWrapper} from 'angular2/src/facade/collection'; +import {RenderViewRef, RenderEventDispatcher} from 'angular2/src/render/api'; +import {Serializer} from 'angular2/src/web-workers/shared/serializer'; +import {EVENT_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; +import {MessageBus} from 'angular2/src/web-workers/shared/message_bus'; +import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; +import {deserializeGenericEvent} from './event_deserializer'; +import {NgZone} from 'angular2/src/core/zone/ng_zone'; + +@Injectable() +export class WebWorkerEventDispatcher { + private _eventDispatchRegistry: Map = + new Map(); + + constructor(bus: MessageBus, private _serializer: Serializer, private _zone: NgZone) { + var source = bus.from(EVENT_CHANNEL); + ObservableWrapper.subscribe( + source, (message) => this._dispatchEvent(new RenderEventData(message, _serializer))); + } + + + private _dispatchEvent(eventData: RenderEventData): void { + var dispatcher = this._eventDispatchRegistry.get(eventData.viewRef); + this._zone.run(() => { + eventData.locals['$event'] = deserializeGenericEvent(eventData.locals['$event']); + dispatcher.dispatchRenderEvent(eventData.elementIndex, eventData.eventName, eventData.locals); + }); + } + + registerEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher): void { + this._eventDispatchRegistry.set(viewRef, dispatcher); + } +} + + +class RenderEventData { + viewRef: RenderViewRef; + elementIndex: number; + eventName: string; + locals: Map; + + constructor(message: StringMap, serializer: Serializer) { + this.viewRef = serializer.deserialize(message['viewRef'], RenderViewRef); + this.elementIndex = message['elementIndex']; + this.eventName = message['eventName']; + this.locals = MapWrapper.createFromStringMap(message['locals']); + } +} diff --git a/modules/angular2/src/web-workers/worker/renderer.ts b/modules/angular2/src/web-workers/worker/renderer.ts index 5c8b3fc1c2..a9ea94f6ac 100644 --- a/modules/angular2/src/web-workers/worker/renderer.ts +++ b/modules/angular2/src/web-workers/worker/renderer.ts @@ -13,7 +13,12 @@ import { RenderFragmentRef } from 'angular2/src/render/api'; import {Promise, PromiseWrapper} from "angular2/src/facade/async"; -import {MessageBroker, FnArg, UiArguments} from "angular2/src/web-workers/worker/broker"; +import { + MessageBroker, + MessageBrokerFactory, + FnArg, + UiArguments +} from "angular2/src/web-workers/worker/broker"; import {isPresent, print, BaseException} from "angular2/src/facade/lang"; import {Injectable} from "angular2/di"; import { @@ -21,16 +26,24 @@ import { WebWorkerRenderViewRef } from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; import {WebWorkerElementRef} from 'angular2/src/web-workers/shared/api'; +import { + RENDER_COMPILER_CHANNEL, + RENDERER_CHANNEL +} from 'angular2/src/web-workers/shared/messaging_api'; +import {WebWorkerEventDispatcher} from 'angular2/src/web-workers/worker/event_dispatcher'; @Injectable() export class WebWorkerCompiler implements RenderCompiler { - constructor(private _messageBroker: MessageBroker) {} + private _messageBroker; + constructor(messageBrokerFactory: MessageBrokerFactory) { + this._messageBroker = messageBrokerFactory.createMessageBroker(RENDER_COMPILER_CHANNEL); + } /** * Creats a ProtoViewDto that contains a single nested component with the given componentId. */ compileHost(directiveMetadata: RenderDirectiveMetadata): Promise { var fnArgs: List = [new FnArg(directiveMetadata, RenderDirectiveMetadata)]; - var args: UiArguments = new UiArguments("compiler", "compileHost", fnArgs); + var args: UiArguments = new UiArguments("compileHost", fnArgs); return this._messageBroker.runOnUiThread(args, ProtoViewDto); } @@ -41,7 +54,7 @@ export class WebWorkerCompiler implements RenderCompiler { */ compile(view: ViewDefinition): Promise { var fnArgs: List = [new FnArg(view, ViewDefinition)]; - var args: UiArguments = new UiArguments("compiler", "compile", fnArgs); + var args: UiArguments = new UiArguments("compile", fnArgs); return this._messageBroker.runOnUiThread(args, ProtoViewDto); } @@ -57,7 +70,7 @@ export class WebWorkerCompiler implements RenderCompiler { mergeProtoViewsRecursively( protoViewRefs: List>): Promise { var fnArgs: List = [new FnArg(protoViewRefs, RenderProtoViewRef)]; - var args: UiArguments = new UiArguments("compiler", "mergeProtoViewsRecursively", fnArgs); + var args: UiArguments = new UiArguments("mergeProtoViewsRecursively", fnArgs); return this._messageBroker.runOnUiThread(args, RenderProtoViewMergeMapping); } } @@ -65,8 +78,12 @@ export class WebWorkerCompiler implements RenderCompiler { @Injectable() export class WebWorkerRenderer implements Renderer { - constructor(private _messageBroker: MessageBroker, - private _renderViewStore: RenderViewWithFragmentsStore) {} + private _messageBroker; + constructor(messageBrokerFactory: MessageBrokerFactory, + private _renderViewStore: RenderViewWithFragmentsStore, + private _eventDispatcher: WebWorkerEventDispatcher) { + this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_CHANNEL); + } /** * Creates a root host view that includes the given element. * Note that the fragmentCount needs to be passed in so that we can create a result @@ -108,7 +125,7 @@ export class WebWorkerRenderer implements Renderer { } fnArgs.push(new FnArg(startIndex, null)); - var args = new UiArguments("renderer", method, fnArgs); + var args = new UiArguments(method, fnArgs); this._messageBroker.runOnUiThread(args, null); return renderViewWithFragments; @@ -119,7 +136,7 @@ export class WebWorkerRenderer implements Renderer { */ destroyView(viewRef: RenderViewRef) { var fnArgs = [new FnArg(viewRef, RenderViewRef)]; - var args = new UiArguments("renderer", "destroyView", fnArgs); + var args = new UiArguments("destroyView", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -132,7 +149,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(previousFragmentRef, RenderFragmentRef), new FnArg(fragmentRef, RenderFragmentRef) ]; - var args = new UiArguments("renderer", "attachFragmentAfterFragment", fnArgs); + var args = new UiArguments("attachFragmentAfterFragment", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -142,7 +159,7 @@ export class WebWorkerRenderer implements Renderer { attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) { var fnArgs = [new FnArg(elementRef, WebWorkerElementRef), new FnArg(fragmentRef, RenderFragmentRef)]; - var args = new UiArguments("renderer", "attachFragmentAfterElement", fnArgs); + var args = new UiArguments("attachFragmentAfterElement", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -151,7 +168,7 @@ export class WebWorkerRenderer implements Renderer { */ detachFragment(fragmentRef: RenderFragmentRef) { var fnArgs = [new FnArg(fragmentRef, RenderFragmentRef)]; - var args = new UiArguments("renderer", "detachFragment", fnArgs); + var args = new UiArguments("detachFragment", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -161,7 +178,7 @@ export class WebWorkerRenderer implements Renderer { */ hydrateView(viewRef: RenderViewRef) { var fnArgs = [new FnArg(viewRef, RenderViewRef)]; - var args = new UiArguments("renderer", "hydrateView", fnArgs); + var args = new UiArguments("hydrateView", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -171,7 +188,7 @@ export class WebWorkerRenderer implements Renderer { */ dehydrateView(viewRef: RenderViewRef) { var fnArgs = [new FnArg(viewRef, RenderViewRef)]; - var args = new UiArguments("renderer", "dehydrateView", fnArgs); + var args = new UiArguments("dehydrateView", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -190,7 +207,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(propertyName, null), new FnArg(propertyValue, null) ]; - var args = new UiArguments("renderer", "setElementProperty", fnArgs); + var args = new UiArguments("setElementProperty", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -203,7 +220,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(attributeName, null), new FnArg(attributeValue, null) ]; - var args = new UiArguments("renderer", "setElementAttribute", fnArgs); + var args = new UiArguments("setElementAttribute", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -216,7 +233,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(className, null), new FnArg(isAdd, null) ]; - var args = new UiArguments("renderer", "setElementClass", fnArgs); + var args = new UiArguments("setElementClass", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -229,7 +246,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(styleName, null), new FnArg(styleValue, null) ]; - var args = new UiArguments("renderer", "setElementStyle", fnArgs); + var args = new UiArguments("setElementStyle", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -243,7 +260,7 @@ export class WebWorkerRenderer implements Renderer { new FnArg(methodName, null), new FnArg(args, null) ]; - var uiArgs = new UiArguments("renderer", "invokeElementMethod", fnArgs); + var uiArgs = new UiArguments("invokeElementMethod", fnArgs); this._messageBroker.runOnUiThread(uiArgs, null); } @@ -253,7 +270,7 @@ export class WebWorkerRenderer implements Renderer { setText(viewRef: RenderViewRef, textNodeIndex: number, text: string) { var fnArgs = [new FnArg(viewRef, RenderViewRef), new FnArg(textNodeIndex, null), new FnArg(text, null)]; - var args = new UiArguments("renderer", "setText", fnArgs); + var args = new UiArguments("setText", fnArgs); this._messageBroker.runOnUiThread(args, null); } @@ -262,8 +279,8 @@ export class WebWorkerRenderer implements Renderer { */ setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher) { var fnArgs = [new FnArg(viewRef, RenderViewRef)]; - var args = new UiArguments("renderer", "setEventDispatcher", fnArgs); - this._messageBroker.registerEventDispatcher(viewRef, dispatcher); + var args = new UiArguments("setEventDispatcher", fnArgs); + this._eventDispatcher.registerEventDispatcher(viewRef, dispatcher); this._messageBroker.runOnUiThread(args, null); } } diff --git a/modules/angular2/src/web-workers/worker/xhr_impl.ts b/modules/angular2/src/web-workers/worker/xhr_impl.ts index 14eea042e5..fc2e72ef8b 100644 --- a/modules/angular2/src/web-workers/worker/xhr_impl.ts +++ b/modules/angular2/src/web-workers/worker/xhr_impl.ts @@ -1,7 +1,13 @@ import {Injectable} from 'angular2/di'; import {Promise} from 'angular2/src/facade/async'; import {XHR} from 'angular2/src/render/xhr'; -import {FnArg, UiArguments, MessageBroker} from 'angular2/src/web-workers/worker/broker'; +import { + FnArg, + UiArguments, + MessageBroker, + MessageBrokerFactory +} from 'angular2/src/web-workers/worker/broker'; +import {XHR_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; /** * Implementation of render/xhr that relays XHR requests to the UI side where they are sent @@ -9,11 +15,16 @@ import {FnArg, UiArguments, MessageBroker} from 'angular2/src/web-workers/worker */ @Injectable() export class WebWorkerXHRImpl extends XHR { - constructor(private _messageBroker: MessageBroker) { super(); } + private _messageBroker: MessageBroker; + + constructor(messageBrokerFactory: MessageBrokerFactory) { + super(); + this._messageBroker = messageBrokerFactory.createMessageBroker(XHR_CHANNEL); + } get(url: string): Promise { var fnArgs: List = [new FnArg(url, null)]; - var args: UiArguments = new UiArguments("xhr", "get", fnArgs); + var args: UiArguments = new UiArguments("get", fnArgs); return this._messageBroker.runOnUiThread(args, String); } } diff --git a/modules/angular2/test/web-workers/shared/message_bus_spec.ts b/modules/angular2/test/web-workers/shared/message_bus_spec.ts new file mode 100644 index 0000000000..34e742af38 --- /dev/null +++ b/modules/angular2/test/web-workers/shared/message_bus_spec.ts @@ -0,0 +1,94 @@ +import { + AsyncTestCompleter, + inject, + describe, + it, + expect, + beforeEach, + createTestInjector, + beforeEachBindings, + SpyObject, + proxy +} from 'angular2/test_lib'; +import {ObservableWrapper} from 'angular2/src/facade/async'; +import {MessageBusInterface} from 'angular2/src/web-workers/shared/message_bus'; +import {createConnectedMessageBus} from './message_bus_util'; + +export function main() { + /** + * Tests the PostMessageBus in TypeScript and the IsolateMessageBus in Dart + */ + describe("MessageBus", () => { + var bus: MessageBusInterface; + + beforeEach(() => { bus = createConnectedMessageBus(); }); + + it("should pass messages in the same channel from sink to source", + inject([AsyncTestCompleter], (async) => { + const CHANNEL = "CHANNEL 1"; + const MESSAGE = "Test message"; + + var fromEmitter = bus.from(CHANNEL); + ObservableWrapper.subscribe(fromEmitter, (message: any) => { + expect(message).toEqual(MESSAGE); + async.done(); + }); + var toEmitter = bus.to(CHANNEL); + ObservableWrapper.callNext(toEmitter, MESSAGE); + })); + + it("should broadcast", inject([AsyncTestCompleter], (async) => { + const CHANNEL = "CHANNEL 1"; + const MESSAGE = "TESTING"; + const NUM_LISTENERS = 2; + + var callCount = 0; + var emitHandler = (message: any) => { + expect(message).toEqual(MESSAGE); + callCount++; + if (callCount == NUM_LISTENERS) { + async.done(); + } + }; + + for (var i = 0; i < NUM_LISTENERS; i++) { + var emitter = bus.from(CHANNEL); + ObservableWrapper.subscribe(emitter, emitHandler); + } + + var toEmitter = bus.to(CHANNEL); + ObservableWrapper.callNext(toEmitter, MESSAGE); + })); + + it("should keep channels independent", inject([AsyncTestCompleter], (async) => { + const CHANNEL_ONE = "CHANNEL 1"; + const CHANNEL_TWO = "CHANNEL 2"; + const MESSAGE_ONE = "This is a message on CHANNEL 1"; + const MESSAGE_TWO = "This is a message on CHANNEL 2"; + var callCount = 0; + + var firstFromEmitter = bus.from(CHANNEL_ONE); + ObservableWrapper.subscribe(firstFromEmitter, (message) => { + expect(message).toEqual(MESSAGE_ONE); + callCount++; + if (callCount == 2) { + async.done(); + } + }); + var secondFromEmitter = bus.from(CHANNEL_TWO); + ObservableWrapper.subscribe(secondFromEmitter, (message) => { + expect(message).toEqual(MESSAGE_TWO); + callCount++; + if (callCount == 2) { + async.done(); + } + }); + + var firstToEmitter = bus.to(CHANNEL_ONE); + ObservableWrapper.callNext(firstToEmitter, MESSAGE_ONE); + + var secondToEmitter = bus.to(CHANNEL_TWO); + ObservableWrapper.callNext(secondToEmitter, MESSAGE_TWO); + })); + }); +} diff --git a/modules/angular2/test/web-workers/shared/message_bus_util.dart b/modules/angular2/test/web-workers/shared/message_bus_util.dart new file mode 100644 index 0000000000..cc525a819e --- /dev/null +++ b/modules/angular2/test/web-workers/shared/message_bus_util.dart @@ -0,0 +1,20 @@ +library angular2.test.web_workers.shared.message_bus_util; + +import 'dart:isolate'; +import 'package:angular2/src/web-workers/shared/message_bus.dart' + show MessageBusInterface; +import 'package:angular2/src/web-workers/shared/isolate_message_bus.dart'; + +/* + * Returns an IsolateMessageBus thats sink is connected to its own source. + * Useful for testing the sink and source. + */ +MessageBusInterface createConnectedMessageBus() { + var receivePort = new ReceivePort(); + var sendPort = receivePort.sendPort; + + var sink = new IsolateMessageBusSink(sendPort); + var source = new IsolateMessageBusSource(receivePort); + + return new IsolateMessageBus(sink, source); +} diff --git a/modules/angular2/test/web-workers/shared/message_bus_util.ts b/modules/angular2/test/web-workers/shared/message_bus_util.ts new file mode 100644 index 0000000000..fb4f794add --- /dev/null +++ b/modules/angular2/test/web-workers/shared/message_bus_util.ts @@ -0,0 +1,30 @@ +import { + PostMessageBusSource, + PostMessageBusSink, + PostMessageBus +} from 'angular2/src/web-workers/shared/post_message_bus'; +import {MessageBusInterface} from 'angular2/src/web-workers/shared/message_bus'; + +/* + * Returns a PostMessageBus thats sink is connected to its own source. + * Useful for testing the sink and source. + */ +export function createConnectedMessageBus(): MessageBusInterface { + var mockPostMessage = new MockPostMessage(); + var source = new PostMessageBusSource(mockPostMessage); + var sink = new PostMessageBusSink(mockPostMessage); + + return new PostMessageBus(sink, source); +} + +class MockPostMessage { + private _listener: EventListener; + + addEventListener(type: string, listener: EventListener, useCapture?: boolean): void { + if (type === "message") { + this._listener = listener; + } + } + + postMessage(data: any, transfer?:[ArrayBuffer]): void { this._listener({data: data}); } +} diff --git a/modules/angular2/test/web-workers/worker/broker_spec.ts b/modules/angular2/test/web-workers/worker/event_dispatcher_spec.ts similarity index 53% rename from modules/angular2/test/web-workers/worker/broker_spec.ts rename to modules/angular2/test/web-workers/worker/event_dispatcher_spec.ts index 55ae8149d0..43471a1078 100644 --- a/modules/angular2/test/web-workers/worker/broker_spec.ts +++ b/modules/angular2/test/web-workers/worker/event_dispatcher_spec.ts @@ -13,8 +13,6 @@ import { import {IMPLEMENTS} from 'angular2/src/facade/lang'; import {Serializer} from 'angular2/src/web-workers/shared/serializer'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; -import {MessageBroker} from 'angular2/src/web-workers/worker/broker'; -import {MockMessageBus, MockMessageBusSink, MockMessageBusSource} from './worker_test_util'; import {ON_WEB_WORKER} from 'angular2/src/web-workers/shared/api'; import {bind} from 'angular2/di'; import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store'; @@ -23,9 +21,13 @@ import { WebWorkerRenderViewRef } from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; import {RenderEventDispatcher, RenderViewRef} from 'angular2/src/render/api'; +import {createPairedMessageBuses} from './worker_test_util'; +import {WebWorkerEventDispatcher} from 'angular2/src/web-workers/worker/event_dispatcher'; +import {ObservableWrapper} from 'angular2/src/facade/async'; +import {EVENT_CHANNEL} from 'angular2/src/web-workers/shared/messaging_api'; export function main() { - describe("MessageBroker", () => { + describe("EventDispatcher", () => { beforeEachBindings(() => [ bind(ON_WEB_WORKER) .toValue(true), @@ -33,47 +35,41 @@ export function main() { RenderViewWithFragmentsStore ]); - it("should dispatch events", inject([Serializer, NgZone], (serializer, zone) => { - var bus = new MockMessageBus(new MockMessageBusSink(), new MockMessageBusSource()); - var broker = new MessageBroker(bus, serializer, zone); - - var eventDispatcher = new SpyEventDispatcher(); - var viewRef = new WebWorkerRenderViewRef(0); - serializer.allocateRenderViews(0); // serialize the ref so it's in the store - viewRef = - serializer.deserialize(serializer.serialize(viewRef, RenderViewRef), RenderViewRef); - broker.registerEventDispatcher(viewRef, eventDispatcher); + it("should dispatch events", + inject([Serializer, NgZone, AsyncTestCompleter], (serializer, zone, async) => { + var messageBuses = createPairedMessageBuses(); + var webWorkerEventDispatcher = + new WebWorkerEventDispatcher(messageBuses.worker, serializer, zone); var elementIndex = 15; var eventName = 'click'; - bus.source.receive({ - 'data': { - 'type': 'event', - 'value': { - 'viewRef': viewRef.serialize(), - 'elementIndex': elementIndex, - 'eventName': eventName, - 'locals': {'$event': {'target': {value: null}}} - } - } + var eventDispatcher = new SpyEventDispatcher((elementIndex, eventName, locals) => { + expect(elementIndex).toEqual(elementIndex); + expect(eventName).toEqual(eventName); + async.done(); }); - expect(eventDispatcher.wasDispatched).toBeTruthy(); - expect(eventDispatcher.elementIndex).toEqual(elementIndex); - expect(eventDispatcher.eventName).toEqual(eventName); + var viewRef = new WebWorkerRenderViewRef(0); + serializer.allocateRenderViews(0); // serialize the ref so it's in the store + viewRef = + serializer.deserialize(serializer.serialize(viewRef, RenderViewRef), RenderViewRef); + webWorkerEventDispatcher.registerEventDispatcher(viewRef, eventDispatcher); + + ObservableWrapper.callNext(messageBuses.ui.to(EVENT_CHANNEL), { + 'viewRef': viewRef.serialize(), + 'elementIndex': elementIndex, + 'eventName': eventName, + 'locals': {'$event': {'target': {value: null}}} + }); })); }); } class SpyEventDispatcher implements RenderEventDispatcher { - wasDispatched: boolean = false; - elementIndex: number; - eventName: string; + constructor(private _callback: Function) {} dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map) { - this.wasDispatched = true; - this.elementIndex = elementIndex; - this.eventName = eventName; + this._callback(elementIndex, eventName, locals); } } diff --git a/modules/angular2/test/web-workers/worker/mock_event_emitter.dart b/modules/angular2/test/web-workers/worker/mock_event_emitter.dart new file mode 100644 index 0000000000..451dc4bd2e --- /dev/null +++ b/modules/angular2/test/web-workers/worker/mock_event_emitter.dart @@ -0,0 +1,21 @@ +library angular2.test.web_workers.worker.mock_event_emitter; + +import 'dart:core'; +import 'dart:async'; +import "package:angular2/src/facade/async.dart"; + +class MockEventEmitter extends EventEmitter { + List _nextFns = new List(); + + @override + StreamSubscription listen(void onData(dynamic line), + {void onError(Error error), void onDone(), bool cancelOnError}) { + _nextFns.add(onData); + return null; + } + + @override + void add(value) { + _nextFns.forEach((fn) => fn(value)); + } +} diff --git a/modules/angular2/test/web-workers/worker/mock_event_emitter.ts b/modules/angular2/test/web-workers/worker/mock_event_emitter.ts new file mode 100644 index 0000000000..b0c671b2e3 --- /dev/null +++ b/modules/angular2/test/web-workers/worker/mock_event_emitter.ts @@ -0,0 +1,18 @@ +import {EventEmitter} from 'angular2/src/facade/async'; +import * as Rx from 'rx'; +import {ListWrapper} from 'angular2/src/facade/collection'; + +export class MockEventEmitter extends EventEmitter { + private _nextFns: List = []; + + constructor() { super(); } + + observer(generator: any): Rx.IDisposable { + this._nextFns.push(generator.next); + return null; + } + + next(value: any) { + ListWrapper.forEach(this._nextFns, (fn) => { fn(value); }); + } +} diff --git a/modules/angular2/test/web-workers/worker/renderer_spec.ts b/modules/angular2/test/web-workers/worker/renderer_integration_spec.ts similarity index 88% rename from modules/angular2/test/web-workers/worker/renderer_spec.ts rename to modules/angular2/test/web-workers/worker/renderer_integration_spec.ts index 6f8f8ac2de..d3fc22e8c4 100644 --- a/modules/angular2/test/web-workers/worker/renderer_spec.ts +++ b/modules/angular2/test/web-workers/worker/renderer_integration_spec.ts @@ -12,7 +12,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {DomTestbed, TestRootView, elRef} from '../../render/dom/dom_testbed'; import {bind} from 'angular2/di'; import {WebWorkerCompiler, WebWorkerRenderer} from "angular2/src/web-workers/worker/renderer"; -import {MessageBroker, UiArguments, FnArg} from "angular2/src/web-workers/worker/broker"; +import {MessageBrokerFactory, UiArguments, FnArg} from "angular2/src/web-workers/worker/broker"; import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {isPresent, isBlank, BaseException, Type} from "angular2/src/facade/lang"; import {MapWrapper, ListWrapper} from "angular2/src/facade/collection"; @@ -37,42 +37,47 @@ import { import {resolveInternalDomProtoView, DomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {someComponent} from '../../render/dom/dom_renderer_integration_spec'; import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl'; -import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; -import {MockMessageBus, MockMessageBusSink, MockMessageBusSource} from './worker_test_util'; +import {MessageBasedRenderCompiler} from 'angular2/src/web-workers/ui/render_compiler'; +import {MessageBasedRenderer} from 'angular2/src/web-workers/ui/renderer'; +import { + createPairedMessageBuses +} from './worker_test_util' -export function main() { - function createBroker(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed, - uiRenderViewStore: RenderViewWithFragmentsStore, - workerRenderViewStore: RenderViewWithFragmentsStore): MessageBroker { - // set up the two message buses to pass messages to each other - var uiMessageBus = new MockMessageBus(new MockMessageBusSink(), new MockMessageBusSource()); - var workerMessageBus = new MockMessageBus(new MockMessageBusSink(), new MockMessageBusSource()); - uiMessageBus.attachToBus(workerMessageBus); - workerMessageBus.attachToBus(uiMessageBus); + export function + main() { + function createBrokerFactory(workerSerializer: Serializer, uiSerializer: Serializer, + tb: DomTestbed, uiRenderViewStore: RenderViewWithFragmentsStore, + workerRenderViewStore: RenderViewWithFragmentsStore): + MessageBrokerFactory { + var messageBuses = createPairedMessageBuses(); + var uiMessageBus = messageBuses.ui; + var workerMessageBus = messageBuses.worker; // set up the worker side - var broker = new MessageBroker(workerMessageBus, workerSerializer, null); + var brokerFactory = new MessageBrokerFactory(workerMessageBus, workerSerializer); // set up the ui side - var webWorkerMain = new WebWorkerMain(tb.compiler, tb.renderer, uiRenderViewStore, uiSerializer, - new AnchorBasedAppRootUrl(), null); - webWorkerMain.attachToWebWorker(uiMessageBus); - return broker; + var renderCompiler = new MessageBasedRenderCompiler(uiMessageBus, uiSerializer, tb.compiler); + var renderer = + new MessageBasedRenderer(uiMessageBus, uiSerializer, uiRenderViewStore, tb.renderer); + new WebWorkerMain(renderCompiler, renderer, null, null); + + return brokerFactory; } function createWorkerRenderer(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed, uiRenderViewStore: RenderViewWithFragmentsStore, workerRenderViewStore: RenderViewWithFragmentsStore): WebWorkerRenderer { - var broker = - createBroker(workerSerializer, uiSerializer, tb, uiRenderViewStore, workerRenderViewStore); - return new WebWorkerRenderer(broker, workerRenderViewStore); + var brokerFactory = createBrokerFactory(workerSerializer, uiSerializer, tb, uiRenderViewStore, + workerRenderViewStore); + return new WebWorkerRenderer(brokerFactory, workerRenderViewStore, null); } function createWorkerCompiler(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed): WebWorkerCompiler { - var broker = createBroker(workerSerializer, uiSerializer, tb, null, null); - return new WebWorkerCompiler(broker); + var brokerFactory = createBrokerFactory(workerSerializer, uiSerializer, tb, null, null); + return new WebWorkerCompiler(brokerFactory); } describe("Web Worker Compiler", function() { diff --git a/modules/angular2/test/web-workers/worker/worker_test_util.ts b/modules/angular2/test/web-workers/worker/worker_test_util.ts index 8509bf3dbc..31bf6b1026 100644 --- a/modules/angular2/test/web-workers/worker/worker_test_util.ts +++ b/modules/angular2/test/web-workers/worker/worker_test_util.ts @@ -1,36 +1,60 @@ +import {StringMap, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import { - MessageBus, - MessageBusSource, + MessageBusInterface, MessageBusSink, - SourceListener -} from "angular2/src/web-workers/shared/message_bus"; -import {MapWrapper} from "angular2/src/facade/collection"; + MessageBusSource, + MessageBus +} from 'angular2/src/web-workers/shared/message_bus'; +import {MockEventEmitter} from './mock_event_emitter'; + +/** + * Returns two MessageBus instances that are attached to each other. + * Such that whatever goes into one's sink comes out the others source. + */ +export function createPairedMessageBuses(): PairedMessageBuses { + var firstChannels: StringMap = {}; + var workerMessageBusSink = new MockMessageBusSink(firstChannels); + var uiMessageBusSource = new MockMessageBusSource(firstChannels); + + var secondChannels: StringMap = {}; + var uiMessageBusSink = new MockMessageBusSink(secondChannels); + var workerMessageBusSource = new MockMessageBusSource(secondChannels); + + return new PairedMessageBuses(new MockMessageBus(uiMessageBusSink, uiMessageBusSource), + new MockMessageBus(workerMessageBusSink, workerMessageBusSource)); +} + +export class PairedMessageBuses { + constructor(public ui: MessageBusInterface, public worker: MessageBusInterface) {} +} export class MockMessageBusSource implements MessageBusSource { - private _listenerStore: Map = new Map(); - private _numListeners: number = 0; + constructor(private _channels: StringMap) {} - addListener(fn: SourceListener): int { - this._listenerStore.set(++this._numListeners, fn); - return this._numListeners; - } - - removeListener(index: int): void { MapWrapper.delete(this._listenerStore, index); } - - receive(message: Object): void { - MapWrapper.forEach(this._listenerStore, (fn: SourceListener, key: int) => { fn(message); }); + from(channel: string): MockEventEmitter { + if (!StringMapWrapper.contains(this._channels, channel)) { + this._channels[channel] = new MockEventEmitter(); + } + return this._channels[channel]; } } export class MockMessageBusSink implements MessageBusSink { - private _sendTo: MockMessageBusSource; + constructor(private _channels: StringMap) {} - send(message: Object): void { this._sendTo.receive({'data': message}); } - - attachToSource(source: MockMessageBusSource) { this._sendTo = source; } + to(channel: string): MockEventEmitter { + if (!StringMapWrapper.contains(this._channels, channel)) { + this._channels[channel] = new MockEventEmitter(); + } + return this._channels[channel]; + } } -export class MockMessageBus implements MessageBus { - constructor(public sink: MockMessageBusSink, public source: MockMessageBusSource) {} - attachToBus(bus: MockMessageBus) { this.sink.attachToSource(bus.source); } +export class MockMessageBus extends MessageBus { + constructor(public sink: MockMessageBusSink, public source: MockMessageBusSource) { super(); } + + + to(channel: string): MockEventEmitter { return this.sink.to(channel); } + + from(channel: string): MockEventEmitter { return this.source.from(channel); } } diff --git a/modules/angular2/test/web-workers/worker/xhr_impl_spec.ts b/modules/angular2/test/web-workers/worker/xhr_impl_spec.ts index 13271e2997..9be068f35c 100644 --- a/modules/angular2/test/web-workers/worker/xhr_impl_spec.ts +++ b/modules/angular2/test/web-workers/worker/xhr_impl_spec.ts @@ -11,7 +11,11 @@ import { proxy } from 'angular2/test_lib'; import {IMPLEMENTS, Type} from 'angular2/src/facade/lang'; -import {MessageBroker, UiArguments} from 'angular2/src/web-workers/worker/broker'; +import { + MessageBroker, + UiArguments, + MessageBrokerFactory +} from 'angular2/src/web-workers/worker/broker'; import {WebWorkerXHRImpl} from "angular2/src/web-workers/worker/xhr_impl"; import {PromiseWrapper} from "angular2/src/facade/async"; @@ -25,14 +29,13 @@ export function main() { var messageBroker: any = new SpyMessageBroker(); messageBroker.spy("runOnUiThread") .andCallFake((args: UiArguments, returnType: Type) => { - expect(args.type).toEqual("xhr"); expect(args.method).toEqual("get"); expect(args.args.length).toEqual(1); expect(args.args[0].value).toEqual(URL); return PromiseWrapper.wrap(() => { return RESPONSE; }); }); - var xhrImpl = new WebWorkerXHRImpl(messageBroker); + var xhrImpl = new WebWorkerXHRImpl(new MockMessageBrokerFactory(messageBroker)); xhrImpl.get(URL).then((response) => { expect(response).toEqual(RESPONSE); async.done(); @@ -47,3 +50,8 @@ class SpyMessageBroker extends SpyObject { constructor() { super(MessageBroker); } noSuchMethod(m) { return super.noSuchMethod(m); } } + +class MockMessageBrokerFactory extends MessageBrokerFactory { + constructor(private _messageBroker: MessageBroker) { super(null, null); } + createMessageBroker(channel: string) { return this._messageBroker; } +} diff --git a/modules/examples/src/message_broker/background_index.dart b/modules/examples/src/message_broker/background_index.dart index 0bd3ea1876..532a720b7d 100644 --- a/modules/examples/src/message_broker/background_index.dart +++ b/modules/examples/src/message_broker/background_index.dart @@ -1,28 +1,29 @@ library angular2.examples.message_broker.background_index; -import "package:angular2/src/web-workers/worker/application.dart" - show WebWorkerMessageBus; import "package:angular2/src/web-workers/worker/broker.dart" show MessageBroker, UiArguments; import "package:angular2/src/web-workers/shared/serializer.dart" show Serializer; - +import "package:angular2/src/web-workers/shared/isolate_message_bus.dart"; +import "package:angular2/src/web-workers/worker/application.dart" + show WebWorkerMessageBusSink; +import "package:angular2/src/facade/async.dart"; import "dart:isolate"; main(List args, SendPort replyTo) { ReceivePort rPort = new ReceivePort(); - WebWorkerMessageBus bus = new WebWorkerMessageBus.fromPorts(replyTo, rPort); - bus.source.addListener((message) { - if (identical(message['data']['type'], "echo")) { - bus.sink - .send({"type": "echo_response", "value": message['data']['value']}); - } + var sink = new WebWorkerMessageBusSink(replyTo, rPort); + var source = new IsolateMessageBusSource(rPort); + IsolateMessageBus bus = new IsolateMessageBus(sink, source); + + ObservableWrapper.subscribe(bus.from("echo"), (value) { + ObservableWrapper.callNext(bus.to("echo"), value); }); MessageBroker broker = - new MessageBroker(bus, new Serializer(null, null, null), null); - var args = new UiArguments("test", "tester"); + new MessageBroker(bus, new Serializer(null, null, null), "test"); + var args = new UiArguments("tester"); broker.runOnUiThread(args, String).then((data) { - bus.sink.send({"type": "result", "value": data}); + ObservableWrapper.callNext(bus.to("result"), data); }); } diff --git a/modules/examples/src/message_broker/background_index.ts b/modules/examples/src/message_broker/background_index.ts index 5913492ca3..77952f053c 100644 --- a/modules/examples/src/message_broker/background_index.ts +++ b/modules/examples/src/message_broker/background_index.ts @@ -1,21 +1,30 @@ import { - WebWorkerMessageBus, - WebWorkerMessageBusSource, - WebWorkerMessageBusSink -} from "angular2/src/web-workers/worker/application"; + PostMessageBus, + PostMessageBusSink, + PostMessageBusSource +} from 'angular2/src/web-workers/shared/post_message_bus'; +import {ObservableWrapper} from 'angular2/src/facade/async'; import {MessageBroker, UiArguments} from "angular2/src/web-workers/worker/broker"; import {Serializer} from "angular2/src/web-workers/shared/serializer"; -export function main() { - var bus = new WebWorkerMessageBus(new WebWorkerMessageBusSink(), new WebWorkerMessageBusSource()); - bus.source.addListener((message) => { - if (message.data.type === "echo") { - bus.sink.send({type: "echo_response", 'value': message.data.value}); - } - }); - - var broker = new MessageBroker(bus, new Serializer(null, null, null), null); - var args = new UiArguments("test", "tester"); - broker.runOnUiThread(args, String) - .then((data: string) => { bus.sink.send({type: "result", value: data}); }); +interface PostMessageInterface { + (message: any, transferrables?:[ArrayBuffer]): void; +} +var _postMessage: PostMessageInterface = postMessage; + +export function main() { + var sink = new PostMessageBusSink({ + postMessage: + (message: any, transferrables?:[ArrayBuffer]) => { _postMessage(message, transferrables); } + }); + var source = new PostMessageBusSource(); + var bus = new PostMessageBus(sink, source); + + ObservableWrapper.subscribe(bus.from("echo"), + (value) => { ObservableWrapper.callNext(bus.to("echo"), value); }); + + var broker = new MessageBroker(bus, new Serializer(null, null, null), "test"); + var args = new UiArguments("tester"); + broker.runOnUiThread(args, String) + .then((data: string) => { ObservableWrapper.callNext(bus.to("result"), data); }); } diff --git a/modules/examples/src/message_broker/index.dart b/modules/examples/src/message_broker/index.dart index 2aff6a6523..159f98639b 100644 --- a/modules/examples/src/message_broker/index.dart +++ b/modules/examples/src/message_broker/index.dart @@ -1,8 +1,8 @@ library angular2.examples.message_broker.index; import "package:angular2/src/web-workers/ui/application.dart" - show spawnWebWorker, UIMessageBus, UIMessageBusSink, UIMessageBusSource; - + show spawnWebWorker; +import "package:angular2/src/facade/async.dart"; import "dart:html"; main() { @@ -10,21 +10,21 @@ main() { spawnWebWorker(Uri.parse("background_index.dart")).then((bus) { querySelector("#send_echo").addEventListener("click", (e) { var val = (querySelector("#echo_input") as InputElement).value; - bus.sink.send({'type': 'echo', 'value': val}); + ObservableWrapper.callNext(bus.to("echo"), val); }); - bus.source.addListener((message) { - var data = message['data']; - if (identical(data['type'], "echo_response")) { - querySelector("#echo_result") - .appendHtml("${data['value']}"); - } else if (identical(data['type'], "test")) { - bus.sink.send({'type': "result", 'id': data['id'], 'value': VALUE}); - } else if (identical(data['type'], "result")) { - querySelector("#ui_result") - .appendHtml("${data['value']}"); - } else if (identical(data['type'], "ready")) { - bus.sink.send({'type': "init"}); - } + + ObservableWrapper.subscribe(bus.from("echo"), (message) { + querySelector("#echo_result") + .appendHtml("${message}"); + }); + ObservableWrapper.subscribe(bus.from("result"), (message) { + querySelector("#ui_result") + .appendHtml("${message}"); + }); + ObservableWrapper.subscribe(bus.from("test"), + (Map message) { + ObservableWrapper.callNext(bus.to("test"), + {'id': message['id'], 'type': "result", 'value': VALUE}); }); }); } diff --git a/modules/examples/src/message_broker/index.ts b/modules/examples/src/message_broker/index.ts index 340736670e..042cb3a380 100644 --- a/modules/examples/src/message_broker/index.ts +++ b/modules/examples/src/message_broker/index.ts @@ -1,29 +1,28 @@ import { - UIMessageBus, - UIMessageBusSink, - UIMessageBusSource -} from "angular2/src/web-workers/ui/application"; + PostMessageBus, + PostMessageBusSink, + PostMessageBusSource +} from 'angular2/src/web-workers/shared/post_message_bus'; +import {ObservableWrapper} from 'angular2/src/facade/async'; -var worker = new Worker("loader.js"); -var bus = new UIMessageBus(new UIMessageBusSink(worker), new UIMessageBusSource(worker)); -var VALUE = 5; +var webWorker = new Worker("loader.js"); +var sink = new PostMessageBusSink(webWorker); +var source = new PostMessageBusSource(webWorker); +var bus = new PostMessageBus(sink, source); +const VALUE = 5; document.getElementById("send_echo") .addEventListener("click", (e) => { var val = (document.getElementById("echo_input")).value; - bus.sink.send({type: "echo", value: val}); + ObservableWrapper.callNext(bus.to("echo"), val); }); -bus.source.addListener((message) => { - if (message.data.type === "echo_response") { - document.getElementById("echo_result").innerHTML = - `${message.data.value}`; - } else if (message.data.type === "test") { - bus.sink.send({type: "result", id: message.data.id, value: VALUE}); - } else if (message.data.type == "result") { - document.getElementById("ui_result").innerHTML = - `${message.data.value}`; - } else if (message.data.type == "ready") { - bus.sink.send({type: "init"}); - } +ObservableWrapper.subscribe(bus.from("echo"), (message) => { + document.getElementById("echo_result").innerHTML = `${message}`; +}); +ObservableWrapper.subscribe(bus.from("result"), (message) => { + document.getElementById("ui_result").innerHTML = `${message}`; +}); +ObservableWrapper.subscribe(bus.from("test"), (message: StringMap) => { + ObservableWrapper.callNext(bus.to("test"), {id: message['id'], type: "result", value: VALUE}); }); diff --git a/modules/examples/src/web_workers/images/services/bitmap.dart b/modules/examples/src/web_workers/images/services/bitmap.dart index e569bb362c..d5bda53ca8 100644 --- a/modules/examples/src/web_workers/images/services/bitmap.dart +++ b/modules/examples/src/web_workers/images/services/bitmap.dart @@ -5,19 +5,19 @@ import 'dart:typed_data'; // TODO(jteplitz602) Implement this class #3493 class BitmapService { - ImageData applySepia (ImageData imageData) { + ImageData applySepia(ImageData imageData) { return null; } - String arrayBufferToDataUri (Uint8ClampedList data) { + String arrayBufferToDataUri(Uint8ClampedList data) { return null; } - ImageData convertToImageData (ByteBuffer buffer) { + ImageData convertToImageData(ByteBuffer buffer) { return null; } - String toDataUri (ImageData imageData) { + String toDataUri(ImageData imageData) { return null; } }