From 67e0d89133cf9125787b181879a77e901c04496f Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 27 Aug 2019 17:02:41 -0700 Subject: [PATCH] fix(ivy): Prevent errors when querying for elements outside Angular context DebugElement.query also searches elements that may have been created outside of Angular (ex: with `document.appendChild`). The current behavior attempts to get the LContext of these nodes but throws an error because the LContext does not exist. --- packages/core/src/debug/debug_node.ts | 45 ++++++++++++--------- packages/core/test/debug/debug_node_spec.ts | 39 +++++++++++++++++- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index a26a49c5c1060..c204f8ef706ea 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -16,7 +16,7 @@ import {TStylingContext} from '../render3/styling_next/interfaces'; import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings'; import {NodeStylingDebug} from '../render3/styling_next/styling_debug'; import {isStylingContext} from '../render3/styling_next/util'; -import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/util/discovery_utils'; +import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils'; import {INTERPOLATION_DELIMITER, isPropMetadataString, renderStringify} from '../render3/util/misc_utils'; import {findComponentView} from '../render3/util/view_traversal_utils'; import {getComponentViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils'; @@ -272,7 +272,11 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme * - attribute bindings (e.g. `[attr.role]="menu"`) */ get properties(): {[key: string]: any;} { - const context = loadLContext(this.nativeNode) !; + const context = loadLContext(this.nativeNode, false); + if (context == null) { + return {}; + } + const lView = context.lView; const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; @@ -297,7 +301,11 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return attributes; } - const context = loadLContext(element); + const context = loadLContext(element, false); + if (context == null) { + return {}; + } + const lView = context.lView; const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs; const lowercaseTNodeAttrs: string[] = []; @@ -413,22 +421,23 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme } function _getStylingDebugInfo(element: any, isClassBased: boolean) { - if (element) { - const context = loadLContextFromNode(element); - const lView = context.lView; - const tData = lView[TVIEW].data; - const tNode = tData[context.nodeIndex] as TNode; - if (isClassBased) { - return isStylingContext(tNode.classes) ? - new NodeStylingDebug(tNode.classes as TStylingContext, lView, true).values : - stylingMapToStringMap(tNode.classes); - } else { - return isStylingContext(tNode.styles) ? - new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values : - stylingMapToStringMap(tNode.styles); - } + const context = loadLContext(element, false); + if (!context) { + return {}; + } + + const lView = context.lView; + const tData = lView[TVIEW].data; + const tNode = tData[context.nodeIndex] as TNode; + if (isClassBased) { + return isStylingContext(tNode.classes) ? + new NodeStylingDebug(tNode.classes as TStylingContext, lView, true).values : + stylingMapToStringMap(tNode.classes); + } else { + return isStylingContext(tNode.styles) ? + new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values : + stylingMapToStringMap(tNode.styles); } - return {}; } /** diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index 328dab2f9f4b6..da2f9187d5726 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common'; -import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; +import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {hasClass} from '@angular/platform-browser/testing/src/browser_util'; @@ -580,6 +580,43 @@ class TestCmptWithPropBindings { expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy(); }); + describe('DebugElement.query doesn\'t fail on elements outside Angular context', () => { + @Component({template: '
'}) + class NativeEl { + constructor(private elementRef: ElementRef) {} + + ngAfterViewInit() { + this.elementRef.nativeElement.children[0].appendChild(document.createElement('p')); + } + } + + let el: DebugElement; + beforeEach(() => { + const fixture = + TestBed.configureTestingModule({declarations: [NativeEl]}).createComponent(NativeEl); + fixture.detectChanges(); + el = fixture.debugElement; + }); + + it('when searching for elements by name', + () => { expect(() => el.query(e => e.name === 'any search text')).not.toThrow(); }); + + it('when searching for elements by their attributes', () => { + expect(() => el.query(e => e.attributes !['name'] === 'any attribute')).not.toThrow(); + }); + + it('when searching for elements by their classes', + () => { expect(() => el.query(e => e.classes['any class'] === true)).not.toThrow(); }); + + it('when searching for elements by their styles', () => { + expect(() => el.query(e => e.styles['any style'] === 'any value')).not.toThrow(); + }); + + it('when searching for elements by their properties', () => { + expect(() => el.query(e => e.properties['any prop'] === 'any value')).not.toThrow(); + }); + }); + it('DebugElement.queryAll should pick up both elements inserted via the view and through Renderer2', () => { @Directive({