fix(ivy): content projection should not corrupt TNode data structures (#29130)

PR Close #29130
This commit is contained in:
Pawel Kozlowski
2019-03-06 14:01:02 +01:00
committed by Andrew Kushnir
parent 268c3fe816
commit 22ddbf4b02
4 changed files with 59 additions and 14 deletions

View File

@ -1308,6 +1308,7 @@ export function createTNode(
outputs: undefined,
tViews: null,
next: null,
projectionNext: null,
child: null,
parent: tParent,
stylingTemplate: null,
@ -2550,26 +2551,24 @@ export function projectionDef(selectors?: CssSelectorList[], textSelectors?: str
if (!componentNode.projection) {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
const pData: (TNode | null)[] = componentNode.projection =
const projectionHeads: (TNode | null)[] = componentNode.projection =
new Array(noOfNodeBuckets).fill(null);
const tails: (TNode | null)[] = pData.slice();
const tails: (TNode | null)[] = projectionHeads.slice();
let componentChild: TNode|null = componentNode.child;
while (componentChild !== null) {
const bucketIndex =
selectors ? matchingSelectorIndex(componentChild, selectors, textSelectors !) : 0;
const nextNode = componentChild.next;
if (tails[bucketIndex]) {
tails[bucketIndex] !.next = componentChild;
tails[bucketIndex] !.projectionNext = componentChild;
} else {
pData[bucketIndex] = componentChild;
projectionHeads[bucketIndex] = componentChild;
}
componentChild.next = null;
tails[bucketIndex] = componentChild;
componentChild = nextNode;
componentChild = componentChild.next;
}
}
}

View File

@ -283,6 +283,14 @@ export interface TNode {
*/
next: TNode|null;
/**
* The next projected sibling. Since in Angular content projection works on the node-by-node basis
* the act of projecting nodes might change nodes relationship at the insertion point (target
* view). At the same time we need to keep initial relationship between nodes as expressed in
* content view.
*/
projectionNext: TNode|null;
/**
* First child of the current node.
*

View File

@ -84,7 +84,7 @@ const projectionNodeStack: (LView | TNode)[] = [];
*/
function walkTNodeTree(
viewToWalk: LView, action: WalkTNodeTreeAction, renderer: Renderer3,
renderParent: RElement | null, beforeNode?: RNode | null) {
renderParent: RElement | null, beforeNode?: RNode | null): void {
const rootTNode = viewToWalk[TVIEW].node as TViewNode;
let projectionNodeIndex = -1;
let currentView = viewToWalk;
@ -141,11 +141,11 @@ function walkTNodeTree(
if (nextTNode === null) {
// this last node was projected, we need to get back down to its projection node
if (tNode.next === null && (tNode.flags & TNodeFlags.isProjected)) {
if (tNode.projectionNext === null && (tNode.flags & TNodeFlags.isProjected)) {
currentView = projectionNodeStack[projectionNodeIndex--] as LView;
tNode = projectionNodeStack[projectionNodeIndex--] as TNode;
}
nextTNode = tNode.next;
nextTNode = (tNode.flags & TNodeFlags.isProjected) ? tNode.projectionNext : tNode.next;
/**
* Find the next node in the TNode tree, taking into account the place where a node is
@ -158,7 +158,7 @@ function walkTNodeTree(
// If parent is null, we're crossing the view boundary, so we should get the host TNode.
tNode = tNode.parent || currentView[T_HOST];
if (tNode === null || tNode === rootTNode) return null;
if (tNode === null || tNode === rootTNode) return;
// When exiting a container, the beforeNode must be restored to the previous value
if (tNode.type === TNodeType.Container) {
@ -176,7 +176,7 @@ function walkTNodeTree(
*/
while (!currentView[NEXT] && currentView[PARENT] &&
!(tNode.parent && tNode.parent.next)) {
if (tNode === rootTNode) return null;
if (tNode === rootTNode) return;
currentView = currentView[PARENT] as LView;
tNode = currentView[T_HOST] !;
}
@ -755,7 +755,7 @@ export function appendProjectedNodes(
nodeToProject.flags |= TNodeFlags.isProjected;
appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView);
}
nodeToProject = nodeToProject.next;
nodeToProject = nodeToProject.projectionNext;
}
}
}