refactor(ivy): traverse tNode tree directly (#25872)

PR Close #25872
This commit is contained in:
Kara Erickson 2018-09-08 10:55:41 -07:00 committed by Igor Minar
parent 96eb79b1c7
commit 91d79939be
5 changed files with 129 additions and 123 deletions

View File

@ -734,7 +734,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
const beforeNode = adjustedIdx + 1 < views.length ?
(getChildLNode(views[adjustedIdx + 1]) !).native :
this._lContainerNode.native;
addRemoveViewFromContainer(this._lContainerNode, lViewNode, true, beforeNode);
addRemoveViewFromContainer(this._lContainerNode, lViewNode.data, true, beforeNode);
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
this._viewRefs.splice(adjustedIdx, 0, viewRef);

View File

@ -25,7 +25,7 @@ import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getLViewChild, getParentLNode, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, readElementValue, stringify} from './util';
@ -2264,49 +2264,54 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
// re-distribution of projectable nodes is stored on a component's view level
const parent = getParentLNode(node);
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;
const componentNode = findComponentHost(viewData);
let nodeToProject = (componentNode.tNode.projection as(TNode | null)[])[selectorIndex];
let projectedView = componentNode.view;
let projectionNodeIndex = -1;
let renderParent: LElementNode|null = null;
const parentView = viewData[HOST_NODE].view;
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];
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 (firstProjectedNode) {
projectionNodeStack[++projectionNodeIndex] = projectedView[nodeToProject.index];
nodeToProject = firstProjectedNode;
projectedView = currentComponentHost.view;
continue;
if (firstProjectedNode) {
projectionNodeStack[++projectionNodeIndex] = projectedView[nodeToProject.index];
nodeToProject = firstProjectedNode;
projectedView = currentComponentHost.view;
continue;
}
} else {
const lNode = projectedView[nodeToProject.index];
// This flag must be set now or we won't know that this node is projected
// if the nodes are inserted into a container later.
lNode.tNode.flags |= TNodeFlags.isProjected;
if (canInsertNativeNode(parent, viewData)) {
let grandparent: LContainerNode;
if (renderParent == null) {
renderParent = parent.tNode.type === TNodeType.View ?
(grandparent = getParentLNode(parent) as LContainerNode) &&
grandparent.data[RENDER_PARENT] ! :
parent as LElementNode;
}
} else {
const lNode = projectedView[nodeToProject.index];
lNode.tNode.flags |= TNodeFlags.isProjected;
appendProjectedNode(
lNode as LTextNode | LElementNode | LContainerNode, parent, viewData, renderParent,
parentView);
projectedView);
}
// 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;
}
// 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;
}
}

View File

