feat(core): provide an error context when an exception happens in an error handler
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
BaseException,
|
||||
assertionsEnabled,
|
||||
print,
|
||||
stringify
|
||||
stringify,
|
||||
isDart
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
@@ -128,7 +129,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
|
||||
DirectiveResolver,
|
||||
Parser,
|
||||
Lexer,
|
||||
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM), []),
|
||||
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM, isDart ? false : true), []),
|
||||
bind(XHR).toValue(new XHRImpl()),
|
||||
ComponentUrlMapper,
|
||||
UrlResolver,
|
||||
@@ -280,8 +281,8 @@ export function commonBootstrap(
|
||||
appComponentType: /*Type*/ any,
|
||||
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
var bootstrapProcess: PromiseCompleter<any> = PromiseWrapper.completer();
|
||||
var zone = createNgZone(new ExceptionHandler(DOM));
|
||||
var bootstrapProcess = PromiseWrapper.completer();
|
||||
var zone = createNgZone(new ExceptionHandler(DOM, isDart ? false : true));
|
||||
zone.run(() => {
|
||||
// TODO(rado): prepopulate template cache, so applications with only
|
||||
// index.html and main.js are possible.
|
||||
@@ -290,19 +291,27 @@ export function commonBootstrap(
|
||||
var exceptionHandler = appInjector.get(ExceptionHandler);
|
||||
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
|
||||
|
||||
var compRefToken: Promise<any> =
|
||||
PromiseWrapper.wrap(() => appInjector.get(appComponentRefPromiseToken));
|
||||
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
|
||||
try {
|
||||
var compRefToken: Promise<any> = appInjector.get(appComponentRefPromiseToken);
|
||||
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));
|
||||
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); });
|
||||
} catch (e) {
|
||||
bootstrapProcess.reject(e, e.stack);
|
||||
}
|
||||
});
|
||||
|
||||
return bootstrapProcess.promise;
|
||||
|
||||
@@ -502,7 +502,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||
var p = this._preBuiltObjects;
|
||||
var index = p.elementRef.boundElementIndex - p.view.elementOffset;
|
||||
var c = this._preBuiltObjects.view.getDebugContext(index, null);
|
||||
return new _Context(c["element"], c["componentElement"], c["injector"]);
|
||||
return isPresent(c) ? new _Context(c.element, c.componentElement, c.injector) : null;
|
||||
}
|
||||
|
||||
private _reattachInjectors(imperativelyCreatedInjector: Injector): void {
|
||||
|
||||
@@ -212,7 +212,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
return isPresent(boundElementIndex) ? this.elementRefs[boundElementIndex] : null;
|
||||
}
|
||||
|
||||
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): StringMap<string, any> {
|
||||
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext {
|
||||
try {
|
||||
var offsettedIndex = this.elementOffset + elementIndex;
|
||||
var hasRefForIndex = offsettedIndex < this.elementRefs.length;
|
||||
@@ -226,18 +226,13 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
var directive = isPresent(directiveIndex) ? this.getDirectiveFor(directiveIndex) : null;
|
||||
var injector = isPresent(ei) ? ei.getInjector() : null;
|
||||
|
||||
return {
|
||||
element: element,
|
||||
componentElement: componentElement,
|
||||
directive: directive,
|
||||
context: this.context,
|
||||
locals: _localsToStringMap(this.locals),
|
||||
injector: injector
|
||||
};
|
||||
return new DebugContext(element, componentElement, directive, this.context,
|
||||
_localsToStringMap(this.locals), injector);
|
||||
|
||||
} catch (e) {
|
||||
// TODO: vsavkin log the exception once we have a good way to log errors and warnings
|
||||
// if an error happens during getting the debug context, we return an empty map.
|
||||
return {};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,29 +257,37 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
|
||||
// returns false if preventDefault must be applied to the DOM event
|
||||
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||
// Most of the time the event will be fired only when the view is in the live document.
|
||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||
// queuing up and firing.
|
||||
var allowDefaultBehavior = true;
|
||||
if (this.hydrated()) {
|
||||
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
|
||||
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
||||
var eventMap = elBinder.hostListeners[eventName];
|
||||
if (isBlank(eventMap)) return allowDefaultBehavior;
|
||||
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
||||
var context;
|
||||
if (directiveIndex === -1) {
|
||||
context = this.context;
|
||||
} else {
|
||||
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
var result = expr.eval(context, new Locals(this.locals, locals));
|
||||
if (isPresent(result)) {
|
||||
allowDefaultBehavior = allowDefaultBehavior && result == true;
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Most of the time the event will be fired only when the view is in the live document.
|
||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||
// queuing up and firing.
|
||||
var allowDefaultBehavior = true;
|
||||
if (this.hydrated()) {
|
||||
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
|
||||
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
||||
var eventMap = elBinder.hostListeners[eventName];
|
||||
if (isBlank(eventMap)) return allowDefaultBehavior;
|
||||
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
||||
var context;
|
||||
if (directiveIndex === -1) {
|
||||
context = this.context;
|
||||
} else {
|
||||
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
var result = expr.eval(context, new Locals(this.locals, locals));
|
||||
if (isPresent(result)) {
|
||||
allowDefaultBehavior = allowDefaultBehavior && result == true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return allowDefaultBehavior;
|
||||
} catch (e) {
|
||||
var c = this.getDebugContext(boundElementIndex - this.elementOffset, null);
|
||||
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
|
||||
c.injector) :
|
||||
null;
|
||||
throw new EventEvaluationError(eventName, e, e.stack, context);
|
||||
}
|
||||
return allowDefaultBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +301,29 @@ function _localsToStringMap(locals: Locals): StringMap<string, any> {
|
||||
return res;
|
||||
}
|
||||
|
||||
export class DebugContext {
|
||||
constructor(public element: any, public componentElement: any, public directive: any,
|
||||
public context: any, public locals: any, public injector: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error context included when an event handler throws an exception.
|
||||
*/
|
||||
class _Context {
|
||||
constructor(public element: any, public componentElement: any, public context: any,
|
||||
public locals: any, public injector: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an exception thrown by an event handler.
|
||||
*/
|
||||
class EventEvaluationError extends BaseException {
|
||||
constructor(eventName: string, originalException: any, originalStack: any, context: any) {
|
||||
super(`Error during evaluation of "${eventName}"`, originalException, originalStack, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -80,7 +80,7 @@ export class ExceptionHandler {
|
||||
|
||||
_longStackTrace(stackTrace: any): any {
|
||||
return isListLikeIterable(stackTrace) ? (<any>stackTrace).join("\n\n-----async gap-----\n") :
|
||||
stackTrace;
|
||||
stackTrace.toString();
|
||||
}
|
||||
|
||||
_findContext(exception: any): any {
|
||||
|
||||
Reference in New Issue
Block a user