Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DevTools DI Visualization Features #51719

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel
Expand Up @@ -125,6 +125,7 @@ ts_library(
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
"//devtools/projects/ng-devtools-backend/src/lib/state-serializer",
"//devtools/projects/protocol",
"//packages/core",
],
)

Expand Down
Expand Up @@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ComponentExplorerViewQuery, ComponentType, DevToolsNode, DirectivePosition, DirectiveType, ElementPosition, Events, MessageBus, ProfilerFrame,} from 'protocol';
import {ComponentExplorerViewQuery, ComponentType, DevToolsNode, DirectivePosition, DirectiveType, ElementPosition, Events, MessageBus, ProfilerFrame, SerializedInjector, SerializedProviderRecord} from 'protocol';
import {debounceTime} from 'rxjs/operators';

import {appIsAngularInDevMode, appIsAngularIvy, appIsSupportedAngularVersion, getAngularVersion,} from './angular-check';
import {ComponentInspector} from './component-inspector/component-inspector';
import {getLatestComponentState, queryDirectiveForest, updateState} from './component-tree';
import {getInjectorFromElementNode, getInjectorProviders, getInjectorResolutionPath, getLatestComponentState, hasDiDebugAPIs, idToInjector, injectorsSeen, isElementInjector, queryDirectiveForest, serializeElementInjectorWithId, serializeEnvironmentInjectorWithId, serializeProviderRecord, updateState} from './component-tree';
import {unHighlight} from './highlighter';
import {disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks} from './hooks';
import {start as startProfiling, stop as stopProfiling} from './hooks/capture';
Expand Down Expand Up @@ -41,6 +41,8 @@ export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void =>
messageBus.on('enableTimingAPI', enableTimingAPI);
messageBus.on('disableTimingAPI', disableTimingAPI);

messageBus.on('getInjectorProviders', getInjectorProvidersCallback(messageBus));

if (appIsAngularInDevMode() && appIsSupportedAngularVersion() && appIsAngularIvy()) {
setupInspector(messageBus);
// Often websites have `scroll` event listener which triggers
Expand Down Expand Up @@ -70,23 +72,35 @@ const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>)

initializeOrGetDirectiveForestHooks().indexForest();

let forest: SerializableComponentTreeNode[];

if (hasDiDebugAPIs()) {
forest = prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(), true);

// cleanup injector id mappings
for (const injectorId of idToInjector.keys()) {
if (!injectorsSeen.has(injectorId)) {
idToInjector.delete(injectorId);
}
}
injectorsSeen.clear();
} else {
forest = prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
}

if (!query) {
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
},
]);
messageBus.emit('latestComponentExplorerView', [{forest}]);
return;
}
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
properties: getLatestComponentState(
query, initializeOrGetDirectiveForestHooks().getDirectiveForest()),
},
]);

const state = getLatestComponentState(
query, initializeOrGetDirectiveForestHooks().getDirectiveForest());
if (state) {
const {directiveProperties} = state;
messageBus.emit('latestComponentExplorerView', [{forest, properties: directiveProperties}]);
}
};

const checkForAngularCallback = (messageBus: MessageBus<Events>) => () =>
Expand Down Expand Up @@ -197,10 +211,11 @@ export interface SerializableComponentTreeNode extends
// Here we drop properties to prepare the tree for serialization.
// We don't need the component instance, so we just traverse the tree
// and leave the component name.
const prepareForestForSerialization =
(roots: ComponentTreeNode[]): SerializableComponentTreeNode[] => {
return roots.map((node) => {
return {
const prepareForestForSerialization = (roots: ComponentTreeNode[], includeResolutionPath = false):
SerializableComponentTreeNode[] => {
const serializedNodes: SerializableComponentTreeNode[] = [];
for (const node of roots) {
const serializedNode = {
element: node.element,
component: node.component ? {
name: node.component.name,
Expand All @@ -213,7 +228,63 @@ const prepareForestForSerialization =
name: d.name,
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance),
})),
children: prepareForestForSerialization(node.children),
children: prepareForestForSerialization(node.children, includeResolutionPath),
} as SerializableComponentTreeNode;
});

if (includeResolutionPath) {
const nodeInjector = getInjectorFromElementNode(node.nativeElement!);
if (!nodeInjector) {
serializedNode['resolutionPath'] = [];
serializedNodes.push(serializedNode);
continue;
}

const serializedResolutionPath: SerializedInjector[] = [];

for (const injector of getInjectorResolutionPath(nodeInjector!)) {
let serializedInjectorWithId: SerializedInjector|null = null;

if (isElementInjector(injector)) {
serializedInjectorWithId = serializeElementInjectorWithId(injector);
} else {
serializedInjectorWithId = serializeEnvironmentInjectorWithId(injector);
}

if (serializedInjectorWithId === null) {
continue;
}

serializedResolutionPath.push(serializedInjectorWithId);
}

serializedNode.resolutionPath = serializedResolutionPath;
}

serializedNodes.push(serializedNode);
}

return serializedNodes;
};


const getInjectorProvidersCallback = (messageBus: MessageBus<Events>) =>
(injector: SerializedInjector) => {
if (!idToInjector.has(injector.id)) {
return;
}

const providerRecords = getInjectorProviders(idToInjector.get(injector.id)!);
let serializedProviderRecords: SerializedProviderRecord[] = [];

if (injector.type === 'environment') {
serializedProviderRecords = providerRecords.map((providerRecord) => {
return serializeProviderRecord(providerRecord, true);
});
} else {
serializedProviderRecords = providerRecords.map((providerRecord) => {
return serializeProviderRecord(providerRecord);
});
}

messageBus.emit('latestInjectorProviders', [injector, serializedProviderRecords]);
};