refactor(ivy): clean up RNode retrieval; add better debug support for TNode (#31040)

PR Close #31040
This commit is contained in:
Misko Hevery 2019-06-14 07:55:17 +02:00 committed by Miško Hevery
parent ef75fb8ecd
commit 4495a46b99
14 changed files with 201 additions and 59 deletions

View File

@ -17,7 +17,7 @@ import {getStylingContextFromLView} from '../render3/styling/util';
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/util/discovery_utils';
import {INTERPOLATION_DELIMITER, isPropMetadataString, renderStringify} from '../render3/util/misc_utils';
import {findComponentView} from '../render3/util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer} from '../render3/util/view_utils';
import {getComponentViewByIndex, getNativeByTNodeOrNull, isComponent, isLContainer} from '../render3/util/view_utils';
import {assertDomNode} from '../util/assert';
import {DebugContext} from '../view/index';
@ -468,7 +468,7 @@ function _queryAllR3(
function _queryNodeChildrenR3(
tNode: TNode, lView: LView, predicate: Predicate<DebugElement>| Predicate<DebugNode>,
matches: DebugElement[] | DebugNode[], elementsOnly: boolean, rootNativeNode: any) {
const nativeNode = getNativeByTNode(tNode, lView);
const nativeNode = getNativeByTNodeOrNull(tNode, lView);
// For each type of TNode, specific logic is executed.
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
// Case 1: the TNode is an element

View File

@ -10,9 +10,14 @@ import {assertDefined, assertEqual, throwError} from '../util/assert';
import {getComponentDef, getNgModuleDef} from './definition';
import {TNode} from './interfaces/node';
import {LView} from './interfaces/view';
import {LView, TVIEW, TView} from './interfaces/view';
import {isLContainer, isLView} from './util/view_utils';
export function assertTNodeForLView(tNode: TNode, lView: LView) {
tNode.hasOwnProperty('tView_') && assertEqual(
(tNode as any as{tView_: TView}).tView_, lView[TVIEW],
'This TNode does not belong to this LView.');
}
export function assertComponentType(
actual: any,

View File

@ -14,7 +14,7 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {TNode, TNodeFlags} from './interfaces/node';
import {RElement, RNode} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
import {getComponentViewByIndex, getNativeByTNode, readPatchedData, unwrapRNode} from './util/view_utils';
import {getComponentViewByIndex, getNativeByTNodeOrNull, readPatchedData, unwrapRNode} from './util/view_utils';
@ -191,7 +191,7 @@ export function isDirectiveInstance(instance: any): boolean {
function findViaNativeElement(lView: LView, target: RElement): number {
let tNode = lView[TVIEW].firstChild;
while (tNode) {
const native = getNativeByTNode(tNode, lView) !;
const native = getNativeByTNodeOrNull(tNode, lView) !;
if (native === target) {
return tNode.index;
}

View File

@ -6,18 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentTemplate} from '..';
import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {TElementNode, TNode, TViewNode} from '../interfaces/node';
import {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {LQueries} from '../interfaces/query';
import {RComment, RElement} from '../interfaces/renderer';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, 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 {TStylingContext} from '../styling_next/interfaces';
import {runtimeIsNewStylingInUse} from '../styling_next/state';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
import {attachDebugObject} from '../util/debug_utils';
@ -103,6 +105,67 @@ export const TViewConstructor = class TView implements ITView {
) {}
};
export const TNodeConstructor = class TNode implements ITNode {
constructor(
public tView_: TView, //
public type: TNodeType, //
public index: number, //
public injectorIndex: number, //
public directiveStart: number, //
public directiveEnd: number, //
public propertyMetadataStartIndex: number, //
public propertyMetadataEndIndex: number, //
public flags: TNodeFlags, //
public providerIndexes: TNodeProviderIndexes, //
public tagName: string|null, //
public attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null, //
public localNames: (string|number)[]|null, //
public initialInputs: (string[]|null)[]|null|undefined, //
public inputs: PropertyAliases|null|undefined, //
public outputs: PropertyAliases|null|undefined, //
public tViews: ITView|ITView[]|null, //
public next: ITNode|null, //
public projectionNext: ITNode|null, //
public child: ITNode|null, //
public parent: TElementNode|TContainerNode|null, //
public stylingTemplate: StylingContext|null, //
public projection: number|(ITNode|RNode[])[]|null, //
public onElementCreationFns: Function[]|null, //
public newStyles: TStylingContext|null, //
public newClasses: TStylingContext|null, //
) {}
get type_(): string {
switch (this.type) {
case TNodeType.Container:
return 'TNodeType.Container';
case TNodeType.Element:
return 'TNodeType.Element';
case TNodeType.ElementContainer:
return 'TNodeType.ElementContainer';
case TNodeType.IcuContainer:
return 'TNodeType.IcuContainer';
case TNodeType.Projection:
return 'TNodeType.Projection';
case TNodeType.View:
return 'TNodeType.View';
default:
return 'TNodeType.???';
}
}
get flags_(): string {
const flags: string[] = [];
if (this.flags & TNodeFlags.hasClassInput) flags.push('TNodeFlags.hasClassInput');
if (this.flags & TNodeFlags.hasContentQuery) flags.push('TNodeFlags.hasContentQuery');
if (this.flags & TNodeFlags.hasStyleInput) flags.push('TNodeFlags.hasStyleInput');
if (this.flags & TNodeFlags.isComponent) flags.push('TNodeFlags.isComponent');
if (this.flags & TNodeFlags.isDetached) flags.push('TNodeFlags.isDetached');
if (this.flags & TNodeFlags.isProjected) flags.push('TNodeFlags.isProjected');
return flags.join('|');
}
};
const TViewData = ngDevMode && createNamedArrayType('TViewData');
let TVIEWDATA_EMPTY:
unknown[]; // can't initialize here or it will not be tree shaken, because `LView`

View File

@ -11,7 +11,7 @@ import {Type} from '../../interface/type';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/security';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertLessThan, assertNotEqual, assertNotSame} from '../../util/assert';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertLView, assertPreviousIsParent} from '../assert';
@ -38,8 +38,7 @@ import {attrsStylingIndexOf} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
import {selectInternal} from './select';
@ -278,7 +277,7 @@ function createTNodeAtIndex(
const parentInSameView = parent && parent !== tHostNode;
const tParentNode = parentInSameView ? parent as TElementNode | TContainerNode : null;
const tNode = tView.data[adjustedIndex] =
createTNode(tParentNode, type, adjustedIndex, name, attrs);
createTNode(tView, tParentNode, type, adjustedIndex, name, attrs);
// The first node is not always the one at index 0, in case of i18n, index 0 can be the
// instruction `i18nStart` and the first node has the index 1 or more
if (index === 0 || !tView.firstChild) {
@ -306,6 +305,7 @@ export function assignTViewNodeToLView(
ngDevMode && tParentNode &&
assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container);
tView.node = tNode = createTNode(
tView,
tParentNode as TElementNode | TContainerNode | null, //
TNodeType.View, index, null, null) as TViewNode;
}
@ -741,6 +741,7 @@ export type TsickleIssue1009 = any;
/**
* Constructs a TNode object from the arguments.
*
* @param tView `TView` to which this `TNode` belongs (used only in `ngDevMode`)
* @param type The type of the node
* @param adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET
* @param tagName The tag name of the node
@ -749,13 +750,45 @@ export type TsickleIssue1009 = any;
* @returns the TNode object
*/
export function createTNode(
tParent: TElementNode | TContainerNode | null, type: TNodeType, adjustedIndex: number,
tagName: string | null, attrs: TAttributes | null): TNode {
tView: TView, tParent: TElementNode | TContainerNode | null, type: TNodeType,
adjustedIndex: number, tagName: string | null, attrs: TAttributes | null): TNode {
ngDevMode && ngDevMode.tNode++;
return {
let injectorIndex = tParent ? tParent.injectorIndex : -1;
return ngDevMode ?
new TNodeConstructor(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
-1, // directiveEnd: number
-1, // propertyMetadataStartIndex: number
-1, // propertyMetadataEndIndex: number
0, // flags: TNodeFlags
0, // providerIndexes: TNodeProviderIndexes
tagName, // tagName: string|null
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
null, // localNames: (string|number)[]|null
undefined, // initialInputs: (string[]|null)[]|null|undefined
undefined, // inputs: PropertyAliases|null|undefined
undefined, // outputs: PropertyAliases|null|undefined
null, // tViews: ITView|ITView[]|null
null, // next: ITNode|null
null, // projectionNext: ITNode|null
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // stylingTemplate: StylingContext|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // onElementCreationFns: Function[]|null
// TODO (matsko): rename this to `styles` once the old styling impl is gone
null, // newStyles: TStylingContext|null
// TODO (matsko): rename this to `classes` once the old styling impl is gone
null, // newClasses: TStylingContext|null
) :
{
type: type,
index: adjustedIndex,
injectorIndex: tParent ? tParent.injectorIndex : -1,
injectorIndex: injectorIndex,
directiveStart: -1,
directiveEnd: -1,
propertyMetadataStartIndex: -1,

View File

@ -22,8 +22,7 @@ import {CHILD_HEAD, CLEANUP, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PAR
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {renderStringify} from './util/misc_utils';
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
import {getNativeByTNode, isLContainer, isLView, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
import {getNativeByTNode, getNativeByTNodeOrNull, isLContainer, isLView, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -603,13 +602,15 @@ function getHighestElementOrICUContainer(tNode: TNode): TNode {
return tNode;
}
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode {
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode|
null {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex] as LView;
ngDevMode && assertDefined(lView[T_HOST], 'Missing Host TNode');
const tViewNodeChild = (lView[T_HOST] as TViewNode).child;
return tViewNodeChild !== null ? getNativeByTNode(tViewNodeChild, lView) : lContainer[NATIVE];
return tViewNodeChild !== null ? getNativeByTNodeOrNull(tViewNodeChild, lView) :
lContainer[NATIVE];
} else {
return lContainer[NATIVE];
}

View File

@ -6,14 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from '../../util/assert';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertLessThan} from '../../util/assert';
import {assertTNodeForLView} from '../assert';
import {LContainer, TYPE} from '../interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
import {ComponentDef, DirectiveDef} from '../interfaces/definition';
import {TNode, TNodeFlags} from '../interfaces/node';
import {RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, TData, TVIEW} from '../interfaces/view';
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, TData, TVIEW, TView} from '../interfaces/view';
@ -127,10 +128,39 @@ export function getNativeByIndex(index: number, lView: LView): RNode {
return unwrapRNode(lView[index + HEADER_OFFSET]);
}
export function getNativeByTNode(tNode: TNode, hostView: LView): RNode {
return unwrapRNode(hostView[tNode.index]);
/**
* Retrieve an `RNode` for a given `TNode` and `LView`.
*
* This function guarantees in dev mode to retrieve a non-null `RNode`.
*
* @param tNode
* @param lView
*/
export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertDataInRange(lView, tNode.index);
const node: RNode = unwrapRNode(lView[tNode.index]);
ngDevMode && assertDomNode(node);
return node;
}
/**
* Retrieve an `RNode` or `null` for a given `TNode` and `LView`.
*
* Some `TNode`s don't have associated `RNode`s. For example `Projection`
*
* @param tNode
* @param lView
*/
export function getNativeByTNodeOrNull(tNode: TNode, lView: LView): RNode|null {
ngDevMode && assertTNodeForLView(tNode, lView);
const index = tNode.index;
const node: RNode|null = index == -1 ? null : unwrapRNode(lView[index]);
ngDevMode && node !== null && assertDomNode(node);
return node;
}
/**
* A helper function that returns `true` if a given `TNode` has any matching directives.
*/

View File

@ -16,7 +16,7 @@ import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view';
import {destroyLView, renderDetachView} from './node_manipulation';
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
import {getNativeByTNode} from './util/view_utils';
import {getNativeByTNode, getNativeByTNodeOrNull} from './util/view_utils';
@ -294,7 +294,7 @@ function collectNativeNodes(lView: LView, parentTNode: TNode, result: any[]): an
let tNodeChild = parentTNode.child;
while (tNodeChild) {
const nativeNode = getNativeByTNode(tNodeChild, lView);
const nativeNode = getNativeByTNodeOrNull(tNodeChild, lView);
nativeNode && result.push(nativeNode);
if (tNodeChild.type === TNodeType.ElementContainer) {
collectNativeNodes(lView, tNodeChild, result);

View File

@ -76,7 +76,8 @@ export function assertDomNode(node: any) {
// If we're in a worker, `Node` will not be defined.
assertEqual(
(typeof Node !== 'undefined' && node instanceof Node) ||
(typeof node === 'object' && node.constructor.name === 'WebWorkerRenderNode'),
(typeof node === 'object' && node != null &&
node.constructor.name === 'WebWorkerRenderNode'),
true, `The provided value must be an instance of a DOM Node but got ${stringify(node)}`);
}

View File

@ -413,6 +413,9 @@
{
"name": "getNativeByTNode"
},
{
"name": "getNativeByTNodeOrNull"
},
{
"name": "getNodeInjectable"
},

View File

@ -290,6 +290,9 @@
{
"name": "getNativeByTNode"
},
{
"name": "getNativeByTNodeOrNull"
},
{
"name": "getNodeInjectable"
},

View File

@ -908,6 +908,9 @@
{
"name": "getNativeByTNode"
},
{
"name": "getNativeByTNodeOrNull"
},
{
"name": "getNativeFromLView"
},

View File

@ -14,14 +14,14 @@ import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorLis
import {initializeStaticContext} from '../../src/render3/styling/class_and_style_bindings';
function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
return createTNode(null, TNodeType.Element, 0, tagName, attrs);
return createTNode(null !, null, TNodeType.Element, 0, tagName, attrs);
}
describe('css selector matching', () => {
function isMatching(
tagName: string, attrsOrTNode: TAttributes | TNode | null, selector: CssSelector): boolean {
const tNode = (!attrsOrTNode || Array.isArray(attrsOrTNode)) ?
createTNode(null, TNodeType.Element, 0, tagName, attrsOrTNode as TAttributes) :
createTNode(null !, null, TNodeType.Element, 0, tagName, attrsOrTNode as TAttributes) :
(attrsOrTNode as TNode);
return isNodeMatchingSelector(tNode, selector, false);
}
@ -330,7 +330,7 @@ describe('css selector matching', () => {
() => {
// selector: 'div.abc'
const selector = ['div', SelectorFlags.CLASS, 'abc'];
const tNode = createTNode(null, TNodeType.Element, 0, 'div', []);
const tNode = createTNode(null !, null, TNodeType.Element, 0, 'div', []);
// <div> (without attrs or styling context)
expect(isMatching('div', tNode, selector)).toBeFalsy();

View File

@ -15,7 +15,7 @@ describe('view_utils', () => {
const div = document.createElement('div');
const tView = createTView(0, null, 0, 0, null, null, null, null);
const lView = createLView(null, tView, {}, 0, div, null, {} as any, {} as any, null, null);
const tNode = createTNode(null, 3, 0, 'div', []);
const tNode = createTNode(null !, null, 3, 0, 'div', []);
const lContainer = createLContainer(lView, lView, div, tNode, true);
const styleContext = createEmptyStylingContext(lContainer, null, null, null);