From e44f69c3877bf5cbb99ce37a30982b598bd74f5e Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 20 Mar 2018 19:06:49 -0700 Subject: [PATCH] refactor(ivy): move dir flags to tnode (#22901) PR Close #22901 --- packages/core/src/render3/component.ts | 4 +- packages/core/src/render3/di.ts | 38 +++--- packages/core/src/render3/hooks.ts | 8 +- packages/core/src/render3/instructions.ts | 123 +++++++++--------- packages/core/src/render3/interfaces/node.ts | 51 ++++---- packages/core/src/render3/node_assert.ts | 21 ++- .../core/src/render3/node_manipulation.ts | 36 +++-- packages/core/src/render3/query.ts | 9 +- .../hello_world/bundle.golden_symbols.json | 6 + packages/core/test/render3/di_spec.ts | 4 +- .../render3/node_selector_matcher_spec.ts | 1 + packages/core/test/render3/render_util.ts | 4 +- 12 files changed, 150 insertions(+), 155 deletions(-) diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index c8802d80c3..bf41e043a6 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -16,7 +16,7 @@ import {assertComponentType, assertNotNull} from './assert'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, enterView, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode} from './interfaces/node'; +import {LElementNode, TNodeFlags} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {LView, LViewFlags, RootContext} from './interfaces/view'; import {stringify} from './util'; @@ -174,7 +174,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef): v // Root component is always created at dir index 1, after host element at 0 queueInitHooks(1, def.onInit, def.doCheck, elementNode.view.tView); - queueLifecycleHooks(elementNode.flags, elementNode.view); + queueLifecycleHooks(elementNode.tNode !.flags, elementNode.view); } /** diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index fa90cc5cdb..0bdb377204 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -22,7 +22,7 @@ import {assertLessThan, assertNotNull} from './assert'; import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView} from './interfaces/view'; @@ -274,7 +274,7 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { export function injectAttribute(attrName: string): string|undefined { ngDevMode && assertPreviousIsParent(); const lElement = getPreviousOrParentNode() as LElementNode; - ngDevMode && assertNodeType(lElement, LNodeFlags.Element); + ngDevMode && assertNodeType(lElement, LNodeType.Element); const tElement = lElement.tNode !; ngDevMode && assertNotNull(tElement, 'expecting tNode'); const attrs = tElement.attrs; @@ -302,7 +302,7 @@ export function getOrCreateChangeDetectorRef( if (currentNode.data === null) { // if data is null, this node is a regular element node (not a component) return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); - } else if ((currentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) { + } else if (currentNode.type === LNodeType.Element) { // if it's an element node with data, it's a component and context will be set later return di.changeDetectorRef = createViewRef(currentNode.data as LView, context); } @@ -316,10 +316,10 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): const hostInjector = hostNode.nodeInjector; const existingRef = hostInjector && hostInjector.changeDetectorRef; - return existingRef ? - existingRef : - createViewRef( - hostNode.data as LView, hostNode.view.data[hostNode.flags >> LNodeFlags.INDX_SHIFT]); + return existingRef ? existingRef : + createViewRef( + hostNode.data as LView, + hostNode.view.data[hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT]); } /** @@ -328,7 +328,7 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): * returns itself. */ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode { - while ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + while (node.type === LNodeType.View) { node = node.view.node; } return node as LElementNode; @@ -386,13 +386,13 @@ export function getOrCreateInjectable( // The size of the node's directive's list is stored in certain bits of the node's flags, // so exact it with a mask and shift it back such that the bits reflect the real value. - const flags = node.flags; - const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; + const flags = node.tNode !.flags; + const size = flags & TNodeFlags.SIZE_MASK; if (size !== 0) { // The start index of the directives list is also part of the node's flags, but there is // nothing to the "left" of it so it doesn't need a mask. - const start = flags >> LNodeFlags.INDX_SHIFT; + const start = flags >> TNodeFlags.INDX_SHIFT; const tData = node.view.tView.data; for (let i = start, ii = start + size; i < ii; i++) { @@ -510,10 +510,8 @@ export class ReadFromInjectorFn { * @returns The ElementRef instance to use */ export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef { - return di.elementRef || - (di.elementRef = new ElementRef( - ((di.node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container) ? null : - di.node.native)); + return di.elementRef || (di.elementRef = new ElementRef( + di.node.type === LNodeType.Container ? null : di.node.native)); } export const QUERY_READ_TEMPLATE_REF = >>( @@ -530,12 +528,12 @@ export const QUERY_READ_ELEMENT_REF = export const QUERY_READ_FROM_NODE = (new ReadFromInjectorFn((injector: LInjector, node: LNode, directiveIdx: number) => { - ngDevMode && assertNodeOfPossibleTypes(node, LNodeFlags.Container, LNodeFlags.Element); + ngDevMode && assertNodeOfPossibleTypes(node, LNodeType.Container, LNodeType.Element); if (directiveIdx > -1) { return node.view.data[directiveIdx]; - } else if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) { + } else if (node.type === LNodeType.Element) { return getOrCreateElementRef(injector); - } else if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container) { + } else if (node.type === LNodeType.Container) { return getOrCreateTemplateRef(injector); } throw new Error('fail'); @@ -613,7 +611,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { // Look for the parent node and increment its dynamic view count. if (this._node.parent !== null && this._node.parent.data !== null) { ngDevMode && - assertNodeOfPossibleTypes(this._node.parent, LNodeFlags.View, LNodeFlags.Element); + assertNodeOfPossibleTypes(this._node.parent, LNodeType.View, LNodeType.Element); this._node.parent.data.dynamicViewCount++; } } @@ -635,7 +633,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { * @returns The TemplateRef instance to use */ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { - ngDevMode && assertNodeType(di.node, LNodeFlags.Container); + ngDevMode && assertNodeType(di.node, LNodeType.Container); const data = (di.node as LContainerNode).data; return di.templateRef || (di.templateRef = new TemplateRef( getOrCreateElementRef(di), data.template !, getRenderer())); diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 6fbe505498..5171ad67be 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -7,7 +7,7 @@ */ import {DirectiveDef} from './interfaces/definition'; -import {LNodeFlags} from './interfaces/node'; +import {TNodeFlags} from './interfaces/node'; import {HookData, LView, LifecycleStage, TView} from './interfaces/view'; /** @@ -43,13 +43,13 @@ export function queueInitHooks( export function queueLifecycleHooks(flags: number, currentView: LView): void { const tView = currentView.tView; if (tView.firstTemplatePass === true) { - const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; - const start = flags >> LNodeFlags.INDX_SHIFT; + const start = flags >> TNodeFlags.INDX_SHIFT; + const end = start + (flags & TNodeFlags.SIZE_MASK); // It's necessary to loop through the directives at elementEnd() (rather than processing in // directiveCreate) so we can preserve the current hook order. Content, view, and destroy // hooks for projected components and directives must be called *before* their hosts. - for (let i = start, end = start + size; i < end; i++) { + for (let i = start; i < end; i++) { const def = (tView.data[i] as DirectiveDef); queueContentHooks(def, tView, i); queueViewHooks(def, tView, i); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 18422aabae..e048ff9064 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,7 +15,7 @@ import {CssSelector, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/pr import {LQueries} from './interfaces/query'; import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; -import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation'; import {matchingSelectorIndex} from './node_selector_matcher'; @@ -296,18 +296,18 @@ export function createLView( * keep the execution code monomorphic and fast. */ export function createLNode( - index: number | null, type: LNodeFlags.Element, native: RElement | RText | null, + index: number | null, type: LNodeType.Element, native: RElement | RText | null, lView?: LView | null): LElementNode; export function createLNode( - index: null, type: LNodeFlags.View, native: null, lView: LView): LViewNode; + index: null, type: LNodeType.View, native: null, lView: LView): LViewNode; export function createLNode( - index: number, type: LNodeFlags.Container, native: undefined, + index: number, type: LNodeType.Container, native: undefined, lContainer: LContainer): LContainerNode; export function createLNode( - index: number, type: LNodeFlags.Projection, native: null, + index: number, type: LNodeType.Projection, native: null, lProjection: LProjection): LProjectionNode; export function createLNode( - index: number | null, type: LNodeFlags, native: RText | RElement | null | undefined, + index: number | null, type: LNodeType, native: RText | RElement | null | undefined, state?: null | LView | LContainer | LProjection): LElementNode<extNode&LViewNode& LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : @@ -317,7 +317,7 @@ export function createLNode( parent && parent.queries && parent.queries.child(); const isState = state != null; const node: LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode = { - flags: type, + type: type, native: native as any, view: currentView, parent: parent as any, @@ -330,7 +330,7 @@ export function createLNode( pNextOrParent: null }; - if ((type & LNodeFlags.ViewOrElement) === LNodeFlags.ViewOrElement && isState) { + if ((type & LNodeType.ViewOrElement) === LNodeType.ViewOrElement && isState) { // Bit of a hack to bust through the readonly because there is a circular dep between // LView and LNode. ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized'); @@ -352,7 +352,7 @@ export function createLNode( if (isParent) { currentQueries = null; if (previousOrParentNode.view === currentView || - (previousOrParentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + previousOrParentNode.type === LNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. ngDevMode && assertNull( previousOrParentNode.child, @@ -399,7 +399,7 @@ export function renderTemplate( resetApplicationState(); rendererFactory = providedRendererFactory; host = createLNode( - null, LNodeFlags.Element, hostNode, + null, LNodeType.Element, hostNode, createLView( -1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template), null, {}, LViewFlags.CheckAlways)); @@ -422,7 +422,7 @@ export function renderEmbeddedTemplate( if (viewNode == null) { const view = createLView(-1, renderer, createTView(), template, context, LViewFlags.CheckAlways); - viewNode = createLNode(null, LNodeFlags.View, null, view); + viewNode = createLNode(null, LNodeType.View, null, view); cm = true; } enterView(viewNode.data, viewNode); @@ -521,7 +521,7 @@ export function elementStart( // Only component views should be added to the view tree directly. Embedded views are // accessed through their containers because they may be removed / re-added later. - node = createLNode(index, LNodeFlags.Element, native, componentView); + node = createLNode(index, LNodeType.Element, native, componentView); if (node.tNode == null) { const localNames: (string | number)[]|null = @@ -700,10 +700,12 @@ export function locateHostElement( */ export function hostElement(rNode: RElement | null, def: ComponentDef): LElementNode { resetApplicationState(); - return createLNode( - 0, LNodeFlags.Element, rNode, createLView( - -1, renderer, getOrCreateTView(def.template), null, null, - def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); + const node = createLNode( + 0, LNodeType.Element, rNode, createLView( + -1, renderer, getOrCreateTView(def.template), null, null, + def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); + if (firstTemplatePass) node.tNode = createTNode(def.tag, null, null, null); + return node; } @@ -740,7 +742,7 @@ export function listener( if (tNode.outputs === undefined) { // if we create TNode here, inputs must be undefined so we know they still need to be // checked - tNode.outputs = generatePropertyAliases(node.flags, BindingDirection.Output); + tNode.outputs = generatePropertyAliases(node.tNode !.flags, BindingDirection.Output); } const outputs = tNode.outputs; @@ -770,10 +772,10 @@ export function elementEnd() { ngDevMode && assertHasParent(); previousOrParentNode = previousOrParentNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Element); const queries = previousOrParentNode.queries; queries && queries.addNode(previousOrParentNode); - queueLifecycleHooks(previousOrParentNode.flags, currentView); + queueLifecycleHooks(previousOrParentNode.tNode !.flags, currentView); } /** @@ -823,7 +825,7 @@ export function elementProperty( // yet been checked if (tNode && tNode.inputs === undefined) { // mark inputs as checked - tNode.inputs = generatePropertyAliases(node.flags, BindingDirection.Input); + tNode.inputs = generatePropertyAliases(node.tNode !.flags, BindingDirection.Input); } const inputData = tNode && tNode.inputs; @@ -855,6 +857,7 @@ function createTNode( tagName: string | null, attrs: string[] | null, data: TContainer | null, localNames: (string | number)[] | null): TNode { return { + flags: 0, tagName: tagName, attrs: attrs, localNames: localNames, @@ -883,13 +886,13 @@ function setInputsForProperty(inputs: PropertyAliasValue, value: any): void { * @param Direction direction whether to consider inputs or outputs * @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise */ -function generatePropertyAliases(lNodeFlags: number, direction: BindingDirection): PropertyAliases| - null { - const size = (lNodeFlags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; +function generatePropertyAliases( + tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null { + const size = tNodeFlags & TNodeFlags.SIZE_MASK; let propStore: PropertyAliases|null = null; if (size > 0) { - const start = lNodeFlags >> LNodeFlags.INDX_SHIFT; + const start = tNodeFlags >> TNodeFlags.INDX_SHIFT; const isInput = direction === BindingDirection.Input; for (let i = start, ii = start + size; i < ii; i++) { @@ -1045,7 +1048,7 @@ export function text(index: number, value?: any): void { (isProceduralRenderer(renderer) ? renderer.createText(stringify(value)) : renderer.createTextNode(stringify(value))) : null; - const node = createLNode(index, LNodeFlags.Element, textNode); + const node = createLNode(index, LNodeType.Element, textNode); // Text nodes are self closing. isParent = false; appendChild(node.parent !, textNode, currentView); @@ -1101,7 +1104,7 @@ export function directiveCreate( ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode'); const tNode: TNode|null = previousOrParentNode.tNode !; - if (currentView.tView.firstTemplatePass && localNames) { + if (firstTemplatePass && localNames) { tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames; } @@ -1128,14 +1131,12 @@ export function baseDirectiveCreate( ngDevMode && assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings'); ngDevMode && assertPreviousIsParent(); - let flags = previousOrParentNode !.flags; - let size = flags & LNodeFlags.SIZE_MASK; - if (size === 0) { - flags = (index << LNodeFlags.INDX_SHIFT) | LNodeFlags.SIZE_SKIP | flags & LNodeFlags.TYPE_MASK; - } else { - flags += LNodeFlags.SIZE_SKIP; + + if (firstTemplatePass) { + const flags = previousOrParentNode.tNode !.flags; + previousOrParentNode.tNode !.flags = + (flags & TNodeFlags.SIZE_MASK) === 0 ? (index << TNodeFlags.INDX_SHIFT) | 1 : flags + 1; } - previousOrParentNode !.flags = flags; ngDevMode && assertDataInRange(index - 1); Object.defineProperty( @@ -1152,8 +1153,7 @@ export function baseDirectiveCreate( diPublic(directiveDef !); } - if (directiveDef !.attributes != null && - (previousOrParentNode.flags & LNodeFlags.TYPE_MASK) == LNodeFlags.Element) { + if (directiveDef !.attributes != null && previousOrParentNode.type == LNodeType.Element) { setUpAttributes( (previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]); } @@ -1169,8 +1169,7 @@ export function baseDirectiveCreate( * @param tNode The static data for this node */ function setInputsFromAttrs(instance: T, inputs: {[key: string]: string}, tNode: TNode): void { - const directiveIndex = - ((previousOrParentNode.flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT) - 1; + const directiveIndex = (previousOrParentNode.tNode !.flags & TNodeFlags.SIZE_MASK) - 1; let initialInputData = tNode.initialInputs as InitialInputData | undefined; if (initialInputData === undefined || directiveIndex >= initialInputData.length) { @@ -1257,7 +1256,7 @@ export function container( queries: null }; - const node = createLNode(index, LNodeFlags.Container, undefined, lContainer); + const node = createLNode(index, LNodeType.Container, undefined, lContainer); if (node.tNode == null) { const localNames: (string | number)[]|null = findMatchingLocalNames(null, localRefs, -1, ''); @@ -1270,7 +1269,7 @@ export function container( hack_declareDirectives(index, index, directiveTypes, localRefs); isParent = false; - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); const queries = node.queries; if (queries) { // check if a given container node matches @@ -1288,7 +1287,7 @@ export function container( export function containerRefreshStart(index: number): void { ngDevMode && assertDataInRange(index); previousOrParentNode = data[index] as LNode; - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); isParent = true; (previousOrParentNode as LContainerNode).data.nextIndex = 0; ngDevMode && assertSame( @@ -1311,14 +1310,14 @@ export function containerRefreshEnd(): void { if (isParent) { isParent = false; } else { - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); ngDevMode && assertHasParent(); previousOrParentNode = previousOrParentNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); const container = previousOrParentNode as LContainerNode; container.native = undefined; - ngDevMode && assertNodeType(container, LNodeFlags.Container); + ngDevMode && assertNodeType(container, LNodeType.Container); const nextIndex = container.data.nextIndex; // remove extra views at the end of the container @@ -1377,13 +1376,13 @@ function scanForView( export function embeddedViewStart(viewBlockId: number): boolean { const container = (isParent ? previousOrParentNode : previousOrParentNode.parent !) as LContainerNode; - ngDevMode && assertNodeType(container, LNodeFlags.Container); + ngDevMode && assertNodeType(container, LNodeType.Container); const lContainer = container.data; const existingViewNode = scanForView(container, lContainer.nextIndex, viewBlockId); if (existingViewNode) { previousOrParentNode = existingViewNode; - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); isParent = true; enterView((existingViewNode as LViewNode).data, existingViewNode as LViewNode); } else { @@ -1395,7 +1394,7 @@ export function embeddedViewStart(viewBlockId: number): boolean { newView.queries = lContainer.queries.enterView(lContainer.nextIndex); } - enterView(newView, createLNode(null, LNodeFlags.View, null, newView)); + enterView(newView, createLNode(null, LNodeType.View, null, newView)); } return !existingViewNode; @@ -1414,7 +1413,7 @@ export function embeddedViewStart(viewBlockId: number): boolean { * @returns TView */ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TView { - ngDevMode && assertNodeType(parent, LNodeFlags.Container); + ngDevMode && assertNodeType(parent, LNodeType.Container); const tContainer = (parent !.tNode as TContainerNode).data; if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) { tContainer[viewIndex] = createTView(); @@ -1429,8 +1428,8 @@ export function embeddedViewEnd(): void { const viewNode = previousOrParentNode = currentView.node as LViewNode; const containerNode = previousOrParentNode.parent as LContainerNode; if (containerNode) { - ngDevMode && assertNodeType(viewNode, LNodeFlags.View); - ngDevMode && assertNodeType(containerNode, LNodeFlags.Container); + ngDevMode && assertNodeType(viewNode, LNodeType.View); + ngDevMode && assertNodeType(containerNode, LNodeType.Container); const lContainer = containerNode.data; if (creationMode) { @@ -1442,7 +1441,7 @@ export function embeddedViewEnd(): void { } leaveView(currentView !.parent !); ngDevMode && assertEqual(isParent, false, 'isParent'); - ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View); + ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); } ///////////// @@ -1456,14 +1455,16 @@ export function embeddedViewEnd(): void { export function componentRefresh(directiveIndex: number, elementIndex: number): void { ngDevMode && assertDataInRange(elementIndex); const element = data ![elementIndex] as LElementNode; - ngDevMode && assertNodeType(element, LNodeFlags.Element); + ngDevMode && assertNodeType(element, LNodeType.Element); ngDevMode && assertNotNull(element.data, `Component's host node should have an LView attached.`); const hostView = element.data !; // Only attached CheckAlways components or attached, dirty OnPush components should be checked if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { ngDevMode && assertDataInRange(directiveIndex); - detectChangesInternal(hostView, element, getDirectiveInstance(data[directiveIndex])); + const template = (tData[directiveIndex] as ComponentDef).template; + detectChangesInternal( + hostView, element, template, getDirectiveInstance(data[directiveIndex])); } } @@ -1561,7 +1562,7 @@ function appendToProjectionNode( */ export function projection( nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void { - const node = createLNode(nodeIndex, LNodeFlags.Projection, null, {head: null, tail: null}); + const node = createLNode(nodeIndex, LNodeType.Projection, null, {head: null, tail: null}); if (node.tNode == null) { node.tNode = createTNode(null, attrs || null, null, null); @@ -1579,7 +1580,7 @@ export function projection( // build the linked list of projected nodes: for (let i = 0; i < nodesForSelector.length; i++) { const nodeToProject = nodesForSelector[i]; - if ((nodeToProject.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Projection) { + if (nodeToProject.type === LNodeType.Projection) { const previouslyProjected = (nodeToProject as LProjectionNode).data; appendToProjectionNode(node, previouslyProjected.head, previouslyProjected.tail); } else { @@ -1609,13 +1610,13 @@ export function projection( */ function findComponentHost(lView: LView): LElementNode { let viewRootLNode = lView.node; - while ((viewRootLNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + while (viewRootLNode.type === LNodeType.View) { ngDevMode && assertNotNull(lView.parent, 'lView.parent'); lView = lView.parent !; viewRootLNode = lView.node; } - ngDevMode && assertNodeType(viewRootLNode, LNodeFlags.Element); + ngDevMode && assertNodeType(viewRootLNode, LNodeType.Element); ngDevMode && assertNotNull(viewRootLNode.data, 'node.data'); return viewRootLNode as LElementNode; @@ -1768,7 +1769,9 @@ export function getRootView(component: any): LView { export function detectChanges(component: T): void { const hostNode = _getComponentHostLElementNode(component); ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView'); - detectChangesInternal(hostNode.data as LView, hostNode, component); + const componentIndex = hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT; + const template = (hostNode.view.tView.data[componentIndex] as ComponentDef).template; + detectChangesInternal(hostNode.data as LView, hostNode, template, component); } @@ -1804,10 +1807,8 @@ function throwErrorIfNoChangesMode(oldValue: any, currValue: any): never|void { /** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */ -function detectChangesInternal(hostView: LView, hostNode: LElementNode, component: T) { - const componentIndex = hostNode.flags >> LNodeFlags.INDX_SHIFT; - const template = (hostNode.view.tView.data[componentIndex] as ComponentDef).template; - +export function detectChangesInternal( + hostView: LView, hostNode: LElementNode, template: ComponentTemplate, component: T) { const oldView = enterView(hostView, hostNode); try { template(component, creationMode); diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index edb208fec8..95bce4163d 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -16,31 +16,24 @@ import {LView, TData, TView} from './view'; /** - * LNodeFlags corresponds to the LNode.flags property. It contains information - * on how to map a particular set of bits in LNode.flags to the node type, directive - * count, or directive starting index. - * - * For example, if you wanted to check the type of a certain node, you would mask - * node.flags with TYPE_MASK and compare it to the value for a certain node type. e.g: - * - *```ts - * if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {...} - *``` + * LNodeType corresponds to the LNode.type property. It contains information + * on how to map a particular set of bits in LNode.flags to the node type. */ -export const enum LNodeFlags { +export const enum LNodeType { Container = 0b00, Projection = 0b01, View = 0b10, Element = 0b11, ViewOrElement = 0b10, - SIZE_SKIP = 0b100, - SIZE_SHIFT = 2, - INDX_SHIFT = 12, - TYPE_MASK = 0b00000000000000000000000000000011, - SIZE_MASK = 0b00000000000000000000111111111100, - INDX_MASK = 0b11111111111111111111000000000000 } +/** + * TNodeFlags corresponds to the TNode.flags property. It contains information + * on how to map a particular set of bits to the node's first directive index + * (with INDX_SHIFT) or the node's directive count (with SIZE_MASK) + */ +export const enum TNodeFlags {INDX_SHIFT = 12, SIZE_MASK = 0b00000000000000000000111111111111} + /** * LNode is an internal data structure which is used for the incremental DOM algorithm. * The "L" stands for "Logical" to differentiate between `RNodes` (actual rendered DOM @@ -58,17 +51,8 @@ export const enum LNodeFlags { * instructions. */ export interface LNode { - /** - * This number stores three values using its bits: - * - * - the type of the node (first 2 bits) - * - the number of directives on that node (next 10 bits) - * - the starting index of the node's directives in the directives array (last 20 bits). - * - * The latter two values are necessary so DI can effectively search the directives associated - * with a node without searching the whole directives array. - */ - flags: LNodeFlags; + /** The type of the node (see LNodeFlags) */ + type: LNodeType; /** * The associated DOM node. Storing this allows us to: @@ -217,6 +201,17 @@ export interface LProjectionNode extends LNode { * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern */ export interface TNode { + /** + * This number stores two values using its bits: + * + * - the number of directives on that node (first 12 bits) + * - the starting index of the node's directives in the directives array (last 20 bits). + * + * These two values are necessary so DI can effectively search the directives associated + * with a node without searching the whole directives array. + */ + flags: TNodeFlags; + /** The tag name associated with this node. */ tagName: string|null; diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index b73cc06f51..b6200a4ad8 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -7,24 +7,23 @@ */ import {assertEqual, assertNotNull} from './assert'; -import {LNode, LNodeFlags} from './interfaces/node'; +import {LNode, LNodeType} from './interfaces/node'; -export function assertNodeType(node: LNode, type: LNodeFlags) { +export function assertNodeType(node: LNode, type: LNodeType) { assertNotNull(node, 'should be called with a node'); - assertEqual(node.flags & LNodeFlags.TYPE_MASK, type, `should be a ${typeName(type)}`); + assertEqual(node.type, type, `should be a ${typeName(type)}`); } -export function assertNodeOfPossibleTypes(node: LNode, ...types: LNodeFlags[]) { +export function assertNodeOfPossibleTypes(node: LNode, ...types: LNodeType[]) { assertNotNull(node, 'should be called with a node'); - const nodeType = node.flags & LNodeFlags.TYPE_MASK; - const found = types.some(type => nodeType === type); + const found = types.some(type => node.type === type); assertEqual(found, true, `Should be one of ${types.map(typeName).join(', ')}`); } -function typeName(type: LNodeFlags): string { - if (type == LNodeFlags.Projection) return 'Projection'; - if (type == LNodeFlags.Container) return 'Container'; - if (type == LNodeFlags.View) return 'View'; - if (type == LNodeFlags.Element) return 'Element'; +function typeName(type: LNodeType): string { + if (type == LNodeType.Projection) return 'Projection'; + if (type == LNodeType.Container) return 'Container'; + if (type == LNodeType.View) return 'View'; + if (type == LNodeType.Element) return 'Element'; return ''; } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 6a71ecaadf..64401bf520 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -9,7 +9,7 @@ import {assertNotNull} from './assert'; import {callHooks} from './hooks'; import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LNodeType, LProjectionNode, LTextNode, LViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RElement, RNode, RText, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; @@ -34,8 +34,7 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem while (currentNode && currentNode !== stopNode) { let pNextOrParent = currentNode.pNextOrParent; if (pNextOrParent) { - let pNextOrParentType = pNextOrParent.flags & LNodeFlags.TYPE_MASK; - while (pNextOrParentType !== LNodeFlags.Projection) { + while (pNextOrParent.type !== LNodeType.Projection) { const nativeNode = findFirstRNode(pNextOrParent); if (nativeNode) { return nativeNode; @@ -55,8 +54,8 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem const parentNode = currentNode.parent; currentNode = null; if (parentNode) { - const parentType = parentNode.flags & LNodeFlags.TYPE_MASK; - if (parentType === LNodeFlags.Container || parentType === LNodeFlags.View) { + const parentType = parentNode.type; + if (parentType === LNodeType.Container || parentType === LNodeType.View) { currentNode = parentNode; } } @@ -77,8 +76,7 @@ function getNextLNodeWithProjection(node: LNode): LNode|null { if (pNextOrParent) { // The node is projected - const isLastProjectedNode = - (pNextOrParent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Projection; + const isLastProjectedNode = pNextOrParent.type === LNodeType.Projection; // returns pNextOrParent if we are not at the end of the list, null otherwise return isLastProjectedNode ? null : pNextOrParent; } @@ -122,16 +120,15 @@ function getNextOrParentSiblingNode(initialNode: LNode, rootNode: LNode): LNode| function findFirstRNode(rootNode: LNode): RElement|RText|null { let node: LNode|null = rootNode; while (node) { - const type = node.flags & LNodeFlags.TYPE_MASK; let nextNode: LNode|null = null; - if (type === LNodeFlags.Element) { + if (node.type === LNodeType.Element) { // A LElementNode has a matching RNode in LElementNode.native return (node as LElementNode).native; - } else if (type === LNodeFlags.Container) { + } else if (node.type === LNodeType.Container) { // For container look at the first node of the view next const childContainerData: LContainer = (node as LContainerNode).data; nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; - } else if (type === LNodeFlags.Projection) { + } else if (node.type === LNodeType.Projection) { // For Projection look at the first projected node nextNode = (node as LProjectionNode).data.head; } else { @@ -164,17 +161,16 @@ export function addRemoveViewFromContainer( export function addRemoveViewFromContainer( container: LContainerNode, rootNode: LViewNode, insertMode: boolean, beforeNode?: RNode | null): void { - ngDevMode && assertNodeType(container, LNodeFlags.Container); - ngDevMode && assertNodeType(rootNode, LNodeFlags.View); + ngDevMode && assertNodeType(container, LNodeType.Container); + ngDevMode && assertNodeType(rootNode, LNodeType.View); const parentNode = container.data.renderParent; const parent = parentNode ? parentNode.native : null; let node: LNode|null = rootNode.child; if (parent) { while (node) { - const type = node.flags & LNodeFlags.TYPE_MASK; let nextNode: LNode|null = null; const renderer = container.view.renderer; - if (type === LNodeFlags.Element) { + if (node.type === LNodeType.Element) { if (insertMode) { isProceduralRenderer(renderer) ? renderer.insertBefore(parent, node.native !, beforeNode as RNode | null) : @@ -184,13 +180,13 @@ export function addRemoveViewFromContainer( parent.removeChild(node.native !); } nextNode = node.next; - } else if (type === LNodeFlags.Container) { + } else if (node.type === LNodeType.Container) { // if we get to a container, it must be a root node of a view because we are only // propagating down into child views / containers and not child elements const childContainerData: LContainer = (node as LContainerNode).data; childContainerData.renderParent = parentNode; nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; - } else if (type === LNodeFlags.Projection) { + } else if (node.type === LNodeType.Projection) { nextNode = (node as LProjectionNode).data.head; } else { nextNode = (node as LViewNode).child; @@ -347,7 +343,7 @@ export function setViewNext(view: LViewNode, next: LViewNode | null): void { */ export function getParentState(state: LViewOrLContainer, rootView: LView): LViewOrLContainer|null { let node; - if ((node = (state as LView) !.node) && (node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + if ((node = (state as LView) !.node) && node.type === LNodeType.View) { // if it's an embedded view, the state needs to go up to the container, in case the // container has a next return node.parent !.data as any; @@ -410,7 +406,7 @@ function executeOnDestroys(view: LView): void { * @return boolean Whether the child element should be inserted. */ export function canInsertNativeNode(parent: LNode, currentView: LView): boolean { - const parentIsElement = (parent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element; + const parentIsElement = parent.type === LNodeType.Element; return parentIsElement && (parent.view !== currentView || parent.data === null /* Regular Element. */); @@ -467,7 +463,7 @@ export function insertChild(node: LNode, currentView: LView): void { export function appendProjectedNode( node: LElementNode | LTextNode | LContainerNode, currentParent: LViewNode | LElementNode, currentView: LView): void { - if ((node.flags & LNodeFlags.TYPE_MASK) !== LNodeFlags.Container) { + if (node.type !== LNodeType.Container) { appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView); } else if (canInsertNativeNode(currentParent, currentView)) { // The node we are adding is a Container and we are adding it to Element which diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 5ca0b05f51..0481589dc3 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -20,7 +20,7 @@ import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di'; import {assertPreviousIsParent, getCurrentQueries, store} from './instructions'; import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'; import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; -import {LContainerNode, LElementNode, LNode, LNodeFlags, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, TNode, TNodeFlags, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LQueries, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {flatten} from './util'; @@ -195,10 +195,9 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { */ function geIdxOfMatchingDirective(node: LNode, type: Type): number|null { const tData = node.view.tView.data; - const flags = node.flags; - for (let i = flags >> LNodeFlags.INDX_SHIFT, - ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT); - i < ii; i++) { + const flags = node.tNode !.flags; + for (let i = flags >> TNodeFlags.INDX_SHIFT, ii = i + (flags & TNodeFlags.SIZE_MASK); i < ii; + i++) { const def = tData[i] as DirectiveDef; if (def.diPublic && def.type === type) { return i; 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 a8565e07c5..05fda153fd 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -128,6 +128,9 @@ { "name": "createLView" }, + { + "name": "createTNode" + }, { "name": "createTView" }, @@ -164,6 +167,9 @@ { "name": "executeInitHooks" }, + { + "name": "firstTemplatePass" + }, { "name": "flattenUnsubscriptionErrors" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index c6498a0cc4..6e1ecc4730 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -13,7 +13,7 @@ import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjecto import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; -import {LNodeFlags} from '../../src/render3/interfaces/node'; +import {LNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; import {ViewRef} from '../../src/render3/view_ref'; @@ -623,7 +623,7 @@ describe('di', () => { createLView(-1, null !, createTView(), null, null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { - const parent = createLNode(0, LNodeFlags.Element, null, null); + const parent = createLNode(0, LNodeType.Element, null, null); // Simulate the situation where the previous parent is not initialized. // This happens on first bootstrap because we don't init existing values diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index f26d19b4e2..06afeacc72 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -12,6 +12,7 @@ import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorWit function testLStaticData(tagName: string, attrs: string[] | null): TNode { return { + flags: 0, tagName, attrs, localNames: null, diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index d2f9511e95..710b948b13 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -10,8 +10,8 @@ import {stringifyElement} from '@angular/platform-browser/testing/src/browser_ut import {CreateComponentOptions} from '../../src/render3/component'; import {ComponentTemplate, ComponentType, DirectiveType, PublicFeature, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; -import {NG_HOST_SYMBOL, createLNode, createLView, renderTemplate} from '../../src/render3/instructions'; -import {LElementNode, LNodeFlags} from '../../src/render3/interfaces/node'; +import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions'; +import {LElementNode} from '../../src/render3/interfaces/node'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {getRendererFactory2} from './imported_renderer2';