fix(ivy): errors not being logged to ErrorHandler (#28447)
Fixes Ivy not passing thrown errors along to the `ErrorHandler`. **Note:** the failing test had to be reworked a little bit, because it has some assertions that depend on an error context being logged, however Ivy doesn't keep track of the error context. This PR resolves FW-840. PR Close #28447
This commit is contained in:
parent
a98d66078d
commit
fc88a79b32
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import {InjectFlags, InjectionToken, Injector} from '../di';
|
import {InjectFlags, InjectionToken, Injector} from '../di';
|
||||||
import {resolveForwardRef} from '../di/forward_ref';
|
import {resolveForwardRef} from '../di/forward_ref';
|
||||||
|
import {ErrorHandler} from '../error_handler';
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
import {validateAttribute, validateProperty} from '../sanitization/sanitization';
|
import {validateAttribute, validateProperty} from '../sanitization/sanitization';
|
||||||
import {Sanitizer} from '../sanitization/security';
|
import {Sanitizer} from '../sanitization/security';
|
||||||
@ -936,6 +937,7 @@ function listenerInternal(
|
|||||||
const tView = lView[TVIEW];
|
const tView = lView[TVIEW];
|
||||||
const firstTemplatePass = tView.firstTemplatePass;
|
const firstTemplatePass = tView.firstTemplatePass;
|
||||||
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
|
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
|
||||||
|
|
||||||
ngDevMode && assertNodeOfPossibleTypes(
|
ngDevMode && assertNodeOfPossibleTypes(
|
||||||
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
||||||
|
|
||||||
@ -2605,13 +2607,17 @@ function wrapListener(
|
|||||||
markViewDirty(startView);
|
markViewDirty(startView);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = listenerFn(e);
|
try {
|
||||||
if (wrapWithPreventDefault && result === false) {
|
const result = listenerFn(e);
|
||||||
e.preventDefault();
|
if (wrapWithPreventDefault && result === false) {
|
||||||
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
|
e.preventDefault();
|
||||||
e.returnValue = false;
|
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
|
||||||
|
e.returnValue = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(lView, error);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -2723,12 +2729,17 @@ export function detectChangesInternal<T>(view: LView, context: T) {
|
|||||||
|
|
||||||
if (rendererFactory.begin) rendererFactory.begin();
|
if (rendererFactory.begin) rendererFactory.begin();
|
||||||
|
|
||||||
if (isCreationMode(view)) {
|
try {
|
||||||
checkView(view, context); // creation mode pass
|
if (isCreationMode(view)) {
|
||||||
|
checkView(view, context); // creation mode pass
|
||||||
|
}
|
||||||
|
checkView(view, context); // update mode pass
|
||||||
|
} catch (error) {
|
||||||
|
handleError(view, error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (rendererFactory.end) rendererFactory.end();
|
||||||
}
|
}
|
||||||
checkView(view, context); // update mode pass
|
|
||||||
|
|
||||||
if (rendererFactory.end) rendererFactory.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3237,3 +3248,10 @@ function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 {
|
|||||||
const componentLView = lView[tNode.index] as LView;
|
const componentLView = lView[tNode.index] as LView;
|
||||||
return componentLView[RENDERER];
|
return componentLView[RENDERER];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles an error thrown in an LView. */
|
||||||
|
function handleError(lView: LView, error: any): void {
|
||||||
|
const injector = lView[INJECTOR];
|
||||||
|
const errorHandler = injector ? injector.get(ErrorHandler, null) : null;
|
||||||
|
errorHandler && errorHandler.handleError(error);
|
||||||
|
}
|
||||||
|
@ -50,12 +50,24 @@
|
|||||||
{
|
{
|
||||||
"name": "EMPTY_OBJ"
|
"name": "EMPTY_OBJ"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ERROR_DEBUG_CONTEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ERROR_LOGGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ERROR_ORIGINAL_ERROR"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ElementRef"
|
"name": "ElementRef"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "EmptyErrorImpl"
|
"name": "EmptyErrorImpl"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ErrorHandler"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FLAGS"
|
"name": "FLAGS"
|
||||||
},
|
},
|
||||||
@ -491,6 +503,9 @@
|
|||||||
{
|
{
|
||||||
"name": "decreaseElementDepthCount"
|
"name": "decreaseElementDepthCount"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "defaultErrorLogger"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "defaultScheduler"
|
"name": "defaultScheduler"
|
||||||
},
|
},
|
||||||
@ -641,6 +656,9 @@
|
|||||||
{
|
{
|
||||||
"name": "getCurrentView"
|
"name": "getCurrentView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getDebugContext"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getDirectiveDef"
|
"name": "getDirectiveDef"
|
||||||
},
|
},
|
||||||
@ -656,6 +674,9 @@
|
|||||||
{
|
{
|
||||||
"name": "getElementDepthCount"
|
"name": "getElementDepthCount"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getErrorLogger"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getHighestElementOrICUContainer"
|
"name": "getHighestElementOrICUContainer"
|
||||||
},
|
},
|
||||||
@ -722,6 +743,9 @@
|
|||||||
{
|
{
|
||||||
"name": "getOrCreateTView"
|
"name": "getOrCreateTView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getOriginalError"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getParentInjectorIndex"
|
"name": "getParentInjectorIndex"
|
||||||
},
|
},
|
||||||
@ -803,6 +827,9 @@
|
|||||||
{
|
{
|
||||||
"name": "getValue"
|
"name": "getValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "handleError"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hasClassInput"
|
"name": "hasClassInput"
|
||||||
},
|
},
|
||||||
|
@ -450,84 +450,97 @@ function declareTestsUsingBootstrap() {
|
|||||||
if (getDOM().supportsDOMEvents()) {
|
if (getDOM().supportsDOMEvents()) {
|
||||||
// This test needs a real DOM....
|
// This test needs a real DOM....
|
||||||
|
|
||||||
fixmeIvy('FW-840: Exceptions thrown in event handlers are not reported to ErrorHandler')
|
it('should keep change detecting if there was an error', (done) => {
|
||||||
.it('should keep change detecting if there was an error', (done) => {
|
@Component({
|
||||||
@Component({
|
selector: COMP_SELECTOR,
|
||||||
selector: COMP_SELECTOR,
|
template:
|
||||||
template:
|
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
|
||||||
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
|
})
|
||||||
})
|
class ErrorComp {
|
||||||
class ErrorComp {
|
value = 0;
|
||||||
value = 0;
|
thrownValue = 0;
|
||||||
thrownValue = 0;
|
next() { this.value++; }
|
||||||
next() { this.value++; }
|
nextAndThrow() {
|
||||||
nextAndThrow() {
|
this.value++;
|
||||||
this.value++;
|
this.throwIfNeeded();
|
||||||
this.throwIfNeeded();
|
}
|
||||||
}
|
throwIfNeeded() {
|
||||||
throwIfNeeded() {
|
NgZone.assertInAngularZone();
|
||||||
NgZone.assertInAngularZone();
|
if (this.thrownValue !== this.value) {
|
||||||
if (this.thrownValue !== this.value) {
|
this.thrownValue = this.value;
|
||||||
this.thrownValue = this.value;
|
throw new Error(`Error: ${this.value}`);
|
||||||
throw new Error(`Error: ${this.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Directive({selector: '[dirClick]'})
|
@Directive({selector: '[dirClick]'})
|
||||||
class EventDir {
|
class EventDir {
|
||||||
@Output()
|
@Output()
|
||||||
dirClick = new EventEmitter();
|
dirClick = new EventEmitter();
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
@HostListener('click', ['$event'])
|
||||||
onClick(event: any) { this.dirClick.next(event); }
|
onClick(event: any) { this.dirClick.next(event); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
declarations: [ErrorComp, EventDir],
|
declarations: [ErrorComp, EventDir],
|
||||||
bootstrap: [ErrorComp],
|
bootstrap: [ErrorComp],
|
||||||
providers: [{provide: ErrorHandler, useValue: errorHandler}],
|
providers: [{provide: ErrorHandler, useValue: errorHandler}],
|
||||||
})
|
})
|
||||||
class TestModule {
|
class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
|
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
|
||||||
NgZone.assertNotInAngularZone();
|
NgZone.assertNotInAngularZone();
|
||||||
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
|
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
|
||||||
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
|
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
|
||||||
const compEl = compRef.location.nativeElement;
|
const compEl = compRef.location.nativeElement;
|
||||||
const nextBtn = compEl.children[0];
|
const nextBtn = compEl.children[0];
|
||||||
const nextAndThrowBtn = compEl.children[1];
|
const nextAndThrowBtn = compEl.children[1];
|
||||||
const nextAndThrowDirBtn = compEl.children[2];
|
const nextAndThrowDirBtn = compEl.children[2];
|
||||||
|
|
||||||
nextBtn.click();
|
// Note: the amount of events sent to the logger will differ between ViewEngine
|
||||||
assertValueAndErrors(compEl, 1, 0);
|
// and Ivy, because Ivy doesn't attach an error context. This means that the amount
|
||||||
nextBtn.click();
|
// of logged errors increases by 1 for Ivy and 2 for ViewEngine after each event.
|
||||||
assertValueAndErrors(compEl, 2, 2);
|
const errorDelta = ivyEnabled ? 1 : 2;
|
||||||
|
let currentErrorIndex = 0;
|
||||||
|
|
||||||
nextAndThrowBtn.click();
|
nextBtn.click();
|
||||||
assertValueAndErrors(compEl, 3, 4);
|
assertValueAndErrors(compEl, 1, currentErrorIndex);
|
||||||
nextAndThrowBtn.click();
|
currentErrorIndex += errorDelta;
|
||||||
assertValueAndErrors(compEl, 4, 6);
|
nextBtn.click();
|
||||||
|
assertValueAndErrors(compEl, 2, currentErrorIndex);
|
||||||
|
currentErrorIndex += errorDelta;
|
||||||
|
|
||||||
nextAndThrowDirBtn.click();
|
nextAndThrowBtn.click();
|
||||||
assertValueAndErrors(compEl, 5, 8);
|
assertValueAndErrors(compEl, 3, currentErrorIndex);
|
||||||
nextAndThrowDirBtn.click();
|
currentErrorIndex += errorDelta;
|
||||||
assertValueAndErrors(compEl, 6, 10);
|
nextAndThrowBtn.click();
|
||||||
|
assertValueAndErrors(compEl, 4, currentErrorIndex);
|
||||||
|
currentErrorIndex += errorDelta;
|
||||||
|
|
||||||
// Assert that there were no more errors
|
nextAndThrowDirBtn.click();
|
||||||
expect(logger.errors.length).toBe(12);
|
assertValueAndErrors(compEl, 5, currentErrorIndex);
|
||||||
done();
|
currentErrorIndex += errorDelta;
|
||||||
});
|
nextAndThrowDirBtn.click();
|
||||||
|
assertValueAndErrors(compEl, 6, currentErrorIndex);
|
||||||
|
currentErrorIndex += errorDelta;
|
||||||
|
|
||||||
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
|
// Assert that there were no more errors
|
||||||
expect(compEl).toHaveText(`Value:${value}`);
|
expect(logger.errors.length).toBe(currentErrorIndex);
|
||||||
expect(logger.errors[errorIndex][0]).toBe('ERROR');
|
done();
|
||||||
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
|
});
|
||||||
expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
|
|
||||||
}
|
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
|
||||||
});
|
expect(compEl).toHaveText(`Value:${value}`);
|
||||||
|
expect(logger.errors[errorIndex][0]).toBe('ERROR');
|
||||||
|
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
|
||||||
|
|
||||||
|
// Ivy doesn't attach an error context.
|
||||||
|
!ivyEnabled && expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user