Skip to content

Commit 3cb497c

Browse files
committed
refactor(ivy): simplify differentiation of LView, RNode, LView, LContainer, StylingContext (angular#28947)
For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`, `StylingContext`) in same location in `LView`. This is because we don't want to pre-allocate space for it because the storage is sparse. This file contains utilities for dealing with such data types. How do we know what is stored at a given location in `LView`. - `Array.isArray(value) === false` => `RNode` (The normal storage value) - `Array.isArray(value) === true` => than the `value[0]` represents the wrapped value. - `typeof value[TYPE] === 'object'` => `LView` - This happens when we have a component at a given location - `typeof value[TYPE] === 'number'` => `StylingContext` - This happens when we have style/class binding at a given location. - `typeof value[TYPE] === true` => `LContainer` - This happens when we have `LContainer` binding at a given location. NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient. PR Close angular#28947
1 parent bd65f58 commit 3cb497c

19 files changed

+225
-112
lines changed

packages/core/src/render3/context_discovery.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {assertDomNode} from '../util/assert';
1212
import {EMPTY_ARRAY} from './empty';
1313
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
1414
import {TNode, TNodeFlags} from './interfaces/node';
15-
import {RElement} from './interfaces/renderer';
15+
import {RElement, RNode} from './interfaces/renderer';
1616
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
17-
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util/view_utils';
17+
import {getComponentViewByIndex, getNativeByTNode, readPatchedData, unwrapRNode} from './util/view_utils';
1818

1919

2020

@@ -71,7 +71,7 @@ export function getLContext(target: any): LContext|null {
7171
// are expensive. Instead, only the target data (the element, component, container, ICU
7272
// expression or directive details) are filled into the context. If called multiple times
7373
// with different target values then the missing target data will be filled in.
74-
const native = readElementValue(lView[nodeIndex]);
74+
const native = unwrapRNode(lView[nodeIndex]);
7575
const existingCtx = readPatchedData(native);
7676
const context: LContext = (existingCtx && !Array.isArray(existingCtx)) ?
7777
existingCtx :
@@ -119,7 +119,7 @@ export function getLContext(target: any): LContext|null {
119119

120120
const index = findViaNativeElement(lView, rElement);
121121
if (index >= 0) {
122-
const native = readElementValue(lView[index]);
122+
const native = unwrapRNode(lView[index]);
123123
const context = createLContext(lView, index, native);
124124
attachPatchData(native, context);
125125
mpValue = context;
@@ -134,7 +134,7 @@ export function getLContext(target: any): LContext|null {
134134
/**
135135
* Creates an empty instance of a `LContext` context
136136
*/
137-
function createLContext(lView: LView, nodeIndex: number, native: RElement): LContext {
137+
function createLContext(lView: LView, nodeIndex: number, native: RNode): LContext {
138138
return {
139139
lView,
140140
nodeIndex,

packages/core/src/render3/debug.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {LQueries} from './interfaces/query';
1414
import {RComment, RElement} from './interfaces/renderer';
1515
import {StylingContext} from './interfaces/styling';
1616
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, TView, T_HOST} from './interfaces/view';
17-
import {readElementValue} from './util/view_utils';
17+
import {unwrapRNode} from './util/view_utils';
1818

1919
/*
2020
* This file contains conditionally attached classes which provide human readable (debug) level
@@ -77,7 +77,7 @@ export function toDebug(obj: any): any {
7777
* (will not serialize child elements).
7878
*/
7979
function toHtml(value: any, includeChildren: boolean = false): string|null {
80-
const node: HTMLElement|null = readElementValue(value) as any;
80+
const node: HTMLElement|null = unwrapRNode(value) as any;
8181
if (node) {
8282
const isTextNode = node.nodeType === Node.TEXT_NODE;
8383
const outerHTML = (isTextNode ? node.textContent : node.outerHTML) || '';
@@ -182,7 +182,7 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul
182182
let tNodeCursor: TNode|null = tNode;
183183
while (tNodeCursor) {
184184
const rawValue = lView[tNode.index];
185-
const native = readElementValue(rawValue);
185+
const native = unwrapRNode(rawValue);
186186
const componentLViewDebug = toDebug(readLViewValue(rawValue));
187187
debugNodes.push({
188188
html: toHtml(native),

packages/core/src/render3/instructions.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingC
4747
import {NO_CHANGE} from './tokens';
4848
import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils';
4949
import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils';
50-
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView} from './util/view_utils';
50+
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode} from './util/view_utils';
5151

5252

5353

@@ -139,7 +139,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
139139
if (instruction !== null) {
140140
viewData[BINDING_INDEX] = bindingRootIndex;
141141
instruction(
142-
RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]),
142+
RenderFlags.Update, unwrapRNode(viewData[currentDirectiveIndex]),
143143
currentElementIndex);
144144
}
145145
currentDirectiveIndex++;
@@ -1018,7 +1018,7 @@ function listenerInternal(
10181018
}
10191019

10201020
const idxOrTargetGetter = eventTargetResolver ?
1021-
(_lView: LView) => eventTargetResolver(readElementValue(_lView[tNode.index])).target :
1021+
(_lView: LView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])).target :
10221022
tNode.index;
10231023
tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, useCaptureOrSubIdx);
10241024
}
@@ -1147,7 +1147,7 @@ export function elementAttribute(
11471147
ngDevMode && validateAgainstEventAttributes(name);
11481148
const lView = getLView();
11491149
const renderer = lView[RENDERER];
1150-
const element = getNativeByIndex(index, lView);
1150+
const element = getNativeByIndex(index, lView) as RElement;
11511151
if (value == null) {
11521152
ngDevMode && ngDevMode.rendererRemoveAttribute++;
11531153
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
@@ -2216,12 +2216,13 @@ export function createLContainer(
22162216
ngDevMode && assertDomNode(native);
22172217
ngDevMode && assertLView(currentView);
22182218
const lContainer: LContainer = [
2219-
hostNative, // host native
2219+
hostNative, // host native
2220+
true, // Boolean `true` in this position signifies that this is an `LContainer`
22202221
isForViewContainerRef ? -1 : 0, // active index
2221-
[], // views
22222222
currentView, // parent
22232223
null, // next
22242224
null, // queries
2225+
[], // views
22252226
native, // native
22262227
];
22272228
ngDevMode && attachLContainerDebug(lContainer);

packages/core/src/render3/interfaces/container.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,22 @@ import {RComment, RElement} from './renderer';
1111
import {StylingContext} from './styling';
1212
import {HOST, LView, NEXT, PARENT, QUERIES} from './view';
1313

14-
14+
/**
15+
* Special location which allows easy identification of type. If we have an array which was
16+
* retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is
17+
* `LContainer`.
18+
*/
19+
export const TYPE = 1;
1520
/**
1621
* Below are constants for LContainer indices to help us look up LContainer members
1722
* without having to remember the specific indices.
1823
* Uglify will inline these when minifying so there shouldn't be a cost.
1924
*/
20-
export const ACTIVE_INDEX = 1;
21-
export const VIEWS = 2;
22-
// PARENT, NEXT, QUERIES, and HOST are indices 2, 3, 4, and 5.
25+
export const ACTIVE_INDEX = 2;
26+
// PARENT, NEXT, and QUERIES are indices 3, 4, and 5.
2327
// As we already have these constants in LView, we don't need to re-create them.
24-
export const NATIVE = 6;
25-
// Because interfaces in TS/JS cannot be instanceof-checked this means that we
26-
// need to rely on predictable characteristics of data-structures to check if they
27-
// are what we expect for them to be. The `LContainer` interface code below has a
28-
// fixed length and the constant value below references that. Using the length value
29-
// below we can predictably gaurantee that we are dealing with an `LContainer` array.
30-
// This value MUST be kept up to date with the length of the `LContainer` array
31-
// interface below so that runtime type checking can work.
32-
export const LCONTAINER_LENGTH = 7;
28+
export const VIEWS = 6;
29+
export const NATIVE = 7;
3330

3431
/**
3532
* The state associated with a container.
@@ -51,6 +48,12 @@ export interface LContainer extends Array<any> {
5148
*/
5249
readonly[HOST]: RElement|RComment|StylingContext|LView;
5350

51+
/**
52+
* This is a type field which allows us to differentiate `LContainer` from `StylingContext` in an
53+
* efficient way. The value is always set to `true`
54+
*/
55+
[TYPE]: true;
56+
5457
/**
5558
* The next active index in the views array to read or write to. This helps us
5659
* keep track of where we are in the views array.
@@ -60,15 +63,6 @@ export interface LContainer extends Array<any> {
6063
*/
6164
[ACTIVE_INDEX]: number;
6265

63-
/**
64-
* A list of the container's currently active child views. Views will be inserted
65-
* here as they are added and spliced from here when they are removed. We need
66-
* to keep a record of current views so we know which views are already in the DOM
67-
* (and don't need to be re-added) and so we can remove views from the DOM when they
68-
* are no longer required.
69-
*/
70-
[VIEWS]: LView[];
71-
7266
/**
7367
* Access to the parent view is necessary so we can propagate back
7468
* up from inside a container to parent[NEXT].
@@ -85,10 +79,21 @@ export interface LContainer extends Array<any> {
8579
* Queries active for this container - all the views inserted to / removed from
8680
* this container are reported to queries referenced here.
8781
*/
88-
[QUERIES]: LQueries|null;
82+
[QUERIES]: LQueries|null; // TODO(misko): This is abuse of `LContainer` since we are storing
83+
// `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template)
84+
85+
/**
86+
* A list of the container's currently active child views. Views will be inserted
87+
* here as they are added and spliced from here when they are removed. We need
88+
* to keep a record of current views so we know which views are already in the DOM
89+
* (and don't need to be re-added) and so we can remove views from the DOM when they
90+
* are no longer required.
91+
*/
92+
[VIEWS]: LView[];
8993

