mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(core): type-safe global ng (#53439)
This PR provides strict type definition for the window.ng object used for both console debugging and devtools. `GlobalDevModeUtils` now gathers all type information about all methods exposed on window.ng. PR Close #53439
This commit is contained in:
parent
e7330560cd
commit
2d7d4e2cf0
17 changed files with 209 additions and 173 deletions
|
|
@ -11,6 +11,7 @@ ts_library(
|
|||
"//devtools/projects/ng-devtools-backend/src/lib/component-inspector",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/hooks",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/state-serializer",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
|
|
@ -110,6 +111,7 @@ ts_library(
|
|||
":utils",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/component-inspector",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/hooks",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/state-serializer",
|
||||
"//devtools/projects/protocol",
|
||||
"//devtools/projects/shared-utils",
|
||||
|
|
@ -123,6 +125,7 @@ ts_library(
|
|||
deps = [
|
||||
":interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/state-serializer",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core",
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import {debounceTime} from 'rxjs/operators';
|
|||
import {appIsAngularInDevMode, appIsAngularIvy, appIsSupportedAngularVersion, getAngularVersion,} from 'shared-utils';
|
||||
|
||||
import {ComponentInspector} from './component-inspector/component-inspector';
|
||||
import {getElementInjectorElement, getInjectorFromElementNode, getInjectorProviders, getInjectorResolutionPath, getLatestComponentState, hasDiDebugAPIs, idToInjector, injectorsSeen, isElementInjector, nodeInjectorToResolutionPath, queryDirectiveForest, serializeProviderRecord, serializeResolutionPath, updateState} from './component-tree';
|
||||
import {getElementInjectorElement, getInjectorFromElementNode, getInjectorProviders, getInjectorResolutionPath, getLatestComponentState, idToInjector, injectorsSeen, isElementInjector, nodeInjectorToResolutionPath, queryDirectiveForest, serializeProviderRecord, serializeResolutionPath, updateState} from './component-tree';
|
||||
import {unHighlight} from './highlighter';
|
||||
import {disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks} from './hooks';
|
||||
import {start as startProfiling, stop as stopProfiling} from './hooks/capture';
|
||||
import {ComponentTreeNode} from './interfaces';
|
||||
import {ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api';
|
||||
import {setConsoleReference} from './set-console-reference';
|
||||
import {serializeDirectiveState} from './state-serializer/state-serializer';
|
||||
import {runOutsideAngular} from './utils';
|
||||
|
|
@ -75,7 +76,8 @@ const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>)
|
|||
initializeOrGetDirectiveForestHooks().indexForest();
|
||||
|
||||
const forest = prepareForestForSerialization(
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(), hasDiDebugAPIs());
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(),
|
||||
ngDebugDependencyInjectionApiIsSupported());
|
||||
|
||||
// cleanup injector id mappings
|
||||
for (const injectorId of idToInjector.keys()) {
|
||||
|
|
@ -219,21 +221,21 @@ const prepareForestForSerialization = (roots: ComponentTreeNode[], includeResolu
|
|||
SerializableComponentTreeNode[] => {
|
||||
const serializedNodes: SerializableComponentTreeNode[] = [];
|
||||
for (const node of roots) {
|
||||
const serializedNode = {
|
||||
const serializedNode: SerializableComponentTreeNode = {
|
||||
element: node.element,
|
||||
component: node.component ? {
|
||||
name: node.component.name,
|
||||
isElement: node.component.isElement,
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance),
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance)!,
|
||||
} :
|
||||
null,
|
||||
directives: node.directives.map(
|
||||
(d) => ({
|
||||
name: d.name,
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance),
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance)!,
|
||||
})),
|
||||
children: prepareForestForSerialization(node.children, includeResolutionPath),
|
||||
} as SerializableComponentTreeNode;
|
||||
};
|
||||
serializedNodes.push(serializedNode);
|
||||
|
||||
if (includeResolutionPath) {
|
||||
|
|
@ -286,6 +288,7 @@ const getInjectorProvidersCallback = (messageBus: MessageBus<Events>) =>
|
|||
for (const [index, providerRecord] of providerRecords.entries()) {
|
||||
const record =
|
||||
serializeProviderRecord(providerRecord, index, injector.type === 'environment');
|
||||
|
||||
allProviderRecords.push(record);
|
||||
|
||||
const records = tokenToRecords.get(providerRecord.token) ?? [];
|
||||
|
|
@ -348,9 +351,8 @@ const logProvider =
|
|||
console.log('provider: ', provider);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`value: `, injector.get(provider.token, null, {optional: true}));
|
||||
} else {
|
||||
const providers =
|
||||
(serializedProvider.index as number[]).map(index => providerRecords[index]);
|
||||
} else if (Array.isArray(serializedProvider.index)) {
|
||||
const providers = serializedProvider.index.map(index => providerRecords[index]);
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('providers: ', providers);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
* 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 {ComponentExplorerViewQuery, DirectiveMetadata, DirectivesProperties, ElementPosition, InjectedService, PropertyQueryTypes, ProviderRecord, SerializedInjectedService, SerializedInjector, SerializedProviderRecord, UpdatedStateData,} from 'protocol';
|
||||
import {ComponentExplorerViewQuery, DirectiveMetadata, DirectivesProperties, ElementPosition, PropertyQueryTypes, SerializedInjectedService, SerializedInjector, SerializedProviderRecord, UpdatedStateData,} from 'protocol';
|
||||
|
||||
import {buildDirectiveTree, getLViewFromDirectiveOrElementInstance} from './directive-forest/index';
|
||||
import {ngDebugApiIsSupported, ngDebugClient, ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api';
|
||||
import {deeplySerializeSelectedProperties, serializeDirectiveState,} from './state-serializer/state-serializer';
|
||||
|
||||
// Need to be kept in sync with Angular framework
|
||||
|
|
@ -21,9 +21,9 @@ enum ChangeDetectionStrategy {
|
|||
}
|
||||
|
||||
import {ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType} from './interfaces';
|
||||
import type {ClassProvider, ExistingProvider, FactoryProvider, InjectionToken, Injector, Type, ValueProvider,} from '@angular/core';
|
||||
|
||||
const ngDebug = () => (window as any).ng;
|
||||
import type {ClassProvider, ExistingProvider, FactoryProvider, InjectOptions, InjectionToken, Injector, Type, ValueProvider, ɵComponentDebugMetadata as ComponentDebugMetadata, ɵProviderRecord as ProviderRecord} from '@angular/core';
|
||||
|
||||
export const injectorToId = new WeakMap<Injector|HTMLElement, string>();
|
||||
export const nodeInjectorToResolutionPath = new WeakMap<HTMLElement, SerializedInjector[]>();
|
||||
export const idToInjector = new Map<string, Injector>();
|
||||
|
|
@ -34,32 +34,8 @@ export function getInjectorId() {
|
|||
return `${injectorId++}`;
|
||||
}
|
||||
|
||||
export function hasDiDebugAPIs(): boolean {
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorResolutionPath')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetDependenciesFromInjectable')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorProviders')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorMetadata')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ngDebugApiIsSupported(api: string): boolean {
|
||||
const ng = ngDebug();
|
||||
return typeof ng[api] === 'function';
|
||||
}
|
||||
|
||||
export function getInjectorMetadata(
|
||||
injector: Injector,
|
||||
): {type: string; source: HTMLElement | string | null}|null {
|
||||
return ngDebug().ɵgetInjectorMetadata(injector);
|
||||
function getInjectorMetadata(injector: Injector) {
|
||||
return ngDebugClient().ɵgetInjectorMetadata(injector);
|
||||
}
|
||||
|
||||
export function getInjectorResolutionPath(injector: Injector): Injector[] {
|
||||
|
|
@ -67,23 +43,23 @@ export function getInjectorResolutionPath(injector: Injector): Injector[] {
|
|||
return [];
|
||||
}
|
||||
|
||||
return ngDebug().ɵgetInjectorResolutionPath(injector);
|
||||
return ngDebugClient().ɵgetInjectorResolutionPath(injector);
|
||||
}
|
||||
|
||||
export function getInjectorFromElementNode(element: Node): Injector|null {
|
||||
return ngDebug().getInjector(element);
|
||||
return ngDebugClient().getInjector(element);
|
||||
}
|
||||
|
||||
export function getDirectivesFromElement(element: HTMLElement):
|
||||
function getDirectivesFromElement(element: HTMLElement):
|
||||
{component: unknown|null; directives: unknown[];} {
|
||||
let component = null;
|
||||
if (element instanceof Element) {
|
||||
component = ngDebug().getComponent(element);
|
||||
component = ngDebugClient().getComponent(element);
|
||||
}
|
||||
|
||||
return {
|
||||
component,
|
||||
directives: ngDebug().getDirectives(element),
|
||||
directives: ngDebugClient().getDirectives(element),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -101,17 +77,15 @@ export const getLatestComponentState = (
|
|||
|
||||
const directiveProperties: DirectivesProperties = {};
|
||||
|
||||
const injector = ngDebug().getInjector(node.nativeElement);
|
||||
|
||||
let resolutionPathWithProviders: {injector: Injector; providers: ProviderRecord[]}[] = [];
|
||||
if (hasDiDebugAPIs()) {
|
||||
resolutionPathWithProviders =
|
||||
getInjectorResolutionPath(injector).map((injector) => ({
|
||||
injector,
|
||||
providers: getInjectorProviders(injector),
|
||||
}));
|
||||
}
|
||||
const injector = ngDebugClient().getInjector(node.nativeElement!);
|
||||
|
||||
const injectors = getInjectorResolutionPath(injector);
|
||||
const resolutionPathWithProviders = !ngDebugDependencyInjectionApiIsSupported() ?
|
||||
[] :
|
||||
injectors.map((injector) => ({
|
||||
injector,
|
||||
providers: getInjectorProviders(injector),
|
||||
}));
|
||||
const populateResultSet = (dir: DirectiveInstanceType|ComponentInstanceType) => {
|
||||
const {instance, name} = dir;
|
||||
const metadata = getDirectiveMetadata(instance);
|
||||
|
|
@ -149,7 +123,7 @@ export const getLatestComponentState = (
|
|||
};
|
||||
};
|
||||
|
||||
export function serializeElementInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
function serializeElementInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
let id: string;
|
||||
const element = getElementInjectorElement(injector);
|
||||
|
||||
|
|
@ -171,7 +145,7 @@ export function serializeElementInjectorWithId(injector: Injector): SerializedIn
|
|||
return {id, ...serializedInjector};
|
||||
}
|
||||
|
||||
export function serializeInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
function serializeInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
if (isElementInjector(injector)) {
|
||||
return serializeElementInjectorWithId(injector);
|
||||
} else {
|
||||
|
|
@ -179,7 +153,7 @@ export function serializeInjectorWithId(injector: Injector): SerializedInjector|
|
|||
}
|
||||
}
|
||||
|
||||
export function serializeEnvironmentInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
function serializeEnvironmentInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
let id: string;
|
||||
|
||||
if (!injectorToId.has(injector)) {
|
||||
|
|
@ -210,18 +184,16 @@ const enum DirectiveMetadataKey {
|
|||
// Gets directive metadata. For newer versions of Angular (v12+) it uses
|
||||
// the global `getDirectiveMetadata`. For prior versions of the framework
|
||||
// the method directly interacts with the directive/component definition.
|
||||
export const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
|
||||
const getMetadata = (window as any).ng.getDirectiveMetadata;
|
||||
if (getMetadata) {
|
||||
const metadata = getMetadata(dir);
|
||||
if (metadata) {
|
||||
return {
|
||||
inputs: metadata.inputs,
|
||||
outputs: metadata.outputs,
|
||||
encapsulation: metadata.encapsulation,
|
||||
onPush: metadata.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
};
|
||||
}
|
||||
const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
|
||||
const getMetadata = ngDebugClient().getDirectiveMetadata;
|
||||
const metadata = getMetadata?.(dir) as ComponentDebugMetadata;
|
||||
if (metadata) {
|
||||
return {
|
||||
inputs: metadata.inputs,
|
||||
outputs: metadata.outputs,
|
||||
encapsulation: metadata.encapsulation,
|
||||
onPush: metadata.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
};
|
||||
}
|
||||
|
||||
// Used in older Angular versions, prior to the introduction of `getDirectiveMetadata`.
|
||||
|
|
@ -242,12 +214,12 @@ export const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
|
|||
};
|
||||
};
|
||||
|
||||
export function getInjectorProviders(injector: Injector): ProviderRecord[] {
|
||||
export function getInjectorProviders(injector: Injector) {
|
||||
if (isNullInjector(injector)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ngDebug().ɵgetInjectorProviders(injector);
|
||||
return ngDebugClient().ɵgetInjectorProviders(injector);
|
||||
}
|
||||
|
||||
const getDependenciesForDirective = (
|
||||
|
|
@ -259,12 +231,8 @@ const getDependenciesForDirective = (
|
|||
return [];
|
||||
}
|
||||
|
||||
let dependencies: InjectedService[] = ngDebug()
|
||||
.ɵgetDependenciesFromInjectable(
|
||||
injector,
|
||||
directive,
|
||||
)
|
||||
.dependencies;
|
||||
let dependencies =
|
||||
ngDebugClient().ɵgetDependenciesFromInjectable(injector, directive)?.dependencies ?? [];
|
||||
const serializedInjectedServices: SerializedInjectedService[] = [];
|
||||
|
||||
let position = 0;
|
||||
|
|
@ -285,10 +253,10 @@ const getDependenciesForDirective = (
|
|||
// +
|
||||
// the import path from the providing injector to the feature module that provided the
|
||||
// dependency (2)
|
||||
const dependencyResolutionPath = [
|
||||
const dependencyResolutionPath: SerializedInjector[] = [
|
||||
// (1)
|
||||
...resolutionPath.slice(0, foundInjectorIndex + 1)
|
||||
.map((node) => serializeInjectorWithId(node.injector)),
|
||||
.map((node) => serializeInjectorWithId(node.injector)!),
|
||||
|
||||
// (2)
|
||||
// We slice the import path to remove the first element because this is the same
|
||||
|
|
@ -296,13 +264,13 @@ const getDependenciesForDirective = (
|
|||
...(foundProvider?.importPath ?? []).slice(1).map((node) => {
|
||||
return {type: 'imported-module', name: valueToLabel(node), id: getInjectorId()};
|
||||
}),
|
||||
] as SerializedInjector[];
|
||||
];
|
||||
|
||||
if (dependency.token && isInjectionToken(dependency.token)) {
|
||||
serializedInjectedServices.push({
|
||||
token: dependency.token!.toString(),
|
||||
value: valueToLabel(dependency.value),
|
||||
flags: dependency.flags,
|
||||
flags: dependency.flags as InjectOptions,
|
||||
position: [position++],
|
||||
resolutionPath: dependencyResolutionPath,
|
||||
});
|
||||
|
|
@ -312,7 +280,7 @@ const getDependenciesForDirective = (
|
|||
serializedInjectedServices.push({
|
||||
token: valueToLabel(dependency.token),
|
||||
value: valueToLabel(dependency.value),
|
||||
flags: dependency.flags,
|
||||
flags: dependency.flags as InjectOptions,
|
||||
position: [position++],
|
||||
resolutionPath: dependencyResolutionPath,
|
||||
});
|
||||
|
|
@ -321,7 +289,7 @@ const getDependenciesForDirective = (
|
|||
return serializedInjectedServices;
|
||||
};
|
||||
|
||||
export const valueToLabel = (value: any): string => {
|
||||
const valueToLabel = (value: any): string => {
|
||||
if (isInjectionToken(value)) {
|
||||
return `InjectionToken(${value['_desc']})`;
|
||||
}
|
||||
|
|
@ -360,7 +328,7 @@ export function serializeInjector(injector: Injector): Omit<SerializedInjector,
|
|||
}
|
||||
|
||||
if (metadata.type === 'element') {
|
||||
const source = metadata.source! as HTMLElement;
|
||||
const source = metadata.source as HTMLElement;
|
||||
const name = stripUnderscore(elementToDirectiveNames(source)[0]);
|
||||
|
||||
return {type: 'element', name, providers};
|
||||
|
|
@ -377,7 +345,7 @@ export function serializeInjector(injector: Injector): Omit<SerializedInjector,
|
|||
}
|
||||
}
|
||||
|
||||
return {type: 'environment', name: stripUnderscore(metadata.source as string), providers};
|
||||
return {type: 'environment', name: stripUnderscore(metadata.source ?? ''), providers};
|
||||
}
|
||||
|
||||
console.error('Angular DevTools: Could not serialize injector.', injector);
|
||||
|
|
@ -444,15 +412,10 @@ export function getElementInjectorElement(elementInjector: Injector): HTMLElemen
|
|||
return getInjectorMetadata(elementInjector)!.source as HTMLElement;
|
||||
}
|
||||
|
||||
export function isInjectionToken(token: Type<unknown>|InjectionToken<unknown>): boolean {
|
||||
function isInjectionToken(token: Type<unknown>|InjectionToken<unknown>): boolean {
|
||||
return token.constructor.name === 'InjectionToken';
|
||||
}
|
||||
|
||||
export function isEnvironmentInjector(injector: Injector) {
|
||||
const metadata = getInjectorMetadata(injector);
|
||||
return metadata !== null && metadata.type === 'environment';
|
||||
}
|
||||
|
||||
export function isElementInjector(injector: Injector) {
|
||||
const metadata = getInjectorMetadata(injector);
|
||||
return metadata !== null && metadata.type === 'element';
|
||||
|
|
@ -480,12 +443,10 @@ const getRootLViewsHelper = (element: Element, rootLViews = new Set<any>()): Set
|
|||
};
|
||||
|
||||
const getRoots = () => {
|
||||
const roots = Array.from(
|
||||
document.documentElement.querySelectorAll('[ng-version]'),
|
||||
) as HTMLElement[];
|
||||
const roots = Array.from(document.documentElement.querySelectorAll('[ng-version]'));
|
||||
|
||||
const isTopLevel = (element: HTMLElement) => {
|
||||
let parent: HTMLElement|null = element;
|
||||
const isTopLevel = (element: Element) => {
|
||||
let parent: Element|null = element;
|
||||
|
||||
while (parent?.parentElement) {
|
||||
parent = parent.parentElement;
|
||||
|
|
@ -541,7 +502,7 @@ export const findNodeFromSerializedPosition = (
|
|||
};
|
||||
|
||||
export const updateState = (updatedStateData: UpdatedStateData): void => {
|
||||
const ngd = ngDebug();
|
||||
const ng = ngDebugClient();
|
||||
const node = queryDirectiveForest(updatedStateData.directiveId.element, buildDirectiveForest());
|
||||
if (!node) {
|
||||
console.warn(
|
||||
|
|
@ -554,13 +515,13 @@ export const updateState = (updatedStateData: UpdatedStateData): void => {
|
|||
if (updatedStateData.directiveId.directive !== undefined) {
|
||||
const directive = node.directives[updatedStateData.directiveId.directive].instance;
|
||||
mutateComponentOrDirective(updatedStateData, directive);
|
||||
ngd.applyChanges(ngd.getOwningComponent(directive));
|
||||
ng.applyChanges(ng.getOwningComponent(directive)!);
|
||||
return;
|
||||
}
|
||||
if (node.component) {
|
||||
const comp = node.component.instance;
|
||||
mutateComponentOrDirective(updatedStateData, comp);
|
||||
ngd.applyChanges(comp);
|
||||
ng.applyChanges(comp);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ ts_library(
|
|||
"//devtools/projects/ng-devtools-backend/src/lib:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib:utils",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib:version",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"@npm//semver-dsl",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
*/
|
||||
|
||||
import {ComponentTreeNode} from '../interfaces';
|
||||
import {ngDebugClient} from '../ng-debug-api/ng-debug-api';
|
||||
import {isCustomElement} from '../utils';
|
||||
|
||||
const extractViewTree =
|
||||
(domNode: Node|Element, result: ComponentTreeNode[], getComponent: (element: Element) => {},
|
||||
(domNode: Node|Element, result: ComponentTreeNode[],
|
||||
getComponent: (element: Element) => {} | null,
|
||||
getDirectives: (node: Node) => {}[]): ComponentTreeNode[] => {
|
||||
const directives = getDirectives(domNode);
|
||||
if (!directives.length && !(domNode instanceof Element)) {
|
||||
|
|
@ -56,8 +58,8 @@ const extractViewTree =
|
|||
|
||||
export class RTreeStrategy {
|
||||
supports(_: any): boolean {
|
||||
return ['getDirectiveMetadata', 'getComponent', 'getDirectives'].every(
|
||||
(method) => typeof (window as any).ng[method] === 'function');
|
||||
return (['getDirectiveMetadata', 'getComponent', 'getDirectives'] as const)
|
||||
.every((method) => typeof ngDebugClient()[method] === 'function');
|
||||
}
|
||||
|
||||
build(element: Element): ComponentTreeNode[] {
|
||||
|
|
@ -66,8 +68,8 @@ export class RTreeStrategy {
|
|||
while (element.parentElement) {
|
||||
element = element.parentElement;
|
||||
}
|
||||
const getComponent = (window as any).ng.getComponent as (element: Element) => {};
|
||||
const getDirectives = (window as any).ng.getDirectives as (node: Node) => {}[];
|
||||
const getComponent = ngDebugClient().getComponent;
|
||||
const getDirectives = ngDebugClient().getDirectives;
|
||||
return extractViewTree(element, [], getComponent, getDirectives);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ ts_library(
|
|||
"//devtools/projects/ng-devtools-backend/src/lib:utils",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/hooks:identity_tracker",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core",
|
||||
"@npm//rxjs",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ngDebugClient} from '../../ng-debug-api/ng-debug-api';
|
||||
|
||||
import {NgProfiler} from './native';
|
||||
import {PatchingProfiler} from './polyfill';
|
||||
import {Profiler} from './shared';
|
||||
|
|
@ -17,8 +19,7 @@ export {Hooks, Profiler} from './shared';
|
|||
* Gives priority to NgProfiler, falls back on PatchingProfiler if framework APIs are not present.
|
||||
*/
|
||||
export const selectProfilerStrategy = (): Profiler => {
|
||||
const ng = (window as any).ng;
|
||||
if (typeof ng?.ɵsetProfiler === 'function') {
|
||||
if (typeof ngDebugClient().ɵsetProfiler === 'function') {
|
||||
return new NgProfiler();
|
||||
}
|
||||
return new PatchingProfiler();
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@
|
|||
import {ɵProfilerEvent} from '@angular/core';
|
||||
|
||||
import {getDirectiveHostElement} from '../../directive-forest';
|
||||
import {ngDebugClient} from '../../ng-debug-api/ng-debug-api';
|
||||
import {runOutsideAngular} from '../../utils';
|
||||
import {IdentityTracker, NodeArray} from '../identity-tracker';
|
||||
|
||||
import {getLifeCycleName, Hooks, Profiler} from './shared';
|
||||
|
||||
type ProfilerCallback = (event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) => void;
|
||||
type ProfilerCallback = (event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) =>
|
||||
void;
|
||||
|
||||
/** Implementation of Profiler that utilizes framework APIs fire profiler hooks. */
|
||||
export class NgProfiler extends Profiler {
|
||||
|
|
@ -24,20 +26,20 @@ export class NgProfiler extends Profiler {
|
|||
|
||||
constructor(config: Partial<Hooks> = {}) {
|
||||
super(config);
|
||||
this._setProfilerCallback((event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) => {
|
||||
if (this[event] === undefined) {
|
||||
return;
|
||||
}
|
||||
this._setProfilerCallback(
|
||||
(event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) => {
|
||||
if (this[event] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[event](instanceOrLView, hookOrListener);
|
||||
});
|
||||
this[event](instanceOrLView, hookOrListener);
|
||||
});
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
private _initialize(): void {
|
||||
const ng = (window as any).ng;
|
||||
ng.ɵsetProfiler(
|
||||
(event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) =>
|
||||
ngDebugClient().ɵsetProfiler(
|
||||
(event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) =>
|
||||
this._callbacks.forEach((cb) => cb(event, instanceOrLView, hookOrListener)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# load("//devtools/tools:typescript.bzl", "ts_library")
|
||||
load("//devtools/tools:ng_module.bzl", "ng_module")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ng_module(
|
||||
name = "ng-debug-api",
|
||||
srcs = glob(
|
||||
include = ["*.ts"],
|
||||
exclude = ["*.spec.ts"],
|
||||
),
|
||||
deps = [
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @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 type {ɵGlobalDevModeUtils as GlobalDevModeUtils} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Returns a handle to window.ng APIs (global angular debugging).
|
||||
*
|
||||
* @returns window.ng
|
||||
*/
|
||||
export const ngDebugClient = () => (window as any as GlobalDevModeUtils).ng;
|
||||
|
||||
/**
|
||||
* Checks whether a given debug API is supported within window.ng
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
export function ngDebugApiIsSupported(api: keyof GlobalDevModeUtils['ng']): boolean {
|
||||
const ng = ngDebugClient();
|
||||
return typeof ng[api] === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether Dependency Injection debug API is supported within window.ng
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
export function ngDebugDependencyInjectionApiIsSupported(): boolean {
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorResolutionPath')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetDependenciesFromInjectable')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorProviders')) {
|
||||
return false;
|
||||
}
|
||||
if (!ngDebugApiIsSupported('ɵgetInjectorMetadata')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import {InjectionToken, InjectOptions, Injector, Type, ViewEncapsulation} from '@angular/core';
|
||||
import {SingleProvider} from '@angular/core/src/di/provider_collection';
|
||||
|
||||
export interface DirectiveType {
|
||||
name: string;
|
||||
|
|
@ -37,17 +36,6 @@ export interface SerializedInjector {
|
|||
providers?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate of the ProviderRecord interface from Angular framework to prevent
|
||||
* needing to publically expose the interface from the framework.
|
||||
*/
|
||||
export interface ProviderRecord {
|
||||
token: Type<unknown>;
|
||||
isViewProvider: boolean;
|
||||
provider: SingleProvider;
|
||||
importPath?: (Injector|Type<unknown>)[];
|
||||
}
|
||||
|
||||
export interface SerializedProviderRecord {
|
||||
token: string;
|
||||
type: 'type'|'existing'|'class'|'value'|'factory'|'multi';
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export {PendingTasks as ɵPendingTasks} from './pending_tasks';
|
|||
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS} from './platform/platform';
|
||||
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
||||
export {AnimationRendererType as ɵAnimationRendererType} from './render/api';
|
||||
export {InjectorProfilerContext as ɵInjectorProfilerContext, setInjectorProfilerContext as ɵsetInjectorProfilerContext} from './render3/debug/injector_profiler';
|
||||
export {InjectorProfilerContext as ɵInjectorProfilerContext, ProviderRecord as ɵProviderRecord, setInjectorProfilerContext as ɵsetInjectorProfilerContext} from './render3/debug/injector_profiler';
|
||||
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass';
|
||||
export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer';
|
||||
export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export {
|
|||
export {
|
||||
AttributeMarker as ɵAttributeMarker,
|
||||
ComponentDef as ɵComponentDef,
|
||||
ComponentDebugMetadata as ɵComponentDebugMetadata,
|
||||
ComponentFactory as ɵRender3ComponentFactory,
|
||||
ComponentRef as ɵRender3ComponentRef,
|
||||
ComponentType as ɵComponentType,
|
||||
|
|
@ -284,10 +285,7 @@ export {
|
|||
isNgModule as ɵisNgModule
|
||||
} from './render3/jit/util';
|
||||
export { Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent } from './render3/profiler';
|
||||
export {
|
||||
publishDefaultGlobalUtils as ɵpublishDefaultGlobalUtils
|
||||
,
|
||||
publishGlobalUtil as ɵpublishGlobalUtil} from './render3/util/global_utils';
|
||||
export { GlobalDevModeUtils as ɵGlobalDevModeUtils } from './render3/util/global_utils';
|
||||
export {ViewRef as ɵViewRef} from './render3/view_ref';
|
||||
export {
|
||||
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ export type InjectorProfilerEvent =
|
|||
|
||||
/**
|
||||
* An object that contains information about a provider that has been configured
|
||||
*
|
||||
* TODO: rename to indicate that it is a debug structure eg. ProviderDebugInfo.
|
||||
*/
|
||||
export interface ProviderRecord {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,6 +33,31 @@ import {getDependenciesFromInjectable, getInjectorMetadata, getInjectorProviders
|
|||
* */
|
||||
export const GLOBAL_PUBLISH_EXPANDO_KEY = 'ng';
|
||||
|
||||
const globalUtilsFunctions = {
|
||||
/**
|
||||
* Warning: functions that start with `ɵ` are considered *INTERNAL* and should not be relied upon
|
||||
* in application's code. The contract of those functions might be changed in any release and/or a
|
||||
* function can be removed completely.
|
||||
*/
|
||||
'ɵgetDependenciesFromInjectable': getDependenciesFromInjectable,
|
||||
'ɵgetInjectorProviders': getInjectorProviders,
|
||||
'ɵgetInjectorResolutionPath': getInjectorResolutionPath,
|
||||
'ɵgetInjectorMetadata': getInjectorMetadata,
|
||||
'ɵsetProfiler': setProfiler,
|
||||
|
||||
'getDirectiveMetadata': getDirectiveMetadata,
|
||||
'getComponent': getComponent,
|
||||
'getContext': getContext,
|
||||
'getListeners': getListeners,
|
||||
'getOwningComponent': getOwningComponent,
|
||||
'getHostElement': getHostElement,
|
||||
'getInjector': getInjector,
|
||||
'getRootComponents': getRootComponents,
|
||||
'getDirectives': getDirectives,
|
||||
'applyChanges': applyChanges,
|
||||
};
|
||||
type GlobalUtilsFunctions = keyof typeof globalUtilsFunctions;
|
||||
|
||||
let _published = false;
|
||||
/**
|
||||
* Publishes a collection of default debug tools onto`window.ng`.
|
||||
|
|
@ -45,51 +70,34 @@ export function publishDefaultGlobalUtils() {
|
|||
_published = true;
|
||||
|
||||
setupFrameworkInjectorProfiler();
|
||||
publishGlobalUtil('ɵgetDependenciesFromInjectable', getDependenciesFromInjectable);
|
||||
publishGlobalUtil('ɵgetInjectorProviders', getInjectorProviders);
|
||||
publishGlobalUtil('ɵgetInjectorResolutionPath', getInjectorResolutionPath);
|
||||
publishGlobalUtil('ɵgetInjectorMetadata', getInjectorMetadata);
|
||||
/**
|
||||
* Warning: this function is *INTERNAL* and should not be relied upon in application's code.
|
||||
* The contract of the function might be changed in any release and/or the function can be
|
||||
* removed completely.
|
||||
*/
|
||||
publishGlobalUtil('ɵsetProfiler', setProfiler);
|
||||
publishGlobalUtil('getDirectiveMetadata', getDirectiveMetadata);
|
||||
publishGlobalUtil('getComponent', getComponent);
|
||||
publishGlobalUtil('getContext', getContext);
|
||||
publishGlobalUtil('getListeners', getListeners);
|
||||
publishGlobalUtil('getOwningComponent', getOwningComponent);
|
||||
publishGlobalUtil('getHostElement', getHostElement);
|
||||
publishGlobalUtil('getInjector', getInjector);
|
||||
publishGlobalUtil('getRootComponents', getRootComponents);
|
||||
publishGlobalUtil('getDirectives', getDirectives);
|
||||
publishGlobalUtil('applyChanges', applyChanges);
|
||||
for (const [methodName, method] of Object.entries(globalUtilsFunctions)) {
|
||||
publishGlobalUtil(methodName as GlobalUtilsFunctions, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export declare type GlobalDevModeContainer = {
|
||||
[GLOBAL_PUBLISH_EXPANDO_KEY]: {[fnName: string]: Function};
|
||||
/**
|
||||
* Default debug tools available under `window.ng`.
|
||||
*/
|
||||
export type GlobalDevModeUtils = {
|
||||
[GLOBAL_PUBLISH_EXPANDO_KEY]: typeof globalUtilsFunctions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Publishes the given function to `window.ng` so that it can be
|
||||
* used from the browser console when an application is not in production.
|
||||
*/
|
||||
export function publishGlobalUtil(name: string, fn: Function): void {
|
||||
export function publishGlobalUtil<K extends GlobalUtilsFunctions>(
|
||||
name: K, fn: typeof globalUtilsFunctions[K]): void {
|
||||
if (typeof COMPILED === 'undefined' || !COMPILED) {
|
||||
// Note: we can't export `ng` when using closure enhanced optimization as:
|
||||
// - closure declares globals itself for minified names, which sometimes clobber our `ng` global
|
||||
// - we can't declare a closure extern as the namespace `ng` is already used within Google
|
||||
// for typings for AngularJS (via `goog.provide('ng....')`).
|
||||
const w = global as any as GlobalDevModeContainer;
|
||||
const w = global as GlobalDevModeUtils;
|
||||
ngDevMode && assertDefined(fn, 'function not defined');
|
||||
if (w) {
|
||||
let container = w[GLOBAL_PUBLISH_EXPANDO_KEY];
|
||||
if (!container) {
|
||||
container = w[GLOBAL_PUBLISH_EXPANDO_KEY] = {};
|
||||
}
|
||||
container[name] = fn;
|
||||
}
|
||||
|
||||
w[GLOBAL_PUBLISH_EXPANDO_KEY] ??= {} as any;
|
||||
w[GLOBAL_PUBLISH_EXPANDO_KEY][name] = fn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,8 +478,8 @@ export function getInjectorProviders(injector: Injector): ProviderRecord[] {
|
|||
* @returns an object containing the type and source of the given injector. If the injector metadata
|
||||
* cannot be determined, returns null.
|
||||
*/
|
||||
export function getInjectorMetadata(injector: Injector):
|
||||
{type: string; source: RElement | string | null}|null {
|
||||
export function getInjectorMetadata(injector: Injector): {type: 'element', source: RElement}|
|
||||
{type: 'environment', source: string | null}|{type: 'null', source: null}|null {
|
||||
if (injector instanceof NodeInjector) {
|
||||
const lView = getNodeInjectorLView(injector);
|
||||
const tNode = getNodeInjectorTNode(injector)!;
|
||||
|
|
|
|||
|
|
@ -7,19 +7,23 @@
|
|||
*/
|
||||
|
||||
import {setProfiler} from '@angular/core/src/render3/profiler';
|
||||
|
||||
import {applyChanges} from '../../src/render3/util/change_detection_utils';
|
||||
import {getComponent, getContext, getDirectiveMetadata, getDirectives, getHostElement, getInjector, getListeners, getOwningComponent, getRootComponents} from '../../src/render3/util/discovery_utils';
|
||||
import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeContainer, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/util/global_utils';
|
||||
import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeUtils, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/util/global_utils';
|
||||
import {global} from '../../src/util/global';
|
||||
|
||||
type GlobalUtilFunctions = keyof GlobalDevModeUtils['ng'];
|
||||
|
||||
describe('global utils', () => {
|
||||
describe('publishGlobalUtil', () => {
|
||||
it('should publish a function to the window', () => {
|
||||
const w = global as any as GlobalDevModeContainer;
|
||||
expect(w[GLOBAL_PUBLISH_EXPANDO_KEY]['foo']).toBeFalsy();
|
||||
const w = global as any as GlobalDevModeUtils;
|
||||
const foo = 'foo' as GlobalUtilFunctions;
|
||||
expect(w[GLOBAL_PUBLISH_EXPANDO_KEY][foo]).toBeFalsy();
|
||||
const fooFn = () => {};
|
||||
publishGlobalUtil('foo', fooFn);
|
||||
expect(w[GLOBAL_PUBLISH_EXPANDO_KEY]['foo']).toBe(fooFn);
|
||||
publishGlobalUtil(foo, fooFn);
|
||||
expect(w[GLOBAL_PUBLISH_EXPANDO_KEY][foo]).toBe(fooFn);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -72,7 +76,7 @@ describe('global utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function assertPublished(name: string, value: Function) {
|
||||
const w = global as any as GlobalDevModeContainer;
|
||||
function assertPublished(name: GlobalUtilFunctions, value: Function) {
|
||||
const w = global as any as GlobalDevModeUtils;
|
||||
expect(w[GLOBAL_PUBLISH_EXPANDO_KEY][name]).toBe(value);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue