fix(ivy): Implement remaining methods for DebugNode (#27387)

PR Close #27387
This commit is contained in:
Miško Hevery
2018-11-28 15:54:38 -08:00
committed by Igor Minar
parent f0b0d64453
commit b2d6f43b49
34 changed files with 605 additions and 406 deletions

View File

@ -154,7 +154,7 @@ export {
} from './sanitization/bypass';
export {
getContext as ɵgetContext
getLContext as ɵgetLContext
} from './render3/context_discovery';
export {

View File

@ -7,11 +7,13 @@
*/
import {Injector} from '../di';
import {DirectiveDef} from '../render3';
import {assertDomNode} from '../render3/assert';
import {getComponent, getInjector, getLocalRefs, loadContext} from '../render3/discovery_utils';
import {TNode, TNodeFlags} from '../render3/interfaces/node';
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/discovery_utils';
import {TNode} from '../render3/interfaces/node';
import {StylingIndex} from '../render3/interfaces/styling';
import {TVIEW} from '../render3/interfaces/view';
import {getProp, getValue, isClassBased} from '../render3/styling/class_and_style_bindings';
import {getStylingContext} from '../render3/styling/util';
import {DebugContext} from '../view/index';
export class EventListener {
@ -204,7 +206,7 @@ class DebugNode__POST_R3__ implements DebugNode {
constructor(nativeNode: Node) { this.nativeNode = nativeNode; }
get parent(): DebugElement|null {
const parent = this.nativeNode.parentNode as HTMLElement;
const parent = this.nativeNode.parentNode as Element;
return parent ? new DebugElement__POST_R3__(parent) : null;
}
@ -212,46 +214,17 @@ class DebugNode__POST_R3__ implements DebugNode {
get componentInstance(): any {
const nativeElement = this.nativeNode;
return nativeElement && getComponent(nativeElement as HTMLElement);
}
get context(): any {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
return nativeElement && getComponent(nativeElement as Element);
}
get context(): any { return getContext(this.nativeNode as Element); }
get listeners(): EventListener[] {
// TODO: add real implementation;
// https://angular-team.atlassian.net/browse/FW-719
return [];
return getListeners(this.nativeNode as Element).filter(isBrowserEvents);
}
get references(): {[key: string]: any;} { return getLocalRefs(this.nativeNode); }
get providerTokens(): any[] {
// TODO move to discoverable utils
const context = loadContext(this.nativeNode as HTMLElement, false) !;
if (!context) return [];
const lView = context.lView;
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode;
const providerTokens: any[] = [];
const nodeFlags = tNode.flags;
const startIndex = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
const endIndex = startIndex + directiveCount;
for (let i = startIndex; i < endIndex; i++) {
let value = tView.data[i];
if (isDirectiveDefHack(value)) {
// The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
// design flaw. We should always store same type so that we can be monomorphic. The issue
// is that for Components/Directives we store the def instead the type. The correct behavior
// is that we should always be storing injectable type in this location.
value = value.type;
}
providerTokens.push(value);
}
return providerTokens;
}
get providerTokens(): any[] { return getInjectionTokens(this.nativeNode as Element); }
}
class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugElement {
@ -264,10 +237,10 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
return this.nativeNode.nodeType == Node.ELEMENT_NODE ? this.nativeNode as Element : null;
}
get name(): string { return (this.nativeElement as HTMLElement).nodeName; }
get name(): string { return this.nativeElement !.nodeName; }
get properties(): {[key: string]: any;} {
const context = loadContext(this.nativeNode) !;
const context = loadLContext(this.nativeNode) !;
const lView = context.lView;
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode;
@ -278,18 +251,77 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
}
get attributes(): {[key: string]: string | null;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const attributes: {[key: string]: string | null;} = {};
const element = this.nativeElement;
if (element) {
const eAttrs = element.attributes;
for (let i = 0; i < eAttrs.length; i++) {
const attr = eAttrs[i];
attributes[attr.name] = attr.value;
}
}
return attributes;
}
get classes(): {[key: string]: boolean;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const classes: {[key: string]: boolean;} = {};
const element = this.nativeElement;
if (element) {
const lContext = loadLContextFromNode(element);
const lNode = lContext.lView[lContext.nodeIndex];
const stylingContext = getStylingContext(lContext.nodeIndex, lContext.lView);
if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) {
if (isClassBased(lNode, i)) {
const className = getProp(lNode, i);
const value = getValue(lNode, i);
if (typeof value == 'boolean') {
// we want to ignore `null` since those don't overwrite the values.
classes[className] = value;
}
}
}
} else {
// Fallback, just read DOM.
const eClasses = element.classList;
for (let i = 0; i < eClasses.length; i++) {
classes[eClasses[i]] = true;
}
}
}
return classes;
}
get styles(): {[key: string]: string | null;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const styles: {[key: string]: string | null;} = {};
const element = this.nativeElement;
if (element) {
const lContext = loadLContextFromNode(element);
const lNode = lContext.lView[lContext.nodeIndex];
const stylingContext = getStylingContext(lContext.nodeIndex, lContext.lView);
if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) {
if (!isClassBased(lNode, i)) {
const styleName = getProp(lNode, i);
const value = getValue(lNode, i) as string | null;
if (value !== null) {
// we want to ignore `null` since those don't overwrite the values.
styles[styleName] = value;
}
}
}
} else {
// Fallback, just read DOM.
const eStyles = (element as HTMLElement).style;
for (let i = 0; i < eStyles.length; i++) {
const name = eStyles.item(i);
styles[name] = eStyles.getPropertyValue(name);
}
}
}
return styles;
}
get childNodes(): DebugNode[] {
@ -332,24 +364,14 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
}
triggerEventHandler(eventName: string, eventObj: any): void {
// This is a hack implementation. The correct implementation would bypass the DOM and `TNode`
// information to invoke the listeners directly.
// https://angular-team.atlassian.net/browse/FW-719
const event = document.createEvent('MouseEvent');
event.initEvent(eventName, true, true);
(this.nativeElement as HTMLElement).dispatchEvent(event);
this.listeners.forEach((listener) => {
if (listener.name === eventName) {
listener.callback(eventObj);
}
});
}
}
/**
* This function should not exist because it is megamorphic and only mostly correct.
*
* See call site for more info.
*/
function isDirectiveDefHack(obj: any): obj is DirectiveDef<any> {
return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined;
}
function _queryNodeChildrenR3(
parentNode: DebugNode, predicate: Predicate<DebugNode>, matches: DebugNode[],
elementsOnly: boolean) {

View File

@ -17,12 +17,12 @@ import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {publishDefaultGlobalUtils} from './global_utils';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util';
@ -235,7 +235,9 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
const dirIndex = rootTView.data.length - 1;
queueInitHooks(dirIndex, def.onInit, def.doCheck, rootTView);
queueLifecycleHooks(dirIndex << TNodeFlags.DirectiveStartingIndexShift | 1, rootTView);
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
// LNode).
queueLifecycleHooks(rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
}
/**

View File

@ -36,7 +36,7 @@ import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatched
*
* @param target Component, Directive or DOM Node.
*/
export function getContext(target: any): LContext|null {
export function getLContext(target: any): LContext|null {
let mpValue = readPatchedData(target);
if (mpValue) {
// only when it's an array is it considered an LView instance
@ -250,8 +250,8 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
// list of directives for the instance.
let tNode = lView[TVIEW].firstChild;
while (tNode) {
const directiveIndexStart = getDirectiveStartIndex(tNode);
const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart);
const directiveIndexStart = tNode.directiveStart;
const directiveIndexEnd = tNode.directiveEnd;
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (lView[i] === directiveInstance) {
return tNode.index;
@ -273,16 +273,16 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
export function getDirectivesAtNodeIndex(
nodeIndex: number, lView: LView, includeComponents: boolean): any[]|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
let directiveStartIndex = tNode.directiveStart;
if (directiveStartIndex == 0) return EMPTY_ARRAY;
const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex);
const directiveEndIndex = tNode.directiveEnd;
if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++;
return lView.slice(directiveStartIndex, directiveEndIndex);
}
export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
let directiveStartIndex = tNode.directiveStart;
return tNode.flags & TNodeFlags.isComponent ? lView[directiveStartIndex] : null;
}
@ -305,18 +305,3 @@ export function discoverLocalRefs(lView: LView, nodeIndex: number): {[key: strin
return null;
}
function getDirectiveStartIndex(tNode: TNode): number {
// the tNode instances store a flag value which then has a
// pointer which tells the starting index of where all the
// active directives are in the master directive array
return tNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
}
function getDirectiveEndIndex(tNode: TNode, startIndex: number): number {
// The end value is also a part of the same flag
// (see `TNodeFlags` to see how the flag bit shifting
// values are used).
const count = tNode.flags & TNodeFlags.DirectiveCountMask;
return count ? (startIndex + count) : -1;
}

View File

@ -393,30 +393,32 @@ export function getOrCreateInjectable<T>(
const NOT_FOUND = {};
function searchTokensOnInjector<T>(
injectorIndex: number, injectorView: LView, token: Type<T>| InjectionToken<T>,
injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>,
previousTView: TView | null) {
const currentTView = injectorView[TVIEW];
const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
// First, we step through providers
let canAccessViewProviders = false;
// We need to determine if view providers can be accessed by the starting element.
// It happens in 2 cases:
// 1) On the initial element injector , if we are instantiating a token which can see the
// viewProviders of the component of that element. Such token are:
// - the component itself (but not other directives)
// - viewProviders tokens of the component (but not providers tokens)
// 2) Upper in the element injector tree, if the starting element is actually in the view of
// the current element. To determine this, we track the transition of view during the climb,
// and check the host node of the current view to identify component views.
if (previousTView == null && isComponent(tNode) && includeViewProviders ||
previousTView != null && previousTView != currentTView &&
(currentTView.node == null || currentTView.node.type === TNodeType.Element)) {
canAccessViewProviders = true;
}
const injectableIdx =
locateDirectiveOrProvider(tNode, injectorView, token, canAccessViewProviders);
// First, we need to determine if view providers can be accessed by the starting element.
// There are two possibities
const canAccessViewProviders = previousTView == null ?
// 1) This is the first invocation `previousTView == null` which means that we are at the
// `TNode` of where injector is starting to look. In such a case the only time we are allowed
// to look into the ViewProviders is if:
// - we are on a component
// - AND the injector set `includeViewProviders` to true (implying that the token can see
// ViewProviders because it is the Component or a Service which itself was declared in
// ViewProviders)
(isComponent(tNode) && includeViewProviders) :
// 2) `previousTView != null` which means that we are now walking across the parent nodes.
// In such a case we are only allowed to look into the ViewProviders if:
// - We just crossed from child View to Parent View `previousTView != currentTView`
// - AND the parent TNode is an Element.
// This means that we just came from the Component's View and therefore are allowed to see
// into the ViewProviders.
(previousTView != currentTView && (tNode.type === TNodeType.Element));
const injectableIdx = locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders);
if (injectableIdx !== null) {
return getNodeInjectable(currentTView.data, injectorView, injectableIdx, tNode as TElementNode);
return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode);
} else {
return NOT_FOUND;
}
@ -439,17 +441,17 @@ export function locateDirectiveOrProvider<T>(
const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data;
const startInjectables = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const startDirectives = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const injectablesStart = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const directivesStart = tNode.directiveStart;
const directiveEnd = tNode.directiveEnd;
const cptViewProvidersCount =
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
const startingIndex =
canAccessViewProviders ? startInjectables : startInjectables + cptViewProvidersCount;
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
for (let i = startingIndex; i < startDirectives + directiveCount; i++) {
canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
for (let i = startingIndex; i < directiveEnd; i++) {
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
if (i < startDirectives && token === providerTokenOrDef ||
i >= startDirectives && (providerTokenOrDef as DirectiveDef<any>).type === token) {
if (i < directivesStart && token === providerTokenOrDef ||
i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) {
return i;
}
}

View File

@ -75,12 +75,11 @@ function resolveProvider(
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
let providerFactory: () => any = providerToFactory(provider);
const previousOrParentTNode = getPreviousOrParentTNode();
const beginIndex =
previousOrParentTNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const endIndex = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const tNode = getPreviousOrParentTNode();
const beginIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const endIndex = tNode.directiveStart;
const cptViewProvidersCount =
previousOrParentTNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
if (isTypeProvider(provider) || !provider.multi) {
// Single provider case: the factory is created and pushed immediately
@ -91,14 +90,13 @@ function resolveProvider(
if (existingFactoryIndex == -1) {
diPublicInInjector(
getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
lView),
tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
lView, token);
tInjectables.push(token);
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) {
previousOrParentTNode.providerIndexes +=
TNodeProviderIndexes.CptViewProvidersCountShifter;
tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
}
lInjectablesBlueprint.push(factory);
lView.push(factory);
@ -142,8 +140,7 @@ function resolveProvider(
// Cases 1.a and 2.a
diPublicInInjector(
getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
lView),
tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
lView, token);
const factory = multiFactory(
isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
@ -152,10 +149,10 @@ function resolveProvider(
lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
}
tInjectables.push(token);
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) {
previousOrParentTNode.providerIndexes +=
TNodeProviderIndexes.CptViewProvidersCountShifter;
tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
}
lInjectablesBlueprint.push(factory);
lView.push(factory);

View File

@ -8,10 +8,12 @@
import {Injector} from '../di/injector';
import {assertDefined} from './assert';
import {discoverLocalRefs, getComponentAtNodeIndex, getContext, getDirectivesAtNodeIndex} from './context_discovery';
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery';
import {LContext} from './interfaces/context';
import {TElementNode} from './interfaces/node';
import {CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {DirectiveDef} from './interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE} from './interfaces/injector';
import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node';
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {readPatchedLView, stringify} from './util';
import {NodeInjector} from './view_engine_compatibility';
@ -20,7 +22,7 @@ import {NodeInjector} from './view_engine_compatibility';
* Returns the component instance associated with a given DOM host element.
* Elements which don't represent components return `null`.
*
* @param element Host DOM element from which the component should be retrieved for.
* @param element Host DOM element from which the component should be retrieved.
*
* ```
* <my-app>
@ -37,9 +39,7 @@ import {NodeInjector} from './view_engine_compatibility';
* @publicApi
*/
export function getComponent<T = {}>(element: Element): T|null {
if (!(element instanceof Node)) throw new Error('Expecting instance of DOM Node');
const context = loadContext(element) !;
const context = loadLContextFromNode(element);
if (context.component === undefined) {
context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView);
@ -48,6 +48,31 @@ export function getComponent<T = {}>(element: Element): T|null {
return context.component as T;
}
/**
* Returns the component instance associated with a given DOM host element.
* Elements which don't represent components return `null`.
*
* @param element Host DOM element from which the component should be retrieved.
*
* ```
* <my-app>
* #VIEW
* <div>
* <child-comp></child-comp>
* </div>
* </mp-app>
*
* expect(getComponent(<child-comp>) instanceof ChildComponent).toBeTruthy();
* expect(getComponent(<my-app>) instanceof MyApp).toBeTruthy();
* ```
*
* @publicApi
*/
export function getContext<T = {}>(element: Element): T|null {
const context = loadLContextFromNode(element) !;
return context.lView[CONTEXT] as T;
}
/**
* Returns the component instance associated with view which owns the DOM element (`null`
* otherwise).
@ -69,7 +94,7 @@ export function getComponent<T = {}>(element: Element): T|null {
* @publicApi
*/
export function getViewComponent<T = {}>(element: Element | {}): T|null {
const context = loadContext(element) !;
const context = loadLContext(element) !;
let lView: LView = context.lView;
while (lView[PARENT] && lView[HOST] === null) {
// As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf`
@ -87,8 +112,8 @@ export function getViewComponent<T = {}>(element: Element | {}): T|null {
*
*/
export function getRootContext(target: LView | {}): RootContext {
const lView = Array.isArray(target) ? target : loadContext(target) !.lView;
const rootLView = getRootView(lView);
const lViewData = Array.isArray(target) ? target : loadLContext(target) !.lView;
const rootLView = getRootView(lViewData);
return rootLView[CONTEXT] as RootContext;
}
@ -113,12 +138,40 @@ export function getRootComponents(target: {}): any[] {
* @publicApi
*/
export function getInjector(target: {}): Injector {
const context = loadContext(target);
const context = loadLContext(target);
const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode;
return new NodeInjector(tNode, context.lView);
}
/**
* Retrieve a set of injection tokens at a given DOM node.
*
* @param element Element for which the injection tokens should be retrieved.
* @publicApi
*/
export function getInjectionTokens(element: Element): any[] {
const context = loadLContext(element, false);
if (!context) return [];
const lView = context.lView;
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode;
const providerTokens: any[] = [];
const startIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const endIndex = tNode.directiveEnd;
for (let i = startIndex; i < endIndex; i++) {
let value = tView.data[i];
if (isDirectiveDefHack(value)) {
// The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
// design flaw. We should always store same type so that we can be monomorphic. The issue
// is that for Components/Directives we store the def instead the type. The correct behavior
// is that we should always be storing injectable type in this location.
value = value.type;
}
providerTokens.push(value);
}
return providerTokens;
}
/**
* Retrieves directives associated with a given DOM host element.
*
@ -127,7 +180,7 @@ export function getInjector(target: {}): Injector {
* @publicApi
*/
export function getDirectives(target: {}): Array<{}> {
const context = loadContext(target) !;
const context = loadLContext(target) !;
if (context.directives === undefined) {
context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false);
@ -141,10 +194,10 @@ export function getDirectives(target: {}): Array<{}> {
* Throws if a given target doesn't have associated LContext.
*
*/
export function loadContext(target: {}): LContext;
export function loadContext(target: {}, throwOnNotFound: false): LContext|null;
export function loadContext(target: {}, throwOnNotFound: boolean = true): LContext|null {
const context = getContext(target);
export function loadLContext(target: {}): LContext;
export function loadLContext(target: {}, throwOnNotFound: false): LContext|null;
export function loadLContext(target: {}, throwOnNotFound: boolean = true): LContext|null {
const context = getLContext(target);
if (!context && throwOnNotFound) {
throw new Error(
ngDevMode ? `Unable to find context associated with ${stringify(target)}` :
@ -185,7 +238,7 @@ export function getRootView(componentOrView: LView | {}): LView {
* @publicApi
*/
export function getLocalRefs(target: {}): {[key: string]: any} {
const context = loadContext(target) !;
const context = loadLContext(target) !;
if (context.localRefs === undefined) {
context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex);
@ -205,7 +258,7 @@ export function getLocalRefs(target: {}): {[key: string]: any} {
* @publicApi
*/
export function getHostElement<T>(directive: T): Element {
return getContext(directive) !.native as never as Element;
return getLContext(directive) !.native as never as Element;
}
/**
@ -221,4 +274,89 @@ export function getHostElement<T>(directive: T): Element {
export function getRenderedText(component: any): string {
const hostElement = getHostElement(component);
return hostElement.textContent || '';
}
}
export function loadLContextFromNode(node: Node): LContext {
if (!(node instanceof Node)) throw new Error('Expecting instance of DOM Node');
return loadLContext(node) !;
}
export interface Listener {
name: string;
element: Element;
callback: (value: any) => any;
useCapture: boolean|null;
}
export function isBrowserEvents(listener: Listener): boolean {
// Browser events are those which don't have `useCapture` as boolean.
return typeof listener.useCapture === 'boolean';
}
/**
* Retrieves a list of DOM listeners.
*
* ```
* <my-app>
* #VIEW
* <div (click)="doSomething()">
* </div>
* </mp-app>
*
* expect(getListeners(<div>)).toEqual({
* name: 'click',
* element: <div>,
* callback: () => doSomething(),
* useCapture: false
* });
* ```
*
* @param element Element for which the DOM listeners should be retrieved.
* @publicApi
*/
export function getListeners(element: Element): Listener[] {
const lContext = loadLContextFromNode(element);
const lView = lContext.lView;
const tView = lView[TVIEW];
const lCleanup = lView[CLEANUP];
const tCleanup = tView.cleanup;
const listeners: Listener[] = [];
if (tCleanup && lCleanup) {
for (let i = 0; i < tCleanup.length;) {
const firstParam = tCleanup[i++];
const secondParam = tCleanup[i++];
if (typeof firstParam === 'string') {
const name: string = firstParam;
const listenerElement: Element = lView[secondParam];
const callback: (value: any) => any = lCleanup[tCleanup[i++]];
const useCaptureOrIndx = tCleanup[i++];
// if useCaptureOrIndx is boolean then report it as is.
// if useCaptureOrIndx is positive number then it in unsubscribe method
// if useCaptureOrIndx is negative number then it is a Subscription
const useCapture = typeof useCaptureOrIndx === 'boolean' ?
useCaptureOrIndx :
(useCaptureOrIndx >= 0 ? false : null);
if (element == listenerElement) {
listeners.push({element, name, callback, useCapture});
}
}
}
}
listeners.sort(sortListeners);
return listeners;
}
function sortListeners(a: Listener, b: Listener) {
if (a.name == b.name) return 0;
return a.name < b.name ? -1 : 1;
}
/**
* This function should not exist because it is megamorphic and only mostly correct.
*
* See call site for more info.
*/
function isDirectiveDefHack(obj: any): obj is DirectiveDef<any> {
return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined;
}

View File

@ -8,7 +8,7 @@
import {global} from '../util';
import {assertDefined} from './assert';
import {getComponent, getDirectives, getHostElement, getInjector, getPlayers, getRootComponents, getViewComponent, markDirty} from './global_utils_api';
import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getPlayers, getRootComponents, getViewComponent, markDirty} from './global_utils_api';
@ -41,6 +41,8 @@ export function publishDefaultGlobalUtils() {
if (!_published) {
_published = true;
publishGlobalUtil('getComponent', getComponent);
publishGlobalUtil('getContext', getContext);
publishGlobalUtil('getListeners', getListeners);
publishGlobalUtil('getViewComponent', getViewComponent);
publishGlobalUtil('getHostElement', getHostElement);
publishGlobalUtil('getInjector', getInjector);

View File

@ -15,6 +15,6 @@
* file in the public_api_guard test.
*/
export {getComponent, getDirectives, getHostElement, getInjector, getRootComponents, getViewComponent} from './discovery_utils';
export {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from './discovery_utils';
export {markDirty} from './instructions';
export {getPlayers} from './players';

View File

@ -8,10 +8,11 @@
import {assertEqual} from './assert';
import {DirectiveDef} from './interfaces/definition';
import {TNodeFlags} from './interfaces/node';
import {TNode} from './interfaces/node';
import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view';
/**
* If this is the first template pass, any ngOnInit or ngDoCheck hooks will be queued into
* TView.initHooks during directiveCreate.
@ -42,16 +43,12 @@ export function queueInitHooks(
* Loops through the directives on a node and queues all their hooks except ngOnInit
* and ngDoCheck, which are queued separately in directiveCreate.
*/
export function queueLifecycleHooks(flags: number, tView: TView): void {
export function queueLifecycleHooks(tView: TView, tNode: TNode): void {
if (tView.firstTemplatePass) {
const start = flags >> TNodeFlags.DirectiveStartingIndexShift;
const count = flags & TNodeFlags.DirectiveCountMask;
const end = start + count;
// It's necessary to loop through the directives at elementEnd() (rather than processing in
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
// hooks for projected components and directives must be called *before* their hosts.
for (let i = start; i < end; i++) {
for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
queueContentHooks(def, tView, i);
queueViewHooks(def, tView, i);

View File

@ -25,7 +25,7 @@ import {throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
@ -112,7 +112,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
currentElementIndex = -instruction;
// Injector block and providers are taken into account.
const providerCount = (tView.expandoInstructions[++i] as number);
bindingRootIndex += INJECTOR_SIZE + providerCount;
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
} else {
@ -215,6 +215,7 @@ export function createNodeAtIndex(
if (tNode == null) {
const previousOrParentTNode = getPreviousOrParentTNode();
const isParent = getIsParent();
// TODO(misko): Refactor createTNode so that it does not depend on LView.
tNode = tView.data[adjustedIndex] = createTNode(lView, type, adjustedIndex, name, attrs, null);
// Now link ourselves into the tree.
@ -246,10 +247,7 @@ export function createViewNode(index: number, view: LView) {
view[TVIEW].node = createTNode(view, TNodeType.View, index, null, null, null) as TViewNode;
}
setIsParent(true);
const tNode = view[TVIEW].node as TViewNode;
setPreviousOrParentTNode(tNode);
return view[HOST_NODE] = tNode;
return view[HOST_NODE] = view[TVIEW].node as TViewNode;
}
@ -526,7 +524,7 @@ export function elementContainerEnd(): void {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementContainerNode);
}
queueLifecycleHooks(previousOrParentTNode.flags, tView);
queueLifecycleHooks(tView, previousOrParentTNode);
}
/**
@ -800,6 +798,9 @@ export function listener(
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
const lView = getLView();
const tNode = getPreviousOrParentTNode();
const tView = lView[TVIEW];
const firstTemplatePass = tView.firstTemplatePass;
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
@ -808,47 +809,45 @@ export function listener(
const native = getNativeByTNode(tNode, lView) as RElement;
ngDevMode && ngDevMode.rendererAddEventListener++;
const renderer = lView[RENDERER];
const lCleanup = getCleanup(lView);
const lCleanupIndex = lCleanup.length;
let useCaptureOrSubIdx: boolean|number = useCapture;
// In order to match current behavior, native DOM event listeners must be added for all
// events (including outputs).
if (isProceduralRenderer(renderer)) {
const cleanupFn = renderer.listen(native, eventName, listenerFn);
storeCleanupFn(lView, cleanupFn);
lCleanup.push(listenerFn, cleanupFn);
useCaptureOrSubIdx = lCleanupIndex + 1;
} else {
const wrappedListener = wrapListenerWithPreventDefault(listenerFn);
native.addEventListener(eventName, wrappedListener, useCapture);
const cleanupInstances = getCleanup(lView);
cleanupInstances.push(wrappedListener);
if (getFirstTemplatePass()) {
getTViewCleanup(lView).push(
eventName, tNode.index, cleanupInstances !.length - 1, useCapture);
}
lCleanup.push(wrappedListener);
}
tCleanup && tCleanup.push(eventName, tNode.index, lCleanupIndex, useCaptureOrSubIdx);
}
// subscribe to directive outputs
if (tNode.outputs === undefined) {
// if we create TNode here, inputs must be undefined so we know they still need to be
// checked
tNode.outputs = generatePropertyAliases(tNode.flags, BindingDirection.Output);
tNode.outputs = generatePropertyAliases(tNode, BindingDirection.Output);
}
const outputs = tNode.outputs;
let outputData: PropertyAliasValue|undefined;
if (outputs && (outputData = outputs[eventName])) {
createOutput(lView, outputData, listenerFn);
}
}
/**
* Iterates through the outputs associated with a particular event name and subscribes to
* each output.
*/
function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Function): void {
for (let i = 0; i < outputs.length; i += 2) {
ngDevMode && assertDataInRange(lView, outputs[i] as number);
const subscription = lView[outputs[i] as number][outputs[i + 1]].subscribe(listener);
storeCleanupWithContext(lView, subscription, subscription.unsubscribe);
let props: PropertyAliasValue|undefined;
if (outputs && (props = outputs[eventName])) {
const propsLength = props.length;
if (propsLength) {
const lCleanup = getCleanup(lView);
for (let i = 0; i < propsLength; i += 2) {
ngDevMode && assertDataInRange(lView, props[i] as number);
const subscription = lView[props[i] as number][props[i + 1]].subscribe(listenerFn);
const idx = lCleanup.length;
lCleanup.push(listenerFn, subscription);
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
}
}
}
}
@ -860,10 +859,11 @@ function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Funct
* - Index of context we just saved in LView.cleanupInstances
*/
export function storeCleanupWithContext(lView: LView, context: any, cleanupFn: Function): void {
getCleanup(lView).push(context);
const lCleanup = getCleanup(lView);
lCleanup.push(context);
if (lView[TVIEW].firstTemplatePass) {
getTViewCleanup(lView).push(cleanupFn, lView[CLEANUP] !.length - 1);
getTViewCleanup(lView).push(cleanupFn, lCleanup.length - 1);
}
}
@ -900,7 +900,7 @@ export function elementEnd(): void {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementNode);
}
queueLifecycleHooks(previousOrParentTNode.flags, getLView()[TVIEW]);
queueLifecycleHooks(getLView()[TVIEW], previousOrParentTNode);
decreaseElementDepthCount();
}
@ -984,7 +984,7 @@ export function elementProperty<T>(
* @returns the TNode object
*/
export function createTNode(
viewData: LView, type: TNodeType, adjustedIndex: number, tagName: string | null,
lView: LView, type: TNodeType, adjustedIndex: number, tagName: string | null,
attrs: TAttributes | null, tViews: TView[] | null): TNode {
const previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && ngDevMode.tNode++;
@ -993,13 +993,15 @@ export function createTNode(
// Parents cannot cross component boundaries because components will be used in multiple places,
// so it's only set if the view is the same.
const parentInSameView = parent && viewData && parent !== viewData[HOST_NODE];
const parentInSameView = parent && lView && parent !== lView[HOST_NODE];
const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null;
return {
type: type,
index: adjustedIndex,
injectorIndex: tParent ? tParent.injectorIndex : -1,
directiveStart: -1,
directiveEnd: -1,
flags: 0,
providerIndexes: 0,
tagName: tagName,
@ -1044,15 +1046,13 @@ function setNgReflectProperties(lView: LView, element: RElement, propName: strin
* @param Direction direction whether to consider inputs or outputs
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
*/
function generatePropertyAliases(
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
function generatePropertyAliases(tNode: TNode, direction: BindingDirection): PropertyAliases|null {
const tView = getLView()[TVIEW];
const count = tNodeFlags & TNodeFlags.DirectiveCountMask;
let propStore: PropertyAliases|null = null;
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
if (count > 0) {
const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + count;
if (end > start) {
const isInput = direction === BindingDirection.Input;
const defs = tView.data;
@ -1093,7 +1093,7 @@ export function elementClassProp(
}
const val =
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
updateElementClassProp(getStylingContext(index, getLView()), classIndex, val);
updateElementClassProp(getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, val);
}
/**
@ -1152,14 +1152,14 @@ export function elementStyling(
if (styleDeclarations && styleDeclarations.length ||
classDeclarations && classDeclarations.length) {
const index = tNode.index - HEADER_OFFSET;
const index = tNode.index;
if (delegateToClassInput(tNode)) {
const lView = getLView();
const stylingContext = getStylingContext(index, lView);
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
setInputsForProperty(lView, tNode.inputs !['class'] !, initialClasses);
}
elementStylingApply(index);
elementStylingApply(index - HEADER_OFFSET);
}
}
@ -1186,7 +1186,7 @@ export function elementStylingApply(index: number, directive?: {}): void {
const lView = getLView();
const isFirstRender = (lView[FLAGS] & LViewFlags.CreationMode) !== 0;
const totalPlayersQueued = renderStyleAndClassBindings(
getStylingContext(index, lView), lView[RENDERER], lView, isFirstRender);
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender);
if (totalPlayersQueued > 0) {
const rootContext = getRootContext(lView);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
@ -1234,7 +1234,8 @@ export function elementStyleProp(
if (directive != undefined) {
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
} else {
updateElementStyleProp(getStylingContext(index, getLView()), styleIndex, valueToAdd);
updateElementStyleProp(
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd);
}
}
@ -1268,7 +1269,7 @@ export function elementStylingMap<T>(
index, classes, styles, directive); // supported in next PR
const lView = getLView();
const tNode = getTNode(index, lView);
const stylingContext = getStylingContext(index, lView);
const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
const classInputVal =
@ -1482,27 +1483,26 @@ function resolveDirectives(
/**
* Instantiate all the directives that were previously resolved on the current node.
*/
function instantiateAllDirectives(tView: TView, viewData: LView, previousOrParentTNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask);
function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
if (!getFirstTemplatePass() && start < end) {
getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, viewData);
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
}
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
if (isComponentDef(def)) {
addComponentLogic(viewData, previousOrParentTNode, def as ComponentDef<any>);
addComponentLogic(lView, tNode, def as ComponentDef<any>);
}
const directive =
getNodeInjectable(tView.data, viewData !, i, previousOrParentTNode as TElementNode);
postProcessDirective(viewData, directive, def, i);
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
postProcessDirective(lView, directive, def, i);
}
}
function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrParentTNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask);
function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const expando = tView.expandoInstructions !;
const firstTemplatePass = getFirstTemplatePass();
for (let i = start; i < end; i++) {
@ -1511,7 +1511,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrP
if (def.hostBindings) {
const previousExpandoLength = expando.length;
setCurrentDirectiveDef(def);
def.hostBindings !(RenderFlags.Create, directive, previousOrParentTNode.index);
def.hostBindings !(RenderFlags.Create, directive, tNode.index);
setCurrentDirectiveDef(null);
// `hostBindings` function may or may not contain `allocHostVars` call
// (e.g. it may not if it only contains host listeners), so we need to check whether
@ -1715,11 +1715,12 @@ export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: n
'expected node flags to not be initialized');
ngDevMode && assertNotEqual(
numberOfDirectives, TNodeFlags.DirectiveCountMask,
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
'Reached the max number of directives');
// When the first directive is created on a node, save the index
tNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent |
numberOfDirectives;
tNode.flags = flags & TNodeFlags.isComponent;
tNode.directiveStart = index;
tNode.directiveEnd = index + numberOfDirectives;
tNode.providerIndexes = index;
}
@ -1894,7 +1895,7 @@ export function template(
if (currentQueries) {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode);
}
queueLifecycleHooks(tNode.flags, tView);
queueLifecycleHooks(tView, tNode);
setIsParent(false);
}
@ -2842,7 +2843,7 @@ function initializeTNodeInputs(tNode: TNode | null) {
if (tNode) {
if (tNode.inputs === undefined) {
// mark inputs as checked
tNode.inputs = generatePropertyAliases(tNode.flags, BindingDirection.Input);
tNode.inputs = generatePropertyAliases(tNode, BindingDirection.Input);
}
return tNode.inputs;
}

View File

@ -14,7 +14,7 @@ import {LView, TData} from './view';
export const TNODE = 8;
export const PARENT_INJECTOR = 8;
export const INJECTOR_SIZE = 9;
export const INJECTOR_BLOOM_PARENT_SIZE = 9;
/**
* Represents a relative location of parent injector.

View File

@ -28,23 +28,17 @@ export const enum TNodeType {
* Corresponds to the TNode.flags property.
*/
export const enum TNodeFlags {
/** The number of directives on this node is encoded on the least significant bits */
DirectiveCountMask = 0b00000000000000000000111111111111,
/** This bit is set if the node is a component */
isComponent = 0b00000000000000000001000000000000,
isComponent = 0b0001,
/** This bit is set if the node has been projected */
isProjected = 0b00000000000000000010000000000000,
isProjected = 0b0010,
/** This bit is set if the node has any content queries */
hasContentQuery = 0b00000000000000000100000000000000,
hasContentQuery = 0b0100,
/** This bit is set if the node has any directives that contain [class properties */
hasClassInput = 0b00000000000000001000000000000000,
/** The index of the first directive on this node is encoded on the most significant bits */
DirectiveStartingIndexShift = 16,
hasClassInput = 0b1000,
}
/**
@ -128,13 +122,17 @@ export interface TNode {
injectorIndex: number;
/**
* This number stores two values using its bits:
*
* - the number of directives on that node (first 12 bits)
* - the starting index of the node's directives in the directives array (last 20 bits).
*
* These two values are necessary so DI can effectively search the directives associated
* with a node without searching the whole directives array.
* Stores starting index of the directives.
*/
directiveStart: number;
/**
* Stores final exclusive index of the directives.
*/
directiveEnd: number;
/**
* Stores if Node isComponent, isProjected, hasContentQuery and hasClassInput
*/
flags: TNodeFlags;
@ -144,6 +142,7 @@ export interface TNode {
* - the index of the first provider on that node (first 16 bits)
* - the count of view providers from the component on this node (last 16 bits)
*/
// TODO(misko): break this into actual vars.
providerIndexes: TNodeProviderIndexes;
/** The tag name associated with this node. */

View File

@ -446,19 +446,25 @@ export interface TView {
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops).
*
* If it's a native DOM listener being stored:
* 1st index is: event name to remove
* 2nd index is: index of native element in LView.data[]
* 3rd index is: index of wrapped listener function in LView.cleanupInstances[]
* 4th index is: useCapture boolean
* If it's a native DOM listener or output subscription being stored:
* 1st index is: event name `name = tView.cleanup[i+0]`
* 2nd index is: index of native element `element = lView[tView.cleanup[i+1]]`
* 3rd index is: index of listener function `listener = lView[CLEANUP][tView.cleanup[i+2]]`
* 4th index is: `useCaptureOrIndx = tView.cleanup[i+3]`
* `typeof useCaptureOrIndx == 'boolean' : useCapture boolean
* `typeof useCaptureOrIndx == 'number':
* `useCaptureOrIndx >= 0` `removeListener = LView[CLEANUP][useCaptureOrIndx]`
* `useCaptureOrIndx < 0` `subscription = LView[CLEANUP][-useCaptureOrIndx]`
*
* If it's a renderer2 style listener or ViewRef destroy hook being stored:
* 1st index is: index of the cleanup function in LView.cleanupInstances[]
* 2nd index is: null
* 2nd index is: `null`
* `lView[CLEANUP][tView.cleanup[i+0]]()`
*
* If it's an output subscription or query list destroy hook:
* 1st index is: output unsubscribe function / query list destroy function
* 2nd index is: index of function context in LView.cleanupInstances[]
* `tView.cleanup[i+0].call(lView[CLEANUP][tView.cleanup[i+1]])`
*/
cleanup: any[]|null;

View File

@ -473,23 +473,37 @@ function cleanUpView(viewOrContainer: LView | LContainer): void {
/** Removes listeners and unsubscribes from output subscriptions */
function removeListeners(lView: LView): void {
const cleanup = lView[TVIEW].cleanup !;
if (cleanup != null) {
for (let i = 0; i < cleanup.length - 1; i += 2) {
if (typeof cleanup[i] === 'string') {
const tCleanup = lView[TVIEW].cleanup !;
if (tCleanup != null) {
const lCleanup = lView[CLEANUP] !;
for (let i = 0; i < tCleanup.length - 1; i += 2) {
if (typeof tCleanup[i] === 'string') {
// This is a listener with the native renderer
const native = readElementValue(lView[cleanup[i + 1]]);
const listener = lView[CLEANUP] ![cleanup[i + 2]];
native.removeEventListener(cleanup[i], listener, cleanup[i + 3]);
const idx = tCleanup[i + 1];
const listener = lCleanup[tCleanup[i + 2]];
const native = readElementValue(lView[idx]);
const useCaptureOrSubIdx = tCleanup[i + 3];
if (typeof useCaptureOrSubIdx === 'boolean') {
// DOM listener
native.removeEventListener(tCleanup[i], listener, useCaptureOrSubIdx);
} else {
if (useCaptureOrSubIdx >= 0) {
// unregister
lCleanup[useCaptureOrSubIdx]();
} else {
// Subscription
lCleanup[-useCaptureOrSubIdx].unsubscribe();
}
}
i += 2;
} else if (typeof cleanup[i] === 'number') {
} else if (typeof tCleanup[i] === 'number') {
// This is a listener with renderer2 (cleanup fn can be found by index)
const cleanupFn = lView[CLEANUP] ![cleanup[i]];
const cleanupFn = lCleanup[tCleanup[i]];
cleanupFn();
} else {
// This is a cleanup function that is grouped with the index of its context
const context = lView[CLEANUP] ![cleanup[i + 1]];
cleanup[i].call(context);
const context = lCleanup[tCleanup[i + 1]];
tCleanup[i].call(context);
}
}
lView[CLEANUP] = null;

View File

@ -7,7 +7,7 @@
*/
import './ng_dev_mode';
import {getContext} from './context_discovery';
import {getLContext} from './context_discovery';
import {getRootContext} from './discovery_utils';
import {scheduleTick} from './instructions';
import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player';
@ -29,7 +29,7 @@ import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayer
*/
export function addPlayer(
ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void {
const context = getContext(ref);
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return;
@ -54,13 +54,13 @@ export function addPlayer(
* @publicApi
*/
export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] {
const context = getContext(ref);
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return [];
}
const stylingContext = getStylingContext(context.nodeIndex - HEADER_OFFSET, context.lView);
const stylingContext = getStylingContext(context.nodeIndex, context.lView);
const playerContext = stylingContext ? getPlayerContext(stylingContext) : null;
return playerContext ? getPlayersInternal(playerContext) : [];
}

View File

@ -668,7 +668,7 @@ function isDirty(context: StylingContext, index: number): boolean {
return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty;
}
function isClassBased(context: StylingContext, index: number): boolean {
export function isClassBased(context: StylingContext, index: number): boolean {
const adjustedIndex =
index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index;
return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class;
@ -776,11 +776,11 @@ function getPointers(context: StylingContext, index: number): number {
return context[adjustedIndex] as number;
}
function getValue(context: StylingContext, index: number): string|boolean|null {
export function getValue(context: StylingContext, index: number): string|boolean|null {
return context[index + StylingIndex.ValueOffset] as string | boolean | null;
}
function getProp(context: StylingContext, index: number): string {
export function getProp(context: StylingContext, index: number): string {
return context[index + StylingIndex.PropertyOffset] as string;
}

View File

@ -8,7 +8,7 @@
import '../ng_dev_mode';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {getContext} from '../context_discovery';
import {getLContext} from '../context_discovery';
import {ACTIVE_INDEX, LContainer} from '../interfaces/container';
import {LContext} from '../interfaces/context';
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
@ -60,7 +60,7 @@ export function allocStylingContext(
* @param viewData The view to search for the styling context
*/
export function getStylingContext(index: number, viewData: LView): StylingContext {
let storageIndex = index + HEADER_OFFSET;
let storageIndex = index;
let slotValue: LContainer|LView|StylingContext|RElement = viewData[storageIndex];
let wrapper: LContainer|LView|StylingContext = viewData;
@ -73,7 +73,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
return wrapper as StylingContext;
} else {
// This is an LView or an LContainer
const stylingTemplate = getTNode(index, viewData).stylingTemplate;
const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate;
if (wrapper !== viewData) {
storageIndex = HOST;
@ -85,9 +85,10 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
}
}
function isStylingContext(value: LView | LContainer | StylingContext) {
export function isStylingContext(value: any): value is StylingContext {
// Not an LView or an LContainer
return typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number';
return Array.isArray(value) && typeof value[FLAGS] !== 'number' &&
typeof value[ACTIVE_INDEX] !== 'number';
}
export function addPlayerInternal(
@ -152,14 +153,14 @@ export function getPlayersInternal(playerContext: PlayerContext): Player[] {
export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext|
null {
context = context || getContext(target) !;
context = context || getLContext(target) !;
if (!context) {
ngDevMode && throwInvalidRefError();
return null;
}
const {lView, nodeIndex} = context;
const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lView);
const stylingContext = getStylingContext(nodeIndex, lView);
return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext);
}

View File

@ -8,7 +8,7 @@
import {global} from '../util';
import {assertDataInRange, assertDefined} from './assert';
import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
@ -100,6 +100,8 @@ export function getNativeByTNode(tNode: TNode, hostView: LView): RElement|RText|
}
export function getTNode(index: number, view: LView): TNode {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, view[TVIEW].data.length, 'wrong index for TNode');
return view[TVIEW].data[index + HEADER_OFFSET] as TNode;
}
@ -157,6 +159,7 @@ export function getRootContext(viewOrComponent: LView | {}): RootContext {
* a component, directive or a DOM node).
*/
export function readPatchedData(target: any): LView|LContext|null {
ngDevMode && assertDefined(target, 'Target expected');
return target[MONKEY_PATCH_KEY_NAME];
}

View File

@ -355,7 +355,7 @@ export function injectChangeDetectorRef(): ViewEngine_ChangeDetectorRef {
export function createViewRef(
hostTNode: TNode, hostView: LView, context: any): ViewEngine_ChangeDetectorRef {
if (isComponent(hostTNode)) {
const componentIndex = hostTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const componentIndex = hostTNode.directiveStart;
const componentView = getComponentViewByIndex(hostTNode.index, hostView);
return new ViewRef(componentView, context, componentIndex);
} else if (hostTNode.type === TNodeType.Element) {

View File

@ -12,7 +12,7 @@ import {stringify} from '../render3/util';
import {BypassType, allowSanitizationBypass} from './bypass';
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
import {SecurityContext} from './security';
import {Sanitizer, SecurityContext} from './security';
import {StyleSanitizeFn, _sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
@ -32,7 +32,7 @@ import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
* and urls have been removed.
*/
export function sanitizeHtml(unsafeHtml: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.HTML, unsafeHtml) || '';
}
@ -56,7 +56,7 @@ export function sanitizeHtml(unsafeHtml: any): string {
* dangerous javascript and urls have been removed.
*/
export function sanitizeStyle(unsafeStyle: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
}
@ -81,7 +81,7 @@ export function sanitizeStyle(unsafeStyle: any): string {
* all of the dangerous javascript has been removed.
*/
export function sanitizeUrl(unsafeUrl: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.URL, unsafeUrl) || '';
}
@ -101,7 +101,7 @@ export function sanitizeUrl(unsafeUrl: any): string {
* only trusted `url`s have been allowed to pass.
*/
export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
}
@ -122,7 +122,7 @@ export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
* because only trusted `scripts` have been allowed to pass.
*/
export function sanitizeScript(unsafeScript: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
}
@ -145,3 +145,8 @@ export const defaultStyleSanitizer = (function(prop: string, value?: string): st
return sanitizeStyle(value);
} as StyleSanitizeFn);
function getSanitizer(): Sanitizer|null {
const lView = getLView();
return lView && lView[SANITIZER];
}