9094
/** The comment element that serves as an anchor for this LContainer. */
91-
readonly[NATIVE]: RComment;
95+
readonly[NATIVE]:
96+
RComment; // TODO(misko): remove as this value can be gotten by unwrapping `[HOST]`
9297
}
9398

9499
// Note: This hack is necessary so we don't erroneously get a circular dependency

packages/core/src/render3/interfaces/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99

10-
import {RElement} from './renderer';
10+
import {RNode} from './renderer';
1111
import {LView} from './view';
1212

1313
/**
@@ -39,7 +39,7 @@ export interface LContext {
3939
/**
4040
* The instance of the DOM node that is attached to the lNode.
4141
*/
42-
native: RElement;
42+
native: RNode;
4343

4444
/**
4545
* The instance of the Component node.

packages/core/src/render3/interfaces/styling.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
*/
88
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
99
import {RElement} from '../interfaces/renderer';
10+
import {LContainer} from './container';
1011
import {PlayerContext} from './player';
12+
import {LView} from './view';
13+
1114

1215
/**
1316
* The styling context acts as a styling manifest (shaped as an array) for determining which
@@ -263,7 +266,7 @@ export interface StylingContext extends
263266
/**
264267
* Location of element that is used as a target for this context.
265268
*/
266-
[StylingIndex.ElementPosition]: RElement|null;
269+
[StylingIndex.ElementPosition]: LContainer|LView|RElement|null;
267270

