feat(WebWorker) Add channel support to MessageBus

closes #3661 and #3686
This commit is contained in:
Jason Teplitz 2015-08-17 10:28:47 -07:00
parent 104302a958
commit 0b59e664ec
36 changed files with 1155 additions and 753 deletions

View File

@ -81,13 +81,13 @@ class ObservableWrapper {
}
class EventEmitter extends Stream {
StreamController<String> _controller;
StreamController<dynamic> _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);

View File

@ -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<String, EventEmitter> _channels = new Map<String, EventEmitter>();
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<String, EventEmitter> _channels = new Map<String, EventEmitter>();
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;
}
}
}

View File

@ -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(); }
}

View File

@ -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";

View File

@ -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;

View File

@ -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<string, EventEmitter> = 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<string, EventEmitter> = 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; }

View File

@ -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<MessageBus> bootstrap(String uri) {
/**
* To be called from the main thread to spawn and communicate with the worker thread
*/
Future<UIMessageBus> spawnWebWorker(Uri uri) {
Future<MessageBus> 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<int, StreamSubscription> _listenerStore =
new Map<int, StreamSubscription>();
int _numListeners = 0;
UIMessageBusSource(ReceivePort port)
: _port = port,
rawDataStream = port.asBroadcastStream();
class UIMessageBusSource extends IsolateMessageBusSource {
UIMessageBusSource(ReceivePort port) : super(port);
Future<SendPort> 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);
}
}

View File

@ -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<int, SourceListener> = new Map<int, SourceListener>();
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);
}

View File

@ -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<Type | Binding | List<any>> {
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());
}

View File

@ -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<string, any>) {
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
});
}
}

View File

@ -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<string, any>, 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<any>;
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<any>, 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<string, any>) {
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<any>, 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<string, any>) {
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<any>;
id: string;

View File

@ -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<string, any>) => this._handleMessage(message));
}
private _handleMessage(map: StringMap<string, any>): void {
var message = new ReceivedMessage(map);
var args = message.args;
var promise: Promise<any>;
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<any>, type: Type): void {
PromiseWrapper.then(promise, (result: any) => {
ObservableWrapper.callNext(
this._sink,
{'type': 'result', 'value': this._serializer.serialize(result, type), 'id': id});
});
}
}

View File

@ -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<string, any>) => this._handleMessage(message));
}
private _createViewHelper(args: List<any>, 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<string, any>): 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");
}
}
}

View File

@ -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});
}
});
}
}

View File

@ -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<string, any>) => this._handleMessage(message));
}
private _handleMessage(map: StringMap<string, any>) {
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<any>, type: Type): void {
PromiseWrapper.then(promise, (result: any) => {
ObservableWrapper.callNext(
this._sink,
{'type': 'result', 'value': this._serializer.serialize(result, type), 'id': id});
});
}
}

View File

@ -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<ApplicationRef> bootstrapWebWorker(
SendPort replyTo, Type appComponentType,
[List<dynamic> 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<int, StreamSubscription> _listenerStore =
new Map<int, StreamSubscription>();
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);
}
}

View File

