Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 10 commits into from
9 changes: 2 additions & 7 deletions packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private _bindingCode: o.Statement[] = [];
private _postfixCode: o.Statement[] = [];
private _temporary = temporaryAllocator(this._prefixCode, TEMPORARY_NAME);
private _projectionDefinitionIndex = -1;
private _valueConverter: ValueConverter;
private _unsupported = unsupported;
private _bindingScope: BindingScope;
Expand Down Expand Up @@ -121,8 +120,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver

// Output a `ProjectionDef` instruction when some `<ng-content>` are present
if (hasNgContent) {
this._projectionDefinitionIndex = this.allocateDataSlot();
const parameters: o.Expression[] = [o.literal(this._projectionDefinitionIndex)];
const parameters: o.Expression[] = [];

// Only selectors with a non-default value are generated
if (ngContentSelectors.length > 1) {
Expand Down Expand Up @@ -217,10 +215,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
visitContent(ngContent: t.Content) {
const slot = this.allocateDataSlot();
const selectorIndex = ngContent.selectorIndex;
const parameters: o.Expression[] = [
o.literal(slot),
o.literal(this._projectionDefinitionIndex),
];
const parameters: o.Expression[] = [o.literal(slot)];

const attributeAsList: string[] = [];

Expand Down
22 changes: 11 additions & 11 deletions packages/compiler/test/render3/r3_compiler_compliance_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,9 +871,9 @@ describe('compiler compliance', () => {
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵpD();
$r3$.ɵE(0, 'div');
$r3$.ɵP(1);
$r3$.ɵe();
}
}
Expand All @@ -891,12 +891,12 @@ describe('compiler compliance', () => {
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵpD(0, $c1$, $c2$);
$r3$.ɵE(1, 'div', $c3$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵpD($c1$, $c2$);
$r3$.ɵE(0, 'div', $c3$);
$r3$.ɵP(1, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c4$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵE(2, 'div', $c4$);
$r3$.ɵP(3, 2);
$r3$.ɵe();
}
}
Expand Down Expand Up @@ -1022,9 +1022,9 @@ describe('compiler compliance', () => {
template: function ContentQueryComponent_Template(
rf: $RenderFlags$, ctx: $ContentQueryComponent$) {
if (rf & 1) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵpD();
$r3$.ɵE(0, 'div');
$r3$.ɵP(1);
$r3$.ɵe();
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/render3/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
204 changes: 87 additions & 117 deletions packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -335,7 +335,6 @@ export function createLNodeObject(
data: state,
queries: queries,
tNode: null !,
pNextOrParent: null,
dynamicLContainerNode: null
};
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1189,7 +1188,8 @@ export function createTNode(
parent: parent,
dynamicContainerNode: null,
detached: null,
stylingTemplate: null
stylingTemplate: null,
projection: null
};
}

Expand Down Expand Up @@ -1948,137 +1948,107 @@ 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
if (selectors) {
const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
distributedNodes[matchedIdx].push(componentChild);
} else {
distributedNodes[0].push(componentChild);
}

componentChild = getNextLNode(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();

let componentChild = componentNode.tNode.child;

ngDevMode && assertDataNext(index + HEADER_OFFSET);
store(index, distributedNodes);
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;
}
}
}

/**
* 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];

const currentParent = getParentLNode(node);
const canInsert = canInsertNativeNode(currentParent, viewData);
const renderParent = currentParent.tNode.type === TNodeType.View ?
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
currentParent 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;

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) {
// 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;

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;
}
} 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);
}
}
}
}

/**
* 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];
// 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;
}
}

ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');

return viewRootLNode as LElementNode;
}

/**
Expand Down