Skip to content

Commit

Permalink
refactor(devtools): Use Chrome DevTools Performance extension API
Browse files Browse the repository at this point in the history
This change is a proof of concept of how the new Chrome DevTools
Performance extension API (https://bit.ly/rpp-e11y) can be used to
surface Angular runtime data directly in the Chrome DevTools Performance
panel.

Specifically, it implements the following changes:

1. Use the profiling status notification API to toggle the Timing API:
The notification API is implemented under the
chrome.devtools.performance extension namespace and consits of two
events: ProfilingStarted and ProfilingStopped, dispatched when the
Performance panel has started and stopped recording, respectively. This
API is used to enable the Timings API when the recording has started in
the Performance panel and disable it when recording has stopped.

2. Use the User Timings `detail` field format specification of the
Performance extension API
(https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/User_timing)
to inject data collected by the Angular Profiler into the
Performance panel timeline. Angular Profiler uses several hooks to
measure framework tasks like change detection. With this change, this
measurements are visible in the same context as the runtime data
collected by the browser in the Performance Panel timeline.

Note: to enable the user timings to be collected in the first place, one
needs to open the Angular DevTools panel so that the related artifacts
are loaded in the page. This shortcoming can be fixed in a follow up so
that the extra step isn't necessary.
  • Loading branch information
and-oli committed Jul 31, 2024
1 parent ca89ef9 commit 6783435
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 7 deletions.
14 changes: 13 additions & 1 deletion devtools/projects/ng-devtools-backend/src/lib/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,19 @@ const endMark = (nodeName: string, method: Method) => {
if (performance.getEntriesByName(start).length > 0) {
// tslint:disable-next-line:ban
performance.mark(end);
performance.measure(name, start, end);

const measureOptions = {
start,
end,
detail: {
devtools: {
dataType: 'track-entry',
color: 'primary',
track: '🅰️ Angular DevTools',
},
},
};
performance.measure(name, measureOptions);
}
performance.clearMarks(start);
performance.clearMarks(end);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@
</mat-tab-nav-panel>

<mat-menu #menu="matMenu">
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); toggleTimingAPI()">
<mat-slide-toggle [checked]="timingAPIEnabled">
Enable timing API
</mat-slide-toggle>
</div>
@if (!profilingNotificationsSupported) {
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); toggleTimingAPI()">
<mat-slide-toggle [checked]="timingAPIEnabled">
Enable timing API
</mat-slide-toggle>
</div>
}
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); themeService.toggleDarkMode(currentTheme === 'light-theme')">
<mat-slide-toggle [checked]="currentTheme === 'dark-theme'">
Dark Mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
routerTreeEnabled = false;
showCommentNodes = false;
timingAPIEnabled = false;
profilingNotificationsSupported = Boolean(
(window.chrome?.devtools as any)?.performance?.onProfilingStarted,
);

currentTheme!: Theme;
routes: Route[] = [];
Expand Down
25 changes: 24 additions & 1 deletion devtools/projects/shell-browser/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {Events, MessageBus} from 'protocol';

@Component({
selector: 'app-root',
Expand All @@ -15,12 +16,34 @@ import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
})
export class AppComponent implements OnInit {
private _cd = inject(ChangeDetectorRef);

private readonly _messageBus = inject<MessageBus<Events>>(MessageBus);
private onProfilingStartedListener = () => {
this._messageBus.emit('enableTimingAPI');
};
private onProfilingStoppedListener = () => {
this._messageBus.emit('disableTimingAPI');
};
ngOnInit(): void {
chrome.devtools.network.onNavigated.addListener(() => {
window.location.reload();
});
// At the moment the chrome.devtools.performance namespace does not
// have an entry in DefinitelyTyped, so this is a temporary
// workaround to prevent TypeScript failures while the corresponding
// type is added upstream.
const chromeDevToolsPerformance = (chrome.devtools as any).performance;
chromeDevToolsPerformance?.onProfilingStarted?.addListener?.(this.onProfilingStartedListener);
chromeDevToolsPerformance?.onProfilingStopped?.addListener?.(this.onProfilingStoppedListener);

this._cd.detectChanges();
}
ngOnDestroy(): void {
const chromeDevToolsPerformance = (chrome.devtools as any).performance;
chromeDevToolsPerformance?.onProfilingStarted?.removeListener?.(
this.onProfilingStartedListener,
);
chromeDevToolsPerformance?.onProfilingStopped?.removeListener?.(
this.onProfilingStoppedListener,
);
}
}

0 comments on commit 6783435

Please sign in to comment.