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

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

View File

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