refactor(ivy): replace pNextOrParent with TNode props (#24752)

PR Close #24752
This commit is contained in:
Kara Erickson
2018-07-03 20:04:36 -07:00
committed by Miško Hevery
parent dc1f1295ee
commit 3a19f70d1c
17 changed files with 618 additions and 495 deletions

View File

@ -9,7 +9,7 @@
import {assertEqual, assertLessThan} from './assert';
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions';
import {RENDER_PARENT} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, TContainerNode, TNodeType} from './interfaces/node';
import {LContainerNode, LElementNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node';
import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
import {stringify} from './util';
@ -243,14 +243,17 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) {
// On first pass, re-organize node tree to put this node in the correct position.
const firstTemplatePass = node.view[TVIEW].firstTemplatePass;
if (firstTemplatePass) {
node.tNode.next = null;
if (previousNode === parentNode && node.tNode !== parentNode.tNode.child) {
node.tNode.next = parentNode.tNode.child;
parentNode.tNode.child = node.tNode;
} else if (previousNode !== parentNode && node.tNode !== previousNode.tNode.next) {
node.tNode.next = previousNode.tNode.next;
previousNode.tNode.next = node.tNode;
} else {
node.tNode.next = null;
}
if (parentNode.view === node.view) node.tNode.parent = parentNode.tNode as TElementNode;
}
// Template containers also have a comment node for the `ViewContainerRef` that should be moved

View File

@ -14,20 +14,20 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyles as renderElementStyles, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp} from './styling';
import {isDifferent, stringify} from './util';
import {ViewRef} from './view_ref';
import {StylingContext, allocStylingContext, createStylingContextTemplate, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp, renderStyles as renderElementStyles} from './styling';
/**
* Directive (D) sets a property on all component instances using this constant as a key and the
@ -335,7 +335,6 @@ export function createLNodeObject(
data: state,
queries: queries,
tNode: null !,
pNextOrParent: null,
dynamicLContainerNode: null
};
}
@ -363,11 +362,11 @@ export function createLNode(
attrs: TAttributes | null, lContainer: LContainer): LContainerNode;
export function createLNode(
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
lProjection: LProjection): LProjectionNode;
lProjection: null): LProjectionNode;
export function createLNode(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null, state?: null | LViewData | LContainer | LProjection): LElementNode&
LTextNode&LViewNode&LContainerNode&LProjectionNode {
attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode&LTextNode&
LViewNode&LContainerNode&LProjectionNode {
const parent = isParent ? previousOrParentNode :
previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode;
// Parents cannot cross component boundaries because components will be used in multiple places,
@ -1189,7 +1188,8 @@ export function createTNode(
parent: parent,
dynamicContainerNode: null,
detached: null,
stylingTemplate: null
stylingTemplate: null,
projection: null
};
}
@ -1948,138 +1948,109 @@ export function viewAttached(view: LViewData): boolean {
* @param selectors A collection of parsed CSS selectors
* @param rawSelectors A collection of CSS selectors in the raw, un-parsed form
*/
export function projectionDef(
index: number, selectors?: CssSelectorList[], textSelectors?: string[]): void {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
const distributedNodes = new Array<LNode[]>(noOfNodeBuckets);
for (let i = 0; i < noOfNodeBuckets; i++) {
distributedNodes[i] = [];
}
export function projectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void {
const componentNode: LElementNode = findComponentHost(viewData);
let componentChild = getChildLNode(componentNode);
while (componentChild !== null) {
// execute selector matching logic if and only if:
// - there are selectors defined
// - a node has a tag name / attributes that can be matched
const bucketIndex =
selectors ? matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !) : 0;
distributedNodes[bucketIndex].push(componentChild);
if (!componentNode.tNode.projection) {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
const pData: (TNode | null)[] = componentNode.tNode.projection =
new Array(noOfNodeBuckets).fill(null);
const tails: (TNode | null)[] = pData.slice();
componentChild = getNextLNode(componentChild);
let componentChild = componentNode.tNode.child;
while (componentChild !== null) {
const bucketIndex =
selectors ? matchingSelectorIndex(componentChild, selectors, textSelectors !) : 0;
const nextNode = componentChild.next;
if (tails[bucketIndex]) {
tails[bucketIndex] !.next = componentChild;
} else {
pData[bucketIndex] = componentChild;
componentChild.next = null;
}
tails[bucketIndex] = componentChild;
componentChild = nextNode;
}
}
ngDevMode && assertDataNext(index + HEADER_OFFSET);
store(index, distributedNodes);
}
/**
* Updates the linked list of a projection node, by appending another linked list.
* Stack used to keep track of projection nodes in projection() instruction.
*
* @param projectionNode Projection node whose projected nodes linked list has to be updated
* @param appendedFirst First node of the linked list to append.
* @param appendedLast Last node of the linked list to append.
* This is deliberately created outside of projection() to avoid allocating
* a new array each time the function is called. Instead the array will be
* re-used by each invocation. This works because the function is not reentrant.
*/
function addToProjectionList(
projectionNode: LProjectionNode,
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
ngDevMode && assertEqual(
!!appendedFirst, !!appendedLast,
'appendedFirst can be null if and only if appendedLast is also null');
if (!appendedLast) {
// nothing to append
return;
}
const projectionNodeData = projectionNode.data;
if (projectionNodeData.tail) {
projectionNodeData.tail.pNextOrParent = appendedFirst;
} else {
projectionNodeData.head = appendedFirst;
}
projectionNodeData.tail = appendedLast;
appendedLast.pNextOrParent = projectionNode;
}
const projectionNodeStack: LProjectionNode[] = [];
/**
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
* to the projectionDef instruction.
*
* @param nodeIndex
* @param localIndex - index under which distribution of projected nodes was memorized
* @param selectorIndex:
* - 0 when the selector is `*` (or unspecified as this is the default value),
* - 1 based index of the selector from the {@link projectionDef}
*/
export function projection(
nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
const node = createLNode(
nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null});
export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
const node = createLNode(nodeIndex, TNodeType.Projection, null, null, attrs || null, null);
// We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
if (node.tNode.projection === null) node.tNode.projection = selectorIndex;
// `<ng-content>` has no content
isParent = false;
// re-distribution of projectable nodes is memorized on a component's view level
const componentNode = findComponentHost(viewData);
const componentLView = componentNode.data as LViewData;
const distributedNodes = loadInternal(localIndex, componentLView) as Array<LNode[]>;
const nodesForSelector = distributedNodes[selectorIndex];
// re-distribution of projectable nodes is stored on a component's view level
const parent = getParentLNode(node);
const currentParent = getParentLNode(node);
const canInsert = canInsertNativeNode(currentParent, viewData);
let grandparent: LContainerNode;
const renderParent = currentParent.tNode.type === TNodeType.View ?
(grandparent = getParentLNode(currentParent) as LContainerNode) &&
grandparent.data[RENDER_PARENT] ! :
currentParent as LElementNode;
if (canInsertNativeNode(parent, viewData)) {
const componentNode = findComponentHost(viewData);
let nodeToProject = (componentNode.tNode.projection as(TNode | null)[])[selectorIndex];
let projectedView = componentNode.view;
let projectionNodeIndex = -1;
let grandparent: LContainerNode;
const renderParent = parent.tNode.type === TNodeType.View ?
(grandparent = getParentLNode(parent) as LContainerNode) &&
grandparent.data[RENDER_PARENT] ! :
parent as LElementNode;
for (let i = 0; i < nodesForSelector.length; i++) {
const nodeToProject = nodesForSelector[i];
let head = nodeToProject as LTextNode | LElementNode | LContainerNode | null;
let tail = nodeToProject as LTextNode | LElementNode | LContainerNode | null;
while (nodeToProject) {
if (nodeToProject.type === TNodeType.Projection) {
// This node is re-projected, so we must go up the tree to get its projected nodes.
const currentComponentHost = findComponentHost(projectedView);
const firstProjectedNode = (currentComponentHost.tNode.projection as(
TNode | null)[])[nodeToProject.projection as number];
if (nodeToProject.tNode.type === TNodeType.Projection) {
const previouslyProjected = (nodeToProject as LProjectionNode).data;
head = previouslyProjected.head;
tail = previouslyProjected.tail;
}
addToProjectionList(node, head, tail);
if (canInsert) {
let currentNode: LNode|null = head;
while (currentNode) {
if (firstProjectedNode) {
projectionNodeStack[++projectionNodeIndex] = projectedView[nodeToProject.index];
nodeToProject = firstProjectedNode;
projectedView = currentComponentHost.view;
continue;
}
} else {
const lNode = projectedView[nodeToProject.index];
lNode.tNode.flags |= TNodeFlags.isProjected;
appendProjectedNode(
currentNode as LTextNode | LElementNode | LContainerNode, currentParent, viewData,
renderParent);
currentNode = currentNode === tail ? null : currentNode.pNextOrParent;
lNode as LTextNode | LElementNode | LContainerNode, parent, viewData, renderParent);
}
// If we are finished with a list of re-projected nodes, we need to get
// back to the root projection node that was re-projected.
if (nodeToProject.next === null && projectedView !== componentNode.view) {
// move down into the view of the component we're projecting right now
const lNode = projectionNodeStack[projectionNodeIndex--];
nodeToProject = lNode.tNode;
projectedView = lNode.view;
}
nodeToProject = nodeToProject.next;
}
}
}
/**
* Given a current view, finds the nearest component's host (LElement).
*
* @param lViewData LViewData for which we want a host element node
* @returns The host node
*/
function findComponentHost(lViewData: LViewData): LElementNode {
let viewRootLNode = lViewData[HOST_NODE];
while (viewRootLNode.tNode.type === TNodeType.View) {
ngDevMode && assertDefined(lViewData[PARENT], 'lViewData.parent');
lViewData = lViewData[PARENT] !;
viewRootLNode = lViewData[HOST_NODE];
}
ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');
return viewRootLNode as LElementNode;
}
/**
* Adds LViewData or LContainer to the end of the current view tree.
*

View File

@ -10,7 +10,6 @@ import {StylingContext} from '../styling';
import {LContainer} from './container';
import {LInjector} from './injector';
import {LProjection} from './projection';
import {LQueries} from './query';
import {RComment, RElement, RText} from './renderer';
import {LViewData, TView} from './view';
@ -36,11 +35,14 @@ export const enum TNodeFlags {
/** The number of directives on this node is encoded on the least significant bits */
DirectiveCountMask = 0b00000000000000000000111111111111,
/** Then this bit is set when the node is a component */
isComponent = 0b1000000000000,
/** This bit is set if the node is a component */
isComponent = 0b00000000000000000001000000000000,
/** This bit is set if the node has been projected */
isProjected = 0b00000000000000000010000000000000,
/** The index of the first directive on this node is encoded on the most significant bits */
DirectiveStartingIndexShift = 13,
DirectiveStartingIndexShift = 14,
}
/**
@ -74,7 +76,7 @@ export interface LNode {
* If LContainerNode, then `data` contains LContainer.
* If LProjectionNode, then `data` contains LProjection.
*/
readonly data: LViewData|LContainer|LProjection|null;
readonly data: LViewData|LContainer|null;
/**
@ -94,14 +96,6 @@ export interface LNode {
*/
queries: LQueries|null;
/**
* If this node is projected, pointer to the next node in the same projection parent
* (which is a container, an element, or a text node), or to the parent projection node
* if this is the last node in the projection.
* If this node is not projected, this field is null.
*/
pNextOrParent: LNode|null;
/**
* Pointer to the corresponding TNode object, which stores static
* data about this node.
@ -156,7 +150,7 @@ export interface LContainerNode extends LNode {
export interface LProjectionNode extends LNode {
readonly native: null;
readonly data: LProjection;
readonly data: null;
dynamicLContainerNode: null;
}
@ -345,6 +339,43 @@ export interface TNode {
detached: boolean|null;
stylingTemplate: StylingContext|null;
/**
* List of projected TNodes for a given component host element OR index into the said nodes.
*
* For easier discussion assume this example:
* `<parent>`'s view definition:
* ```
* <child id="c1">content1</child>
* <child id="c2"><span>content2</span></child>
* ```
* `<child>`'s view definition:
* ```
* <ng-content id="cont1"></ng-content>
* ```
*
* If `Array.isArray(projection)` then `TNode` is a host element:
* - `projection` stores the content nodes which are to be projected.
* - The nodes represent categories defined by the selector: For example:
* `<ng-content/><ng-content select="abc"/>` would represent the heads for `<ng-content/>`
* and `<ng-content select="abc"/>` respectively.
* - The nodes we store in `projection` are heads only, we used `.next` to get their
* siblings.
* - The nodes `.next` is sorted/rewritten as part of the projection setup.
* - `projection` size is equal to the number of projections `<ng-content>`. The size of
* `c1` will be `1` because `<child>` has only one `<ng-content>`.
* - we store `projection` with the host (`c1`, `c2`) rather than the `<ng-content>` (`cont1`)
* because the same component (`<child>`) can be used in multiple locations (`c1`, `c2`) and as
* a result have different set of nodes to project.
* - without `projection` it would be difficult to efficiently traverse nodes to be projected.
*
* If `typeof projection == 'number'` then `TNode` is a `<ng-content>` element:
* - `projection` is an index of the host's `projection`Nodes.
* - This would return the first head node to project:
* `getHost(currentTNode).projection[currentTNode.projection]`.
* - When projecting nodes the parent node retrieved may be a `<ng-content>` node, in which case
* the process is recursive in nature (not implementation).
*/
projection: (TNode|null)[]|number|null;
}
/** Static data for an LElementNode */
@ -359,6 +390,13 @@ export interface TElementNode extends TNode {
*/
parent: TElementNode|null;
tViews: null;
/**
* If this is a component TNode with projection, this will be an array of projected
* TNodes (see TNode.projection for more info). If it's a regular element node or a
* component without projection, it will be null.
*/
projection: (TNode|null)[]|null;
}
/** Static data for an LTextNode */
@ -373,6 +411,7 @@ export interface TTextNode extends TNode {
*/
parent: TElementNode|null;
tViews: null;
projection: null;
}
/** Static data for an LContainerNode */
@ -394,6 +433,7 @@ export interface TContainerNode extends TNode {
*/
parent: TElementNode|null;
tViews: TView|TView[]|null;
projection: null;
}
/** Static data for an LViewNode */
@ -403,6 +443,7 @@ export interface TViewNode extends TNode {
child: TElementNode|TTextNode|TContainerNode|TProjectionNode|null;
parent: TContainerNode|null;
tViews: null;
projection: null;
}
/** Static data for an LProjectionNode */
@ -416,6 +457,9 @@ export interface TProjectionNode extends TNode {
*/
parent: TElementNode|null;
tViews: null;
/** Index of the projection node. (See TNode.projection for more info.) */
projection: number;
}
/**

View File

@ -1,3 +1,4 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
@ -6,15 +7,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {LContainerNode, LElementNode, LTextNode} from './node';
/**
* Linked list of projected nodes (using the pNextOrParent property).
*/
export interface LProjection {
head: LElementNode|LTextNode|LContainerNode|null;
tail: LElementNode|LTextNode|LContainerNode|null;
}
/**
* Expresses a single CSS Selector.

View File

@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined} from './assert';
import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
@ -24,7 +25,7 @@ export function getNextLNode(node: LNode): LNode|null {
const viewData = node.data as LViewData;
return viewData[NEXT] ? (viewData[NEXT] as LViewData)[HOST_NODE] : null;
}
return node.tNode.next ? node.view[node.tNode.next !.index] : null;
return node.tNode.next ? node.view[node.tNode.next.index] : null;
}
/** Retrieves the first child of a given node */
@ -52,27 +53,6 @@ export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNo
return parent ? node.view[parent.index] : node.view[HOST_NODE];
}
/**
* Get the next node in the LNode tree, taking into account the place where a node is
* projected (in the shadow DOM) rather than where it comes from (in the light DOM).
*
* @param node The node whose next node in the LNode tree must be found.
* @return LNode|null The next sibling in the LNode tree.
*/
function getNextLNodeWithProjection(node: LNode): LNode|null {
const pNextOrParent = node.pNextOrParent;
if (pNextOrParent) {
// The node is projected
const isLastProjectedNode = pNextOrParent.tNode.type === TNodeType.Projection;
// returns pNextOrParent if we are not at the end of the list, null otherwise
return isLastProjectedNode ? null : pNextOrParent;
}
// returns node.next because the the node is not projected
return getNextLNode(node);
}
const enum WalkLNodeTreeAction {
/** node insert in the native environment */
Insert = 0,
@ -84,12 +64,22 @@ const enum WalkLNodeTreeAction {
Destroy = 2,
}
/**
* Stack used to keep track of projection nodes in walkLNodeTree.
*
* This is deliberately created outside of walkLNodeTree to avoid allocating
* a new array each time the function is called. Instead the array will be
* re-used by each invocation. This works because the function is not reentrant.
*/
const projectionNodeStack: LProjectionNode[] = [];
/**
* Walks a tree of LNodes, applying a transformation on the LElement nodes, either only on the first
* one found, or on all of them.
*
* @param startingNode the node from which the walk is started.
* @param rootNode the root node considered.
* @param rootNode the root node considered. This prevents walking past that node.
* @param action identifies the action to be performed on the LElement nodes.
* @param renderer the current renderer.
* @param renderParentNode Optional the render parent node to be set in all LContainerNodes found,
@ -101,18 +91,19 @@ function walkLNodeTree(
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer: Renderer3,
renderParentNode?: LElementNode | null, beforeNode?: RNode | null) {
let node: LNode|null = startingNode;
let projectionNodeIndex = -1;
while (node) {
let nextNode: LNode|null = null;
const parent = renderParentNode ? renderParentNode.native : null;
if (node.tNode.type === TNodeType.Element) {
const nodeType = node.tNode.type;
if (nodeType === TNodeType.Element) {
// Execute the action
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
if (node.dynamicLContainerNode) {
executeNodeAction(
action, renderer, parent, node.dynamicLContainerNode.native !, beforeNode);
}
nextNode = getNextLNode(node);
} else if (node.tNode.type === TNodeType.Container) {
} else if (nodeType === TNodeType.Container) {
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
const lContainerNode: LContainerNode = (node as LContainerNode);
const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ?
@ -130,15 +121,26 @@ function walkLNodeTree(
lContainerNode.dynamicLContainerNode.native :
lContainerNode.native;
}
} else if (node.tNode.type === TNodeType.Projection) {
// For Projection look at the first projected node
nextNode = (node as LProjectionNode).data.head;
} else if (nodeType === TNodeType.Projection) {
const componentHost = findComponentHost(node.view);
const head =
(componentHost.tNode.projection as(TNode | null)[])[node.tNode.projection as number];
projectionNodeStack[++projectionNodeIndex] = node as LProjectionNode;
nextNode = head ? (componentHost.data as LViewData)[PARENT] ![head.index] : null;
} else {
// Otherwise look at the first child
nextNode = getChildLNode(node as LViewNode);
}
if (nextNode == null) {
if (nextNode === null) {
nextNode = getNextLNode(node);
// this last node was projected, we need to get back down to its projection node
if (nextNode === null && (node.tNode.flags & TNodeFlags.isProjected)) {
nextNode = getNextLNode(projectionNodeStack[projectionNodeIndex--] as LNode);
}
/**
* Find the next node in the LNode tree, taking into account the place where a node is
* projected (in the shadow DOM) rather than where it comes from (in the light DOM).
@ -146,29 +148,43 @@ function walkLNodeTree(
* If there is no sibling node, then it goes to the next sibling of the parent node...
* until it reaches rootNode (at which point null is returned).
*/
let currentNode: LNode|null = node;
node = getNextLNodeWithProjection(currentNode);
while (currentNode && !node) {
// if node.pNextOrParent is not null here, it is not the next node
// (because, at this point, nextNode is null, so it is the parent)
currentNode = currentNode.pNextOrParent || getParentLNode(currentNode);
if (currentNode === rootNode) {
return null;
while (node && !nextNode) {
node = getParentLNode(node);
if (node === null || node === rootNode) return null;
// When exiting a container, the beforeNode must be restored to the previous value
if (!node.tNode.next && nodeType === TNodeType.Container) {
beforeNode = node.native;
}
// When the walker exits a container, the beforeNode has to be restored to the previous
// value.
if (currentNode && !currentNode.pNextOrParent &&
currentNode.tNode.type === TNodeType.Container) {
beforeNode = currentNode.native;
}
node = currentNode && getNextLNodeWithProjection(currentNode);
nextNode = getNextLNode(node);
}
} else {
node = nextNode;
}
node = nextNode;
}
}
/**
* Given a current view, finds the nearest component's host (LElement).
*
* @param lViewData LViewData for which we want a host element node
* @returns The host node
*/
export function findComponentHost(lViewData: LViewData): LElementNode {
let viewRootLNode = lViewData[HOST_NODE];
while (viewRootLNode.tNode.type === TNodeType.View) {
ngDevMode && assertDefined(lViewData[PARENT], 'lViewData.parent');
lViewData = lViewData[PARENT] !;
viewRootLNode = lViewData[HOST_NODE];
}
ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');
return viewRootLNode as LElementNode;
}
/**
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
* being passed as an argument.