268271
/**
269272
* A numeric value representing the configuration status (whether the context is dirty or not)

packages/core/src/render3/interfaces/view.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ export interface LView extends Array<any> {
7272
* The host node for this LView instance, if this is a component view.
7373
*
7474
* If this is an embedded view, HOST will be null.
75+
*
76+
* If the component uses host bindings for styling that the `RElement` will be wrapped with
77+
* `StylingContext`.
7578
*/
7679
[HOST]: RElement|StylingContext|null;
7780

packages/core/src/render3/node_manipulation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {CHILD_HEAD, CLEANUP, FLAGS, HEADER_OFFSET, HookData, LView, LViewFlags,
2121
import {assertNodeType} from './node_assert';
2222
import {renderStringify} from './util/misc_utils';
2323
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
24-
import {getNativeByTNode, isComponent, isLContainer, isLView, isRootView, readElementValue} from './util/view_utils';
24+
import {getNativeByTNode, isComponent, isLContainer, isLView, isRootView, unwrapRNode} from './util/view_utils';
2525

2626
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
2727

@@ -200,8 +200,8 @@ function walkTNodeTree(
200200
* being passed as an argument.
201201
*/
202202
function executeNodeAction(
203-
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null,
204-
node: RComment | RElement | RText, tNode: TNode, beforeNode?: RNode | null) {
203+
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, node: RNode,
204+
tNode: TNode, beforeNode?: RNode | null) {
205205
if (action === WalkTNodeTreeAction.Insert) {
206206
nativeInsertBefore(renderer, parent !, node, beforeNode || null);
207207
} else if (action === WalkTNodeTreeAction.Detach) {
@@ -454,7 +454,7 @@ function removeListeners(lView: LView): void {
454454
const idxOrTargetGetter = tCleanup[i + 1];
455455
const target = typeof idxOrTargetGetter === 'function' ?
456456
idxOrTargetGetter(lView) :
457-
readElementValue(lView[idxOrTargetGetter]);
457+
unwrapRNode(lView[idxOrTargetGetter]);
458458
const listener = lCleanup[tCleanup[i + 2]];
459459
const useCaptureOrSubIdx = tCleanup[i + 3];
460460
if (typeof useCaptureOrSubIdx === 'boolean') {

packages/core/src/render3/styling/util.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ import '../../util/ng_dev_mode';
99

1010
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
1111
import {getLContext} from '../context_discovery';
12-
import {LCONTAINER_LENGTH, LContainer} from '../interfaces/container';
12+
import {LContainer} from '../interfaces/container';
1313
import {LContext} from '../interfaces/context';
1414
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
1515
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
1616
import {RElement} from '../interfaces/renderer';
17-
import {InitialStylingValues, InitialStylingValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
17+
import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
1818
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
19-
import {getTNode} from '../util/view_utils';
19+
import {getTNode, isStylingContext} from '../util/view_utils';
2020

2121
import {CorePlayerHandler} from './core_player_handler';
2222

2323
export const ANIMATION_PROP_PREFIX = '@';
2424

2525
export function createEmptyStylingContext(
26-
element?: RElement | null, sanitizer?: StyleSanitizeFn | null,
26+
wrappedElement?: LContainer | LView | RElement | null, sanitizer?: StyleSanitizeFn | null,
2727
initialStyles?: InitialStylingValues | null,
2828
initialClasses?: InitialStylingValues | null): StylingContext {
2929
const context: StylingContext = [
30-
element || null, // Element
30+
wrappedElement || null, // Element
3131
0, // MasterFlags
3232
[] as any, // DirectiveRefs (this gets filled below)
3333
initialStyles || [null, null], // InitialStyles
@@ -95,7 +95,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
9595
}
9696

9797
if (isStylingContext(wrapper)) {
98-
return wrapper as StylingContext;
98+
return wrapper;
9999
} else {
100100
// This is an LView or an LContainer
101101
const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate;
@@ -110,15 +110,6 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
110110
}
111111
}
112112

113-
export function isStylingContext(value: any): boolean {
114-
// Not an LView or an LContainer
115-
if (Array.isArray(value) && value.length >= StylingIndex.SingleStylesStartPosition) {
116-
return typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
117-
value[StylingIndex.InitialClassValuesPosition]
118-
[InitialStylingValuesIndex.DefaultNullValuePosition] === null;
119-
}
120-
return false;
121-
}
122113

123114
export function isAnimationProp(name: string): boolean {
124115
return name[0] === ANIMATION_PROP_PREFIX;

packages/core/src/render3/util/discovery_utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node';
1717
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW} from '../interfaces/view';
1818
import {renderStringify} from './misc_utils';
1919
import {getLViewParent, getRootContext} from './view_traversal_utils';
20-
import {readElementValue} from './view_utils';
20+
import {unwrapRNode} from './view_utils';
2121

2222

2323

@@ -297,7 +297,7 @@ export function getListeners(element: Element): Listener[] {
297297
const secondParam = tCleanup[i++];
298298
if (typeof firstParam === 'string') {
299299
const name: string = firstParam;
300-
const listenerElement = readElementValue(lView[secondParam]) as any as Element;
300+
const listenerElement = unwrapRNode(lView[secondParam]) as any as Element;
301301
const callback: (value: any) => any = lCleanup[tCleanup[i++]];
302302
const useCaptureOrIndx = tCleanup[i++];
303303
// if useCaptureOrIndx is boolean then report it as is.

0 commit comments

Comments
 (0)