From 696edde17c05af04221b04311919744c81ce2390 Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Thu, 3 Sep 2015 10:31:46 -0700 Subject: [PATCH] fix(WebWorker): Fix Todo Server demo and add test to ensure the demo can bootstrap. Closes #3970 --- .../src/web_workers/ui/event_serializer.dart | 140 +++++++----------- .../web_workers/worker/application_common.ts | 52 ++++--- .../debug_tools/bootstrap.server.spec.dart | 25 ++++ .../shared/mock_event_emitter.dart | 7 +- .../web_workers/shared/mock_event_emitter.ts | 6 +- modules/examples/pubspec.yaml | 9 ++ .../src/web_workers/todo/index_common.ts | 4 +- .../web_workers/todo/services/TodoStore.ts | 2 +- .../transform/common/annotation_matcher.dart | 6 + 9 files changed, 135 insertions(+), 116 deletions(-) create mode 100644 modules/angular2/test/web_workers/debug_tools/bootstrap.server.spec.dart diff --git a/modules/angular2/src/web_workers/ui/event_serializer.dart b/modules/angular2/src/web_workers/ui/event_serializer.dart index 6c10df0341..b5e90aa29f 100644 --- a/modules/angular2/src/web_workers/ui/event_serializer.dart +++ b/modules/angular2/src/web_workers/ui/event_serializer.dart @@ -1,66 +1,8 @@ library angular2.src.web_workers.event_serializer; -import 'package:angular2/src/core/facade/collection.dart'; -// TODO(jteplitz602): Remove Mirrors from serialization #3348 -@MirrorsUsed( - symbols: "altKey, bubbles, button, cancelable, client, ctrlKey, " + - "defaultPrevented, detail, eventPhase, layer, metaKey, offset, page, region, screen, " + - "shiftKey, timeStamp, type, magnitude, x, y, charCode, keyCode, keyLocation, location, repeat") -import 'dart:mirrors'; import 'dart:core'; import 'dart:html'; -// These Maps can't be const due to a dartj2 bug (see http://github.com/dart-lang/sdk/issues/21825) -// Once that bug is fixed these should be const -final Map MOUSE_EVENT_PROPERTIES = { - #altKey: bool, - #bubbles: bool, - #button: int, - #cancelable: bool, - #client: Point, - #ctrlKey: bool, - #defaultPrevented: bool, - #detail: int, - #eventPhase: int, - #layer: Point, - #metaKey: bool, - #offset: Point, - #page: Point, - #region: String, - #screen: Point, - #shiftKey: bool, - #timeStamp: int, - #type: String -}; - -final Map KEYBOARD_EVENT_PROPERTIES = { - #altKey: bool, - #bubbles: bool, - #cancelable: bool, - #charCode: int, - #ctrlKey: bool, - #defaultPrevented: bool, - #detail: int, - #eventPhase: int, - #keyCode: int, - #keyLocation: int, - #layer: Point, - #location: int, - #repeat: bool, - #shiftKey: bool, - #timeStamp: int, - #type: String -}; - -final Map EVENT_PROPERTIES = { - #bubbles: bool, - #cancelable: bool, - #defaultPrevented: bool, - #eventPhase: int, - #timeStamp: int, - #type: String -}; - // List of all elements with HTML value attribute. // Taken from: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes final Set NODES_WITH_VALUE = new Set.from([ @@ -75,23 +17,74 @@ final Set NODES_WITH_VALUE = new Set.from([ ]); Map serializeGenericEvent(dynamic e) { - return serializeEvent(e, EVENT_PROPERTIES); + var serialized = new Map(); + serialized['bubbles'] = e.bubbles; + serialized['cancelable'] = e.cancelable; + serialized['defaultPrevented'] = e.defaultPrevented; + serialized['eventPhase'] = e.eventPhase; + serialized['timeStamp'] = e.timeStamp; + serialized['type'] = e.type; + return serialized; } // TODO(jteplitz602): Allow users to specify the properties they need rather than always // adding value #3374 Map serializeEventWithTarget(dynamic e) { - var serializedEvent = serializeEvent(e, EVENT_PROPERTIES); + var serializedEvent = serializeGenericEvent(e); return addTarget(e, serializedEvent); } Map serializeMouseEvent(dynamic e) { - return serializeEvent(e, MOUSE_EVENT_PROPERTIES); + var serialized = new Map(); + serialized['altKey'] = e.altKey; + serialized['bubbles'] = e.bubbles; + serialized['button'] = e.button; + serialized['cancelable'] = e.cancelable; + serialized['client'] = serializePoint(e.client); + serialized['ctrlKey'] = e.ctrlKey; + serialized['defaultPrevented'] = e.defaultPrevented; + serialized['detail'] = e.detail; + serialized['eventPhase'] = e.eventPhase; + serialized['layer'] = serializePoint(e.layer); + serialized['metaKey'] = e.metaKey; + serialized['offset'] = serializePoint(e.offset); + serialized['page'] = serializePoint(e.page); + serialized['region'] = e.region; + serialized['screen'] = serializePoint(e.screen); + serialized['shiftKey'] = e.shiftKey; + serialized['timeStamp'] = e.timeStamp; + serialized['type'] = e.type; + return serialized; +} + +Map serializePoint(Point point) { + var serialized = new Map(); + serialized['magnitude'] = point.magnitude; + serialized['x'] = point.x; + serialized['y'] = point.y; + return serialized; } Map serializeKeyboardEvent(dynamic e) { - var serializedEvent = serializeEvent(e, KEYBOARD_EVENT_PROPERTIES); - return addTarget(e, serializedEvent); + var serialized = new Map(); + serialized['altKey'] = e.altKey; + serialized['bubbles'] = e.bubbles; + serialized['cancelable'] = e.cancelable; + serialized['charCode'] = e.charCode; + serialized['ctrlKey'] = e.ctrlKey; + serialized['defaultPrevented'] = e.defaultPrevented; + serialized['detail'] = e.detail; + serialized['eventPhase'] = e.eventPhase; + serialized['keyCode'] = e.keyCode; + serialized['keyLocation'] = e.keyLocation; + serialized['layer'] = serializePoint(e.layer); + serialized['location'] = e.location; + serialized['repeat'] = e.repeat; + serialized['shiftKey'] = e.shiftKey; + serialized['timeStamp'] = e.timeStamp; + serialized['type'] = e.type; + //return addTarget(e, serialized); + return serialized; } // TODO(jteplitz602): #3374. See above. @@ -105,24 +98,3 @@ Map addTarget( } return serializedEvent; } - -Map serializeEvent(dynamic e, Map PROPERTIES) { - var serialized = StringMapWrapper.create(); - var mirror = reflect(e); - PROPERTIES.forEach((property, type) { - var value = mirror.getField(property).reflectee; - var propertyName = MirrorSystem.getName(property); - if (type == int || type == bool || type == String) { - serialized[propertyName] = value; - } else if (type == Point) { - var point = reflect(value); - serialized[propertyName] = { - 'magnitude': point.getField(#magnitude).reflectee, - 'x': point.getField(#x).reflectee, - 'y': point.getField(#y).reflectee - }; - } - }); - - return serialized; -} diff --git a/modules/angular2/src/web_workers/worker/application_common.ts b/modules/angular2/src/web_workers/worker/application_common.ts index 83503f21be..a6b73e7a34 100644 --- a/modules/angular2/src/web_workers/worker/application_common.ts +++ b/modules/angular2/src/web_workers/worker/application_common.ts @@ -90,7 +90,6 @@ function _injectorBindings(appComponentType, bus: MessageBus, initData: StringMa bind(APP_COMPONENT_REF_PROMISE) .toFactory( (dynamicComponentLoader, injector) => { - // TODO(rado): investigate whether to support bindings on root component. return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector) .then((componentRef) => { return componentRef; }); @@ -150,32 +149,39 @@ export function bootstrapWebWorkerCommon( // index.html and main.js are possible. // - var subscription: any; var emitter = bus.from(SETUP_CHANNEL); subscription = ObservableWrapper.subscribe(emitter, (message: StringMap) => { - var appInjector = - _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, message); - var compRefToken = PromiseWrapper.wrap(() => { - try { - return appInjector.get(APP_COMPONENT_REF_PROMISE); - } catch (e) { - throw e; + var exceptionHandler; + try { + var appInjector = + _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, message); + exceptionHandler = appInjector.get(ExceptionHandler); + zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s)); + var compRefToken: Promise = appInjector.get(APP_COMPONENT_REF_PROMISE); + var tick = (componentRef) => { + var appChangeDetector = internalView(componentRef.hostView).changeDetector; + // retrieve life cycle: may have already been created if injected in root component + var lc = appInjector.get(LifeCycle); + lc.registerWith(zone, appChangeDetector); + lc.tick(); // the first tick that will bootstrap the app + + bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector)); + }; + + var tickResult = PromiseWrapper.then(compRefToken, tick); + + PromiseWrapper.then(tickResult, + (_) => {}); // required for Dart to trigger the default error handler + PromiseWrapper.then(tickResult, null, + (err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); }); + ObservableWrapper.dispose(subscription); + } catch (e) { + if (isPresent(exceptionHandler)) { + exceptionHandler.call(e, e.stack); } - }); - var tick = (componentRef) => { - var appChangeDetector = internalView(componentRef.hostView).changeDetector; - // retrieve life cycle: may have already been created if injected in root component - var lc = appInjector.get(LifeCycle); - lc.registerWith(zone, appChangeDetector); - lc.tick(); // the first tick that will bootstrap the app - - bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector)); - }; - PromiseWrapper.then(compRefToken, tick, - (err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); }); - - ObservableWrapper.dispose(subscription); + bootstrapProcess.reject(e, e.stack); + } }); ObservableWrapper.callNext(bus.to(SETUP_CHANNEL), "ready"); diff --git a/modules/angular2/test/web_workers/debug_tools/bootstrap.server.spec.dart b/modules/angular2/test/web_workers/debug_tools/bootstrap.server.spec.dart new file mode 100644 index 0000000000..f4ac872280 --- /dev/null +++ b/modules/angular2/test/web_workers/debug_tools/bootstrap.server.spec.dart @@ -0,0 +1,25 @@ +library angular2.test.web_workers.debug_tools.bootstrap; + +import "package:angular2/test_lib.dart"; +import "package:angular2/src/core/reflection/reflection_capabilities.dart"; +import "package:angular2/src/core/reflection/reflection.dart"; +import "package:angular2/web_worker/worker.dart"; +import "package:angular2/src/web_workers/worker/application_common.dart"; +import "../shared/web_worker_test_util.dart"; +import "dart:convert"; + +main() { + describe("bootstrapWebWorkerCommon", () { + it ("should bootstrap on a Dart VM", () { + reflector.reflectionCapabilities = new ReflectionCapabilities(); + var buses = createPairedMessageBuses(); + bootstrapWebWorkerCommon(App, buses.worker); + }); + }); +} + +@Component(selector: "app") +@View(template: "

