From d3d46c947516dfd0e64f2808311b99aa9d5e08c0 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Thu, 6 Aug 2026 18:37:56 +0200 Subject: [PATCH] fix(devtools): fix regression for node serialization. This fixes a regression introduced by #69309 where the format of `serializedId` from a coma separated string, to a JSON representation of an array. fixes #70104 (cherry picked from commit 03f1758e2ae7e033c14360a72583d51825993217) --- .../component-tree/component-tree.ts | 26 ++++----- .../shell-browser/src/app/BUILD.bazel | 21 ++++++++ .../app/chrome-application-operations.spec.ts | 54 +++++++++++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 devtools/projects/shell-browser/src/app/chrome-application-operations.spec.ts diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree/component-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree/component-tree.ts index 2ce79a151d1..0135176e5ca 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree/component-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree/component-tree.ts @@ -7,18 +7,19 @@ */ import type { + ɵAcxComponentDebugMetadata as AcxComponentDebugMetadata, + ɵAngularComponentDebugMetadata as AngularComponentDebugMetadata, ClassProvider, ExistingProvider, FactoryProvider, InjectOptions, InjectionToken, Injector, + ɵProviderRecord as ProviderRecord, Type, ValueProvider, - ɵAngularComponentDebugMetadata as AngularComponentDebugMetadata, - ɵAcxComponentDebugMetadata as AcxComponentDebugMetadata, - ɵProviderRecord as ProviderRecord, } from '@angular/core'; +import {buildDirectiveForestWithStrategy} from '..'; import { ChangeDetection, ComponentExplorerViewQuery, @@ -32,7 +33,11 @@ import { SerializedProviderRecord, UpdatedStateData, } from '../../../../../protocol'; -import {buildDirectiveForestWithStrategy} from '..'; +import { + ComponentInstanceType, + ComponentTreeNode, + DirectiveInstanceType, +} from '../../shared/interfaces'; import { ngDebugApiIsSupported, ngDebugClient, @@ -42,16 +47,11 @@ import { deeplySerializeSelectedProperties, serializeDirectiveState, } from '../../shared/state-serializer/state-serializer'; -import {mutateNestedProp} from '../property-mutation/property-mutation'; -import { - ComponentTreeNode, - DirectiveInstanceType, - ComponentInstanceType, -} from '../../shared/interfaces'; -import {getAppRoots} from './get-roots'; -import {AcxChangeDetectionStrategy, ChangeDetectionStrategy, Framework} from '../core-enums'; import {unwrapSignal} from '../../shared/utils/general'; +import {AcxChangeDetectionStrategy, ChangeDetectionStrategy, Framework} from '../core-enums'; +import {mutateNestedProp} from '../property-mutation/property-mutation'; import {getLViewFromDirectiveOrElementInstance} from '../tree-strategies/ltree'; +import {getAppRoots} from './get-roots'; export const injectorToId = new WeakMap(); export const nodeInjectorToResolutionPath = new WeakMap(); @@ -734,7 +734,7 @@ export const findNodeInForest = ( export const findNodeFromSerializedPosition = ( serializedPosition: string, ): ComponentTreeNode | null => { - const position: number[] = serializedPosition.split(',').map((index) => parseInt(index, 10)); + const position: number[] = JSON.parse(serializedPosition); return queryDirectiveForest(position, buildDirectiveForest()); }; diff --git a/devtools/projects/shell-browser/src/app/BUILD.bazel b/devtools/projects/shell-browser/src/app/BUILD.bazel index 2f5baef968e..7c72708a49f 100644 --- a/devtools/projects/shell-browser/src/app/BUILD.bazel +++ b/devtools/projects/shell-browser/src/app/BUILD.bazel @@ -108,6 +108,27 @@ ts_project( ], ) +ts_test_library( + name = "chrome_application_operations_test_lib", + srcs = ["chrome-application-operations.spec.ts"], + deps = [ + ":chrome_application_operations", + "//:node_modules/@angular/cdk", + "//:node_modules/@angular/core", + "//:node_modules/@types/chrome", + "//:node_modules/jasmine", + "//devtools/projects/ng-devtools", + "//devtools/projects/protocol", + ], +) + +zoneless_web_test_suite( + name = "chrome_application_operations_test", + deps = [ + ":chrome_application_operations_test_lib", + ], +) + ts_project( name = "same_page_message_bus", srcs = [ diff --git a/devtools/projects/shell-browser/src/app/chrome-application-operations.spec.ts b/devtools/projects/shell-browser/src/app/chrome-application-operations.spec.ts new file mode 100644 index 00000000000..2f8f3964313 --- /dev/null +++ b/devtools/projects/shell-browser/src/app/chrome-application-operations.spec.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/// + +import {Platform} from '@angular/cdk/platform'; +import {TestBed} from '@angular/core/testing'; +import {Frame} from '../../../ng-devtools'; +import {ChromeApplicationOperations} from './chrome-application-operations'; + +describe('ChromeApplicationOperations', () => { + let operations: ChromeApplicationOperations; + + beforeEach(() => { + // Mock chrome global + (globalThis as any).chrome = { + devtools: { + inspectedWindow: { + eval: jasmine.createSpy('eval'), + }, + }, + }; + + TestBed.configureTestingModule({ + providers: [ChromeApplicationOperations, {provide: Platform, useValue: {FIREFOX: false}}], + }); + operations = TestBed.inject(ChromeApplicationOperations); + }); + + afterEach(() => { + delete (globalThis as any).chrome; + }); + + describe('viewSource', () => { + it('should call chrome.devtools.inspectedWindow.eval with correct string', () => { + const target: Frame = { + name: 'test1', + id: 0, + url: new URL('http://localhost:4200/url'), + } as any; + operations.viewSource([0, 0], target, 0); + + expect(chrome.devtools.inspectedWindow.eval).toHaveBeenCalledWith( + 'inspect(inspectedApplication.findConstructorByPosition("[0,0]", 0))', + {frameURL: 'http://localhost:4200/url'}, + ); + }); + }); +});