mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Angular DevTools depends on many modern Angular features in order to function. As a result, at present the last officially supported version is v12. Angular DevTools may function for some Angular 9, 10 and 11 applications, but they are not officially supported. This commit fixes an issue where DevTools would not inject a backend script into an Angular application if it detected it was below version 12. This backend script is important because it's used to inform the DevTools panel that the inspected application is in fact Angular, but that it is not on a supported version. Angular 9, 10 and 11 applications that successfully have Angular DevTools initialize will now have a red highlight and tooltip on their version number, informing the user that they are using Angular DevTools on a version of Angular that is no longer supported. Angular DevTools for applications that are below version 9 will continue to display the "Angular Devtools supports Angular versions 12 and above" message. PR Close #55233
298 lines
7 KiB
TypeScript
298 lines
7 KiB
TypeScript
/**
|
|
* @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.io/license
|
|
*/
|
|
|
|
import {InjectionToken, InjectOptions, Injector, Type, ViewEncapsulation} from '@angular/core';
|
|
|
|
export interface DirectiveType {
|
|
name: string;
|
|
id: number;
|
|
}
|
|
|
|
export interface ComponentType {
|
|
name: string;
|
|
isElement: boolean;
|
|
id: number;
|
|
}
|
|
|
|
export type HydrationStatus =
|
|
| null
|
|
| {status: 'hydrated' | 'skipped'}
|
|
| {
|
|
status: 'mismatched';
|
|
expectedNodeDetails: string | null;
|
|
actualNodeDetails: string | null;
|
|
};
|
|
|
|
export interface DevToolsNode<DirType = DirectiveType, CmpType = ComponentType> {
|
|
element: string;
|
|
directives: DirType[];
|
|
component: CmpType | null;
|
|
children: DevToolsNode<DirType, CmpType>[];
|
|
nativeElement?: Node;
|
|
resolutionPath?: SerializedInjector[];
|
|
hydration: HydrationStatus;
|
|
}
|
|
|
|
export interface SerializedInjector {
|
|
id: string;
|
|
name: string;
|
|
type: string;
|
|
node?: DevToolsNode;
|
|
providers?: number;
|
|
}
|
|
|
|
export interface SerializedProviderRecord {
|
|
token: string;
|
|
type: 'type' | 'existing' | 'class' | 'value' | 'factory' | 'multi';
|
|
multi: boolean;
|
|
isViewProvider: boolean;
|
|
index?: number | number[];
|
|
}
|
|
|
|
/**
|
|
* Duplicate of the InjectedService interface from Angular framework to prevent
|
|
* needing to publicly expose the interface from the framework.
|
|
*/
|
|
export interface InjectedService {
|
|
token?: Type<unknown> | InjectionToken<unknown>;
|
|
value: unknown;
|
|
flags?: InjectOptions;
|
|
providedIn: Injector;
|
|
}
|
|
|
|
export type ContainerType = 'WritableSignal' | 'ReadonlySignal' | null;
|
|
|
|
export enum PropType {
|
|
Number,
|
|
String,
|
|
Null,
|
|
Undefined,
|
|
Symbol,
|
|
HTMLNode,
|
|
Boolean,
|
|
BigInt,
|
|
Function,
|
|
Object,
|
|
Date,
|
|
Array,
|
|
Set,
|
|
Map,
|
|
Unknown,
|
|
}
|
|
|
|
export interface Descriptor {
|
|
expandable: boolean;
|
|
value?: any;
|
|
editable: boolean;
|
|
type: PropType;
|
|
preview: string;
|
|
containerType: ContainerType;
|
|
}
|
|
|
|
export interface DirectivesProperties {
|
|
[name: string]: Properties;
|
|
}
|
|
|
|
export interface DirectiveMetadata {
|
|
inputs: {[name: string]: string};
|
|
outputs: {[name: string]: string};
|
|
encapsulation: ViewEncapsulation;
|
|
onPush: boolean;
|
|
dependencies?: SerializedInjectedService[];
|
|
}
|
|
|
|
export interface SerializedInjectedService {
|
|
token: string;
|
|
value: string;
|
|
position: number[];
|
|
flags?: InjectOptions;
|
|
resolutionPath?: SerializedInjector[];
|
|
}
|
|
|
|
export interface Properties {
|
|
props: {[name: string]: Descriptor};
|
|
metadata?: DirectiveMetadata;
|
|
}
|
|
|
|
export type ElementPosition = number[];
|
|
|
|
export interface DirectivePosition {
|
|
element: ElementPosition;
|
|
directive?: number;
|
|
}
|
|
|
|
export interface NestedProp {
|
|
name: string | number;
|
|
children: NestedProp[];
|
|
}
|
|
|
|
export interface ComponentExplorerViewProperties {
|
|
[directive: string]: NestedProp[];
|
|
}
|
|
|
|
export enum PropertyQueryTypes {
|
|
All,
|
|
Specified,
|
|
}
|
|
|
|
export interface AllPropertiesQuery {
|
|
type: PropertyQueryTypes.All;
|
|
}
|
|
|
|
export interface SelectedPropertiesQuery {
|
|
type: PropertyQueryTypes.Specified;
|
|
properties: ComponentExplorerViewProperties;
|
|
}
|
|
|
|
export type PropertyQuery = AllPropertiesQuery | SelectedPropertiesQuery;
|
|
|
|
export interface ComponentExplorerViewQuery {
|
|
selectedElement: ElementPosition;
|
|
propertyQuery: PropertyQuery;
|
|
}
|
|
|
|
export interface ComponentExplorerView {
|
|
forest: DevToolsNode[];
|
|
properties?: DirectivesProperties;
|
|
}
|
|
|
|
export interface LifecycleProfile {
|
|
ngOnInit?: number;
|
|
ngOnDestroy?: number;
|
|
ngOnChanges?: number;
|
|
ngDoCheck?: number;
|
|
ngAfterContentInit?: number;
|
|
ngAfterContentChecked?: number;
|
|
ngAfterViewInit?: number;
|
|
ngAfterViewChecked?: number;
|
|
}
|
|
|
|
export interface OutputProfile {
|
|
[outputName: string]: number;
|
|
}
|
|
|
|
export interface DirectiveProfile {
|
|
name: string;
|
|
isElement: boolean;
|
|
isComponent: boolean;
|
|
lifecycle: LifecycleProfile;
|
|
outputs: OutputProfile;
|
|
changeDetection?: number;
|
|
}
|
|
|
|
export interface ElementProfile {
|
|
directives: DirectiveProfile[];
|
|
children: ElementProfile[];
|
|
}
|
|
|
|
export interface ProfilerFrame {
|
|
source: string;
|
|
duration: number;
|
|
directives: ElementProfile[];
|
|
}
|
|
|
|
export interface UpdatedStateData {
|
|
directiveId: DirectivePosition;
|
|
keyPath: string[];
|
|
newValue: any;
|
|
}
|
|
|
|
export interface Route {
|
|
name: string;
|
|
hash: string | null;
|
|
path: string;
|
|
specificity: string | null;
|
|
handler: string;
|
|
data: any;
|
|
children?: Array<Route>;
|
|
isAux: boolean;
|
|
}
|
|
|
|
export interface AngularDetection {
|
|
// This is necessary because the runtime
|
|
// message listener handles messages globally
|
|
// including from other extensions. We don't
|
|
// want to set icon and/or popup based on
|
|
// a message coming from an unrelated extension.
|
|
isAngularDevTools: true;
|
|
isIvy: boolean;
|
|
isAngular: boolean;
|
|
isDebugMode: boolean;
|
|
isSupportedAngularVersion: boolean;
|
|
}
|
|
|
|
export type Topic = keyof Events;
|
|
|
|
export interface InjectorGraphViewQuery {
|
|
directivePosition: DirectivePosition;
|
|
paramIndex: number;
|
|
}
|
|
|
|
export interface Events {
|
|
handshake: () => void;
|
|
shutdown: () => void;
|
|
queryNgAvailability: () => void;
|
|
ngAvailability: (config: {
|
|
version: string | undefined;
|
|
devMode: boolean;
|
|
ivy: boolean;
|
|
hydration: boolean;
|
|
}) => void;
|
|
|
|
inspectorStart: () => void;
|
|
inspectorEnd: () => void;
|
|
|
|
getNestedProperties: (position: DirectivePosition, path: string[]) => void;
|
|
nestedProperties: (position: DirectivePosition, data: Properties, path: string[]) => void;
|
|
|
|
setSelectedComponent: (position: ElementPosition) => void;
|
|
getRoutes: () => void;
|
|
updateRouterTree: (routes: Route[]) => void;
|
|
|
|
componentTreeDirty: () => void;
|
|
getLatestComponentExplorerView: (query?: ComponentExplorerViewQuery) => void;
|
|
latestComponentExplorerView: (view: ComponentExplorerView) => void;
|
|
|
|
updateState: (value: UpdatedStateData) => void;
|
|
|
|
startProfiling: () => void;
|
|
stopProfiling: () => void;
|
|
sendProfilerChunk: (results: ProfilerFrame) => void;
|
|
profilerResults: (results: ProfilerFrame) => void;
|
|
|
|
createHighlightOverlay: (position: ElementPosition) => void;
|
|
removeHighlightOverlay: () => void;
|
|
|
|
createHydrationOverlay: () => void;
|
|
removeHydrationOverlay: () => void;
|
|
|
|
highlightComponent: (id: number) => void;
|
|
selectComponent: (id: number) => void;
|
|
removeComponentHighlight: () => void;
|
|
|
|
enableTimingAPI: () => void;
|
|
disableTimingAPI: () => void;
|
|
|
|
// todo: type properly
|
|
getInjectorProviders: (injector: SerializedInjector) => void;
|
|
latestInjectorProviders: (
|
|
injector: SerializedInjector,
|
|
providers: SerializedProviderRecord[],
|
|
) => void;
|
|
|
|
logProvider: (injector: SerializedInjector, providers: SerializedProviderRecord) => void;
|
|
|
|
contentScriptConnected: (frameId: number, name: string, url: string) => void;
|
|
contentScriptDisconnected: (frameId: number, name: string, url: string) => void;
|
|
enableFrameConnection: (frameId: number, tabId: number) => void;
|
|
frameConnected: (frameId: number) => void;
|
|
detectAngular: (detectionResult: AngularDetection) => void;
|
|
backendReady: () => void;
|
|
|
|
log: (logEvent: {message: string; level: 'log' | 'warn' | 'debug' | 'error'}) => void;
|
|
}
|