fix(core): improve injector debug information in `ngDevMode` (#40476)
- `LViewDebug` now properly shows when `TNode` has `NO_NODE_INJECTOR`. - Provide `injectorResolutionPath` property `DebugNode` PR Close #40476
This commit is contained in:
parent
07b7af332f
commit
1e4b51e9f7
|
@ -15,7 +15,7 @@ import {assertDefined} from '../../util/assert';
|
||||||
import {createNamedArrayType} from '../../util/named_array_type';
|
import {createNamedArrayType} from '../../util/named_array_type';
|
||||||
import {initNgDevMode} from '../../util/ng_dev_mode';
|
import {initNgDevMode} from '../../util/ng_dev_mode';
|
||||||
import {assertNodeInjector} from '../assert';
|
import {assertNodeInjector} from '../assert';
|
||||||
import {getInjectorIndex} from '../di';
|
import {getInjectorIndex, getParentInjectorLocation} from '../di';
|
||||||
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||||
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||||
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
|
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
|
||||||
|
@ -25,7 +25,7 @@ import {LQueries, TQueries} from '../interfaces/query';
|
||||||
import {Renderer3, RendererFactory3} from '../interfaces/renderer';
|
import {Renderer3, RendererFactory3} from '../interfaces/renderer';
|
||||||
import {RComment, RElement, RNode} from '../interfaces/renderer_dom';
|
import {RComment, RElement, RNode} from '../interfaces/renderer_dom';
|
||||||
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
|
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
|
||||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
|
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, NodeInjectorDebug, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
|
||||||
import {attachDebugObject} from '../util/debug_utils';
|
import {attachDebugObject} from '../util/debug_utils';
|
||||||
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
|
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
|
||||||
import {unwrapRNode} from '../util/view_utils';
|
import {unwrapRNode} from '../util/view_utils';
|
||||||
|
@ -216,8 +216,20 @@ class TNode implements ITNode {
|
||||||
debugNodeInjectorPath(lView: LView): DebugNode[] {
|
debugNodeInjectorPath(lView: LView): DebugNode[] {
|
||||||
const path: DebugNode[] = [];
|
const path: DebugNode[] = [];
|
||||||
let injectorIndex = getInjectorIndex(this, lView);
|
let injectorIndex = getInjectorIndex(this, lView);
|
||||||
ngDevMode && assertNodeInjector(lView, injectorIndex);
|
if (injectorIndex === -1) {
|
||||||
|
// Looks like the current `TNode` does not have `NodeInjector` associated with it => look for
|
||||||
|
// parent NodeInjector.
|
||||||
|
const parentLocation = getParentInjectorLocation(this, lView);
|
||||||
|
if (parentLocation !== NO_PARENT_INJECTOR) {
|
||||||
|
// We found a parent, so start searching from the parent location.
|
||||||
|
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||||
|
lView = getParentInjectorView(parentLocation, lView);
|
||||||
|
} else {
|
||||||
|
// No parents have been found, so there are no `NodeInjector`s to consult.
|
||||||
|
}
|
||||||
|
}
|
||||||
while (injectorIndex !== -1) {
|
while (injectorIndex !== -1) {
|
||||||
|
ngDevMode && assertNodeInjector(lView, injectorIndex);
|
||||||
const tNode = lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
|
const tNode = lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
|
||||||
path.push(buildDebugNode(tNode, lView));
|
path.push(buildDebugNode(tNode, lView));
|
||||||
const parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
|
const parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
|
||||||
|
@ -583,15 +595,19 @@ export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode {
|
||||||
return {
|
return {
|
||||||
html: toHtml(native),
|
html: toHtml(native),
|
||||||
type: toTNodeTypeAsString(tNode.type),
|
type: toTNodeTypeAsString(tNode.type),
|
||||||
|
tNode,
|
||||||
native: native as any,
|
native: native as any,
|
||||||
children: toDebugNodes(tNode.child, lView),
|
children: toDebugNodes(tNode.child, lView),
|
||||||
factories,
|
factories,
|
||||||
instances,
|
instances,
|
||||||
injector: buildNodeInjectorDebug(tNode, tView, lView)
|
injector: buildNodeInjectorDebug(tNode, tView, lView),
|
||||||
|
get injectorResolutionPath() {
|
||||||
|
return (tNode as TNode).debugNodeInjectorPath(lView);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView) {
|
function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView): NodeInjectorDebug {
|
||||||
const viewProviders: Type<any>[] = [];
|
const viewProviders: Type<any>[] = [];
|
||||||
for (let i = (tNode as TNode).providerIndexStart_; i < (tNode as TNode).providerIndexEnd_; i++) {
|
for (let i = (tNode as TNode).providerIndexStart_; i < (tNode as TNode).providerIndexEnd_; i++) {
|
||||||
viewProviders.push(tView.data[i] as Type<any>);
|
viewProviders.push(tView.data[i] as Type<any>);
|
||||||
|
@ -633,6 +649,9 @@ function binary(array: any[], idx: number): string {
|
||||||
* @param idx
|
* @param idx
|
||||||
*/
|
*/
|
||||||
function toBloom(array: any[], idx: number): string {
|
function toBloom(array: any[], idx: number): string {
|
||||||
|
if (idx < 0) {
|
||||||
|
return 'NO_NODE_INJECTOR';
|
||||||
|
}
|
||||||
return `${binary(array, idx + 7)}_${binary(array, idx + 6)}_${binary(array, idx + 5)}_${
|
return `${binary(array, idx + 7)}_${binary(array, idx + 6)}_${binary(array, idx + 5)}_${
|
||||||
binary(array, idx + 4)}_${binary(array, idx + 3)}_${binary(array, idx + 2)}_${
|
binary(array, idx + 4)}_${binary(array, idx + 3)}_${binary(array, idx + 2)}_${
|
||||||
binary(array, idx + 1)}_${binary(array, idx + 0)}`;
|
binary(array, idx + 1)}_${binary(array, idx + 0)}`;
|
||||||
|
|
|
@ -1089,6 +1089,11 @@ export interface DebugNode {
|
||||||
*/
|
*/
|
||||||
html: string|null;
|
html: string|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associated `TNode`
|
||||||
|
*/
|
||||||
|
tNode: TNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Human readable node type.
|
* Human readable node type.
|
||||||
*/
|
*/
|
||||||
|
@ -1118,6 +1123,11 @@ export interface DebugNode {
|
||||||
* NodeInjector information.
|
* NodeInjector information.
|
||||||
*/
|
*/
|
||||||
injector: NodeInjectorDebug;
|
injector: NodeInjectorDebug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injector resolution path.
|
||||||
|
*/
|
||||||
|
injectorResolutionPath: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeInjectorDebug {
|
export interface NodeInjectorDebug {
|
||||||
|
|
|
@ -6,15 +6,19 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core';
|
import {Component, Injectable, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core';
|
||||||
|
import {getLContext} from '@angular/core/src/render3/context_discovery';
|
||||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '@angular/core/src/render3/instructions/element';
|
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '@angular/core/src/render3/instructions/element';
|
||||||
import {TNodeDebug} from '@angular/core/src/render3/instructions/lview_debug';
|
import {TNodeDebug} from '@angular/core/src/render3/instructions/lview_debug';
|
||||||
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
|
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
|
||||||
|
import {MONKEY_PATCH_KEY_NAME} from '@angular/core/src/render3/interfaces/context';
|
||||||
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
||||||
import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
|
import {LView, LViewDebug, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
|
||||||
import {enterView, leaveView} from '@angular/core/src/render3/state';
|
import {enterView, leaveView} from '@angular/core/src/render3/state';
|
||||||
import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list';
|
import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list';
|
||||||
|
import {getComponentLView} from '@angular/core/src/render3/util/discovery_utils';
|
||||||
import {KeyValueArray} from '@angular/core/src/util/array_utils';
|
import {KeyValueArray} from '@angular/core/src/util/array_utils';
|
||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {TemplateFixture} from '../render_util';
|
import {TemplateFixture} from '../render_util';
|
||||||
|
|
||||||
describe('lView_debug', () => {
|
describe('lView_debug', () => {
|
||||||
|
@ -239,4 +243,62 @@ describe('lView_debug', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('debugNodeInjectorPath', () => {
|
||||||
|
@Injectable()
|
||||||
|
class MyService {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class OtherService {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'parent',
|
||||||
|
template: `Parent: [<child></child>]`,
|
||||||
|
providers: [MyService],
|
||||||
|
})
|
||||||
|
class ParentComponent {
|
||||||
|
}
|
||||||
|
@Component({
|
||||||
|
selector: 'child',
|
||||||
|
template: `<b>Child!</b>`,
|
||||||
|
providers: [OtherService],
|
||||||
|
})
|
||||||
|
class ChildComponent {
|
||||||
|
constructor(private myService: MyService, private otherService: OtherService) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should display injection path', () => {
|
||||||
|
expect(ngDevMode).toBeTruthy();
|
||||||
|
TestBed.configureTestingModule({declarations: [ParentComponent, ChildComponent]});
|
||||||
|
const parentFixture = TestBed.createComponent(ParentComponent);
|
||||||
|
const parentHostElement = parentFixture.nativeElement as HTMLElement;
|
||||||
|
const childElement = parentHostElement.querySelector('child')! as HTMLElement;
|
||||||
|
if (!(childElement as any)[MONKEY_PATCH_KEY_NAME]) {
|
||||||
|
// In these browsers:
|
||||||
|
// - Chrome Mobile 72.0.3626 (Android 0.0.0)
|
||||||
|
// - IE 11.0.0 (Windows 8.1.0.0)
|
||||||
|
// Retrieving `LContext` does not work for unknown reasons, and we are unable to debug it.
|
||||||
|
// Exiting tests early to prevent breaking the test suite.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const childLViewDebug = getComponentLView(childElement).debug!;
|
||||||
|
const parentLViewDebug = childLViewDebug.parent as LViewDebug;
|
||||||
|
const rootLViewDebug = parentLViewDebug.parent! as LViewDebug;
|
||||||
|
const childRootNode = childLViewDebug.nodes[0];
|
||||||
|
expect(childRootNode.injector.bloom).toEqual('NO_NODE_INJECTOR');
|
||||||
|
expect(childRootNode.injector.cumulativeBloom).toEqual('NO_NODE_INJECTOR');
|
||||||
|
const injectorResolutionPath = childRootNode.injectorResolutionPath;
|
||||||
|
expect(injectorResolutionPath.length).toEqual(2);
|
||||||
|
expect(injectorResolutionPath[0].injector)
|
||||||
|
.toEqual(
|
||||||
|
parentLViewDebug.nodes[1].injector,
|
||||||
|
);
|
||||||
|
expect(injectorResolutionPath[1].injector)
|
||||||
|
.toEqual(
|
||||||
|
rootLViewDebug.nodes[0].injector,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue