Skip to content

Commit 6169fa5

Browse files
tsc036mmalerba
authored andcommitted
fix(devtools): stop second component tree traversal, if devtools metadata is placed on the body tag (angular#64161)
Stop multiple component tree traversals if the app root is the body tag. This caused the devtools ui to duplicate the component information, if the app root was the body tag PR Close angular#64161
1 parent 8233842 commit 6169fa5

File tree

2 files changed

+69
-2
lines changed

2 files changed

+69
-2
lines changed

devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.spec.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,21 @@
77
*/
88

99
import {Injector, ɵGlobalDevModeUtils} from '@angular/core';
10-
import {getInjectorFromElementNode} from './component-tree';
10+
import {getInjectorFromElementNode, getRootElements} from './component-tree';
1111

1212
type Ng = ɵGlobalDevModeUtils['ng'];
13+
const NG_VERSION = 'ng-version';
14+
const VERSION = '0.0.0-PLACEHOLDER';
15+
16+
function setNgVersion(element: Element) {
17+
element.setAttribute(NG_VERSION, VERSION);
18+
}
19+
20+
function createRoot() {
21+
const root = document.createElement('div');
22+
setNgVersion(root);
23+
return root;
24+
}
1325

1426
describe('component-tree', () => {
1527
afterEach(() => {
@@ -39,4 +51,55 @@ describe('component-tree', () => {
3951
expect(getInjectorFromElementNode(el)).toBeNull();
4052
});
4153
});
54+
55+
describe('getRootElements', () => {
56+
beforeEach(() => {
57+
const ng: Partial<Ng> = {
58+
getComponent: jasmine.createSpy('getComponent').and.returnValue({}),
59+
};
60+
(window as any).ng = ng;
61+
});
62+
63+
afterEach(() => {
64+
document.body.replaceChildren();
65+
document.body.removeAttribute(NG_VERSION);
66+
delete (window as any).ng;
67+
});
68+
69+
it('should return root element', () => {
70+
const rootElement = createRoot();
71+
const childElement = createRoot();
72+
73+
rootElement.appendChild(childElement);
74+
document.body.appendChild(rootElement);
75+
76+
const roots = getRootElements();
77+
78+
expect(roots.length).toEqual(1);
79+
expect(roots.pop()).toEqual(rootElement);
80+
});
81+
82+
it('should return multiple sibling roots', () => {
83+
const firstRoot = createRoot();
84+
const secondRoot = createRoot();
85+
86+
document.body.appendChild(firstRoot);
87+
document.body.appendChild(secondRoot);
88+
89+
const roots = getRootElements();
90+
91+
expect(roots.length).toEqual(2);
92+
expect(roots).toContain(firstRoot);
93+
expect(roots).toContain(secondRoot);
94+
});
95+
96+
it('should only return document.body when document.body is the root', () => {
97+
setNgVersion(document.body);
98+
const child1 = createRoot();
99+
document.body.appendChild(child1);
100+
const roots = getRootElements();
101+
expect(roots.length).toEqual(1);
102+
expect(roots).toContain(document.body);
103+
});
104+
});
42105
});

devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,8 @@ const getRootLViewsHelper = (element: Element, rootLViews = new Set<any>()): Set
552552
return rootLViews;
553553
};
554554

555-
function getRootElements(): Element[] {
555+
/** Gets the all the root components in the Dom, including those outside the application root */
556+
export function getRootElements(): Element[] {
556557
if (!ngDebugClient().getComponent) {
557558
// If the ngDebugClient does not support getComponent, we cannot proceed.
558559
return [];
@@ -631,6 +632,9 @@ function getRootElements(): Element[] {
631632
* @param roots A set of root elements found during the traversal.
632633
*/
633634
function discoverNonApplicationRootComponents(element: Element, roots: Set<Element>): void {
635+
if (roots.has(element)) {
636+
return;
637+
}
634638
const children = Array.from(element.children);
635639
for (const child of children) {
636640
if (roots.has(child)) {

0 commit comments

Comments
 (0)