test(ivy): Have more descriptive names for LView (#33449)

When debugging `LView`s it is easy to get lost since all of them have
the same name. This change does three things:

1. It makes `TView` have an explicit type:
  - `Host`: for the top level `TView` for bootstrap
  - `Component`: for the `TView` which represents components template
  - `Embedded`: for the `TView` which represents an embedded template
2. It changes the name of `LView` to `LHostView`, `LComponentView`, and
  `LEmbeddedView` depending on the `TView` type.
3. For `LComponentView` and `LEmbeddedView` we also append the name of
  of the `context` constructor. The result is that we have `LView`s which
  are name as: `LComponentView_MyComponent` and `LEmbeddedView_NgIfContext`.

The above changes will make it easier to understand the structure of the
application when debugging.

NOTE: All of these are behind `ngDevMode` and will get removed in
production application.

PR Close #33449
This commit is contained in:
Miško Hevery
2019-10-28 12:08:17 -07:00
committed by Andrew Scott
parent 7bccef516f
commit 4924d73b8e
22 changed files with 227 additions and 62 deletions

View File

@ -17,12 +17,12 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, incrementActiveDirectiveId, leaveView, setActiveHostElement} from './state';
import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, stringifyForError} from './util/misc_utils';
@ -124,7 +124,7 @@ export function renderComponent<T>(
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
const rootTView = createTView(-1, null, 1, 0, null, null, null, null, null);
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
const rootView: LView = createLView(
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, undefined,
opts.injector || null);
@ -171,8 +171,9 @@ export function createRootComponentView(
rootView[0 + HEADER_OFFSET] = rNode;
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
const componentView = createLView(
rootView, getOrCreateTView(def), null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
rootView[HEADER_OFFSET], tNode, rendererFactory, renderer, sanitizer);
rootView, getOrCreateTComponentView(def), null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
rendererFactory, renderer, sanitizer);
if (tView.firstCreatePass) {
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);

View File

@ -28,7 +28,7 @@ import {assignTViewNodeToLView, createLView, createTView, elementCreate, locateH
import {ComponentDef} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node';
import {RNode, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {LView, LViewFlags, TVIEW} from './interfaces/view';
import {LView, LViewFlags, TVIEW, TViewType} from './interfaces/view';
import {enterView, leaveView} from './state';
import {defaultScheduler} from './util/misc_utils';
import {getTNode} from './util/view_utils';
@ -158,7 +158,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
}
// Create the root view. Uses empty TView and ContentTemplate.
const rootTView = createTView(-1, null, 1, 0, null, null, null, null, null);
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
const rootLView = createLView(
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, sanitizer,
rootViewInjector);

View File

@ -13,7 +13,7 @@ import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/c
import {ComponentTemplate} from '../interfaces/definition';
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node';
import {isDirectiveHost} from '../interfaces/type_checks';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild, removeView} from '../node_manipulation';
import {getBindingIndex, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
@ -81,8 +81,8 @@ export function ɵɵtemplate(
registerPostOrderHooks(tView, tContainerNode);
const embeddedTView = tContainerNode.tViews = createTView(
-1, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null,
tView.schemas, tViewConsts);
TViewType.Embedded, -1, templateFn, decls, vars, tView.directiveRegistry,
tView.pipeRegistry, null, tView.schemas, tViewConsts);
const embeddedTViewNode = createTNode(tView, null, TNodeType.View, -1, null, null) as TViewNode;
embeddedTViewNode.injectorIndex = tContainerNode.injectorIndex;
embeddedTView.node = embeddedTViewNode;

View File

@ -11,7 +11,7 @@ import {assertLContainerOrUndefined} from '../assert';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {RenderFlags} from '../interfaces/definition';
import {TContainerNode, TNodeType} from '../interfaces/node';
import {CONTEXT, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
import {CONTEXT, LView, LViewFlags, PARENT, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {insertView, removeView} from '../node_manipulation';
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, leaveViewProcessExit, setIsParent, setPreviousOrParentTNode} from '../state';
@ -87,8 +87,8 @@ function getOrCreateEmbeddedTView(
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
containerTViews[viewIndex] = createTView(
viewIndex, null, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null, null,
tView.consts);
TViewType.Embedded, viewIndex, null, decls, vars, tView.directiveRegistry,
tView.pipeRegistry, null, null, tView.consts);
}
return containerTViews[viewIndex];
}

View File

@ -7,7 +7,7 @@
*/
import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core';
import {SchemaMetadata, Type} from '../../core';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
@ -19,7 +19,7 @@ import {SelectorFlags} from '../interfaces/projection';
import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {TStylingContext} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, TViewType, T_HOST} from '../interfaces/view';
import {DebugNodeStyling, NodeStylingDebug} from '../styling/styling_debug';
import {attachDebugObject} from '../util/debug_utils';
import {isStylingContext} from '../util/styling_utils';
@ -56,25 +56,64 @@ const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNg
* ```
*/
export const LViewArray = NG_DEV_MODE && createNamedArrayType('LView') || null !as ArrayConstructor;
let LVIEW_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
// constructor could have side-effects.
let LVIEW_COMPONENT_CACHE !: Map<string|null, Array<any>>;
let LVIEW_EMBEDDED_CACHE !: Map<string|null, Array<any>>;
let LVIEW_ROOT !: Array<any>;
interface TViewDebug extends ITView {
type: TViewType;
}
/**
* This function clones a blueprint and creates LView.
*
* Simple slice will keep the same type, and we need it to be LView
*/
export function cloneToLView(list: any[]): LView {
if (LVIEW_EMPTY === undefined) LVIEW_EMPTY = new LViewArray();
return LVIEW_EMPTY.concat(list) as any;
export function cloneToLViewFromTViewBlueprint(tView: TView): LView {
const debugTView = tView as TViewDebug;
const lView = getLViewToClone(debugTView.type, tView.template && tView.template.name);
return lView.concat(tView.blueprint) as any;
}
function getLViewToClone(type: TViewType, name: string | null): Array<any> {
switch (type) {
case TViewType.Root:
if (LVIEW_ROOT === undefined) LVIEW_ROOT = new (createNamedArrayType('LRootView'))();
return LVIEW_ROOT;
case TViewType.Component:
if (LVIEW_COMPONENT_CACHE === undefined) LVIEW_COMPONENT_CACHE = new Map();
let componentArray = LVIEW_COMPONENT_CACHE.get(name);
if (componentArray === undefined) {
componentArray = new (createNamedArrayType('LComponentView' + nameSuffix(name)))();
LVIEW_COMPONENT_CACHE.set(name, componentArray);
}
return componentArray;
case TViewType.Embedded:
if (LVIEW_EMBEDDED_CACHE === undefined) LVIEW_EMBEDDED_CACHE = new Map();
let embeddedArray = LVIEW_EMBEDDED_CACHE.get(name);
if (embeddedArray === undefined) {
embeddedArray = new (createNamedArrayType('LEmbeddedView' + nameSuffix(name)))();
LVIEW_EMBEDDED_CACHE.set(name, embeddedArray);
}
return embeddedArray;
}
throw new Error('unreachable code');
}
function nameSuffix(text: string | null | undefined): string {
if (text == null) return '';
const index = text.lastIndexOf('_Template');
return '_' + (index === -1 ? text : text.substr(0, index));
}
/**
* This class is a debug version of Object literal so that we can have constructor name show up in
* This class is a debug version of Object literal so that we can have constructor name show up
* in
* debug tools in ngDevMode.
*/
export const TViewConstructor = class TView implements ITView {
constructor(
public type: TViewType, //
public id: number, //
public blueprint: LView, //
public template: ComponentTemplate<{}>|null, //
@ -259,8 +298,10 @@ export function toDebug(obj: any): any {
* reading.
*
* @param value possibly wrapped native DOM node.
* @param includeChildren If `true` then the serialized HTML form will include child elements (same
* as `outerHTML`). If `false` then the serialized HTML form will only contain the element itself
* @param includeChildren If `true` then the serialized HTML form will include child elements
* (same
* as `outerHTML`). If `false` then the serialized HTML form will only contain the element
* itself
* (will not serialize child elements).
*/
function toHtml(value: any, includeChildren: boolean = false): string|null {
@ -305,7 +346,8 @@ export class LViewDebug {
get html(): string { return (this.nodes || []).map(node => toHtml(node.native, true)).join(''); }
get context(): {}|null { return this._raw_lView[CONTEXT]; }
/**
* The tree of nodes associated with the current `LView`. The nodes have been normalized into a
* The tree of nodes associated with the current `LView`. The nodes have been normalized into
* a
* tree structure with relevant details pulled out for readability.
*/
get nodes(): DebugNode[]|null {

View File

@ -27,7 +27,7 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {ActiveElementFlags, enterView, executeElementExitFn, getBindingIndex, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
@ -40,7 +40,7 @@ import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
@ -158,7 +158,8 @@ export function createLView<T>(
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null,
sanitizer?: Sanitizer | null, injector?: Injector | null): LView {
const lView = ngDevMode ? cloneToLView(tView.blueprint) : tView.blueprint.slice() as LView;
const lView =
ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView;
lView[HOST] = host;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
resetPreOrderHookFlags(lView);
@ -568,10 +569,11 @@ export function saveResolvedLocalsInData(
* @param def ComponentDef
* @returns TView
*/
export function getOrCreateTView(def: ComponentDef<any>): TView {
return def.tView || (def.tView = createTView(
-1, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs,
def.viewQuery, def.schemas, def.consts));
export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
return def.tView ||
(def.tView = createTView(
TViewType.Component, -1, def.template, def.decls, def.vars, def.directiveDefs,
def.pipeDefs, def.viewQuery, def.schemas, def.consts));
}
@ -588,8 +590,8 @@ export function getOrCreateTView(def: ComponentDef<any>): TView {
* @param consts Constants for this view
*/
export function createTView(
viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number, vars: number,
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number,
vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null,
consts: TConstants | null): TView {
ngDevMode && ngDevMode.tView++;
@ -601,6 +603,7 @@ export function createTView(
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
return blueprint[TVIEW as any] = ngDevMode ?
new TViewConstructor(
type,
viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
@ -1304,7 +1307,7 @@ function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDe
function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: ComponentDef<T>): void {
const native = getNativeByTNode(hostTNode, lView) as RElement;
const tView = getOrCreateTView(def);
const tView = getOrCreateTComponentView(def);
// Only component views should be added to the view tree directly. Embedded views are
// accessed through their containers because they may be removed / re-added later.

View File

@ -310,6 +310,35 @@ export const enum PreOrderHookFlags {
*/
export interface ExpandoInstructions extends Array<number|HostBindingsFunction<any>|null> {}
/**
* Explicitly marks `TView` as a specific type in `ngDevMode`
*
* It is useful to know conceptually what time of `TView` we are dealing with when
* debugging an application (even if the runtime does not need it.) For this reason
* we store this information in the `ngDevMode` `TView` and than use it for
* better debugging experience.
*/
export const enum TViewType {
/**
* Root `TView` is the used to bootstrap components into. It is used in conjunction with
* `LView` which takes an existing DOM node not owned by Angular and wraps it in `TView`/`LView`
* so that other components can be loaded into it.
*/
Root = 0,
/**
* `TView` associated with a Component. This would be the `TView` directly associated with the
* component view (as opposed an `Embedded` `TView` which would be a child of `Component` `TView`)
*/
Component = 1,
/**
* `TView` associated with a template. Such as `*ngIf`, `<ng-template>` etc... A `Component`
* can have zero or more `Embedede` `TView`s.
*/
Embedded = 2,
}
/**
* The static data for an LView (shared between all templates of a
* given type).

View File

@ -370,3 +370,20 @@ export function getDebugNode(element: Node): DebugNode|null {
return debugNode;
}
/**
* Retrieve the component `LView` from component/element.
*
* NOTE: `LView` is a private and should not be leaked outside.
* Don't export this method to `ng.*` on window.
*
* @param target Component or Element instance.
*/
export function getComponentLView(target: any): LView {
const lContext = loadLContext(target);
const nodeIndx = lContext.nodeIndex;
const lView = lContext.lView;
const componentLView = lView[nodeIndx];
ngDevMode && assertLView(componentLView);
return componentLView;
}