Hello {{name}}

") +class App { + String name = "Tester"; +} diff --git a/modules/angular2/test/web_workers/shared/mock_event_emitter.dart b/modules/angular2/test/web_workers/shared/mock_event_emitter.dart index 69f9ade133..6f83ce7f60 100644 --- a/modules/angular2/test/web_workers/shared/mock_event_emitter.dart +++ b/modules/angular2/test/web_workers/shared/mock_event_emitter.dart @@ -5,17 +5,16 @@ import 'dart:async'; import "package:angular2/src/core/facade/async.dart"; class MockEventEmitter extends EventEmitter { - List _nextFns = new List(); + final controller = new StreamController.broadcast(sync: true); @override StreamSubscription listen(void onData(dynamic line), {void onError(Error error), void onDone(), bool cancelOnError}) { - _nextFns.add(onData); - return null; + return controller.stream.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); } @override void add(value) { - _nextFns.forEach((fn) => fn(value)); + controller.add(value); } } diff --git a/modules/angular2/test/web_workers/shared/mock_event_emitter.ts b/modules/angular2/test/web_workers/shared/mock_event_emitter.ts index b29d8e68a4..bf65c2d793 100644 --- a/modules/angular2/test/web_workers/shared/mock_event_emitter.ts +++ b/modules/angular2/test/web_workers/shared/mock_event_emitter.ts @@ -9,10 +9,14 @@ export class MockEventEmitter extends EventEmitter { observer(generator: any): Rx.IDisposable { this._nextFns.push(generator.next); - return null; + return new MockDisposable(); } next(value: any) { ListWrapper.forEach(this._nextFns, (fn) => { fn(value); }); } } + +class MockDisposable implements Rx.IDisposable { + dispose(): void {} +} diff --git a/modules/examples/pubspec.yaml b/modules/examples/pubspec.yaml index 32379b5e42..3153975d4c 100644 --- a/modules/examples/pubspec.yaml +++ b/modules/examples/pubspec.yaml @@ -35,6 +35,15 @@ transformers: - web/src/observable_models/index.dart - web/src/person_management/index.dart - web/src/template_driven_forms/index.dart + - web/src/web_workers/todo/server_index.dart + - web/src/web_workers/todo/background_index.dart + - web/src/web_workers/message_broker/background_index.dart + - web/src/web_workers/kitchen_sink/background_index.dart + + # These entrypoints are disabled untl the transformer supports UI bootstrap (issue #3971) + # - web/src/web_workers/message_broker/index.dart + # - web/src/web_workers/kitchen_sink/index.dart + # - web/src/web_workers/todo/index.dart # These entrypoints are disabled until cross-package urls are working (issue #2982) # - web/src/material/button/index.dart # - web/src/material/checkbox/index.dart diff --git a/modules/examples/src/web_workers/todo/index_common.ts b/modules/examples/src/web_workers/todo/index_common.ts index e415329e62..9bf2ddfe3a 100644 --- a/modules/examples/src/web_workers/todo/index_common.ts +++ b/modules/examples/src/web_workers/todo/index_common.ts @@ -1,6 +1,4 @@ -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 {NgFor, View, Component, FORM_DIRECTIVES} from 'angular2/web_worker/worker'; import {Store, Todo, TodoFactory} from './services/TodoStore'; @Component({selector: 'todo-app', viewBindings: [Store, TodoFactory]}) diff --git a/modules/examples/src/web_workers/todo/services/TodoStore.ts b/modules/examples/src/web_workers/todo/services/TodoStore.ts index 3b38ce3395..2281822f84 100644 --- a/modules/examples/src/web_workers/todo/services/TodoStore.ts +++ b/modules/examples/src/web_workers/todo/services/TodoStore.ts @@ -1,4 +1,4 @@ -import {Injectable} from 'angular2/angular2'; +import {Injectable} from 'angular2/web_worker/worker'; import {ListWrapper, Predicate} from 'angular2/src/core/facade/collection'; // base model for RecordStore diff --git a/modules_dart/transform/lib/src/transform/common/annotation_matcher.dart b/modules_dart/transform/lib/src/transform/common/annotation_matcher.dart index d8a7d60765..d151932b98 100644 --- a/modules_dart/transform/lib/src/transform/common/annotation_matcher.dart +++ b/modules_dart/transform/lib/src/transform/common/annotation_matcher.dart @@ -15,6 +15,7 @@ const INJECTABLES = const [ const ClassDescriptor('Injectable', 'package:angular2/di.dart'), const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'), const ClassDescriptor('Injectable', 'package:angular2/bootstrap_static.dart'), + const ClassDescriptor('Injectable', 'package:angular2/web_worker/worker.dart'), ]; const DIRECTIVES = const [ @@ -30,6 +31,8 @@ const DIRECTIVES = const [ superClass: 'Injectable'), const ClassDescriptor('Directive', 'package:angular2/bootstrap_static.dart', superClass: 'Injectable'), + const ClassDescriptor('Directive', 'package:angular2/web_worker/worker.dart', + superClass: 'Injectable'), ]; const COMPONENTS = const [ @@ -45,10 +48,13 @@ const COMPONENTS = const [ superClass: 'Directive'), const ClassDescriptor('Component', 'package:angular2/core.dart', superClass: '`Directive'), + const ClassDescriptor('Component', 'package:angular2/web_worker/worker.dart', + superClass: '`Directive'), ]; const VIEWS = const [ const ClassDescriptor('View', 'package:angular2/angular2.dart'), + const ClassDescriptor('View', 'package:angular2/web_worker/worker.dart'), const ClassDescriptor('View', 'package:angular2/bootstrap_static.dart'), const ClassDescriptor('View', 'package:angular2/core.dart'), const ClassDescriptor('View', 'package:angular2/src/core/metadata/view.dart'),