fix(WebWorker): Fix Todo Server demo and add test to ensure the demo can bootstrap.

Closes #3970
This commit is contained in:
Jason Teplitz 2015-09-03 10:31:46 -07:00
parent 3ff321475d
commit 696edde17c
9 changed files with 135 additions and 116 deletions

View File

@ -1,66 +1,8 @@
library angular2.src.web_workers.event_serializer; 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:core';
import 'dart:html'; 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. // List of all elements with HTML value attribute.
// Taken from: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes // Taken from: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
final Set<String> NODES_WITH_VALUE = new Set<String>.from([ final Set<String> NODES_WITH_VALUE = new Set<String>.from([
@ -75,23 +17,74 @@ final Set<String> NODES_WITH_VALUE = new Set<String>.from([
]); ]);
Map<String, dynamic> serializeGenericEvent(dynamic e) { Map<String, dynamic> serializeGenericEvent(dynamic e) {
return serializeEvent(e, EVENT_PROPERTIES); var serialized = new Map<String, dynamic>();
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 // TODO(jteplitz602): Allow users to specify the properties they need rather than always
// adding value #3374 // adding value #3374
Map<String, dynamic> serializeEventWithTarget(dynamic e) { Map<String, dynamic> serializeEventWithTarget(dynamic e) {
var serializedEvent = serializeEvent(e, EVENT_PROPERTIES); var serializedEvent = serializeGenericEvent(e);
return addTarget(e, serializedEvent); return addTarget(e, serializedEvent);
} }
Map<String, dynamic> serializeMouseEvent(dynamic e) { Map<String, dynamic> serializeMouseEvent(dynamic e) {
return serializeEvent(e, MOUSE_EVENT_PROPERTIES); var serialized = new Map<String, dynamic>();
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<String, dynamic> serializePoint(Point point) {
var serialized = new Map<String, dynamic>();
serialized['magnitude'] = point.magnitude;
serialized['x'] = point.x;
serialized['y'] = point.y;
return serialized;
} }
Map<String, dynamic> serializeKeyboardEvent(dynamic e) { Map<String, dynamic> serializeKeyboardEvent(dynamic e) {
var serializedEvent = serializeEvent(e, KEYBOARD_EVENT_PROPERTIES); var serialized = new Map<String, dynamic>();
return addTarget(e, serializedEvent); 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. // TODO(jteplitz602): #3374. See above.
@ -105,24 +98,3 @@ Map<String, dynamic> addTarget(
} }
return serializedEvent; return serializedEvent;
} }
Map<String, dynamic> serializeEvent(dynamic e, Map<Symbol, Type> 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;
}

View File

@ -90,7 +90,6 @@ function _injectorBindings(appComponentType, bus: MessageBus, initData: StringMa
bind(APP_COMPONENT_REF_PROMISE) bind(APP_COMPONENT_REF_PROMISE)
.toFactory( .toFactory(
(dynamicComponentLoader, injector) => { (dynamicComponentLoader, injector) => {
// TODO(rado): investigate whether to support bindings on root component. // TODO(rado): investigate whether to support bindings on root component.
return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector) return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector)
.then((componentRef) => { return componentRef; }); .then((componentRef) => { return componentRef; });
@ -150,32 +149,39 @@ export function bootstrapWebWorkerCommon(
// index.html and main.js are possible. // index.html and main.js are possible.
// //
var subscription: any; var subscription: any;
var emitter = bus.from(SETUP_CHANNEL); var emitter = bus.from(SETUP_CHANNEL);
subscription = ObservableWrapper.subscribe(emitter, (message: StringMap<string, any>) => { subscription = ObservableWrapper.subscribe(emitter, (message: StringMap<string, any>) => {
var appInjector = var exceptionHandler;
_createAppInjector(appComponentType, componentInjectableBindings, zone, bus, message); try {
var compRefToken = PromiseWrapper.wrap(() => { var appInjector =
try { _createAppInjector(appComponentType, componentInjectableBindings, zone, bus, message);
return appInjector.get(APP_COMPONENT_REF_PROMISE); exceptionHandler = appInjector.get(ExceptionHandler);
} catch (e) { zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
throw e; var compRefToken: Promise<any> = 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);
} }
}); bootstrapProcess.reject(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);
}); });
ObservableWrapper.callNext(bus.to(SETUP_CHANNEL), "ready"); ObservableWrapper.callNext(bus.to(SETUP_CHANNEL), "ready");

View File

@ -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: "<p>Hello {{name}}</p>")
class App {
String name = "Tester";
}

View File

@ -5,17 +5,16 @@ import 'dart:async';
import "package:angular2/src/core/facade/async.dart"; import "package:angular2/src/core/facade/async.dart";
class MockEventEmitter extends EventEmitter { class MockEventEmitter extends EventEmitter {
List<Function> _nextFns = new List(); final controller = new StreamController.broadcast(sync: true);
@override @override
StreamSubscription listen(void onData(dynamic line), StreamSubscription listen(void onData(dynamic line),
{void onError(Error error), void onDone(), bool cancelOnError}) { {void onError(Error error), void onDone(), bool cancelOnError}) {
_nextFns.add(onData); return controller.stream.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
return null;
} }
@override @override
void add(value) { void add(value) {
_nextFns.forEach((fn) => fn(value)); controller.add(value);
} }
} }

View File

@ -9,10 +9,14 @@ export class MockEventEmitter extends EventEmitter {
observer(generator: any): Rx.IDisposable { observer(generator: any): Rx.IDisposable {
this._nextFns.push(generator.next); this._nextFns.push(generator.next);
return null; return new MockDisposable();
} }
next(value: any) { next(value: any) {
ListWrapper.forEach(this._nextFns, (fn) => { fn(value); }); ListWrapper.forEach(this._nextFns, (fn) => { fn(value); });
} }
} }
class MockDisposable implements Rx.IDisposable {
dispose(): void {}
}

View File

@ -35,6 +35,15 @@ transformers:
- web/src/observable_models/index.dart - web/src/observable_models/index.dart
- web/src/person_management/index.dart - web/src/person_management/index.dart
- web/src/template_driven_forms/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) # These entrypoints are disabled until cross-package urls are working (issue #2982)
# - web/src/material/button/index.dart # - web/src/material/button/index.dart
# - web/src/material/checkbox/index.dart # - web/src/material/checkbox/index.dart

View File

@ -1,6 +1,4 @@
import {NgFor} from 'angular2/src/core/directives/ng_for'; import {NgFor, View, Component, FORM_DIRECTIVES} from 'angular2/web_worker/worker';
import {View, Component} from 'angular2/src/core/metadata';
import {FORM_DIRECTIVES} from 'angular2/src/forms/directives';
import {Store, Todo, TodoFactory} from './services/TodoStore'; import {Store, Todo, TodoFactory} from './services/TodoStore';
@Component({selector: 'todo-app', viewBindings: [Store, TodoFactory]}) @Component({selector: 'todo-app', viewBindings: [Store, TodoFactory]})

View File

@ -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'; import {ListWrapper, Predicate} from 'angular2/src/core/facade/collection';
// base model for RecordStore // base model for RecordStore

View File

@ -15,6 +15,7 @@ const INJECTABLES = const [
const ClassDescriptor('Injectable', 'package:angular2/di.dart'), const ClassDescriptor('Injectable', 'package:angular2/di.dart'),
const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'), const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'),
const ClassDescriptor('Injectable', 'package:angular2/bootstrap_static.dart'), const ClassDescriptor('Injectable', 'package:angular2/bootstrap_static.dart'),
const ClassDescriptor('Injectable', 'package:angular2/web_worker/worker.dart'),
]; ];
const DIRECTIVES = const [ const DIRECTIVES = const [
@ -30,6 +31,8 @@ const DIRECTIVES = const [
superClass: 'Injectable'), superClass: 'Injectable'),
const ClassDescriptor('Directive', 'package:angular2/bootstrap_static.dart', const ClassDescriptor('Directive', 'package:angular2/bootstrap_static.dart',
superClass: 'Injectable'), superClass: 'Injectable'),
const ClassDescriptor('Directive', 'package:angular2/web_worker/worker.dart',
superClass: 'Injectable'),
]; ];
const COMPONENTS = const [ const COMPONENTS = const [
@ -45,10 +48,13 @@ const COMPONENTS = const [
superClass: 'Directive'), superClass: 'Directive'),
const ClassDescriptor('Component', 'package:angular2/core.dart', const ClassDescriptor('Component', 'package:angular2/core.dart',
superClass: '`Directive'), superClass: '`Directive'),
const ClassDescriptor('Component', 'package:angular2/web_worker/worker.dart',
superClass: '`Directive'),
]; ];
const VIEWS = const [ const VIEWS = const [
const ClassDescriptor('View', 'package:angular2/angular2.dart'), 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/bootstrap_static.dart'),
const ClassDescriptor('View', 'package:angular2/core.dart'), const ClassDescriptor('View', 'package:angular2/core.dart'),
const ClassDescriptor('View', 'package:angular2/src/core/metadata/view.dart'), const ClassDescriptor('View', 'package:angular2/src/core/metadata/view.dart'),