refactor(ivy): update context discovery to prep for dir merge (#26262)

PR Close #26262
This commit is contained in:
Kara Erickson 2018-10-04 20:40:39 -07:00 committed by Jason Aden
parent 51dfdd5dd1
commit 8f25321787
6 changed files with 62 additions and 132 deletions

View File

@ -34,9 +34,9 @@ export interface LContext {
lViewData: LViewData;
/**
* The index instance of the LNode.
* The index instance of the node.
*/
lNodeIndex: number;
nodeIndex: number;
/**
* The instance of the DOM node that is attached to the lNode.
@ -48,11 +48,6 @@ export interface LContext {
*/
component: {}|null|undefined;
/**
* The list of indices for the active directives that exist on this element.
*/
directiveIndices: number[]|null|undefined;
/**
* The list of active directives that exist on this element.
*/
@ -89,27 +84,25 @@ export function getContext(target: any): LContext|null {
// ... otherwise it's an already constructed LContext instance
if (Array.isArray(mpValue)) {
const lViewData: LViewData = mpValue !;
let lNodeIndex: number;
let nodeIndex: number;
let component: any = undefined;
let directiveIndices: number[]|null|undefined = undefined;
let directives: any[]|null|undefined = undefined;
if (isComponentInstance(target)) {
lNodeIndex = findViaComponent(lViewData, target);
if (lNodeIndex == -1) {
nodeIndex = findViaComponent(lViewData, target);
if (nodeIndex == -1) {
throw new Error('The provided component was not found in the application');
}
component = target;
} else if (isDirectiveInstance(target)) {
lNodeIndex = findViaDirective(lViewData, target);
if (lNodeIndex == -1) {
nodeIndex = findViaDirective(lViewData, target);
if (nodeIndex == -1) {
throw new Error('The provided directive was not found in the application');
}
directiveIndices = discoverDirectiveIndices(lViewData, lNodeIndex);
directives = directiveIndices ? discoverDirectives(lViewData, directiveIndices) : null;
directives = discoverDirectives(nodeIndex, lViewData);
} else {
lNodeIndex = findViaNativeElement(lViewData, target as RElement);
if (lNodeIndex == -1) {
nodeIndex = findViaNativeElement(lViewData, target as RElement);
if (nodeIndex == -1) {
return null;
}
}
@ -118,11 +111,11 @@ export function getContext(target: any): LContext|null {
// are expensive. Instead, only the target data (the element, compontent or
// directive details) are filled into the context. If called multiple times
// with different target values then the missing target data will be filled in.
const lNode = getLNodeFromViewData(lViewData, lNodeIndex) !;
const lNode = getLNodeFromViewData(lViewData, nodeIndex) !;
const existingCtx = readPatchedData(lNode.native);
const context: LContext = (existingCtx && !Array.isArray(existingCtx)) ?
existingCtx :
createLContext(lViewData, lNodeIndex, lNode.native);
createLContext(lViewData, nodeIndex, lNode.native);
// only when the component has been discovered then update the monkey-patch
if (component && context.component === undefined) {
@ -131,8 +124,7 @@ export function getContext(target: any): LContext|null {
}
// only when the directives have been discovered then update the monkey-patch
if (directives && directiveIndices && context.directives === undefined) {
context.directiveIndices = directiveIndices;
if (directives && context.directives === undefined) {
context.directives = directives;
for (let i = 0; i < directives.length; i++) {
attachPatchData(directives[i], context);
@ -185,31 +177,13 @@ export function getContext(target: any): LContext|null {
function createLContext(lViewData: LViewData, lNodeIndex: number, native: RElement): LContext {
return {
lViewData,
lNodeIndex,
native,
nodeIndex: lNodeIndex, native,
component: undefined,
directiveIndices: undefined,
directives: undefined,
localRefs: undefined,
};
}
/**
* A utility function for retrieving the matching lElementNode
* from a given DOM element, component or directive.
*/
export function getLElementNode(target: any): LElementNode|null {
const context = getContext(target);
return context ? getLNodeFromViewData(context.lViewData, context.lNodeIndex) : null;
}
export function getLElementFromRootComponent(rootComponentInstance: {}): LElementNode|null {
// the host element for the root component is ALWAYS the first element
// in the lViewData array (which is where HEADER_OFFSET points to)
const lViewData = readPatchedLViewData(rootComponentInstance) !;
return readElementValue(lViewData[HEADER_OFFSET]);
}
/**
* A simplified lookup function for finding the LElementNode from a component instance.
*
@ -230,7 +204,7 @@ export function getLElementFromComponent(componentInstance: {}): LElementNode {
attachPatchData(context.native, context);
} else {
const context = lViewData as any as LContext;
lNode = readElementValue(context.lViewData[context.lNodeIndex]);
lNode = readElementValue(context.lViewData[context.nodeIndex]);
}
return lNode;
@ -330,20 +304,19 @@ function findViaDirective(lViewData: LViewData, directiveInstance: {}): number {
// if a directive is monkey patched then it will (by default)
// have a reference to the LViewData of the current view. The
// element bound to the directive being search lives somewhere
// in the view data. By first checking to see if the instance
// is actually present we can narrow down to which lElementNode
// contains the instance of the directive and then return the index
// in the view data. We loop through the nodes and check their
// list of directives for the instance.
const directivesAcrossView = lViewData[DIRECTIVES];
const directiveIndex =
directivesAcrossView ? directivesAcrossView.indexOf(directiveInstance) : -1;
if (directiveIndex >= 0) {
let tNode = lViewData[TVIEW].firstChild;
if (directivesAcrossView != null) {
while (tNode) {
const directiveIndexStart = getDirectiveStartIndex(tNode);
const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart);
if (directiveIndex >= directiveIndexStart && directiveIndex < directiveIndexEnd) {
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (directivesAcrossView[i] === directiveInstance) {
return tNode.index;
}
}
tNode = traverseNextElement(tNode);
}
}
@ -368,50 +341,21 @@ function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LEle
}
/**
* Returns a collection of directive index values that are used on the element
* (which is referenced by the lNodeIndex)
*/
export function discoverDirectiveIndices(
lViewData: LViewData, lNodeIndex: number, includeComponents?: boolean): number[]|null {
const directivesAcrossView = lViewData[DIRECTIVES];
const tNode = lViewData[TVIEW].data[lNodeIndex] as TNode;
if (directivesAcrossView && directivesAcrossView.length) {
// this check for tNode is to determine if the value is a LElementNode instance
const directiveIndexStart = getDirectiveStartIndex(tNode);
const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart);
const directiveIndices: number[] = [];
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
// special case since the instance of the component (if it exists)
// is stored in the directives array.
if (i > directiveIndexStart ||
!isComponentInstance(directivesAcrossView[directiveIndexStart])) {
directiveIndices.push(i);
}
}
return directiveIndices.length ? directiveIndices : null;
}
return null;
}
/**
* Returns a list of directives extracted from the given view based on the
* provided list of directive index values.
* Returns a list of directives extracted from the given view. Does not contain
* the component.
*
* @param lViewData The target view data
* @param indices A collection of directive index values which will be used to
* figure out the directive instances
*/
export function discoverDirectives(lViewData: LViewData, indices: number[]): number[]|null {
const directives: any[] = [];
const directiveInstances = lViewData[DIRECTIVES];
if (directiveInstances) {
for (let i = 0; i < indices.length; i++) {
const directiveIndex = indices[i];
const directive = directiveInstances[directiveIndex];
directives.push(directive);
export function discoverDirectives(nodeIndex: number, lViewData: LViewData): any[]|null {
const directivesAcrossView = lViewData[DIRECTIVES];
if (directivesAcrossView != null) {
const tNode = lViewData[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex);
if (tNode.flags & TNodeFlags.isComponent) directiveStartIndex++;
return directivesAcrossView.slice(directiveStartIndex, directiveEndIndex);
}
}
return directives;
return null;
}
/**

View File

@ -11,10 +11,11 @@ import {Renderer2, RendererType2} from '../render/api';
import {DebugContext} from '../view';
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
import {getLElementNode} from './context_discovery';
import * as di from './di';
import {_getViewData} from './instructions';
import {CONTEXT, DIRECTIVES, LViewData, TVIEW} from './interfaces/view';
import {TNodeFlags} from './interfaces/node';
import {CONTEXT, LViewData, TVIEW} from './interfaces/view';
/**
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
@ -68,25 +69,16 @@ class Render3DebugContext implements DebugContext {
// TODO(vicb): add view providers when supported
get providerTokens(): any[] {
const matchedDirectives: any[] = [];
// TODO(vicb): why/when
if (this.nodeIndex === null) {
return matchedDirectives;
const directiveDefs = this.view[TVIEW].directives;
if (this.nodeIndex === null || directiveDefs == null) {
return [];
}
const directives = this.view[DIRECTIVES];
if (directives) {
const currentNode = this.view[this.nodeIndex];
for (let dirIndex = 0; dirIndex < directives.length; dirIndex++) {
const directive = directives[dirIndex];
if (getLElementNode(directive) === currentNode) {
matchedDirectives.push(directive.constructor);
}
}
}
return matchedDirectives;
const currentTNode = this.view[TVIEW].data[this.nodeIndex];
const dirStart = currentTNode >> TNodeFlags.DirectiveStartingIndexShift;
const dirEnd = dirStart + (currentTNode & TNodeFlags.DirectiveCountMask);
return directiveDefs.slice(dirStart, dirEnd);
}
get references(): {[key: string]: any} {

View File

@ -8,7 +8,7 @@
import {Injector} from '../di/injector';
import {assertDefined} from './assert';
import {LContext, discoverDirectiveIndices, discoverDirectives, discoverLocalRefs, getContext, isComponentInstance, readPatchedLViewData} from './context_discovery';
import {LContext, discoverDirectives, discoverLocalRefs, getContext, isComponentInstance, readPatchedLViewData} from './context_discovery';
import {NodeInjector} from './di';
import {LElementNode, TElementNode, TNode, TNodeFlags} from './interfaces/node';
import {CONTEXT, FLAGS, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
@ -59,9 +59,9 @@ export function getComponent<T = {}>(target: {}): T|null {
*/
export function getHostComponent<T = {}>(target: {}): T|null {
const context = loadContext(target);
const tNode = context.lViewData[TVIEW].data[context.lNodeIndex] as TNode;
const tNode = context.lViewData[TVIEW].data[context.nodeIndex] as TNode;
if (tNode.flags & TNodeFlags.isComponent) {
const lNode = context.lViewData[context.lNodeIndex] as LElementNode;
const lNode = context.lViewData[context.nodeIndex] as LElementNode;
return lNode.data ![CONTEXT] as any as T;
}
return null;
@ -91,7 +91,7 @@ export function getRootComponents(target: {}): any[] {
*/
export function getInjector(target: {}): Injector {
const context = loadContext(target);
const tNode = context.lViewData[TVIEW].data[context.lNodeIndex] as TElementNode;
const tNode = context.lViewData[TVIEW].data[context.nodeIndex] as TElementNode;
return new NodeInjector(tNode, context.lViewData);
}
@ -104,10 +104,7 @@ export function getDirectives(target: {}): Array<{}> {
const context = loadContext(target) !;
if (context.directives === undefined) {
context.directiveIndices = discoverDirectiveIndices(context.lViewData, context.lNodeIndex);
context.directives = context.directiveIndices ?
discoverDirectives(context.lViewData, context.directiveIndices) :
null;
context.directives = discoverDirectives(context.nodeIndex, context.lViewData);
}
return context.directives || [];
@ -151,7 +148,7 @@ export function getLocalRefs(target: {}): {[key: string]: any} {
const context = loadContext(target) !;
if (context.localRefs === undefined) {
context.localRefs = discoverLocalRefs(context.lViewData, context.lNodeIndex);
context.localRefs = discoverLocalRefs(context.lViewData, context.nodeIndex);
}
return context.localRefs || {};

View File

@ -29,11 +29,11 @@ export function getOrCreatePlayerContext(target: {}, context?: LContext | null):
'Only elements that exist in an Angular application can be used for player access');
}
const {lViewData, lNodeIndex} = context;
const value = lViewData[lNodeIndex];
const {lViewData, nodeIndex} = context;
const value = lViewData[nodeIndex];
let stylingContext = value as StylingContext;
if (!Array.isArray(value)) {
stylingContext = lViewData[lNodeIndex] = createEmptyStylingContext(value as LElementNode);
stylingContext = lViewData[nodeIndex] = createEmptyStylingContext(value as LElementNode);
}
return stylingContext[StylingIndex.PlayerContext] || allocPlayerContext(stylingContext);
}

View File

@ -446,9 +446,6 @@
{
"name": "directiveInject"
},
{
"name": "discoverDirectiveIndices"
},
{
"name": "discoverDirectives"
},

View File

@ -1765,14 +1765,14 @@ describe('render3 integration test', () => {
const section = fixture.hostElement.querySelector('section') !;
const sectionContext = getContext(section) !;
const sectionLView = sectionContext.lViewData !;
expect(sectionContext.lNodeIndex).toEqual(HEADER_OFFSET);
expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET);
expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET);
expect(sectionContext.native).toBe(section);
const div = fixture.hostElement.querySelector('div') !;
const divContext = getContext(div) !;
const divLView = divContext.lViewData !;
expect(divContext.lNodeIndex).toEqual(HEADER_OFFSET + 1);
expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1);
expect(divLView.length).toBeGreaterThan(HEADER_OFFSET);
expect(divContext.native).toBe(div);
@ -2110,7 +2110,7 @@ describe('render3 integration test', () => {
const div1 = hostElm.querySelector('div:first-child') !as any;
const div2 = hostElm.querySelector('div:last-child') !as any;
const context = getContext(hostElm) !;
const elementNode = context.lViewData[context.lNodeIndex];
const elementNode = context.lViewData[context.nodeIndex];
const elmData = elementNode.data !;
const dirs = elmData[DIRECTIVES];
@ -2134,15 +2134,15 @@ describe('render3 integration test', () => {
expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d2Context);
expect((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d3Context);
expect(d1Context.lNodeIndex).toEqual(HEADER_OFFSET);
expect(d1Context.nodeIndex).toEqual(HEADER_OFFSET);
expect(d1Context.native).toBe(div1);
expect(d1Context.directives as any[]).toEqual([myDir1Instance, myDir2Instance]);
expect(d2Context.lNodeIndex).toEqual(HEADER_OFFSET);
expect(d2Context.nodeIndex).toEqual(HEADER_OFFSET);
expect(d2Context.native).toBe(div1);
expect(d2Context.directives as any[]).toEqual([myDir1Instance, myDir2Instance]);
expect(d3Context.lNodeIndex).toEqual(HEADER_OFFSET + 1);
expect(d3Context.nodeIndex).toEqual(HEADER_OFFSET + 1);
expect(d3Context.native).toBe(div2);
expect(d3Context.directives as any[]).toEqual([myDir3Instance]);
});
@ -2292,14 +2292,14 @@ describe('render3 integration test', () => {
const context = getContext(child) !;
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const componentData = context.lViewData[context.lNodeIndex].data;
const componentData = context.lViewData[context.nodeIndex].data;
const component = componentData[CONTEXT];
expect(component instanceof ChildComp).toBeTruthy();
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lViewData);
const componentContext = getContext(component) !;
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(componentContext);
expect(componentContext.lNodeIndex).toEqual(context.lNodeIndex);
expect(componentContext.nodeIndex).toEqual(context.nodeIndex);
expect(componentContext.native).toEqual(context.native);
expect(componentContext.lViewData).toEqual(context.lViewData);
});