fix(core): Store the currently selected ICU in LView (#38345)

The currently selected ICU was incorrectly being stored it `TNode`
rather than in `LView`.

Remove: `TIcuContainerNode.activeCaseIndex`
Add: `LView[TIcu.currentCaseIndex]`

PR Close #38345
This commit is contained in:
Misko Hevery
2020-08-10 12:41:17 -07:00
committed by Andrew Kushnir
parent 6ff28ac944
commit 6d8c73a4d6
20 changed files with 624 additions and 283 deletions
@@ -62,7 +62,7 @@
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
"bundle": 1213130
"bundle": 1213769
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@
*/
import {devModeEqual} from '../change_detection/change_detection_util';
import {assertDataInRange, assertLessThan, assertNotSame} from '../util/assert';
import {assertIndexInRange, assertLessThan, assertNotSame} from '../util/assert';
import {getExpressionChangedErrorDetails, throwErrorIfNoChangesMode} from './errors';
import {LView} from './interfaces/view';
@@ -24,7 +24,7 @@ export function updateBinding(lView: LView, bindingIndex: number, value: any): a
/** Gets the current binding value. */
export function getBinding(lView: LView, bindingIndex: number): any {
ngDevMode && assertDataInRange(lView, bindingIndex);
ngDevMode && assertIndexInRange(lView, bindingIndex);
ngDevMode &&
assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
return lView[bindingIndex];
+2 -2
View File
@@ -11,7 +11,7 @@
import {Type} from '../core';
import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/sanitizer';
import {assertDataInRange} from '../util/assert';
import {assertIndexInRange} from '../util/assert';
import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
@@ -172,7 +172,7 @@ export function createRootComponentView(
rNode: RElement|null, def: ComponentDef<any>, rootView: LView,
rendererFactory: RendererFactory3, hostRenderer: Renderer3, sanitizer?: Sanitizer|null): LView {
const tView = rootView[TVIEW];
ngDevMode && assertDataInRange(rootView, 0 + HEADER_OFFSET);
ngDevMode && assertIndexInRange(rootView, 0 + HEADER_OFFSET);
rootView[0 + HEADER_OFFSET] = rNode;
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,7 @@
* 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 {assertDataInRange, assertGreaterThan} from '../../util/assert';
import {assertGreaterThan, assertIndexInRange} from '../../util/assert';
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view';
import {getCheckNoChangesMode, getLView, getSelectedIndex, getTView, setSelectedIndex} from '../state';
@@ -52,7 +52,7 @@ export function ɵɵselect(index: number): void {
export function selectIndexInternal(
tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) {
ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
// Flush the initial hooks for elements in the view that have been added up to this point.
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert';
import {assertFirstCreatePass, assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
@@ -79,7 +79,7 @@ export function ɵɵelementStart(
getBindingIndex(), tView.bindingStartIndex,
'elements should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateElement++;
ngDevMode && assertDataInRange(lView, adjustedIndex);
ngDevMode && assertIndexInRange(lView, adjustedIndex);
const renderer = lView[RENDERER];
const native = lView[adjustedIndex] = elementCreate(name, renderer, getNamespace());
@@ -5,7 +5,7 @@
* 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 {assertDataInRange, assertEqual} from '../../util/assert';
import {assertEqual, assertIndexInRange} from '../../util/assert';
import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
@@ -66,7 +66,7 @@ export function ɵɵelementContainerStart(
const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
ngDevMode && assertDataInRange(lView, adjustedIndex);
ngDevMode && assertIndexInRange(lView, adjustedIndex);
ngDevMode &&
assertEqual(
getBindingIndex(), tView.bindingStartIndex,
@@ -7,7 +7,7 @@
*/
import {assertDataInRange} from '../../util/assert';
import {assertIndexInRange} from '../../util/assert';
import {isObservable} from '../../util/lang';
import {EMPTY_OBJ} from '../empty';
import {PropertyAliasValue, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
@@ -204,7 +204,7 @@ function listenerInternal(
if (propsLength) {
for (let i = 0; i < propsLength; i += 2) {
const index = props[i] as number;
ngDevMode && assertDataInRange(lView, index);
ngDevMode && assertIndexInRange(lView, index);
const minifiedName = props[i + 1];
const directiveInstance = lView[index];
const output = directiveInstance[minifiedName];
@@ -362,19 +362,24 @@ export function toDebug(obj: any): any {
* (will not serialize child elements).
*/
function toHtml(value: any, includeChildren: boolean = false): string|null {
const node: HTMLElement|null = unwrapRNode(value) as any;
const node: Node|null = unwrapRNode(value) as any;
if (node) {
const isTextNode = node.nodeType === Node.TEXT_NODE;
const outerHTML = (isTextNode ? node.textContent : node.outerHTML) || '';
if (includeChildren || isTextNode) {
return outerHTML;
} else {
const innerHTML = '>' + node.innerHTML + '<';
return (outerHTML.split(innerHTML)[0]) + '>';
switch (node.nodeType) {
case Node.TEXT_NODE:
return node.textContent;
case Node.COMMENT_NODE:
return `<!--${(node as Comment).textContent}-->`;
case Node.ELEMENT_NODE:
const outerHTML = (node as Element).outerHTML;
if (includeChildren) {
return outerHTML;
} else {
const innerHTML = '>' + (node as Element).innerHTML + '<';
return (outerHTML.split(innerHTML)[0]) + '>';
}
}
} else {
return null;
}
return null;
}
export class LViewDebug implements ILViewDebug {
+120 -112
View File
@@ -12,7 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertLessThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
@@ -236,13 +236,6 @@ export function getOrCreateTNode(
const tNode = tView.data[adjustedIndex] as TNode ||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
setPreviousOrParentTNode(tNode, true);
if (ngDevMode) {
// For performance reasons it is important that the tNode retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
// FIXME(misko): re-enable this once i18n code is compliant with this.
// Object.seal(tNode);
}
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
TProjectionNode & TIcuContainerNode;
}
@@ -665,44 +658,44 @@ export function createTView(
// that has a host binding, we will update the blueprint with that def's hostVars count.
const initialViewLength = bindingStartIndex + vars;
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
return blueprint[TVIEW as any] = ngDevMode ?
const tView = blueprint[TVIEW as any] = ngDevMode ?
new TViewConstructor(
type,
viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null!, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ?
directives() :
directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
consts, // consts: TConstants|null
false, // incompleteFirstPass: boolean
decls, // ngDevMode only: decls
vars, // ngDevMode only: vars
) :
type,
viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null!, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ?
directives() :
directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
consts, // consts: TConstants|null
false, // incompleteFirstPass: boolean
decls, // ngDevMode only: decls
vars, // ngDevMode only: vars
) :
{
type: type,
id: viewIndex,
@@ -736,6 +729,13 @@ export function createTView(
consts: consts,
incompleteFirstPass: false
};
if (ngDevMode) {
// For performance reasons it is important that the tView retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
Object.seal(tView);
}
return tView;
}
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
@@ -825,71 +825,79 @@ export function createTNode(
tagName: string|null, attrs: TAttributes|null): TNode {
ngDevMode && ngDevMode.tNode++;
let injectorIndex = tParent ? tParent.injectorIndex : -1;
return ngDevMode ? new TNodeDebug(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
-1, // directiveEnd: number
-1, // directiveStylingLast: number
null, // propertyBindings: number[]|null
0, // flags: TNodeFlags
0, // providerIndexes: TNodeProviderIndexes
tagName, // tagName: string|null
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
null, // mergedAttrs
null, // localNames: (string|number)[]|null
undefined, // initialInputs: (string[]|null)[]|null|undefined
null, // inputs: PropertyAliases|null
null, // outputs: PropertyAliases|null
null, // tViews: ITView|ITView[]|null
null, // next: ITNode|null
null, // projectionNext: ITNode|null
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // styles: string|null
null, // stylesWithoutHost: string|null
undefined, // residualStyles: string|null
null, // classes: string|null
null, // classesWithoutHost: string|null
undefined, // residualClasses: string|null
0 as any, // classBindings: TStylingRange;
0 as any, // styleBindings: TStylingRange;
) :
{
type: type,
index: adjustedIndex,
injectorIndex: injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
propertyBindings: null,
flags: 0,
providerIndexes: 0,
tagName: tagName,
attrs: attrs,
mergedAttrs: null,
localNames: null,
initialInputs: undefined,
inputs: null,
outputs: null,
tViews: null,
next: null,
projectionNext: null,
child: null,
parent: tParent,
projection: null,
styles: null,
stylesWithoutHost: null,
residualStyles: undefined,
classes: null,
classesWithoutHost: null,
residualClasses: undefined,
classBindings: 0 as any,
styleBindings: 0 as any,
};
const tNode = ngDevMode ?
new TNodeDebug(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
-1, // directiveEnd: number
-1, // directiveStylingLast: number
null, // propertyBindings: number[]|null
0, // flags: TNodeFlags
0, // providerIndexes: TNodeProviderIndexes
tagName, // tagName: string|null
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
null, // mergedAttrs
null, // localNames: (string|number)[]|null
undefined, // initialInputs: (string[]|null)[]|null|undefined
null, // inputs: PropertyAliases|null
null, // outputs: PropertyAliases|null
null, // tViews: ITView|ITView[]|null
null, // next: ITNode|null
null, // projectionNext: ITNode|null
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // styles: string|null
null, // stylesWithoutHost: string|null
undefined, // residualStyles: string|null
null, // classes: string|null
null, // classesWithoutHost: string|null
undefined, // residualClasses: string|null
0 as any, // classBindings: TStylingRange;
0 as any, // styleBindings: TStylingRange;
) :
{
type: type,
index: adjustedIndex,
injectorIndex: injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
propertyBindings: null,
flags: 0,
providerIndexes: 0,
tagName: tagName,
attrs: attrs,
mergedAttrs: null,
localNames: null,
initialInputs: undefined,
inputs: null,
outputs: null,
tViews: null,
next: null,
projectionNext: null,
child: null,
parent: tParent,
projection: null,
styles: null,
stylesWithoutHost: null,
residualStyles: undefined,
classes: null,
classesWithoutHost: null,
residualClasses: undefined,
classBindings: 0 as any,
styleBindings: 0 as any,
};
if (ngDevMode) {
// For performance reasons it is important that the tNode retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
Object.seal(tNode);
}
return tNode;
}
@@ -2040,7 +2048,7 @@ export function setInputsForProperty(
const index = inputs[i++] as number;
const privateName = inputs[i++] as string;
const instance = lView[index];
ngDevMode && assertDataInRange(lView, index);
ngDevMode && assertIndexInRange(lView, index);
const def = tView.data[index] as DirectiveDef<any>;
if (def.setInput !== null) {
def.setInput!(instance, value, publicName, privateName);
@@ -2055,7 +2063,7 @@ export function setInputsForProperty(
*/
export function textBindingInternal(lView: LView, index: number, value: string): void {
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
const element = getNativeByIndex(index, lView) as any as RText;
ngDevMode && assertDefined(element, 'native element should exist');
ngDevMode && ngDevMode.rendererSetText++;
@@ -5,11 +5,12 @@
* 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 {assertDataInRange, assertEqual} from '../../util/assert';
import {assertEqual, assertIndexInRange} from '../../util/assert';
import {TElementNode, TNodeType} from '../interfaces/node';
import {HEADER_OFFSET, RENDERER, T_HOST} from '../interfaces/view';
import {appendChild, createTextNode} from '../node_manipulation';
import {getBindingIndex, getLView, getTView, setPreviousOrParentTNode} from '../state';
import {getOrCreateTNode} from './shared';
@@ -31,7 +32,7 @@ export function ɵɵtext(index: number, value: string = ''): void {
assertEqual(
getBindingIndex(), tView.bindingStartIndex,
'text nodes should be created before any bindings');
ngDevMode && assertDataInRange(lView, adjustedIndex);
ngDevMode && assertIndexInRange(lView, adjustedIndex);
const tNode = tView.firstCreatePass ?
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, null, null) :
@@ -346,6 +346,14 @@ export interface TIcu {
*/
vars: number[];
/**
* Currently selected ICU case pointer.
*
* `lView[currentCaseLViewIndex]` stores the currently selected case. This is needed to know how
* to clean up the current case when transitioning no the new case.
*/
currentCaseLViewIndex: number;
/**
* An optional array of child/sub ICUs.
*
@@ -709,13 +709,6 @@ export interface TIcuContainerNode extends TNode {
parent: TElementNode|TElementContainerNode|null;
tViews: null;
projection: null;
/**
* Indicates the current active case for an ICU expression.
* It is null when there is no active case.
*
*/
// FIXME(misko): This is at a wrong location as activeCase is `LView` (not `TView`) concern
activeCaseIndex: number|null;
}
/** Static data for a view */
+2 -2
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDataInRange} from '../util/assert';
import {assertIndexInRange} from '../util/assert';
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
import {LView} from './interfaces/view';
import {getBindingRoot, getLView} from './state';
@@ -287,7 +287,7 @@ export function ɵɵpureFunctionV(
* it to `undefined`.
*/
function getPureFunctionReturnValue(lView: LView, returnValueIndex: number) {
ngDevMode && assertDataInRange(lView, returnValueIndex);
ngDevMode && assertIndexInRange(lView, returnValueIndex);
const lastReturnValue = lView[returnValueIndex];
return lastReturnValue === NO_CHANGE ? undefined : lastReturnValue;
}
+4 -4
View File
@@ -15,7 +15,7 @@ import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list';
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {assertDataInRange, assertDefined, throwError} from '../util/assert';
import {assertDefined, assertIndexInRange, throwError} from '../util/assert';
import {stringify} from '../util/stringify';
import {assertFirstCreatePass, assertLContainer} from './assert';
@@ -140,7 +140,7 @@ class TQueries_ implements TQueries {
}
getByIndex(index: number): TQuery {
ngDevMode && assertDataInRange(this.queries, index);
ngDevMode && assertIndexInRange(this.queries, index);
return this.queries[index];
}
@@ -358,7 +358,7 @@ function materializeViewResults<T>(
// null as a placeholder
result.push(null);
} else {
ngDevMode && assertDataInRange(tViewData, matchedNodeIdx);
ngDevMode && assertIndexInRange(tViewData, matchedNodeIdx);
const tNode = tViewData[matchedNodeIdx] as TNode;
result.push(createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read));
}
@@ -551,7 +551,7 @@ export function ɵɵloadQuery<T>(): QueryList<T> {
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
ngDevMode &&
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
ngDevMode && assertDataInRange(lView[QUERIES]!.queries, queryIndex);
ngDevMode && assertIndexInRange(lView[QUERIES]!.queries, queryIndex);
return lView[QUERIES]!.queries[queryIndex].queryList;
}
@@ -7,7 +7,7 @@
*/
import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils';
import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert';
import {assertEqual, assertIndexInRange, assertNotEqual} from '../../util/assert';
import {assertFirstUpdatePass} from '../assert';
import {TNode} from '../interfaces/node';
import {getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange, TStylingKey, TStylingKeyPrimitive, TStylingRange} from '../interfaces/styling';
@@ -370,7 +370,7 @@ function markDuplicates(
// - we are a map in which case we have to continue searching even after we find what we were
// looking for since we are a wild card and everything needs to be flipped to duplicate.
while (cursor !== 0 && (foundDuplicate === false || isMap)) {
ngDevMode && assertDataInRange(tData, cursor);
ngDevMode && assertIndexInRange(tData, cursor);
const tStylingValueAtCursor = tData[cursor] as TStylingKey;
const tStyleRangeAtCursor = tData[cursor + 1] as TStylingRange;
if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) {
+4 -4
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDataInRange, assertDefined, assertDomNode, assertGreaterThan, assertLessThan} from '../../util/assert';
import {assertDefined, assertDomNode, assertGreaterThan, assertIndexInRange, assertLessThan} from '../../util/assert';
import {assertTNodeForLView} from '../assert';
import {LContainer, TYPE} from '../interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
@@ -91,7 +91,7 @@ export function getNativeByIndex(index: number, lView: LView): RNode {
*/
export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertDataInRange(lView, tNode.index);
ngDevMode && assertIndexInRange(lView, tNode.index);
const node: RNode = unwrapRNode(lView[tNode.index]);
ngDevMode && !isProceduralRenderer(lView[RENDERER]) && assertDomNode(node);
return node;
@@ -125,13 +125,13 @@ export function getTNode(tView: TView, index: number): TNode {
/** Retrieves a value from any `LView` or `TData`. */
export function load<T>(view: LView|TData, index: number): T {
ngDevMode && assertDataInRange(view, index + HEADER_OFFSET);
ngDevMode && assertIndexInRange(view, index + HEADER_OFFSET);
return view[index + HEADER_OFFSET];
}
export function getComponentLViewByIndex(nodeIndex: number, hostView: LView): LView {
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
ngDevMode && assertDataInRange(hostView, nodeIndex);
ngDevMode && assertIndexInRange(hostView, nodeIndex);
const slotValue = hostView[nodeIndex];
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
return lView;
+1 -1
View File
@@ -110,7 +110,7 @@ export function assertDomNode(node: any): asserts node is Node {
}
export function assertDataInRange(arr: any[], index: number) {
export function assertIndexInRange(arr: any[], index: number) {
const maxLen = arr ? arr.length : 0;
assertLessThan(index, maxLen, `Index expected to be less than ${maxLen} but got ${index}`);
}
File diff suppressed because it is too large Load Diff
+56 -53
View File
@@ -261,7 +261,7 @@ describe('Runtime i18n', () => {
}, null, nbConsts, index) as TI18n;
expect(opCodes).toEqual({
vars: 5,
vars: 6,
update: debugMatch([
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
@@ -272,60 +272,61 @@ describe('Runtime i18n', () => {
]),
icus: [<TIcu>{
type: 1,
vars: [4, 3, 3],
currentCaseLViewIndex: 22,
vars: [5, 4, 4],
childIcus: [[], [], []],
cases: ['0', '1', 'other'],
create: [
debugMatch([
'lView[2] = document.createTextNode("no ")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("b")',
'lView[3] = document.createTextNode("no ")',
'(lView[1] as Element).appendChild(lView[3])',
'(lView[3] as Element).setAttribute("title", "none")',
'lView[4] = document.createTextNode("emails")',
'(lView[3] as Element).appendChild(lView[4])',
'lView[5] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[5])'
'lView[4] = document.createElement("b")',
'(lView[1] as Element).appendChild(lView[4])',
'(lView[4] as Element).setAttribute("title", "none")',
'lView[5] = document.createTextNode("emails")',
'(lView[4] as Element).appendChild(lView[5])',
'lView[6] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[6])',
]),
debugMatch([
'lView[2] = document.createTextNode("one ")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("i")',
'lView[3] = document.createTextNode("one ")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("email")',
'(lView[3] as Element).appendChild(lView[4])'
'lView[4] = document.createElement("i")',
'(lView[1] as Element).appendChild(lView[4])',
'lView[5] = document.createTextNode("email")',
'(lView[4] as Element).appendChild(lView[5])',
]),
debugMatch([
'lView[2] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("span")',
'lView[3] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("emails")',
'(lView[3] as Element).appendChild(lView[4])'
'lView[4] = document.createElement("span")',
'(lView[1] as Element).appendChild(lView[4])',
'lView[5] = document.createTextNode("emails")',
'(lView[4] as Element).appendChild(lView[5])',
])
],
remove: [
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
'(lView[0] as Element).remove(lView[5])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[6])',
]),
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
'(lView[0] as Element).remove(lView[5])',
'(lView[0] as Element).remove(lView[4])',
]),
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
'(lView[0] as Element).remove(lView[5])',
'(lView[0] as Element).remove(lView[4])',
])
],
update: [
debugMatch([]), debugMatch([]), debugMatch([
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { (lView[3] as Element).setAttribute(\'title\', `${lView[2]}`); }'
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { (lView[4] as Element).setAttribute(\'title\', `${lView[2]}`); }'
])
]
}]
@@ -355,7 +356,7 @@ describe('Runtime i18n', () => {
const nestedTIcuIndex = 0;
expect(opCodes).toEqual({
vars: 6,
vars: 9,
create: debugMatch([
'lView[1] = document.createComment("ICU 1")',
'(lView[0] as Element).appendChild(lView[1])'
@@ -367,27 +368,28 @@ describe('Runtime i18n', () => {
icus: [
{
type: 0,
vars: [1, 1, 1],
vars: [2, 2, 2],
currentCaseLViewIndex: 26,
childIcus: [[], [], []],
cases: ['cat', 'dog', 'other'],
create: [
debugMatch([
'lView[5] = document.createTextNode("cats")',
'(lView[3] as Element).appendChild(lView[5])'
'lView[7] = document.createTextNode("cats")',
'(lView[4] as Element).appendChild(lView[7])'
]),
debugMatch([
'lView[5] = document.createTextNode("dogs")',
'(lView[3] as Element).appendChild(lView[5])'
'lView[7] = document.createTextNode("dogs")',
'(lView[4] as Element).appendChild(lView[7])'
]),
debugMatch([
'lView[5] = document.createTextNode("animals")',
'(lView[3] as Element).appendChild(lView[5])'
'lView[7] = document.createTextNode("animals")',
'(lView[4] as Element).appendChild(lView[7])'
]),
],
remove: [
debugMatch(['(lView[0] as Element).remove(lView[5])']),
debugMatch(['(lView[0] as Element).remove(lView[5])']),
debugMatch(['(lView[0] as Element).remove(lView[5])'])
debugMatch(['(lView[0] as Element).remove(lView[7])']),
debugMatch(['(lView[0] as Element).remove(lView[7])']),
debugMatch(['(lView[0] as Element).remove(lView[7])'])
],
update: [
debugMatch([]),
@@ -397,36 +399,37 @@ describe('Runtime i18n', () => {
},
{
type: 1,
vars: [1, 4],
vars: [2, 6],
childIcus: [[], [0]],
currentCaseLViewIndex: 22,
cases: ['0', 'other'],
create: [
debugMatch([
'lView[2] = document.createTextNode("zero")',
'(lView[1] as Element).appendChild(lView[2])'
'lView[3] = document.createTextNode("zero")',
'(lView[1] as Element).appendChild(lView[3])'
]),
debugMatch([
'lView[2] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createComment("nested ICU 0")',
'lView[3] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[4])'
'lView[4] = document.createComment("nested ICU 0")',
'(lView[1] as Element).appendChild(lView[4])',
'lView[5] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[5])'
]),
],
remove: [
debugMatch(['(lView[0] as Element).remove(lView[2])']),
debugMatch(['(lView[0] as Element).remove(lView[3])']),
debugMatch([
'(lView[0] as Element).remove(lView[2])', '(lView[0] as Element).remove(lView[4])',
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[3])'
'(lView[0] as Element).remove(lView[3])', '(lView[0] as Element).remove(lView[5])',
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[4])'
]),
],
update: [
debugMatch([]),
debugMatch([
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { icuSwitchCase(lView[3] as Comment, 0, `${lView[2]}`); }',
'if (mask & 0b10) { icuUpdateCase(lView[3] as Comment, 0); }'
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { icuSwitchCase(lView[4] as Comment, 0, `${lView[2]}`); }',
'if (mask & 0b10) { icuUpdateCase(lView[4] as Comment, 0); }'
]),
]
}