fix(devtools): Add support for null-prototype objects

null-prototype don't inherite from the Object prototype and thus don't have constructors.

fixes #68798
This commit is contained in:
Matthieu Riegler
2026-05-22 13:08:39 +02:00
committed by Alex Rickabaugh
parent d808866f89
commit d7161fd431
3 changed files with 55 additions and 7 deletions
@@ -61,9 +61,13 @@ const typeToDescriptorPreview: Formatter<string> = {
[PropType.HTMLNode]: (prop: Node) => prop.constructor.name,
[PropType.Null]: (_: null) => 'null',
[PropType.Number]: (prop: any) => prop.toString(),
[PropType.Object]: (prop: Object) =>
(prop.constructor.name !== 'Object' ? `${prop.constructor.name} ` : '') +
(getKeys(prop).length > 0 ? '{...}' : '{}'),
[PropType.Object]: (prop: Object) => {
// Some type of objects don't have a constructor (e.g. Object.create(null)),
return (
(prop.constructor && prop.constructor.name !== 'Object' ? `${prop.constructor.name} ` : '') +
(getKeys(prop).length > 0 ? '{...}' : '{}')
);
},
[PropType.Symbol]: (symbol: symbol) => `Symbol(${symbol.description})`,
[PropType.Undefined]: (_: undefined) => 'undefined',
[PropType.Date]: (prop: unknown) => {
@@ -9,7 +9,7 @@
import {PropType} from '../../../../protocol';
import {getDescriptor, getKeys} from './object-utils';
import {deeplySerializeSelectedProperties} from './state-serializer';
import {deeplySerializeSelectedProperties, serializeDirectiveState} from './state-serializer';
const QUERY_1_1: any[] = [];
@@ -494,6 +494,50 @@ describe('deeplySerializeSelectedProperties', () => {
});
});
it('should preview objects without prototypes as plain objects', () => {
const grouped = Object.create(null);
grouped.foo = 1;
const result = serializeDirectiveState({grouped});
expect(result['grouped']).toEqual({
type: PropType.Object,
editable: false,
expandable: true,
preview: '{...}',
containerType: null,
});
});
it('should deeply serialize selected properties from objects without prototypes', () => {
const grouped = Object.create(null);
grouped.foo = 1;
const result = deeplySerializeSelectedProperties({grouped}, [
{name: 'grouped', children: [{name: 'foo', children: []}]},
]);
expect(result).toEqual({
grouped: {
type: PropType.Object,
editable: false,
expandable: true,
preview: '{...}',
value: {
foo: {
type: PropType.Number,
expandable: false,
editable: true,
preview: '1',
value: 1,
containerType: null,
},
},
containerType: null,
},
});
});
it('getDescriptor should get the descriptors for both getters and setters correctly from the prototype', () => {
const instance = {
__proto__: {
@@ -25,11 +25,11 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {ZippyComponent} from './zippy.component';
import {RouterModule, RouterOutlet} from '@angular/router';
import {CookieRecipe} from './cookies.component';
import {HeavyComponent} from './heavy.component';
import {SamplePropertiesComponent} from './sample-properties.component';
import {RouterOutlet, RouterModule} from '@angular/router';
import {CookieRecipe} from './cookies.component';
import {ZippyComponent} from './zippy.component';
// structual directive example
@Directive({