From 3cb497c6ac07d90175684ea67b6c019da0d5e393 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 23 Feb 2019 11:14:35 -0800 Subject: [PATCH] refactor(ivy): simplify differentiation of LView, `RNode`, `LView`, `LContainer`, `StylingContext` (#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 #28947 --- .../core/src/render3/context_discovery.ts | 10 +- packages/core/src/render3/debug.ts | 6 +- packages/core/src/render3/instructions.ts | 13 +- .../core/src/render3/interfaces/container.ts | 53 ++++---- .../core/src/render3/interfaces/context.ts | 4 +- .../core/src/render3/interfaces/styling.ts | 5 +- packages/core/src/render3/interfaces/view.ts | 3 + .../core/src/render3/node_manipulation.ts | 8 +- packages/core/src/render3/styling/util.ts | 21 +-- .../core/src/render3/util/discovery_utils.ts | 4 +- packages/core/src/render3/util/view_utils.ts | 128 ++++++++++++++---- .../src/render3/view_engine_compatibility.ts | 2 +- .../cyclic_import/bundle.golden_symbols.json | 12 +- .../hello_world/bundle.golden_symbols.json | 12 +- .../bundling/todo/bundle.golden_symbols.json | 12 +- packages/core/test/render3/di_spec.ts | 2 +- .../test/render3/view_container_ref_spec.ts | 2 +- packages/core/test/render3/view_utils_spec.ts | 38 ++++++ .../test/sanitization/sanatization_spec.ts | 2 +- 19 files changed, 225 insertions(+), 112 deletions(-) create mode 100644 packages/core/test/render3/view_utils_spec.ts diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index acaff2267e..ed9df85062 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -12,9 +12,9 @@ import {assertDomNode} from '../util/assert'; import {EMPTY_ARRAY} from './empty'; import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {TNode, TNodeFlags} from './interfaces/node'; -import {RElement} from './interfaces/renderer'; +import {RElement, RNode} from './interfaces/renderer'; import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view'; -import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util/view_utils'; +import {getComponentViewByIndex, getNativeByTNode, readPatchedData, unwrapRNode} from './util/view_utils'; @@ -71,7 +71,7 @@ export function getLContext(target: any): LContext|null { // are expensive. Instead, only the target data (the element, component, container, ICU // expression or directive details) are filled into the context. If called multiple times // with different target values then the missing target data will be filled in. - const native = readElementValue(lView[nodeIndex]); + const native = unwrapRNode(lView[nodeIndex]); const existingCtx = readPatchedData(native); const context: LContext = (existingCtx && !Array.isArray(existingCtx)) ? existingCtx : @@ -119,7 +119,7 @@ export function getLContext(target: any): LContext|null { const index = findViaNativeElement(lView, rElement); if (index >= 0) { - const native = readElementValue(lView[index]); + const native = unwrapRNode(lView[index]); const context = createLContext(lView, index, native); attachPatchData(native, context); mpValue = context; @@ -134,7 +134,7 @@ export function getLContext(target: any): LContext|null { /** * Creates an empty instance of a `LContext` context */ -function createLContext(lView: LView, nodeIndex: number, native: RElement): LContext { +function createLContext(lView: LView, nodeIndex: number, native: RNode): LContext { return { lView, nodeIndex, diff --git a/packages/core/src/render3/debug.ts b/packages/core/src/render3/debug.ts index 7bd49431a8..bc1d7982ab 100644 --- a/packages/core/src/render3/debug.ts +++ b/packages/core/src/render3/debug.ts @@ -14,7 +14,7 @@ import {LQueries} from './interfaces/query'; import {RComment, RElement} from './interfaces/renderer'; import {StylingContext} from './interfaces/styling'; 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'; -import {readElementValue} from './util/view_utils'; +import {unwrapRNode} from './util/view_utils'; /* * This file contains conditionally attached classes which provide human readable (debug) level @@ -77,7 +77,7 @@ export function toDebug(obj: any): any { * (will not serialize child elements). */ function toHtml(value: any, includeChildren: boolean = false): string|null { - const node: HTMLElement|null = readElementValue(value) as any; + const node: HTMLElement|null = unwrapRNode(value) as any; if (node) { const isTextNode = node.nodeType === Node.TEXT_NODE; const outerHTML = (isTextNode ? node.textContent : node.outerHTML) || ''; @@ -182,7 +182,7 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul let tNodeCursor: TNode|null = tNode; while (tNodeCursor) { const rawValue = lView[tNode.index]; - const native = readElementValue(rawValue); + const native = unwrapRNode(rawValue); const componentLViewDebug = toDebug(readLViewValue(rawValue)); debugNodes.push({ html: toHtml(native), diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 8bec067312..3961bc4e83 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -47,7 +47,7 @@ import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingC import {NO_CHANGE} from './tokens'; import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils'; import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils'; -import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView} from './util/view_utils'; +import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode} from './util/view_utils'; @@ -139,7 +139,7 @@ export function setHostBindings(tView: TView, viewData: LView): void { if (instruction !== null) { viewData[BINDING_INDEX] = bindingRootIndex; instruction( - RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]), + RenderFlags.Update, unwrapRNode(viewData[currentDirectiveIndex]), currentElementIndex); } currentDirectiveIndex++; @@ -1018,7 +1018,7 @@ function listenerInternal( } const idxOrTargetGetter = eventTargetResolver ? - (_lView: LView) => eventTargetResolver(readElementValue(_lView[tNode.index])).target : + (_lView: LView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])).target : tNode.index; tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, useCaptureOrSubIdx); } @@ -1147,7 +1147,7 @@ export function elementAttribute( ngDevMode && validateAgainstEventAttributes(name); const lView = getLView(); const renderer = lView[RENDERER]; - const element = getNativeByIndex(index, lView); + const element = getNativeByIndex(index, lView) as RElement; if (value == null) { ngDevMode && ngDevMode.rendererRemoveAttribute++; isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) : @@ -2216,12 +2216,13 @@ export function createLContainer( ngDevMode && assertDomNode(native); ngDevMode && assertLView(currentView); const lContainer: LContainer = [ - hostNative, // host native + hostNative, // host native + true, // Boolean `true` in this position signifies that this is an `LContainer` isForViewContainerRef ? -1 : 0, // active index - [], // views currentView, // parent null, // next null, // queries + [], // views native, // native ]; ngDevMode && attachLContainerDebug(lContainer); diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 5df2b3da48..c562cab613 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -11,25 +11,22 @@ import {RComment, RElement} from './renderer'; import {StylingContext} from './styling'; import {HOST, LView, NEXT, PARENT, QUERIES} from './view'; - +/** + * Special location which allows easy identification of type. If we have an array which was + * retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is + * `LContainer`. + */ +export const TYPE = 1; /** * Below are constants for LContainer indices to help us look up LContainer members * without having to remember the specific indices. * Uglify will inline these when minifying so there shouldn't be a cost. */ -export const ACTIVE_INDEX = 1; -export const VIEWS = 2; -// PARENT, NEXT, QUERIES, and HOST are indices 2, 3, 4, and 5. +export const ACTIVE_INDEX = 2; +// PARENT, NEXT, and QUERIES are indices 3, 4, and 5. // As we already have these constants in LView, we don't need to re-create them. -export const NATIVE = 6; -// Because interfaces in TS/JS cannot be instanceof-checked this means that we -// need to rely on predictable characteristics of data-structures to check if they -// are what we expect for them to be. The `LContainer` interface code below has a -// fixed length and the constant value below references that. Using the length value -// below we can predictably gaurantee that we are dealing with an `LContainer` array. -// This value MUST be kept up to date with the length of the `LContainer` array -// interface below so that runtime type checking can work. -export const LCONTAINER_LENGTH = 7; +export const VIEWS = 6; +export const NATIVE = 7; /** * The state associated with a container. @@ -51,6 +48,12 @@ export interface LContainer extends Array { */ readonly[HOST]: RElement|RComment|StylingContext|LView; + /** + * This is a type field which allows us to differentiate `LContainer` from `StylingContext` in an + * efficient way. The value is always set to `true` + */ + [TYPE]: true; + /** * The next active index in the views array to read or write to. This helps us * keep track of where we are in the views array. @@ -60,15 +63,6 @@ export interface LContainer extends Array { */ [ACTIVE_INDEX]: number; - /** - * A list of the container's currently active child views. Views will be inserted - * here as they are added and spliced from here when they are removed. We need - * to keep a record of current views so we know which views are already in the DOM - * (and don't need to be re-added) and so we can remove views from the DOM when they - * are no longer required. - */ - [VIEWS]: LView[]; - /** * Access to the parent view is necessary so we can propagate back * up from inside a container to parent[NEXT]. @@ -85,10 +79,21 @@ export interface LContainer extends Array { * Queries active for this container - all the views inserted to / removed from * this container are reported to queries referenced here. */ - [QUERIES]: LQueries|null; + [QUERIES]: LQueries|null; // TODO(misko): This is abuse of `LContainer` since we are storing + // `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template) + + /** + * A list of the container's currently active child views. Views will be inserted + * here as they are added and spliced from here when they are removed. We need + * to keep a record of current views so we know which views are already in the DOM + * (and don't need to be re-added) and so we can remove views from the DOM when they + * are no longer required. + */ + [VIEWS]: LView[]; /** The comment element that serves as an anchor for this LContainer. */ - readonly[NATIVE]: RComment; + readonly[NATIVE]: + RComment; // TODO(misko): remove as this value can be gotten by unwrapping `[HOST]` } // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts index e1bab45c72..5bf23e10e7 100644 --- a/packages/core/src/render3/interfaces/context.ts +++ b/packages/core/src/render3/interfaces/context.ts @@ -7,7 +7,7 @@ */ -import {RElement} from './renderer'; +import {RNode} from './renderer'; import {LView} from './view'; /** @@ -39,7 +39,7 @@ export interface LContext { /** * The instance of the DOM node that is attached to the lNode. */ - native: RElement; + native: RNode; /** * The instance of the Component node. diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 453dff8d3f..1ac69ee063 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -7,7 +7,10 @@ */ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {RElement} from '../interfaces/renderer'; +import {LContainer} from './container'; import {PlayerContext} from './player'; +import {LView} from './view'; + /** * The styling context acts as a styling manifest (shaped as an array) for determining which @@ -263,7 +266,7 @@ export interface StylingContext extends /** * Location of element that is used as a target for this context. */ - [StylingIndex.ElementPosition]: RElement|null; + [StylingIndex.ElementPosition]: LContainer|LView|RElement|null; /** * A numeric value representing the configuration status (whether the context is dirty or not) diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 9b6bfc388c..c60313b735 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -72,6 +72,9 @@ export interface LView extends Array { * The host node for this LView instance, if this is a component view. * * If this is an embedded view, HOST will be null. + * + * If the component uses host bindings for styling that the `RElement` will be wrapped with + * `StylingContext`. */ [HOST]: RElement|StylingContext|null; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 21f07e3838..dc2439cd5c 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -21,7 +21,7 @@ import {CHILD_HEAD, CLEANUP, FLAGS, HEADER_OFFSET, HookData, LView, LViewFlags, import {assertNodeType} from './node_assert'; import {renderStringify} from './util/misc_utils'; import {findComponentView, getLViewParent} from './util/view_traversal_utils'; -import {getNativeByTNode, isComponent, isLContainer, isLView, isRootView, readElementValue} from './util/view_utils'; +import {getNativeByTNode, isComponent, isLContainer, isLView, isRootView, unwrapRNode} from './util/view_utils'; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; @@ -200,8 +200,8 @@ function walkTNodeTree( * being passed as an argument. */ function executeNodeAction( - action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, - node: RComment | RElement | RText, tNode: TNode, beforeNode?: RNode | null) { + action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, node: RNode, + tNode: TNode, beforeNode?: RNode | null) { if (action === WalkTNodeTreeAction.Insert) { nativeInsertBefore(renderer, parent !, node, beforeNode || null); } else if (action === WalkTNodeTreeAction.Detach) { @@ -454,7 +454,7 @@ function removeListeners(lView: LView): void { const idxOrTargetGetter = tCleanup[i + 1]; const target = typeof idxOrTargetGetter === 'function' ? idxOrTargetGetter(lView) : - readElementValue(lView[idxOrTargetGetter]); + unwrapRNode(lView[idxOrTargetGetter]); const listener = lCleanup[tCleanup[i + 2]]; const useCaptureOrSubIdx = tCleanup[i + 3]; if (typeof useCaptureOrSubIdx === 'boolean') { diff --git a/packages/core/src/render3/styling/util.ts b/packages/core/src/render3/styling/util.ts index af6f215cf0..2984841b72 100644 --- a/packages/core/src/render3/styling/util.ts +++ b/packages/core/src/render3/styling/util.ts @@ -9,25 +9,25 @@ import '../../util/ng_dev_mode'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {getLContext} from '../context_discovery'; -import {LCONTAINER_LENGTH, LContainer} from '../interfaces/container'; +import {LContainer} from '../interfaces/container'; import {LContext} from '../interfaces/context'; import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node'; import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; import {RElement} from '../interfaces/renderer'; -import {InitialStylingValues, InitialStylingValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; +import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view'; -import {getTNode} from '../util/view_utils'; +import {getTNode, isStylingContext} from '../util/view_utils'; import {CorePlayerHandler} from './core_player_handler'; export const ANIMATION_PROP_PREFIX = '@'; export function createEmptyStylingContext( - element?: RElement | null, sanitizer?: StyleSanitizeFn | null, + wrappedElement?: LContainer | LView | RElement | null, sanitizer?: StyleSanitizeFn | null, initialStyles?: InitialStylingValues | null, initialClasses?: InitialStylingValues | null): StylingContext { const context: StylingContext = [ - element || null, // Element + wrappedElement || null, // Element 0, // MasterFlags [] as any, // DirectiveRefs (this gets filled below) initialStyles || [null, null], // InitialStyles @@ -95,7 +95,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex } if (isStylingContext(wrapper)) { - return wrapper as StylingContext; + return wrapper; } else { // This is an LView or an LContainer const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate; @@ -110,15 +110,6 @@ export function getStylingContext(index: number, viewData: LView): StylingContex } } -export function isStylingContext(value: any): boolean { - // Not an LView or an LContainer - if (Array.isArray(value) && value.length >= StylingIndex.SingleStylesStartPosition) { - return typeof value[StylingIndex.MasterFlagPosition] === 'number' && - value[StylingIndex.InitialClassValuesPosition] - [InitialStylingValuesIndex.DefaultNullValuePosition] === null; - } - return false; -} export function isAnimationProp(name: string): boolean { return name[0] === ANIMATION_PROP_PREFIX; diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index 35bb9ea02b..13bc23117c 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -17,7 +17,7 @@ import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node'; import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW} from '../interfaces/view'; import {renderStringify} from './misc_utils'; import {getLViewParent, getRootContext} from './view_traversal_utils'; -import {readElementValue} from './view_utils'; +import {unwrapRNode} from './view_utils'; @@ -297,7 +297,7 @@ export function getListeners(element: Element): Listener[] { const secondParam = tCleanup[i++]; if (typeof firstParam === 'string') { const name: string = firstParam; - const listenerElement = readElementValue(lView[secondParam]) as any as Element; + const listenerElement = unwrapRNode(lView[secondParam]) as any as Element; const callback: (value: any) => any = lCleanup[tCleanup[i++]]; const useCaptureOrIndx = tCleanup[i++]; // if useCaptureOrIndx is boolean then report it as is. diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index a5743424b1..4c6a155fc2 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -7,43 +7,128 @@ */ import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from '../../util/assert'; -import {LCONTAINER_LENGTH, LContainer} from '../interfaces/container'; +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 {RComment, RElement, RText} from '../interfaces/renderer'; +import {RNode} from '../interfaces/renderer'; +import {StylingContext} from '../interfaces/styling'; import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, TData, TVIEW} from '../interfaces/view'; /** - * Takes the value of a slot in `LView` and returns the element node. + * 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. * - * Normally, element nodes are stored flat, but if the node has styles/classes on it, - * it might be wrapped in a styling context. Or if that node has a directive that injects - * ViewContainerRef, it may be wrapped in an LContainer. Or if that node is a component, - * it will be wrapped in LView. It could even have all three, so we keep looping - * until we find something that isn't an array. + * 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` => then 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. * - * @param value The initial value in `LView` + * + * NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient. */ -export function readElementValue(value: any): RElement { + +/** + * Returns `RNode`. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function unwrapRNode(value: RNode | LView | LContainer | StylingContext): RNode { while (Array.isArray(value)) { value = value[HOST] as any; } - return value; + return value as RNode; +} + +/** + * Returns `LView` or `null` if not found. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function unwrapLView(value: RNode | LView | LContainer | StylingContext): LView|null { + while (Array.isArray(value)) { + // This check is same as `isLView()` but we don't call at as we don't want to call + // `Array.isArray()` twice and give JITer more work for inlining. + if (typeof value[TYPE] === 'object') return value as LView; + value = value[HOST] as any; + } + return null; +} + +/** + * Returns `LContainer` or `null` if not found. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function unwrapLContainer(value: RNode | LView | LContainer | StylingContext): LContainer| + null { + while (Array.isArray(value)) { + // This check is same as `isLContainer()` but we don't call at as we don't want to call + // `Array.isArray()` twice and give JITer more work for inlining. + if (value[TYPE] === true) return value as LContainer; + value = value[HOST] as any; + } + return null; +} + +/** + * Returns `StylingContext` or `null` if not found. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function unwrapStylingContext(value: RNode | LView | LContainer | StylingContext): + StylingContext|null { + while (Array.isArray(value)) { + // This check is same as `isStylingContext()` but we don't call at as we don't want to call + // `Array.isArray()` twice and give JITer more work for inlining. + if (typeof value[TYPE] === 'number') return value as StylingContext; + value = value[HOST] as any; + } + return null; +} + +/** + * True if `value` is `LView`. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function isLView(value: RNode | LView | LContainer | StylingContext | {} | null): + value is LView { + return Array.isArray(value) && typeof value[TYPE] === 'object'; +} + +/** + * True if `value` is `LContainer`. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function isLContainer(value: RNode | LView | LContainer | StylingContext | {} | null): + value is LContainer { + return Array.isArray(value) && value[TYPE] === true; +} + +/** + * True if `value` is `StylingContext`. + * @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext` + */ +export function isStylingContext(value: RNode | LView | LContainer | StylingContext | {} | null): + value is StylingContext { + return Array.isArray(value) && typeof value[TYPE] === 'number'; } /** * Retrieves an element value from the provided `viewData`, by unwrapping * from any containers, component views, or style contexts. */ -export function getNativeByIndex(index: number, lView: LView): RElement { - return readElementValue(lView[index + HEADER_OFFSET]); +export function getNativeByIndex(index: number, lView: LView): RNode { + return unwrapRNode(lView[index + HEADER_OFFSET]); } -export function getNativeByTNode(tNode: TNode, hostView: LView): RElement|RText|RComment { - return readElementValue(hostView[tNode.index]); +export function getNativeByTNode(tNode: TNode, hostView: LView): RNode { + return unwrapRNode(hostView[tNode.index]); } export function getTNode(index: number, view: LView): TNode { @@ -52,14 +137,6 @@ export function getTNode(index: number, view: LView): TNode { return view[TVIEW].data[index + HEADER_OFFSET] as TNode; } -/** - * Returns true if the value is an {@link LView} - * @param value the value to check - */ -export function isLView(value: any): value is LView { - return Array.isArray(value) && value.length >= HEADER_OFFSET; -} - /** Retrieves a value from any `LView` or `TData`. */ export function loadInternal(view: LView | TData, index: number): T { ngDevMode && assertDataInRange(view, index + HEADER_OFFSET); @@ -85,11 +162,6 @@ export function isComponentDef(def: DirectiveDef): def is ComponentDef return (def as ComponentDef).template !== null; } -export function isLContainer(value: any): value is LContainer { - // Styling contexts are also arrays, but their first index contains an element node - return Array.isArray(value) && value.length === LCONTAINER_LENGTH; -} - export function isRootView(target: LView): boolean { return (target[FLAGS] & LViewFlags.IsRoot) !== 0; } diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 45560d57fe..04c6848b60 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -61,7 +61,7 @@ export function createElementRef( // TODO: Fix class name, should be ElementRef, but there appears to be a rollup bug R3ElementRef = class ElementRef_ extends ElementRefToken {}; } - return new R3ElementRef(getNativeByTNode(tNode, view)); + return new R3ElementRef(getNativeByTNode(tNode, view) as RElement); } let R3TemplateRef: { diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index be2e574c15..bff0cb4f66 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -59,9 +59,6 @@ { "name": "INJECTOR_BLOOM_PARENT_SIZE" }, - { - "name": "LCONTAINER_LENGTH" - }, { "name": "MONKEY_PATCH_KEY_NAME" }, @@ -128,6 +125,9 @@ { "name": "TVIEW" }, + { + "name": "TYPE" + }, { "name": "T_HOST" }, @@ -536,9 +536,6 @@ { "name": "readClassValueFromTNode" }, - { - "name": "readElementValue" - }, { "name": "readPatchedData" }, @@ -653,6 +650,9 @@ { "name": "tickRootContext" }, + { + "name": "unwrapRNode" + }, { "name": "viewAttached" } diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 0766039f98..089b50c895 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -53,9 +53,6 @@ { "name": "INJECTOR_BLOOM_PARENT_SIZE" }, - { - "name": "LCONTAINER_LENGTH" - }, { "name": "MONKEY_PATCH_KEY_NAME" }, @@ -107,6 +104,9 @@ { "name": "TVIEW" }, + { + "name": "TYPE" + }, { "name": "T_HOST" }, @@ -383,9 +383,6 @@ { "name": "queueComponentIndexForCheck" }, - { - "name": "readElementValue" - }, { "name": "readPatchedData" }, @@ -455,6 +452,9 @@ { "name": "tickRootContext" }, + { + "name": "unwrapRNode" + }, { "name": "viewAttached" } diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index fd36edb1c4..fd7839f54e 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -104,9 +104,6 @@ { "name": "IterableDiffers" }, - { - "name": "LCONTAINER_LENGTH" - }, { "name": "MONKEY_PATCH_KEY_NAME" }, @@ -218,6 +215,9 @@ { "name": "TVIEW" }, + { + "name": "TYPE" + }, { "name": "T_HOST" }, @@ -1085,9 +1085,6 @@ { "name": "readClassValueFromTNode" }, - { - "name": "readElementValue" - }, { "name": "readPatchedData" }, @@ -1283,6 +1280,9 @@ { "name": "trackByIdentity" }, + { + "name": "unwrapRNode" + }, { "name": "updateClassProp" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 411c6f90c3..2f2b514320 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1532,7 +1532,7 @@ describe('di', () => { if (rf & RenderFlags.Create) { elementStart(0, 'div', ['dir', '', 'dirSame', '']); elementEnd(); - div = getNativeByIndex(0, getLView()); + div = getNativeByIndex(0, getLView()) as RElement; } }, 1, 0, [Directive, DirectiveSameInstance]); diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index b10c1ae45b..a3f6eb29dd 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -2066,7 +2066,7 @@ describe('ViewContainerRef', () => { element(0, 'div', ['bar', ''], ['foo', '']); } // testing only - fooEl = getNativeByIndex(0, getLView()); + fooEl = getNativeByIndex(0, getLView()) as RElement; }, viewQuery: function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { diff --git a/packages/core/test/render3/view_utils_spec.ts b/packages/core/test/render3/view_utils_spec.ts new file mode 100644 index 0000000000..056a7a8b89 --- /dev/null +++ b/packages/core/test/render3/view_utils_spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {createLContainer, createLView, createTView} from '@angular/core/src/render3/instructions'; +import {createEmptyStylingContext} from '@angular/core/src/render3/styling/util'; +import {isLContainer, isLView, isStylingContext, unwrapLContainer, unwrapLView, unwrapRNode, unwrapStylingContext} from '@angular/core/src/render3/util/view_utils'; + +describe('view_utils', () => { + it('should verify unwrap methods', () => { + 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 lContainer = createLContainer(lView, lView, div, true); + const styleContext = createEmptyStylingContext(lContainer, null, null, null); + + expect(unwrapRNode(styleContext)).toBe(div); + expect(unwrapStylingContext(styleContext)).toBe(styleContext); + expect(unwrapLContainer(styleContext)).toBe(lContainer); + expect(unwrapLView(styleContext)).toBe(lView); + + expect(isLView(lView)).toBe(true); + expect(isLView(lContainer)).toBe(false); + expect(isLView(styleContext)).toBe(false); + + expect(isLContainer(lView)).toBe(false); + expect(isLContainer(lContainer)).toBe(true); + expect(isLContainer(styleContext)).toBe(false); + + expect(isStylingContext(lView)).toBe(false); + expect(isStylingContext(lContainer)).toBe(false); + expect(isStylingContext(styleContext)).toBe(true); + }); +}); \ No newline at end of file diff --git a/packages/core/test/sanitization/sanatization_spec.ts b/packages/core/test/sanitization/sanatization_spec.ts index 7fea17a5e2..e774521033 100644 --- a/packages/core/test/sanitization/sanatization_spec.ts +++ b/packages/core/test/sanitization/sanatization_spec.ts @@ -16,7 +16,7 @@ import {getUrlSanitizer, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sani import {SecurityContext} from '../../src/sanitization/security'; function fakeLView(): LView { - return Array.from({length: HEADER_OFFSET}) as LView; + return [null, {}] as LView; } describe('sanitization', () => {