From b2d6f43b4994d2f9ca43c7ead8e5667b65a875c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Wed, 28 Nov 2018 15:54:38 -0800 Subject: [PATCH] fix(ivy): Implement remaining methods for DebugNode (#27387) PR Close #27387 --- .../core/src/core_render3_private_export.ts | 2 +- packages/core/src/debug/debug_node.ts | 142 ++++++++------ packages/core/src/render3/component.ts | 10 +- .../core/src/render3/context_discovery.ts | 27 +-- packages/core/src/render3/di.ts | 58 +++--- packages/core/src/render3/di_setup.ts | 27 ++- packages/core/src/render3/discovery_utils.ts | 178 ++++++++++++++++-- packages/core/src/render3/global_utils.ts | 4 +- packages/core/src/render3/global_utils_api.ts | 2 +- packages/core/src/render3/hooks.ts | 11 +- packages/core/src/render3/instructions.ts | 129 ++++++------- .../core/src/render3/interfaces/injector.ts | 2 +- packages/core/src/render3/interfaces/node.ts | 33 ++-- packages/core/src/render3/interfaces/view.ts | 18 +- .../core/src/render3/node_manipulation.ts | 36 ++-- packages/core/src/render3/players.ts | 8 +- .../styling/class_and_style_bindings.ts | 6 +- packages/core/src/render3/styling/util.ts | 15 +- packages/core/src/render3/util.ts | 5 +- .../src/render3/view_engine_compatibility.ts | 2 +- .../core/src/sanitization/sanitization.ts | 17 +- .../bundle.golden_symbols.json | 22 +-- .../hello_world/bundle.golden_symbols.json | 2 +- .../hello_world_r2/bundle.golden_symbols.json | 2 +- .../bundling/todo/bundle.golden_symbols.json | 8 +- .../todo_r2/bundle.golden_symbols.json | 42 +++-- packages/core/test/debug/debug_node_spec.ts | 86 ++++----- .../core/test/render3/discovery_utils_spec.ts | 49 ++++- .../core/test/render3/global_utils_spec.ts | 6 +- .../core/test/render3/host_binding_spec.ts | 2 +- .../core/test/render3/integration_spec.ts | 48 ++--- packages/core/test/render3/render_util.ts | 4 +- .../styling/class_and_style_bindings_spec.ts | 4 +- tools/public_api_guard/global_utils.d.ts | 4 + 34 files changed, 605 insertions(+), 406 deletions(-) diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 283a613159..ec9c758776 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -154,7 +154,7 @@ export { } from './sanitization/bypass'; export { - getContext as ɵgetContext + getLContext as ɵgetLContext } from './render3/context_discovery'; export { diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index 6068e11ef0..9623b44846 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -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 { - return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined; -} - function _queryNodeChildrenR3( parentNode: DebugNode, predicate: Predicate, matches: DebugNode[], elementsOnly: boolean) { diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index a36af73f38..595d1ac598 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -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): 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); } /** diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index 4837c7553e..812b3c21d1 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -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; -} diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 2fff6fadca..fd3d172ba3 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -393,30 +393,32 @@ export function getOrCreateInjectable( const NOT_FOUND = {}; function searchTokensOnInjector( - injectorIndex: number, injectorView: LView, token: Type| InjectionToken, + injectorIndex: number, lView: LView, token: Type| InjectionToken, 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( 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| Type| DirectiveDef; - if (i < startDirectives && token === providerTokenOrDef || - i >= startDirectives && (providerTokenOrDef as DirectiveDef).type === token) { + if (i < directivesStart && token === providerTokenOrDef || + i >= directivesStart && (providerTokenOrDef as DirectiveDef).type === token) { return i; } } diff --git a/packages/core/src/render3/di_setup.ts b/packages/core/src/render3/di_setup.ts index 9968053e98..58ad14c41a 100644 --- a/packages/core/src/render3/di_setup.ts +++ b/packages/core/src/render3/di_setup.ts @@ -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); diff --git a/packages/core/src/render3/discovery_utils.ts b/packages/core/src/render3/discovery_utils.ts index 82128939ad..883acd193c 100644 --- a/packages/core/src/render3/discovery_utils.ts +++ b/packages/core/src/render3/discovery_utils.ts @@ -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. * * ``` * @@ -37,9 +39,7 @@ import {NodeInjector} from './view_engine_compatibility'; * @publicApi */ export function getComponent(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(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. + * + * ``` + * + * #VIEW + *
+ * + *
+ * + * + * expect(getComponent() instanceof ChildComponent).toBeTruthy(); + * expect(getComponent() instanceof MyApp).toBeTruthy(); + * ``` + * + * @publicApi + */ +export function getContext(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(element: Element): T|null { * @publicApi */ export function getViewComponent(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(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(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(directive: T): Element { export function getRenderedText(component: any): string { const hostElement = getHostElement(component); return hostElement.textContent || ''; -} \ No newline at end of file +} + +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. + * + * ``` + * + * #VIEW + *
+ *
+ * + * + * expect(getListeners(
)).toEqual({ + * name: 'click', + * element:
, + * 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 { + return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined; +} diff --git a/packages/core/src/render3/global_utils.ts b/packages/core/src/render3/global_utils.ts index 666c11f44d..7abd6f9189 100644 --- a/packages/core/src/render3/global_utils.ts +++ b/packages/core/src/render3/global_utils.ts @@ -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); diff --git a/packages/core/src/render3/global_utils_api.ts b/packages/core/src/render3/global_utils_api.ts index b85207d2a4..1279e05f83 100644 --- a/packages/core/src/render3/global_utils_api.ts +++ b/packages/core/src/render3/global_utils_api.ts @@ -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'; diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index ad910ca5bb..77a10e98c4 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -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; queueContentHooks(def, tView, i); queueViewHooks(def, tView, i); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index d5de0dc541..eadc65eca3 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -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( * @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) : (!!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( 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; if (isComponentDef(def)) { - addComponentLogic(viewData, previousOrParentTNode, def as ComponentDef); + addComponentLogic(lView, tNode, def as ComponentDef); } - 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; } diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index e790c145a9..043cd39d8f 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -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. diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 007564cdda..ae8b49682f 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -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. */ diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 915f474c63..9ef2e94d14 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -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; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 681b9678cc..839bada777 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -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; diff --git a/packages/core/src/render3/players.ts b/packages/core/src/render3/players.ts index feb239501e..b703ac1baa 100644 --- a/packages/core/src/render3/players.ts +++ b/packages/core/src/render3/players.ts @@ -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) : []; } diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index ed4acc318d..bf2046dd86 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -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; } diff --git a/packages/core/src/render3/styling/util.ts b/packages/core/src/render3/styling/util.ts index 8c88b1972b..19a2fede6c 100644 --- a/packages/core/src/render3/styling/util.ts +++ b/packages/core/src/render3/styling/util.ts @@ -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); } diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index f0c1e82344..32c520bfa1 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -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]; } diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 422f6dba8e..c782f4bfbf 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -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) { diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index df1c85fcd2..5e03458356 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -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]; +} \ No newline at end of file diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 01af8add4f..763775b61d 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -84,7 +84,7 @@ "name": "INJECTOR" }, { - "name": "INJECTOR_SIZE" + "name": "INJECTOR_BLOOM_PARENT_SIZE" }, { "name": "InjectFlags" @@ -416,9 +416,6 @@ { "name": "createNodeAtIndex" }, - { - "name": "createOutput" - }, { "name": "createRootComponent" }, @@ -605,21 +602,12 @@ { "name": "getContainerRenderParent" }, - { - "name": "getContext" - }, { "name": "getCreationMode" }, { "name": "getDirectiveDef" }, - { - "name": "getDirectiveEndIndex" - }, - { - "name": "getDirectiveStartIndex" - }, { "name": "getDirectivesAtNodeIndex" }, @@ -656,6 +644,9 @@ { "name": "getLContainer" }, + { + "name": "getLContext" + }, { "name": "getLView" }, @@ -924,7 +915,7 @@ "name": "listener" }, { - "name": "loadContext" + "name": "loadLContext" }, { "name": "locateDirectiveOrProvider" @@ -1148,9 +1139,6 @@ { "name": "storeCleanupFn" }, - { - "name": "storeCleanupWithContext" - }, { "name": "stringify" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 0488fe037e..47a2cc3df3 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -51,7 +51,7 @@ "name": "INJECTOR" }, { - "name": "INJECTOR_SIZE" + "name": "INJECTOR_BLOOM_PARENT_SIZE" }, { "name": "MONKEY_PATCH_KEY_NAME" diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 36d2b1d197..17107811f9 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -171,7 +171,7 @@ "name": "INJECTOR$1" }, { - "name": "INJECTOR_SIZE" + "name": "INJECTOR_BLOOM_PARENT_SIZE" }, { "name": "Inject" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 03ead4a7ba..ab7a8a67a2 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -72,7 +72,7 @@ "name": "INJECTOR" }, { - "name": "INJECTOR_SIZE" + "name": "INJECTOR_BLOOM_PARENT_SIZE" }, { "name": "InjectFlags" @@ -473,9 +473,6 @@ { "name": "createNodeAtIndex" }, - { - "name": "createOutput" - }, { "name": "createRootComponent" }, @@ -1169,9 +1166,6 @@ { "name": "storeCleanupFn" }, - { - "name": "storeCleanupWithContext" - }, { "name": "stringify" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index ecdaeab2bf..d47ef9743c 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -381,7 +381,7 @@ "name": "INJECTOR$1" }, { - "name": "INJECTOR_SIZE" + "name": "INJECTOR_BLOOM_PARENT_SIZE" }, { "name": "INSPECT_GLOBAL_NAME" @@ -1379,9 +1379,6 @@ { "name": "createNodeAtIndex" }, - { - "name": "createOutput" - }, { "name": "createPlatform" }, @@ -1736,12 +1733,6 @@ { "name": "getDirectiveDef" }, - { - "name": "getDirectiveEndIndex" - }, - { - "name": "getDirectiveStartIndex" - }, { "name": "getDirectivesAtNodeIndex" }, @@ -1775,6 +1766,9 @@ { "name": "getInjectableDef" }, + { + "name": "getInjectionTokens" + }, { "name": "getInjector" }, @@ -1790,6 +1784,9 @@ { "name": "getLContainer" }, + { + "name": "getLContext" + }, { "name": "getLView" }, @@ -1799,6 +1796,9 @@ { "name": "getLastDefinedValue" }, + { + "name": "getListeners" + }, { "name": "getLocalRefs" }, @@ -2120,6 +2120,12 @@ { "name": "isBlackListedEvent" }, + { + "name": "isBrowserEvents" + }, + { + "name": "isClassBased" + }, { "name": "isComponent" }, @@ -2256,10 +2262,13 @@ "name": "listener" }, { - "name": "loadContext" + "name": "loadInternal" }, { - "name": "loadInternal" + "name": "loadLContext" + }, + { + "name": "loadLContextFromNode" }, { "name": "localeEn" @@ -2348,9 +2357,6 @@ { "name": "noopScope" }, - { - "name": "notImplemented" - }, { "name": "observable" }, @@ -2621,6 +2627,9 @@ { "name": "shouldSearchParent" }, + { + "name": "sortListeners" + }, { "name": "staticError" }, @@ -2633,9 +2642,6 @@ { "name": "storeCleanupFn" }, - { - "name": "storeCleanupWithContext" - }, { "name": "strToNumber" }, diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index 815a5f14d0..d96cdb5a23 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -13,7 +13,6 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/src/matchers'; -import {fixmeIvy} from '@angular/private/testing'; @Injectable() class Logger { @@ -259,35 +258,32 @@ class TestApp { expect(list.children.length).toEqual(3); }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should list element attributes', () => { - fixture = TestBed.createComponent(TestApp); - fixture.detectChanges(); - const bankElem = fixture.debugElement.children[0]; + it('should list element attributes', () => { + fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + const bankElem = fixture.debugElement.children[0]; - expect(bankElem.attributes['bank']).toEqual('RBC'); - expect(bankElem.attributes['account']).toEqual('4747'); - }); + expect(bankElem.attributes['bank']).toEqual('RBC'); + expect(bankElem.attributes['account']).toEqual('4747'); + }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should list element classes', () => { - fixture = TestBed.createComponent(TestApp); - fixture.detectChanges(); - const bankElem = fixture.debugElement.children[0]; + it('should list element classes', () => { + fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + const bankElem = fixture.debugElement.children[0]; - expect(bankElem.classes['closed']).toBe(true); - expect(bankElem.classes['open']).toBe(false); - }); + expect(bankElem.classes['closed']).toBe(true); + expect(bankElem.classes['open']).toBe(false); + }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should list element styles', () => { - fixture = TestBed.createComponent(TestApp); - fixture.detectChanges(); - const bankElem = fixture.debugElement.children[0]; + it('should list element styles', () => { + fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + const bankElem = fixture.debugElement.children[0]; - expect(bankElem.styles['width']).toEqual('200px'); - expect(bankElem.styles['color']).toEqual('red'); - }); + expect(bankElem.styles['width']).toEqual('200px'); + expect(bankElem.styles['color']).toEqual('red'); + }); it('should query child elements by css', () => { fixture = TestBed.createComponent(ParentComp); @@ -312,13 +308,12 @@ class TestApp { expect(getDOM().hasClass(childTestEls[3].nativeElement, 'childnested')).toBe(true); }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should list providerTokens', () => { - fixture = TestBed.createComponent(ParentComp); - fixture.detectChanges(); + it('should list providerTokens', () => { + fixture = TestBed.createComponent(ParentComp); + fixture.detectChanges(); - expect(fixture.debugElement.providerTokens).toContain(Logger); - }); + expect(fixture.debugElement.providerTokens).toContain(Logger); + }); it('should list locals', () => { fixture = TestBed.createComponent(LocalsComp); @@ -327,25 +322,22 @@ class TestApp { expect(fixture.debugElement.children[0].references !['alice']).toBeAnInstanceOf(MyDir); }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should allow injecting from the element injector', () => { - fixture = TestBed.createComponent(ParentComp); - fixture.detectChanges(); + it('should allow injecting from the element injector', () => { + fixture = TestBed.createComponent(ParentComp); + fixture.detectChanges(); - expect(((fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([ - 'parent', 'nestedparent', 'child', 'nestedchild' - ]); - }); + expect(((fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([ + 'parent', 'nestedparent', 'child', 'nestedchild' + ]); + }); - fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && - it('should list event listeners', () => { - fixture = TestBed.createComponent(EventsComp); - fixture.detectChanges(); + it('should list event listeners', () => { + fixture = TestBed.createComponent(EventsComp); + fixture.detectChanges(); - expect(fixture.debugElement.children[0].listeners.length).toEqual(1); - expect(fixture.debugElement.children[1].listeners.length).toEqual(1); - - }); + expect(fixture.debugElement.children[0].listeners.length).toEqual(1); + expect(fixture.debugElement.children[1].listeners.length).toEqual(1); + }); it('should trigger event handlers', () => { fixture = TestBed.createComponent(EventsComp); diff --git a/packages/core/test/render3/discovery_utils_spec.ts b/packages/core/test/render3/discovery_utils_spec.ts index f0c1229d35..ceaf899dae 100644 --- a/packages/core/test/render3/discovery_utils_spec.ts +++ b/packages/core/test/render3/discovery_utils_spec.ts @@ -5,18 +5,16 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {ReferenceFilter} from '@angular/compiler'; import {createInjector} from '@angular/core'; import {StaticInjector} from '../../src/di/injector'; -import {getComponent, getDirectives, getInjector, getLocalRefs, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; +import {getComponent, getContext, getDirectives, getInjectionTokens, getInjector, getListeners, getLocalRefs, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, getHostElement} from '../../src/render3/index'; -import {container, element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty} from '../../src/render3/instructions'; +import {element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty, listener} from '../../src/render3/instructions'; import {ComponentFixture} from './render_util'; import {NgIf} from './common_with_def'; -import {getRootContext} from '@angular/core/src/render3/util'; describe('discovery utils', () => { let fixture: ComponentFixture; @@ -27,8 +25,10 @@ describe('discovery utils', () => { let span: NodeListOf; let div: NodeListOf; let p: NodeListOf; + let log: any[]; beforeEach(() => { + log = []; myApp = []; dirA = []; childComponent = []; @@ -46,7 +46,7 @@ describe('discovery utils', () => { * ``` * * <#VIEW> - * {{text}} + * {{text}} *
* * <#VIEW> @@ -110,6 +110,7 @@ describe('discovery utils', () => { template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { elementStart(0, 'span'); + listener('click', $event => log.push($event)); text(1); elementEnd(); element(2, 'div', ['dirA', ''], ['div', '', 'foo', 'dirA']); @@ -146,6 +147,18 @@ describe('discovery utils', () => { }); }); + describe('getContext', () => { + it('should throw when called on non-element', () => { + expect(() => getContext(dirA[0] as any)).toThrowError(/Expecting instance of DOM Node/); + expect(() => getContext(dirA[1] as any)).toThrowError(/Expecting instance of DOM Node/); + }); + it('should return context from element', () => { + expect(getContext(child[0])).toEqual(myApp[0]); + expect(getContext<{$implicit: boolean}>(child[2]) !.$implicit).toEqual(true); + expect(getContext(p[0])).toEqual(childComponent[0]); + }); + }); + describe('getHostElement', () => { it('should return element on component', () => { expect(getHostElement(myApp[0])).toEqual(fixture.hostElement); @@ -217,7 +230,7 @@ describe('discovery utils', () => { }); }); - describe('localRefs', () => { + describe('getLocalRefs', () => { it('should retrieve empty map', () => { expect(getLocalRefs(fixture.hostElement)).toEqual({}); expect(getLocalRefs(myApp[0])).toEqual({}); @@ -249,6 +262,30 @@ describe('discovery utils', () => { }); }); + describe('getListeners', () => { + it('should return no listeners', () => { + expect(getListeners(fixture.hostElement)).toEqual([]); + expect(getListeners(child[0])).toEqual([]); + }); + it('should return the listeners', () => { + const listeners = getListeners(span[0]); + expect(listeners.length).toEqual(1); + expect(listeners[0].name).toEqual('click'); + expect(listeners[0].element).toEqual(span[0]); + expect(listeners[0].useCapture).toEqual(false); + listeners[0].callback('CLICKED'); + expect(log).toEqual(['CLICKED']); + }); + }); + + describe('getInjectionTokens', () => { + it('should retrieve tokens', () => { + expect(getInjectionTokens(fixture.hostElement)).toEqual([MyApp]); + expect(getInjectionTokens(child[0])).toEqual([String, Child]); + expect(getInjectionTokens(child[1])).toEqual([String, Child, DirectiveA]); + }); + }); + describe('markDirty', () => { it('should re-render component', () => { expect(span[0].textContent).toEqual('INIT'); diff --git a/packages/core/test/render3/global_utils_spec.ts b/packages/core/test/render3/global_utils_spec.ts index bece3e429e..5e8d5ebf69 100644 --- a/packages/core/test/render3/global_utils_spec.ts +++ b/packages/core/test/render3/global_utils_spec.ts @@ -7,7 +7,7 @@ */ import {ɵmarkDirty as markDirty} from '@angular/core'; -import {getComponent, getDirectives, getHostElement, getInjector, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; +import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeContainer, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/global_utils'; import {getPlayers} from '../../src/render3/players'; import {global} from '../../src/util'; @@ -28,6 +28,10 @@ describe('global utils', () => { it('should publish getComponent', () => { assertPublished('getComponent', getComponent); }); + it('should publish getContext', () => { assertPublished('getContext', getContext); }); + + it('should publish getListeners', () => { assertPublished('getListeners', getListeners); }); + it('should publish getViewComponent', () => { assertPublished('getViewComponent', getViewComponent); }); diff --git a/packages/core/test/render3/host_binding_spec.ts b/packages/core/test/render3/host_binding_spec.ts index d00fc12ea6..aa3ad8d636 100644 --- a/packages/core/test/render3/host_binding_spec.ts +++ b/packages/core/test/render3/host_binding_spec.ts @@ -659,7 +659,7 @@ describe('host bindings', () => { selectors: [['', 'hostDir', '']], factory: () => new HostBindingDir(), hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { - // LViewData [..., title, ctx.title, pf1] + // LView [..., title, ctx.title, pf1] if (rf & RenderFlags.Create) { allocHostVars(3); } diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 3359ef3a9f..78e98fa102 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -21,7 +21,7 @@ import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; import {NgIf} from './common_with_def'; import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util'; -import {getContext} from '../../src/render3/context_discovery'; +import {getLContext} from '../../src/render3/context_discovery'; import {StylingIndex} from '../../src/render3/interfaces/styling'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; @@ -2041,14 +2041,14 @@ describe('render3 integration test', () => { fixture.update(); const section = fixture.hostElement.querySelector('section') !; - const sectionContext = getContext(section) !; + const sectionContext = getLContext(section) !; const sectionLView = sectionContext.lView !; 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 divContext = getLContext(div) !; const divLView = divContext.lView !; expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1); expect(divLView.length).toBeGreaterThan(HEADER_OFFSET); @@ -2080,7 +2080,7 @@ describe('render3 integration test', () => { const result1 = section[MONKEY_PATCH_KEY_NAME]; expect(Array.isArray(result1)).toBeTruthy(); - const context = getContext(section) !; + const context = getLContext(section) !; const result2 = section[MONKEY_PATCH_KEY_NAME]; expect(Array.isArray(result2)).toBeFalsy(); @@ -2116,7 +2116,7 @@ describe('render3 integration test', () => { const p = fixture.hostElement.querySelector('p') !as any; expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy(); - const pContext = getContext(p) !; + const pContext = getLContext(p) !; expect(pContext.native).toBe(p); expect(p[MONKEY_PATCH_KEY_NAME]).toBe(pContext); }); @@ -2154,7 +2154,7 @@ describe('render3 integration test', () => { expect(Array.isArray(elementResult)).toBeTruthy(); expect(elementResult[StylingIndex.ElementPosition]).toBe(section); - const context = getContext(section) !; + const context = getLContext(section) !; const result2 = section[MONKEY_PATCH_KEY_NAME]; expect(Array.isArray(result2)).toBeFalsy(); @@ -2248,9 +2248,9 @@ describe('render3 integration test', () => { expect(pText[MONKEY_PATCH_KEY_NAME]).toBeFalsy(); expect(projectedTextNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); - const parentContext = getContext(section) !; - const shadowContext = getContext(header) !; - const projectedContext = getContext(p) !; + const parentContext = getLContext(section) !; + const shadowContext = getLContext(header) !; + const projectedContext = getLContext(p) !; const parentComponentData = parentContext.lView; const shadowComponentData = shadowContext.lView; @@ -2263,12 +2263,12 @@ describe('render3 integration test', () => { it('should return `null` when an element context is retrieved that isn\'t situated in Angular', () => { const elm1 = document.createElement('div'); - const context1 = getContext(elm1); + const context1 = getLContext(elm1); expect(context1).toBeFalsy(); const elm2 = document.createElement('div'); document.body.appendChild(elm2); - const context2 = getContext(elm2); + const context2 = getLContext(elm2); expect(context2).toBeFalsy(); }); @@ -2296,7 +2296,7 @@ describe('render3 integration test', () => { const manuallyCreatedElement = document.createElement('div'); section.appendChild(manuallyCreatedElement); - const context = getContext(manuallyCreatedElement); + const context = getLContext(manuallyCreatedElement); expect(context).toBeFalsy(); }); @@ -2324,11 +2324,11 @@ describe('render3 integration test', () => { const hostLView = (hostElm as any)[MONKEY_PATCH_KEY_NAME]; expect(hostLView).toBe(componentLView); - const context1 = getContext(hostElm) !; + const context1 = getLContext(hostElm) !; expect(context1.lView).toBe(hostLView); expect(context1.native).toEqual(hostElm); - const context2 = getContext(component) !; + const context2 = getLContext(component) !; expect(context2).toBe(context1); expect(context2.lView).toBe(hostLView); expect(context2.native).toEqual(hostElm); @@ -2387,7 +2387,7 @@ describe('render3 integration test', () => { const hostElm = fixture.hostElement; const div1 = hostElm.querySelector('div:first-child') !as any; const div2 = hostElm.querySelector('div:last-child') !as any; - const context = getContext(hostElm) !; + const context = getLContext(hostElm) !; const componentView = context.lView[context.nodeIndex]; expect(componentView).toContain(myDir1Instance); @@ -2398,9 +2398,9 @@ describe('render3 integration test', () => { expect(Array.isArray((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); expect(Array.isArray((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); - const d1Context = getContext(myDir1Instance) !; - const d2Context = getContext(myDir2Instance) !; - const d3Context = getContext(myDir3Instance) !; + const d1Context = getLContext(myDir1Instance) !; + const d2Context = getLContext(myDir2Instance) !; + const d3Context = getLContext(myDir3Instance) !; expect(d1Context.lView).toEqual(componentView); expect(d2Context.lView).toEqual(componentView); @@ -2487,28 +2487,28 @@ describe('render3 integration test', () => { expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView); expect((childComponentInstance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView); - const childNodeContext = getContext(childCompHostElm) !; + const childNodeContext = getLContext(childCompHostElm) !; expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.directives).toBeFalsy(); assertMonkeyPatchValueIsLView(myDir1Instance); assertMonkeyPatchValueIsLView(myDir2Instance); assertMonkeyPatchValueIsLView(childComponentInstance); - expect(getContext(myDir1Instance)).toBe(childNodeContext); + expect(getLContext(myDir1Instance)).toBe(childNodeContext); expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.directives !.length).toEqual(2); assertMonkeyPatchValueIsLView(myDir1Instance, false); assertMonkeyPatchValueIsLView(myDir2Instance, false); assertMonkeyPatchValueIsLView(childComponentInstance); - expect(getContext(myDir2Instance)).toBe(childNodeContext); + expect(getLContext(myDir2Instance)).toBe(childNodeContext); expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.directives !.length).toEqual(2); assertMonkeyPatchValueIsLView(myDir1Instance, false); assertMonkeyPatchValueIsLView(myDir2Instance, false); assertMonkeyPatchValueIsLView(childComponentInstance); - expect(getContext(childComponentInstance)).toBe(childNodeContext); + expect(getLContext(childComponentInstance)).toBe(childNodeContext); expect(childNodeContext.component).toBeTruthy(); expect(childNodeContext.directives !.length).toEqual(2); assertMonkeyPatchValueIsLView(myDir1Instance, false); @@ -2565,7 +2565,7 @@ describe('render3 integration test', () => { const child = host.querySelector('child-comp') as any; expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); - const context = getContext(child) !; + const context = getLContext(child) !; expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); const componentData = context.lView[context.nodeIndex]; @@ -2573,7 +2573,7 @@ describe('render3 integration test', () => { expect(component instanceof ChildComp).toBeTruthy(); expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lView); - const componentContext = getContext(component) !; + const componentContext = getLContext(component) !; expect(component[MONKEY_PATCH_KEY_NAME]).toBe(componentContext); expect(componentContext.nodeIndex).toEqual(context.nodeIndex); expect(componentContext.native).toEqual(context.native); diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 88984c1cb3..837d5174d2 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -21,7 +21,7 @@ import {SWITCH_TEMPLATE_REF_FACTORY__POST_R3__ as R3_TEMPLATE_REF_FACTORY} from import {SWITCH_VIEW_CONTAINER_REF_FACTORY__POST_R3__ as R3_VIEW_CONTAINER_REF_FACTORY} from '../../src/linker/view_container_ref'; import {SWITCH_RENDERER2_FACTORY__POST_R3__ as R3_RENDERER2_FACTORY} from '../../src/render/api'; import {CreateComponentOptions} from '../../src/render3/component'; -import {getContext, getDirectivesAtNodeIndex, isComponentInstance} from '../../src/render3/context_discovery'; +import {getDirectivesAtNodeIndex, getLContext, isComponentInstance} from '../../src/render3/context_discovery'; import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition'; import {NG_ELEMENT_ID} from '../../src/render3/fields'; import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; @@ -266,7 +266,7 @@ export function renderComponent(type: ComponentType, opts?: CreateComponen export function toHtml(componentOrElement: T | RElement, keepNgReflect = false): string { let element: any; if (isComponentInstance(componentOrElement)) { - const context = getContext(componentOrElement); + const context = getLContext(componentOrElement); element = context ? context.native : null; } else { element = componentOrElement; diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts index 566e29ec31..75c3fa9130 100644 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {createRootContext} from '../../../src/render3/component'; -import {getContext} from '../../../src/render3/context_discovery'; +import {getLContext} from '../../../src/render3/context_discovery'; import {defineComponent} from '../../../src/render3/index'; import {createLView, createTView, elementClassProp, elementEnd, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap} from '../../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../../src/render3/interfaces/definition'; @@ -2264,7 +2264,7 @@ describe('style and class based bindings', () => { fixture.update(); const target = fixture.hostElement.querySelector('div') !as any; - const elementContext = getContext(target) !; + const elementContext = getLContext(target) !; const context = elementContext.lView[elementContext.nodeIndex] as StylingContext; expect(players.length).toEqual(4); diff --git a/tools/public_api_guard/global_utils.d.ts b/tools/public_api_guard/global_utils.d.ts index e8e9917945..499ec0e235 100644 --- a/tools/public_api_guard/global_utils.d.ts +++ b/tools/public_api_guard/global_utils.d.ts @@ -1,11 +1,15 @@ export declare function getComponent(element: Element): T | null; +export declare function getContext(element: Element): T | null; + export declare function getDirectives(target: {}): Array<{}>; export declare function getHostElement(directive: T): Element; export declare function getInjector(target: {}): Injector; +export declare function getListeners(element: Element): Listener[]; + export declare function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[]; export declare function getRootComponents(target: {}): any[];