diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 7140a6c6b9..3c882957bc 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -47,7 +47,7 @@ import { import {QueryList} from './query_list'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {RenderDirectiveMetadata} from 'angular2/src/core/render/api'; -import {EventConfig} from 'angular2/src/core/render/dom/util'; +import {EventConfig} from 'angular2/src/core/render/event_config'; import {PipeBinding} from '../pipes/pipe_binding'; var _staticKeys; diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts index 24664f10ce..4c022b2775 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.ts +++ b/modules/angular2/src/core/compiler/proto_view_factory.ts @@ -1,7 +1,13 @@ import {Injectable} from 'angular2/di'; import {List, ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection'; -import {isPresent, isBlank, BaseException, assertionsEnabled} from 'angular2/src/core/facade/lang'; +import { + StringWrapper, + isPresent, + isBlank, + BaseException, + assertionsEnabled +} from 'angular2/src/core/facade/lang'; import {reflector} from 'angular2/src/core/reflection/reflection'; import { @@ -505,7 +511,7 @@ function _findDirectiveIndexByExportAs(renderElementBinder, directiveBindings, e } } - if (isBlank(matchedDirective) && exportAs !== "$implicit") { + if (isBlank(matchedDirective) && !StringWrapper.equals(exportAs, "$implicit")) { throw new BaseException(`Cannot find directive with exportAs = '${exportAs}'`); } diff --git a/modules/angular2/src/core/render/dom/compiler/directive_parser.ts b/modules/angular2/src/core/render/dom/compiler/directive_parser.ts index 6dd0acc9e1..70fa28432b 100644 --- a/modules/angular2/src/core/render/dom/compiler/directive_parser.ts +++ b/modules/angular2/src/core/render/dom/compiler/directive_parser.ts @@ -10,7 +10,8 @@ import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; import {RenderDirectiveMetadata} from '../../api'; -import {EventConfig, dashCaseToCamelCase, camelCaseToDashCase} from '../util'; +import {dashCaseToCamelCase, camelCaseToDashCase} from '../util'; +import {EventConfig} from '../../event_config'; import {DirectiveBuilder, ElementBinderBuilder} from '../view/proto_view_builder'; /** diff --git a/modules/angular2/src/core/render/dom/util.ts b/modules/angular2/src/core/render/dom/util.ts index af78f4dc0f..88ed6518f2 100644 --- a/modules/angular2/src/core/render/dom/util.ts +++ b/modules/angular2/src/core/render/dom/util.ts @@ -8,8 +8,6 @@ import {TemplateCloner} from './template_cloner'; export const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; export const NG_BINDING_CLASS = 'ng-binding'; -export const EVENT_TARGET_SEPARATOR = ':'; - export const NG_CONTENT_ELEMENT_NAME = 'ng-content'; export const NG_SHADOW_ROOT_ELEMENT_NAME = 'shadow-root'; @@ -29,27 +27,6 @@ export function dashCaseToCamelCase(input: string): string { (m) => { return m[1].toUpperCase(); }); } -export class EventConfig { - constructor(public fieldName: string, public eventName: string, public isLongForm: boolean) {} - - static parse(eventConfig: string): EventConfig { - var fieldName = eventConfig, eventName = eventConfig, isLongForm = false; - var separatorIdx = eventConfig.indexOf(EVENT_TARGET_SEPARATOR); - if (separatorIdx > -1) { - // long format: 'fieldName: eventName' - fieldName = StringWrapper.substring(eventConfig, 0, separatorIdx).trim(); - eventName = StringWrapper.substring(eventConfig, separatorIdx + 1).trim(); - isLongForm = true; - } - return new EventConfig(fieldName, eventName, isLongForm); - } - - getFullName(): string { - return this.isLongForm ? `${this.fieldName}${EVENT_TARGET_SEPARATOR}${this.eventName}` : - this.eventName; - } -} - // Attention: This is on the hot path, so don't use closures or default values! export function queryBoundElements(templateContent: Node, isSingleElementChild: boolean): Element[] { diff --git a/modules/angular2/src/core/render/dom/view/proto_view_builder.ts b/modules/angular2/src/core/render/dom/view/proto_view_builder.ts index cac570c5e7..f9cb9cd3e8 100644 --- a/modules/angular2/src/core/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/core/render/dom/view/proto_view_builder.ts @@ -34,12 +34,8 @@ import { PropertyBindingType } from '../../api'; -import { - NG_BINDING_CLASS, - EVENT_TARGET_SEPARATOR, - queryBoundTextNodeIndices, - camelCaseToDashCase -} from '../util'; +import {NG_BINDING_CLASS, queryBoundTextNodeIndices, camelCaseToDashCase} from '../util'; +import {EVENT_TARGET_SEPARATOR} from "../../event_config"; export class ProtoViewBuilder { variableBindings: Map = new Map(); diff --git a/modules/angular2/src/core/render/event_config.ts b/modules/angular2/src/core/render/event_config.ts new file mode 100644 index 0000000000..618c7986b1 --- /dev/null +++ b/modules/angular2/src/core/render/event_config.ts @@ -0,0 +1,23 @@ +import {StringWrapper} from 'angular2/src/core/facade/lang'; +export const EVENT_TARGET_SEPARATOR = ':'; + +export class EventConfig { + constructor(public fieldName: string, public eventName: string, public isLongForm: boolean) {} + + static parse(eventConfig: string): EventConfig { + var fieldName = eventConfig, eventName = eventConfig, isLongForm = false; + var separatorIdx = eventConfig.indexOf(EVENT_TARGET_SEPARATOR); + if (separatorIdx > -1) { + // long format: 'fieldName: eventName' + fieldName = StringWrapper.substring(eventConfig, 0, separatorIdx).trim(); + eventName = StringWrapper.substring(eventConfig, separatorIdx + 1).trim(); + isLongForm = true; + } + return new EventConfig(fieldName, eventName, isLongForm); + } + + getFullName(): string { + return this.isLongForm ? `${this.fieldName}${EVENT_TARGET_SEPARATOR}${this.eventName}` : + this.eventName; + } +} diff --git a/modules/angular2/src/web_workers/debug_tools/multi_client_server_message_bus.dart b/modules/angular2/src/web_workers/debug_tools/multi_client_server_message_bus.dart new file mode 100644 index 0000000000..8303232bb5 --- /dev/null +++ b/modules/angular2/src/web_workers/debug_tools/multi_client_server_message_bus.dart @@ -0,0 +1,216 @@ +library angular2.src.web_workers.debug_tools.multi_client_server_message_bus; + +import "package:angular2/src/web_workers/shared/message_bus.dart" + show MessageBus, MessageBusSink, MessageBusSource; +import 'dart:io'; +import 'dart:convert' show JSON; +import 'dart:async'; +import 'package:angular2/src/core/facade/async.dart' show EventEmitter; +import 'package:angular2/src/web_workers/shared/messaging_api.dart'; + +// TODO(jteplitz602): Remove hard coded result type and +// clear messageHistory once app is done with it #3859 +class MultiClientServerMessageBus implements MessageBus { + final MultiClientServerMessageBusSink sink; + MultiClientServerMessageBusSource source; + bool hasPrimary = false; + + MultiClientServerMessageBus(this.sink, this.source); + + MultiClientServerMessageBus.fromHttpServer(HttpServer server) + : sink = new MultiClientServerMessageBusSink() { + source = new MultiClientServerMessageBusSource(resultReceived); + server.listen((HttpRequest request) { + if (request.uri.path == "/ws") { + WebSocketTransformer.upgrade(request).then((WebSocket socket) { + var wrapper = new WebSocketWrapper( + sink.messageHistory, sink.resultMarkers, socket); + if (!hasPrimary) { + wrapper.setPrimary(true); + hasPrimary = true; + } + sink.addConnection(wrapper); + source.addConnection(wrapper); + + wrapper.stream.listen(null, onDone: _handleDisconnect(wrapper)); + }); + } + }); + } + + void resultReceived() { + sink.resultReceived(); + } + + EventEmitter from(String channel) { + return source.from(channel); + } + + EventEmitter to(String channel) { + return sink.to(channel); + } + + Function _handleDisconnect(WebSocketWrapper wrapper) { + return () { + sink.removeConnection(wrapper); + if (wrapper.isPrimary) { + hasPrimary = false; + } + }; + } +} + +class WebSocketWrapper { + WebSocket socket; + Stream stream; + int _numResultsReceived = 0; + bool _isPrimary = false; + bool caughtUp = false; + List _messageHistory; + List _resultMarkers; + + WebSocketWrapper(this._messageHistory, this._resultMarkers, this.socket) { + stream = socket.asBroadcastStream(); + stream.listen((encodedMessage) { + var message = JSON.decode(encodedMessage)['message']; + if (message is Map && message.containsKey("type")) { + if (message['type'] == 'result') { + resultReceived(); + } + } + }); + } + + bool get isPrimary => _isPrimary; + + void resultReceived() { + if (!isPrimary && !caughtUp) { + _numResultsReceived++; + sendToMarker(_numResultsReceived); + } + } + + void setPrimary(bool primary) { + _isPrimary = primary; + if (primary) { + caughtUp = true; + } + } + + // Sends up to the given result marker + void sendToMarker(int markerIndex) { + int numMessages; + int curr; + if (markerIndex >= _resultMarkers.length) { + // we're past the final result marker so send all messages in history + curr = (_resultMarkers.length > 0) + ? _resultMarkers[_resultMarkers.length - 1] + : 0; + numMessages = _messageHistory.length - curr; + caughtUp = true; + } else { + curr = (markerIndex == 0) ? 0 : _resultMarkers[markerIndex - 1]; + var end = _resultMarkers[markerIndex]; + numMessages = end - curr; + } + while (numMessages > 0) { + socket.add(_messageHistory[curr]); + curr++; + numMessages--; + } + } +} + +class MultiClientServerMessageBusSink implements MessageBusSink { + final List messageHistory = new List(); + final Set openConnections = new Set(); + final Map _channels = new Map(); + final List resultMarkers = new List(); + + void resultReceived() { + resultMarkers.add(messageHistory.length); + } + + void addConnection(WebSocketWrapper webSocket) { + openConnections.add(webSocket); + // send messages up to the first result marker to this socket + webSocket.sendToMarker(0); + } + + void removeConnection(WebSocketWrapper webSocket) { + openConnections.remove(webSocket); + } + + EventEmitter to(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + emitter.listen((message) { + _send({'channel': channel, 'message': message}); + }); + return emitter; + } + } + + void _send(dynamic message) { + String encodedMessage = JSON.encode(message); + openConnections.forEach((WebSocketWrapper webSocket) { + if (webSocket.caughtUp) { + webSocket.socket.add(encodedMessage); + } + }); + messageHistory.add(encodedMessage); + } +} + +class MultiClientServerMessageBusSource implements MessageBusSource { + final Map _channels = new Map(); + Function onResultReceived; + + MultiClientServerMessageBusSource(this.onResultReceived); + + EventEmitter from(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + _channels[channel] = emitter; + return emitter; + } + } + + void addConnection(WebSocketWrapper webSocket) { + if (webSocket.isPrimary) { + webSocket.stream.listen((encodedMessage) { + var decodedMessage = decodeMessage(encodedMessage); + var channel = decodedMessage['channel']; + var message = decodedMessage['message']; + if (message is Map && message.containsKey("type")) { + if (message['type'] == 'result') { + // tell the bus that a result was received on the primary + onResultReceived(); + } + } + + if (_channels.containsKey(channel)) { + _channels[channel].add(message); + } + }); + } else { + webSocket.stream.listen((encodedMessage) { + // handle events from non-primary browser + var decodedMessage = decodeMessage(encodedMessage); + var channel = decodedMessage['channel']; + var message = decodedMessage['message']; + if (_channels.containsKey(EVENT_CHANNEL) && channel == EVENT_CHANNEL) { + _channels[channel].add(message); + } + }); + } + } + + Map decodeMessage(dynamic message) { + return JSON.decode(message); + } +} diff --git a/modules/angular2/src/web_workers/debug_tools/single_client_server_message_bus.dart b/modules/angular2/src/web_workers/debug_tools/single_client_server_message_bus.dart new file mode 100644 index 0000000000..ceee149203 --- /dev/null +++ b/modules/angular2/src/web_workers/debug_tools/single_client_server_message_bus.dart @@ -0,0 +1,140 @@ +library angular2.src.web_workers.debug_tools.single_client_server_message_bus; + +import "package:angular2/src/web_workers/shared/message_bus.dart" + show MessageBus, MessageBusSink, MessageBusSource; +import 'dart:io'; +import 'dart:convert' show JSON; +import 'dart:async'; +import "package:angular2/src/core/facade/async.dart" show EventEmitter; + +class SingleClientServerMessageBus implements MessageBus { + final SingleClientServerMessageBusSink sink; + SingleClientServerMessageBusSource source; + bool connected = false; + + SingleClientServerMessageBus(this.sink, this.source); + + SingleClientServerMessageBus.fromHttpServer(HttpServer server) + : sink = new SingleClientServerMessageBusSink() { + source = new SingleClientServerMessageBusSource(); + server.listen((HttpRequest request) { + if (request.uri.path == "/ws") { + if (!connected) { + WebSocketTransformer.upgrade(request).then((WebSocket socket) { + sink.setConnection(socket); + + var stream = socket.asBroadcastStream(); + source.setConnectionFromStream(stream); + stream.listen(null, onDone: _handleDisconnect); + }).catchError((error) { + throw error; + connected = false; + }); + connected = true; + } else { + // refuse additional clients + request.response.statusCode = HttpStatus.SERVICE_UNAVAILABLE; + request.response.write("Maximum number of clients connected."); + request.response.close(); + } + } + }); + } + + void _handleDisconnect() { + sink.removeConnection(); + source.removeConnection(); + connected = false; + } + + EventEmitter from(String channel) { + return source.from(channel); + } + + EventEmitter to(String channel) { + return sink.to(channel); + } +} + +class SingleClientServerMessageBusSink implements MessageBusSink { + final List _messageBuffer = new List(); + WebSocket _socket = null; + final Map _channels = new Map(); + + void setConnection(WebSocket webSocket) { + _socket = webSocket; + _sendBufferedMessages(); + } + + EventEmitter to(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + emitter.listen((message) { + _send({'channel': channel, 'message': message}); + }); + return emitter; + } + } + + void removeConnection() { + _socket = null; + } + + void _send(dynamic message) { + String encodedMessage = JSON.encode(message); + if (_socket != null) { + _socket.add(encodedMessage); + } else { + _messageBuffer.add(encodedMessage); + } + } + + void _sendBufferedMessages() { + _messageBuffer.forEach((message) => _socket.add(message)); + _messageBuffer.clear(); + } +} + +class SingleClientServerMessageBusSource implements MessageBusSource { + final Map _channels = new Map(); + Stream _stream; + + SingleClientServerMessageBusSource(); + + EventEmitter from(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + _channels[channel] = emitter; + return emitter; + } + } + + void setConnectionFromWebSocket(WebSocket socket) { + setConnectionFromStream(socket.asBroadcastStream()); + } + + void setConnectionFromStream(Stream stream) { + _stream = stream; + _stream.listen((encodedMessage) { + var decodedMessage = decodeMessage(encodedMessage); + var channel = decodedMessage['channel']; + var message = decodedMessage['message']; + + if (_channels.containsKey(channel)) { + _channels[channel].add(message); + } + }); + } + + void removeConnection() { + _stream = null; + } + + Map decodeMessage(dynamic message) { + return JSON.decode(message); + } +} diff --git a/modules/angular2/src/web_workers/debug_tools/web_socket_message_bus.dart b/modules/angular2/src/web_workers/debug_tools/web_socket_message_bus.dart new file mode 100644 index 0000000000..6f47cc1bd0 --- /dev/null +++ b/modules/angular2/src/web_workers/debug_tools/web_socket_message_bus.dart @@ -0,0 +1,78 @@ +library angular2.src.web_workers.worker.web_socket_message_bus; + +import 'dart:html'; +import 'dart:convert' show JSON; +import "package:angular2/src/web_workers/shared/message_bus.dart" + show MessageBus, MessageBusSink, MessageBusSource; +import 'package:angular2/src/core/facade/async.dart' show EventEmitter; + +class WebSocketMessageBus implements MessageBus { + final WebSocketMessageBusSink sink; + final WebSocketMessageBusSource source; + + WebSocketMessageBus(this.sink, this.source); + + WebSocketMessageBus.fromWebSocket(WebSocket webSocket) + : sink = new WebSocketMessageBusSink(webSocket), + source = new WebSocketMessageBusSource(webSocket); + + EventEmitter from(String channel) { + return source.from(channel); + } + + EventEmitter to(String channel) { + return sink.to(channel); + } +} + +class WebSocketMessageBusSink implements MessageBusSink { + final WebSocket _webSocket; + final Map _channels = new Map(); + + WebSocketMessageBusSink(this._webSocket); + + EventEmitter to(String channel) { + if (_channels.containsKey(channel)) { + return _channels[channel]; + } else { + var emitter = new EventEmitter(); + emitter.listen((message) { + _send({'channel': channel, 'message': message}); + }); + _channels[channel] = emitter; + return emitter; + } + } + + void _send(message) { + _webSocket.send(JSON.encode(message)); + } +} + +class WebSocketMessageBusSource implements MessageBusSource { + final Map _channels = new Map(); + + WebSocketMessageBusSource(WebSocket webSocket) { + webSocket.onMessage.listen((MessageEvent encodedMessage) { + var message = decodeMessage(encodedMessage.data); + 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; + } + } + + Map decodeMessage(dynamic message) { + return JSON.decode(message); + } +} diff --git a/modules/angular2/src/web_workers/shared/client_message_broker.ts b/modules/angular2/src/web_workers/shared/client_message_broker.ts index 9813fd3e2a..ede2897b4d 100644 --- a/modules/angular2/src/web_workers/shared/client_message_broker.ts +++ b/modules/angular2/src/web_workers/shared/client_message_broker.ts @@ -11,7 +11,7 @@ import { import {ListWrapper, StringMapWrapper, MapWrapper} from "angular2/src/core/facade/collection"; import {Serializer} from "angular2/src/web_workers/shared/serializer"; import {Injectable} from "angular2/di"; -import {Type} from "angular2/src/core/facade/lang"; +import {Type, StringWrapper} from "angular2/src/core/facade/lang"; @Injectable() export class ClientMessageBrokerFactory { @@ -90,10 +90,10 @@ export class ClientMessageBroker { private _handleMessage(message: StringMap): void { var data = new MessageData(message); // TODO(jteplitz602): replace these strings with messaging constants #3685 - if (data.type === "result" || data.type === "error") { + if (StringWrapper.equals(data.type, "result") || StringWrapper.equals(data.type, "error")) { var id = data.id; if (this._pending.has(id)) { - if (data.type === "result") { + if (StringWrapper.equals(data.type, "result")) { this._pending.get(id).resolve(data.value); } else { this._pending.get(id).reject(data.value, null); diff --git a/modules/angular2/src/web_workers/shared/isolate_message_bus.dart b/modules/angular2/src/web_workers/shared/isolate_message_bus.dart index 87325f8c37..6132158743 100644 --- a/modules/angular2/src/web_workers/shared/isolate_message_bus.dart +++ b/modules/angular2/src/web_workers/shared/isolate_message_bus.dart @@ -51,7 +51,7 @@ class IsolateMessageBusSource extends MessageBusSource { IsolateMessageBusSource(ReceivePort port) : rawDataStream = port.asBroadcastStream() { rawDataStream.listen((message) { - if (message is SendPort){ + if (message is SendPort) { return; } diff --git a/modules/angular2/src/web_workers/ui/setup.ts b/modules/angular2/src/web_workers/ui/setup.ts index 7c4c15d800..9e06877f89 100644 --- a/modules/angular2/src/web_workers/ui/setup.ts +++ b/modules/angular2/src/web_workers/ui/setup.ts @@ -3,6 +3,7 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async'; import {MessageBus} from 'angular2/src/web_workers/shared/message_bus'; import {AnchorBasedAppRootUrl} from 'angular2/src/core/services/anchor_based_app_root_url'; import {Injectable} from 'angular2/di'; +import {StringWrapper} from 'angular2/src/core/facade/lang'; @Injectable() export class WebWorkerSetup { @@ -12,7 +13,7 @@ export class WebWorkerSetup { var source = bus.from(SETUP_CHANNEL); ObservableWrapper.subscribe(source, (message: string) => { - if (message === "ready") { + if (StringWrapper.equals(message, "ready")) { ObservableWrapper.callNext(sink, {"rootUrl": rootUrl}); } }); diff --git a/modules/angular2/src/web_workers/worker/application_common.ts b/modules/angular2/src/web_workers/worker/application_common.ts index 0aa8ec3928..5e313f94c4 100644 --- a/modules/angular2/src/web_workers/worker/application_common.ts +++ b/modules/angular2/src/web_workers/worker/application_common.ts @@ -24,9 +24,9 @@ import { defaultKeyValueDiffers } from 'angular2/src/core/change_detection/change_detection'; import {DEFAULT_PIPES} from 'angular2/pipes'; -import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver'; import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver'; import {PipeResolver} from 'angular2/src/core/compiler/pipe_resolver'; import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; import {List, ListWrapper} from 'angular2/src/core/facade/collection'; @@ -42,7 +42,6 @@ import { ComponentRef, DynamicComponentLoader } from 'angular2/src/core/compiler/dynamic_component_loader'; -import {Testability} from 'angular2/src/core/testability/testability'; import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; @@ -51,7 +50,6 @@ import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {WebWorkerRenderer, WebWorkerCompiler} from './renderer'; import {Renderer, RenderCompiler} from 'angular2/src/core/render/api'; import {internalView} from 'angular2/src/core/compiler/view_ref'; - import {ClientMessageBrokerFactory} from 'angular2/src/web_workers/shared/client_message_broker'; import {MessageBus} from 'angular2/src/web_workers/shared/message_bus'; import {APP_COMPONENT_REF_PROMISE, APP_COMPONENT} from 'angular2/src/core/application_tokens'; @@ -125,6 +123,8 @@ function _injectorBindings(appComponentType, bus: MessageBus, initData: StringMa bind(KeyValueDiffers).toValue(defaultKeyValueDiffers), bind(ChangeDetection).toValue(bestChangeDetection), DirectiveResolver, + UrlResolver, + StyleUrlResolver, PipeResolver, Parser, Lexer, @@ -132,10 +132,7 @@ function _injectorBindings(appComponentType, bus: MessageBus, initData: StringMa WebWorkerXHRImpl, bind(XHR).toAlias(WebWorkerXHRImpl), ComponentUrlMapper, - UrlResolver, - StyleUrlResolver, DynamicComponentLoader, - Testability, bind(AppRootUrl).toValue(new AppRootUrl(initData['rootUrl'])), WebWorkerEventDispatcher ]; diff --git a/modules/angular2/test/core/render/dom/util_spec.ts b/modules/angular2/test/core/render/event_config_spec.ts similarity index 94% rename from modules/angular2/test/core/render/dom/util_spec.ts rename to modules/angular2/test/core/render/event_config_spec.ts index f8b6b92a76..cce4de8dad 100644 --- a/modules/angular2/test/core/render/dom/util_spec.ts +++ b/modules/angular2/test/core/render/event_config_spec.ts @@ -1,4 +1,4 @@ -import {EventConfig} from 'angular2/src/core/render/dom/util'; +import {EventConfig} from 'angular2/src/core/render/event_config'; import {ddescribe, describe, expect, it} from 'angular2/test_lib'; export function main() { diff --git a/modules/angular2/test/web_workers/debug_tools/message_bus_common.dart b/modules/angular2/test/web_workers/debug_tools/message_bus_common.dart new file mode 100644 index 0000000000..4c7c0cc994 --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/message_bus_common.dart @@ -0,0 +1,23 @@ +library angular2.test.web_workers.debug_tools.message_bus_common; + +import "dart:convert" show JSON; +import "package:angular2/src/web_workers/shared/message_bus.dart"; +import "package:angular2/test_lib.dart" + show AsyncTestCompleter, expect, SpyObject; + +var MESSAGE = const {'test': 10}; +const CHANNEL = "TEST_CHANNEL"; + +void expectSinkSendsEncodedJson(SpyObject socket, MessageBusSink sink, + String sendMethodName, AsyncTestCompleter async) { + socket.spy(sendMethodName).andCallFake((message) { + expectMessageEquality(message, MESSAGE, CHANNEL); + async.done(); + }); + sink.to(CHANNEL).add(MESSAGE); +} + +void expectMessageEquality(String message, Map expectedData, String channel) { + expect(JSON.decode(message)) + .toEqual({'channel': channel, 'message': expectedData}); +} diff --git a/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart b/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart new file mode 100644 index 0000000000..561efbfcbd --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart @@ -0,0 +1,223 @@ +library angular2.test.web_workers.debug_tools.multi_client_server_message_bus; + +import "dart:io"; +import "dart:async"; +import "package:angular2/test_lib.dart" + show + AsyncTestCompleter, + inject, + describe, + it, + expect, + beforeEach, + createTestInjector, + beforeEachBindings, + SpyObject, + proxy; +import "package:angular2/src/web_workers/debug_tools/multi_client_server_message_bus.dart"; +import "package:angular2/src/web_workers/shared/messaging_api.dart"; +import "./message_bus_common.dart"; +import "./spy_web_socket.dart"; +import "dart:convert" show JSON; +import 'dart:math'; + +main() { + List messageHistory = new List(); + List resultMarkers = new List(); + describe("MultiClientServerMessageBusSink", () { + const CHANNEL = "TEST_CHANNEL"; + var MESSAGE = const {'test': 10}; + + beforeEach(() { + messageHistory.clear(); + resultMarkers.clear(); + }); + + it( + "should send messages to all connected clients", + inject([AsyncTestCompleter], (async) { + const NUM_CLIENTS = 5; + var sink = new MultiClientServerMessageBusSink(); + int numMessagesSent = 0; + // initialize all the sockets + var sockets = new List(NUM_CLIENTS); + for (var i = 0; i < sockets.length; i++) { + var messageSent = + false; // ensure this socket only receives one message + var socketWrapper = createSocket(messageHandler: (message) { + expect(messageSent).toEqual(false); + messageSent = true; + expectMessageEquality(message, MESSAGE, CHANNEL); + numMessagesSent++; + if (numMessagesSent == NUM_CLIENTS) { + async.done(); + } + }); + var socket = socketWrapper.socket; + sockets[i] = + new WebSocketWrapper(messageHistory, resultMarkers, socket); + sink.addConnection(sockets[i]); + } + sink.to(CHANNEL).add(MESSAGE); + })); + }); + + describe("WebSocketWrapper", () { + beforeEach(() { + messageHistory.clear(); + resultMarkers.clear(); + }); + + /** + * Generates the given number of random messages, inserts them into messageHistory, + * and then returns a cloned version of messageHistory + */ + List generateRandomMessages(int numMessages) { + const MAX = 1 << 31; + var random = new Random(); + for (var i = 0; i < numMessages; i++) { + var message = {'value': random.nextInt(MAX)}; + messageHistory + .add(JSON.encode({'channel': CHANNEL, 'message': message})); + } + // copy the message history to ensure the test fails if the wrapper modifies the list + return new List.from(messageHistory); + } + + it( + "should send all messages when there are no markers", + inject([AsyncTestCompleter], (async) { + const NUM_MESSAGES = 10; + var messageHistoryClone = generateRandomMessages(NUM_MESSAGES); + + int numMessagesSent = 0; + var socketWrapper = createSocket(messageHandler: (message) { + expect(message).toEqual(messageHistoryClone[numMessagesSent]); + //expectMessageEquality(message, expected, CHANNEL); + numMessagesSent++; + if (numMessagesSent == messageHistoryClone.length) { + async.done(); + } + }); + var wrapper = new WebSocketWrapper( + messageHistory, resultMarkers, socketWrapper.socket); + wrapper.sendToMarker(0); + })); + + it( + "should send between two markers", + inject([AsyncTestCompleter], (async) { + const NUM_MESSAGES = 50; + const FIRST_MARKER = 5; + const SECOND_MARKER = 15; + var messageHistoryClone = generateRandomMessages(NUM_MESSAGES); + + int numMessagesSent = 0; + resultMarkers.add(FIRST_MARKER); + resultMarkers.add(SECOND_MARKER); + var socketWrapper = createSocket(messageHandler: (message) { + expect(message) + .toEqual(messageHistoryClone[FIRST_MARKER + numMessagesSent]); + numMessagesSent++; + if (numMessagesSent == SECOND_MARKER - FIRST_MARKER) { + async.done(); + } + }); + var wrapper = new WebSocketWrapper( + messageHistory, resultMarkers, socketWrapper.socket); + wrapper.sendToMarker(1); + })); + }); + + describe("MultiClientServerMessageBusSource", () { + beforeEach(() { + messageHistory.clear(); + resultMarkers.clear(); + }); + + void sendMessage(StreamController controller, dynamic message) { + controller.add(JSON.encode(message)); + } + + void testForwardingMessages(bool primary, bool events, Function done) { + var result = createSocket(); + var controller = result.controller; + var socket = + new WebSocketWrapper(messageHistory, resultMarkers, result.socket); + socket.setPrimary(primary); + + var source = new MultiClientServerMessageBusSource(null); + source.addConnection(socket); + + var channel = events ? EVENT_CHANNEL : CHANNEL; + source.from(channel).listen((message) { + expect(message).toEqual(MESSAGE); + done(); + }); + + var message = {'channel': channel, 'message': MESSAGE}; + sendMessage(controller, message); + } + + it( + "should forward messages from the primary", + inject([AsyncTestCompleter], (async) { + testForwardingMessages(true, false, async.done); + })); + + it( + "should forward event channel messages from non primaries", + inject([AsyncTestCompleter], (async) { + testForwardingMessages(false, true, async.done); + })); + + it( + "should forward event channel messages from the primary", + inject([AsyncTestCompleter], (async) { + testForwardingMessages(true, true, async.done); + })); + + it( + "should mark results from the primary", + inject([AsyncTestCompleter], (async) { + var result = createSocket(); + var controller = result.controller; + var socket = new WebSocketWrapper( + messageHistory, resultMarkers, result.socket); + socket.setPrimary(true); + + var source = + new MultiClientServerMessageBusSource(() => async.done()); + source.addConnection(socket); + + var message = { + 'channel': CHANNEL, + 'message': {'type': 'result'} + }; + sendMessage(controller, message); + })); + }); +} + +/** + * Returns a new SpyWebSocket that calls messageHandler when a new message is sent. + * Also returns a StreamController instance that you can use to send messages to anything + * that is subscribed to this socket. + */ +SpySocketWrapper createSocket({Function messageHandler}) { + var socket = new SpyWebSocket(); + if (messageHandler != null) { + socket.spy("add").andCallFake(messageHandler); + } + + var controller = new StreamController.broadcast(); + socket.spy("asBroadcastStream").andCallFake(() => controller.stream); + return new SpySocketWrapper(socket, controller); +} + +class SpySocketWrapper { + SpyWebSocket socket; + StreamController controller; + + SpySocketWrapper(this.socket, this.controller); +} diff --git a/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart b/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart new file mode 100644 index 0000000000..d53b7d97e9 --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart @@ -0,0 +1,97 @@ +library angular2.test.web_workers.debug_tools.single_client_server_message_bus; + +import "dart:io"; +import "dart:async"; +import "package:angular2/test_lib.dart" + show + AsyncTestCompleter, + inject, + describe, + it, + expect, + beforeEach, + createTestInjector, + beforeEachBindings, + SpyObject, + proxy; +import "package:angular2/src/web_workers/debug_tools/single_client_server_message_bus.dart"; +import "./message_bus_common.dart"; +import "./spy_web_socket.dart"; +import "dart:convert" show JSON; + +main() { + var MESSAGE = const {'test': 10}; + const CHANNEL = "TEST_CHANNEL"; + describe("SingleClientServerMessageBusSink", () { + it( + "should send JSON encoded data over the WebSocket", + inject([AsyncTestCompleter], (async) { + var socket = new SpyWebSocket(); + var sink = new SingleClientServerMessageBusSink(); + sink.setConnection(socket); + expectSinkSendsEncodedJson(socket, sink, "add", async); + })); + + it( + "should buffer messages before connect", + inject([AsyncTestCompleter], (async) { + var sink = new SingleClientServerMessageBusSink(); + sink.to(CHANNEL).add(MESSAGE); + + var socket = new SpyWebSocket(); + socket.spy("add").andCallFake((message) { + expectMessageEquality(message, MESSAGE, CHANNEL); + async.done(); + }); + sink.setConnection(socket); + })); + + it( + "should buffer messages in between disconnect and connect", + inject([AsyncTestCompleter], (async) { + var SECOND_MESSAGE = const {'test': 12, 'second': 'hi'}; + var sink = new SingleClientServerMessageBusSink(); + sink.to(CHANNEL).add(MESSAGE); + + var socket = new SpyWebSocket(); + sink.setConnection(socket); + + int numMessages = 0; + + socket.spy("add").andCallFake((message) { + numMessages++; + if (numMessages == 1) { + expectMessageEquality(message, MESSAGE, CHANNEL); + } else { + expectMessageEquality(message, SECOND_MESSAGE, CHANNEL); + async.done(); + } + }); + + sink.removeConnection(); + sink.to(CHANNEL).add(SECOND_MESSAGE); + sink.setConnection(socket); + })); + }); + + describe("SingleClientServerMessageBusSource", () { + it( + "should decode JSON messages and emit them", + inject([AsyncTestCompleter], (async) { + var socket = new SpyWebSocket(); + StreamController controller = + new StreamController.broadcast(); + socket.spy("asBroadcastStream").andCallFake(() => controller.stream); + + var source = new SingleClientServerMessageBusSource(); + source.setConnectionFromWebSocket(socket); + source.from(CHANNEL).listen((message) { + expect(message).toEqual(MESSAGE); + async.done(); + }); + + controller + .add(JSON.encode({'channel': CHANNEL, 'message': MESSAGE})); + })); + }); +} diff --git a/modules/angular2/test/web_workers/debug_tools/spy_web_socket.dart b/modules/angular2/test/web_workers/debug_tools/spy_web_socket.dart new file mode 100644 index 0000000000..692945b078 --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/spy_web_socket.dart @@ -0,0 +1,15 @@ +/** + * Contains code shared between all server message bus tests + */ +library angular2.test.web_workers.debug_tools.server_message_bus_common; + +import "package:angular2/test_lib.dart"; +import "dart:io"; + +@proxy +class SpyWebSocket extends SpyObject implements WebSocket { + SpyWebSocket() : super(SpyWebSocket); + noSuchMethod(m) { + return super.noSuchMethod(m); + } +} diff --git a/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart b/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart new file mode 100644 index 0000000000..040ab05d17 --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart @@ -0,0 +1,72 @@ +library angular2.test.web_workers.debug_tools.web_socket_server_message_bus; + +import "package:angular2/test_lib.dart" + show + AsyncTestCompleter, + inject, + describe, + it, + expect, + beforeEach, + createTestInjector, + beforeEachBindings, + SpyObject, + proxy; +import "package:angular2/src/web_workers/debug_tools/web_socket_message_bus.dart"; +import "dart:html" show WebSocket, MessageEvent; +import "./message_bus_common.dart"; +import "dart:async"; +import "dart:convert" show JSON; + +main() { + var MESSAGE = const {'test': 10}; + const CHANNEL = "TEST_CHANNEL"; + + describe("WebSocketMessageBusSink", () { + it( + "should send JSON encoded data over the WebSocket", + inject([AsyncTestCompleter], (async) { + var socket = new SpyWebSocket(); + var sink = new WebSocketMessageBusSink(socket); + expectSinkSendsEncodedJson(socket, sink, "send", async); + })); + }); + + describe("WebSocketMessageBusSource", () { + it( + "should decode JSON messages and emit them", + inject([AsyncTestCompleter], (async) { + var socket = new SpyWebSocket(); + StreamController controller = + new StreamController.broadcast(); + socket.spy("get:onMessage").andCallFake(() => controller.stream); + var source = new WebSocketMessageBusSource(socket); + + source.from(CHANNEL).listen((message) { + expect(message).toEqual(MESSAGE); + async.done(); + }); + + var event = new SpyMessageEvent(); + event.spy("get:data").andCallFake( + () => JSON.encode({'channel': CHANNEL, 'message': MESSAGE})); + controller.add(event); + })); + }); +} + +@proxy +class SpyMessageEvent extends SpyObject implements MessageEvent { + SpyMessageEvent() : super(SpyMessageEvent); + noSuchMethod(m) { + return super.noSuchMethod(m); + } +} + +@proxy +class SpyWebSocket extends SpyObject implements WebSocket { + SpyWebSocket() : super(SpyWebSocket); + noSuchMethod(m) { + return super.noSuchMethod(m); + } +} diff --git a/modules/examples/src/web_workers/todo/index_common.ts b/modules/examples/src/web_workers/todo/index_common.ts index 29571ad299..e415329e62 100644 --- a/modules/examples/src/web_workers/todo/index_common.ts +++ b/modules/examples/src/web_workers/todo/index_common.ts @@ -1,4 +1,6 @@ -import {NgFor, Component, View, FORM_DIRECTIVES} from 'angular2/angular2'; +import {NgFor} from 'angular2/src/core/directives/ng_for'; +import {View, Component} from 'angular2/src/core/metadata'; +import {FORM_DIRECTIVES} from 'angular2/src/forms/directives'; import {Store, Todo, TodoFactory} from './services/TodoStore'; @Component({selector: 'todo-app', viewBindings: [Store, TodoFactory]}) diff --git a/modules/examples/src/web_workers/todo/index_web_socket.dart b/modules/examples/src/web_workers/todo/index_web_socket.dart new file mode 100644 index 0000000000..ad8548258a --- /dev/null +++ b/modules/examples/src/web_workers/todo/index_web_socket.dart @@ -0,0 +1,16 @@ +library angular2.examples.web_workers.todo.index_web_socket; + +import "package:angular2/src/core/reflection/reflection_capabilities.dart"; +import "package:angular2/src/core/reflection/reflection.dart"; +import "package:angular2/src/web_workers/ui/impl.dart" show bootstrapUICommon; +import "package:angular2/src/web_workers/debug_tools/web_socket_message_bus.dart"; +import 'dart:html' + show WebSocket; + +main() { + reflector.reflectionCapabilities = new ReflectionCapabilities(); + var webSocket = new WebSocket("ws://127.0.0.1:1337/ws"); + var bus = new WebSocketMessageBus.fromWebSocket(webSocket); + + bootstrapUICommon(bus); +} diff --git a/modules/examples/src/web_workers/todo/index_web_socket.html b/modules/examples/src/web_workers/todo/index_web_socket.html new file mode 100644 index 0000000000..a52efc9238 --- /dev/null +++ b/modules/examples/src/web_workers/todo/index_web_socket.html @@ -0,0 +1,13 @@ + + + Todo Angular 2 - WebWorker + + + + Loading... + + + + $SCRIPTS$ + + diff --git a/modules/examples/src/web_workers/todo/server_index.dart b/modules/examples/src/web_workers/todo/server_index.dart new file mode 100644 index 0000000000..16150aaa5a --- /dev/null +++ b/modules/examples/src/web_workers/todo/server_index.dart @@ -0,0 +1,16 @@ +library angular2.examples.web_workers.todo.server_index; +import "index_common.dart" show TodoApp; +import "package:angular2/src/web_workers/debug_tools/multi_client_server_message_bus.dart"; +import "package:angular2/src/web_workers/worker/application_common.dart" + show bootstrapWebWorkerCommon; +import 'dart:io'; +import "package:angular2/src/core/reflection/reflection_capabilities.dart"; +import "package:angular2/src/core/reflection/reflection.dart"; + +void main() { + reflector.reflectionCapabilities = new ReflectionCapabilities(); + HttpServer.bind('127.0.0.1', 1337).then((HttpServer server) { + var bus = new MultiClientServerMessageBus.fromHttpServer(server); + bootstrapWebWorkerCommon(TodoApp, bus).catchError((error) => throw error); + }); +} diff --git a/modules_dart/transform/lib/src/transform/template_compiler/reflection/model.dart b/modules_dart/transform/lib/src/transform/template_compiler/reflection/model.dart index 51522ef045..7277ae8fc4 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/reflection/model.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/reflection/model.dart @@ -1,6 +1,7 @@ library angular2.transform.template_compiler.reflection.model; import 'package:angular2/src/core/render/dom/util.dart'; +import 'package:angular2/src/core/render/event_config.dart'; /// Defines the names of getters, setters, and methods which need to be /// available to Angular 2 via the `reflector` at runtime.