@ -10,7 +10,7 @@ import {assertDefined} from './assert';
import {attachPatchData} from './context_discovery';
import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, TViewNode, 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, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
@ -19,16 +19,6 @@ import {readElementValue, stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
/** Retrieves the sibling node for the given node. */
export function getNextLNode(node: LNode): LNode|null {
// View nodes don't have TNodes, so their next must be retrieved through their LView.
if (node.tNode.type === TNodeType.View) {
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;
}
/** Retrieves the first child of a given node */
export function getChildLNode(node: LNode): LNode|null {
if (node.tNode.child) {
@ -68,7 +58,7 @@ function getRenderParent(viewNode: LViewNode): LElementNode|null {
return container ? container.data[RENDER_PARENT] : null;
}
const enum WalkLNodeTreeAction {
const enum WalkTNodeTreeAction {
/** node insert in the native environment */
Insert = 0,
@ -81,20 +71,19 @@ const enum WalkLNodeTreeAction {
/**
* Stack used to keep track of projection nodes in walkLNodeTree.
* Stack used to keep track of projection nodes in walkTNodeTree.
*
* This is deliberately created outside of walkLNodeTree to avoid allocating
* This is deliberately created outside of walkTNodeTree 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[] = [];
const projectionNodeStack: (LViewData | TNode)[] = [];
/**
* Walks a tree of LNodes, applying a transformation on the LElement nodes, either only on the first
* Walks a tree of TNodes, applying a transformation on the element 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. This prevents walking past that node.
* @param viewToWalk the view to walk
* @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,
@ -102,60 +91,69 @@ const projectionNodeStack: LProjectionNode[] = [];
* @param beforeNode Optional the node before which elements should be added, required for action
* Insert.
*/
function walkLNodeTree(
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer: Renderer3,
function walkTNodeTree(
viewToWalk: LViewData, action: WalkTNodeTreeAction, renderer: Renderer3,
renderParentNode?: LElementNode | null, beforeNode?: RNode | null) {
let node: LNode|null = startingNode;
const rootTNode = viewToWalk[TVIEW].node as TViewNode;
let projectionNodeIndex = -1;
while (node) {
let nextNode: LNode|null = null;
let currentView = viewToWalk;
let tNode: TNode|null = rootTNode.child as TNode;
while (tNode) {
let nextTNode: TNode|null = null;
const parent = renderParentNode ? renderParentNode.native : null;
const nodeType = node.tNode.type;
if (nodeType === TNodeType.Element) {
// Execute the action
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
if (node.dynamicLContainerNode) {
if (tNode.type === TNodeType.Element) {
const elementNode = readElementValue(currentView ![tNode.index]);
executeNodeAction(action, renderer, parent, elementNode.native !, beforeNode);
if (elementNode.dynamicLContainerNode) {
executeNodeAction(
action, renderer, parent, node.dynamicLContainerNode.native !, beforeNode);
action, renderer, parent, elementNode.dynamicLContainerNode.native !, beforeNode);
}
} else if (nodeType === TNodeType.Container) {
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
const lContainerNode: LContainerNode = (node as LContainerNode);
} else if (tNode.type === TNodeType.Container) {
const lContainerNode: LContainerNode = currentView ![tNode.index] as LContainerNode;
executeNodeAction(action, renderer, parent, lContainerNode.native !, beforeNode);
const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.data :
lContainerNode.data;
if (renderParentNode) {
childContainerData[RENDER_PARENT] = renderParentNode;
}
nextNode =
childContainerData[VIEWS].length ? getChildLNode(childContainerData[VIEWS][0]) : null;
if (nextNode) {
if (childContainerData[VIEWS].length) {
currentView = childContainerData[VIEWS][0].data;
nextTNode = currentView[TVIEW].node;
// When the walker enters a container, then the beforeNode has to become the local native
// comment node.
beforeNode = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.native :
lContainerNode.native;
}
} else if (nodeType === TNodeType.Projection) {
const componentHost = findComponentHost(node.view);
const head =
(componentHost.tNode.projection as(TNode | null)[])[node.tNode.projection as number];
} else if (tNode.type === TNodeType.Projection) {
const componentHost = findComponentHost(currentView !);
const head: TNode|null =
(componentHost.tNode.projection as(TNode | null)[])[tNode.projection as number];
projectionNodeStack[++projectionNodeIndex] = node as LProjectionNode;
nextNode = head ? (componentHost.data as LViewData)[PARENT] ![head.index] : null;
// Must store both the TNode and the view because this projection node could be nested
// deeply inside embedded views, and we need to get back down to this particular nested view.
projectionNodeStack[++projectionNodeIndex] = tNode;
projectionNodeStack[++projectionNodeIndex] = currentView !;
if (head) {
currentView = (componentHost.data as LViewData)[PARENT] !;
nextTNode = currentView[TVIEW].data[head.index] as TNode;
}
} else {
// Otherwise look at the first child
nextNode = getChildLNode(node as LViewNode | LElementContainerNode);
// Otherwise, this is a View or an ElementContainer
nextTNode = tNode.child;
}
if (nextNode === null) {
nextNode = getNextLNode(node);
if (nextTNode === null) {
// 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);
if (tNode.next === null && (tNode.flags & TNodeFlags.isProjected)) {
currentView = projectionNodeStack[projectionNodeIndex--] as LViewData;
tNode = projectionNodeStack[projectionNodeIndex--] as TNode;
}
nextTNode = tNode.next;
/**
* 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).
@ -163,22 +161,30 @@ 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).
*/
while (node && !nextNode) {
node = getParentLNode(node);
if (node === null || node === rootNode) return null;
while (!nextTNode) {
// If parent is null, we're crossing the view boundary, so we should get the host TNode.
tNode = tNode.parent || currentView[TVIEW].node;
if (tNode === null || tNode === rootTNode) 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;
if (tNode.type === TNodeType.Container) {
currentView = currentView[PARENT] !;
beforeNode = currentView[tNode.index].native;
}
if (tNode.type === TNodeType.View && currentView[NEXT]) {
currentView = currentView[NEXT] as LViewData;
nextTNode = currentView[TVIEW].node;
} else {
nextTNode = tNode.next;
}
nextNode = getNextLNode(node);
}
}
node = nextNode;
tNode = nextTNode;
}
}
/**
* Given a current view, finds the nearest component's host (LElement).
*
@ -205,17 +211,17 @@ export function findComponentHost(lViewData: LViewData): LElementNode {
* being passed as an argument.
*/
function executeNodeAction(
action: WalkLNodeTreeAction, renderer: Renderer3, parent: RElement | null,
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null,
node: RComment | RElement | RText, beforeNode?: RNode | null) {
if (action === WalkLNodeTreeAction.Insert) {
if (action === WalkTNodeTreeAction.Insert) {
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3).insertBefore(parent !, node, beforeNode as RNode | null) :
parent !.insertBefore(node, beforeNode as RNode | null, true);
} else if (action === WalkLNodeTreeAction.Detach) {
} else if (action === WalkTNodeTreeAction.Detach) {
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3).removeChild(parent !, node) :
parent !.removeChild(node);
} else if (action === WalkLNodeTreeAction.Destroy) {
} else if (action === WalkTNodeTreeAction.Destroy) {
ngDevMode && ngDevMode.rendererDestroyNode++;
(renderer as ProceduralRenderer3).destroyNode !(node);
}
@ -234,28 +240,26 @@ export function createTextNode(value: any, renderer: Renderer3): RText {
* views beneath it.
*
* @param container The container to which the root view belongs
* @param rootNode The view from which elements should be added or removed
* @param viewToWalk The view from which elements should be added or removed
* @param insertMode Whether or not elements should be added (if false, removing)
* @param beforeNode The node before which elements should be added, if insert mode
*/
export function addRemoveViewFromContainer(
container: LContainerNode, rootNode: LViewNode, insertMode: true,
container: LContainerNode, viewToWalk: LViewData, insertMode: true,
beforeNode: RNode | null): void;
export function addRemoveViewFromContainer(
container: LContainerNode, rootNode: LViewNode, insertMode: false): void;
container: LContainerNode, viewToWalk: LViewData, insertMode: false): void;
export function addRemoveViewFromContainer(
container: LContainerNode, rootNode: LViewNode, insertMode: boolean,
container: LContainerNode, viewToWalk: LViewData, insertMode: boolean,
beforeNode?: RNode | null): void {
ngDevMode && assertNodeType(container.tNode, TNodeType.Container);
ngDevMode && assertNodeType(rootNode.tNode, TNodeType.View);
const parentNode = container.data[RENDER_PARENT];
const parent = parentNode ? parentNode.native : null;
ngDevMode && assertNodeType(viewToWalk[TVIEW].node as TNode, TNodeType.View);
if (parent) {
let node: LNode|null = getChildLNode(rootNode);
const renderer = container.view[RENDERER];
walkLNodeTree(
node, rootNode, insertMode ? WalkLNodeTreeAction.Insert : WalkLNodeTreeAction.Detach,
renderer, parentNode, beforeNode);
walkTNodeTree(
viewToWalk, insertMode ? WalkTNodeTreeAction.Insert : WalkTNodeTreeAction.Detach, renderer,
parentNode, beforeNode);
}
}
@ -374,7 +378,7 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie
}
views.splice(removeIndex, 1);
if (!container.tNode.detached) {
addRemoveViewFromContainer(container, viewNode, false);
addRemoveViewFromContainer(container, viewNode.data, false);
}
// Notify query that view has been removed
const removedLView = viewNode.data;
@ -397,8 +401,8 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie
*/
export function removeView(container: LContainerNode, removeIndex: number): LViewNode {
const viewNode = container.data[VIEWS][removeIndex];
detachView(container, removeIndex);
destroyLView(viewNode.data);
detachView(container, removeIndex);
return viewNode;
}
@ -420,7 +424,7 @@ export function getLViewChild(viewData: LViewData): LViewData|LContainer|null {
export function destroyLView(view: LViewData) {
const renderer = view[RENDERER];
if (isProceduralRenderer(renderer) && renderer.destroyNode) {
walkLNodeTree(view[HOST_NODE], view[HOST_NODE], WalkLNodeTreeAction.Destroy, renderer);
walkTNodeTree(view, WalkTNodeTreeAction.Destroy, renderer);
}
destroyViewTree(view);
// Sets the destroyed flag
@ -713,16 +717,19 @@ export function appendProjectedNode(
lContainer[RENDER_PARENT] = renderParent;
const views = lContainer[VIEWS];
for (let i = 0; i < views.length; i++) {
addRemoveViewFromContainer(node as LContainerNode, views[i], true, node.native);
const viewNode = views[i];
addRemoveViewFromContainer(node as LContainerNode, viewNode.data, true, node.native);
}
} else if (node.tNode.type === TNodeType.ElementContainer) {
let ngContainerChild = getChildLNode(node as LElementContainerNode);
const parentView = currentView[HOST_NODE].view;
const parentView = currentView[PARENT] !;
while (ngContainerChild) {
appendProjectedNode(
ngContainerChild as LElementNode | LElementContainerNode | LTextNode | LContainerNode,
currentParent, currentView, renderParent, parentView);
ngContainerChild = getNextLNode(ngContainerChild);
ngContainerChild = ngContainerChild.tNode.next ?
readElementValue(ngContainerChild.view[ngContainerChild.tNode.next.index]) :
null;
}
}
if (node.dynamicLContainerNode) {

View File

@ -578,9 +578,6 @@
{
"name": "getMultiStartIndex"
},
{
"name": "getNextLNode"
},
{
"name": "getOrCreateChangeDetectorRef"
},
@ -969,7 +966,7 @@
"name": "viewAttached"
},
{
"name": "walkLNodeTree"
"name": "walkTNodeTree"
},
{
"name": "walkUpViews"

View File

@ -1655,9 +1655,6 @@
{
"name": "getNamedFormat"
},
{
"name": "getNextLNode"
},
{
"name": "getNgZone"
},
@ -2409,7 +2406,7 @@
"name": "viewAttached"
},
{
"name": "walkLNodeTree"
"name": "walkTNodeTree"
},
{
"name": "walkUpViews"