@ -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 = <any>postMessage;
interface PostMessageInterface {
(message: any, transferrables?:[ArrayBuffer]): void;
}
var _postMessage: PostMessageInterface = <any>postMessage;
/**
* Bootstrapping a Webworker Application
@ -26,44 +29,12 @@ var _postMessage: (message: any, transferrables?:[ArrayBuffer]) => void = <any>p
export function bootstrapWebWorker(
appComponentType: Type, componentInjectableBindings: List<Type | Binding | List<any>> = null):
Promise<ApplicationRef> {
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<int, SourceListener>;
private numListeners: int;
constructor() {
this.numListeners = 0;
this.listenerStore = new Map<int, SourceListener>();
}
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);
}
}

View File

@ -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<string, any>): List<Type | Binding | List<any>> {
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<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
var bootstrapProcess: PromiseCompleter<any> = 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<string, any>) => {
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<string, any>) => {
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<Type | Binding | List<any>>,
zone: NgZone, bus: WebWorkerMessageBus,
zone: NgZone, bus: MessageBusInterface,
initData: StringMap<string, any>): Injector {
if (isBlank(_rootInjector)) _rootInjector = Injector.resolveAndCreate(_rootBindings);
var mergedBindings: any[] =
@ -197,4 +197,4 @@ function _createAppInjector(appComponentType: Type, bindings: List<Type | Bindin
_injectorBindings(appComponentType, bus, initData);
mergedBindings.push(bind(NgZone).toValue(zone));
return _rootInjector.resolveAndCreateChild(mergedBindings);
}
}

View File

@ -1,24 +1,35 @@
/// <reference path="../../../globals.d.ts" />
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<string, PromiseCompleter<any>> = new Map<string, PromiseCompleter<any>>();
private _eventDispatchRegistry: Map<RenderViewRef, RenderEventDispatcher> =
new Map<RenderViewRef, RenderEventDispatcher>();
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<any> = 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<string, any>): 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<string, any>;
constructor(message: StringMap<string, any>, 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<FnArg>) {}
constructor(public method: string, public args?: List<FnArg>) {}
}

View File

@ -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<RenderViewRef, RenderEventDispatcher> =
new Map<RenderViewRef, RenderEventDispatcher>();
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<string, any>;
constructor(message: StringMap<string, any>, serializer: Serializer) {
this.viewRef = serializer.deserialize(message['viewRef'], RenderViewRef);
this.elementIndex = message['elementIndex'];
this.eventName = message['eventName'];
this.locals = MapWrapper.createFromStringMap(message['locals']);
}
}

View File

@ -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<ProtoViewDto> {
var fnArgs: List<FnArg> = [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<ProtoViewDto> {
var fnArgs: List<FnArg> = [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<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping> {
var fnArgs: List<FnArg> = [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);
}
}

View File

@ -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<string> {
var fnArgs: List<FnArg> = [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);
}
}

View File

@ -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);
}));
});
}

View File

@ -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);
}

View File

@ -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(<any>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(<any>{data: data}); }
}

View File

@ -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<string, any>) {
this.wasDispatched = true;
this.elementIndex = elementIndex;
this.eventName = eventName;
this._callback(elementIndex, eventName, locals);
}
}

View File

@ -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<Function> _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));
}
}

View File

@ -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<Function> = [];
constructor() { super(); }
observer(generator: any): Rx.IDisposable {
this._nextFns.push(generator.next);
return null;
}
next(value: any) {
ListWrapper.forEach(this._nextFns, (fn) => { fn(value); });
}
}

View File

@ -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() {

View File

@ -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<string, MockEventEmitter> = {};
var workerMessageBusSink = new MockMessageBusSink(firstChannels);
var uiMessageBusSource = new MockMessageBusSource(firstChannels);
var secondChannels: StringMap<string, MockEventEmitter> = {};
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<int, SourceListener> = new Map<int, SourceListener>();
private _numListeners: number = 0;
constructor(private _channels: StringMap<string, MockEventEmitter>) {}
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<string, MockEventEmitter>) {}
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); }
}

View File

@ -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; }
}

View File

@ -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<String> 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);
});
}

View File

@ -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 = <any>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); });
}

View File

@ -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("<span class='response'>${data['value']}</span>");
} 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("<span class='result'>${data['value']}</span>");
} else if (identical(data['type'], "ready")) {
bus.sink.send({'type': "init"});
}
ObservableWrapper.subscribe(bus.from("echo"), (message) {
querySelector("#echo_result")
.appendHtml("<span class='response'>${message}</span>");
});
ObservableWrapper.subscribe(bus.from("result"), (message) {
querySelector("#ui_result")
.appendHtml("<span class='result'>${message}</span>");
});
ObservableWrapper.subscribe(bus.from("test"),
(Map<String, dynamic> message) {
ObservableWrapper.callNext(bus.to("test"),
{'id': message['id'], 'type': "result", 'value': VALUE});
});
});
}

View File

@ -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 = (<HTMLInputElement>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 =
`<span class='response'>${message.data.value}</span>`;
} 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 =
`<span class='result'>${message.data.value}</span>`;
} else if (message.data.type == "ready") {
bus.sink.send({type: "init"});
}
ObservableWrapper.subscribe(bus.from("echo"), (message) => {
document.getElementById("echo_result").innerHTML = `<span class='response'>${message}</span>`;
});
ObservableWrapper.subscribe(bus.from("result"), (message) => {
document.getElementById("ui_result").innerHTML = `<span class='result'>${message}</span>`;
});
ObservableWrapper.subscribe(bus.from("test"), (message: StringMap<string, any>) => {
ObservableWrapper.callNext(bus.to("test"), {id: message['id'], type: "result", value: VALUE});
});

View File

@ -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;
}
}