diff --git a/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel index 4ca96e710f2..5220e61d737 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel @@ -48,7 +48,7 @@ ts_project( "//devtools/projects/ng-devtools-backend/src/lib/console:set-console-reference", "//devtools/projects/ng-devtools-backend/src/lib/directive-forest:manager", "//devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree", - "//devtools/projects/ng-devtools-backend/src/lib/hydration:hydration-highlighting", + "//devtools/projects/ng-devtools-backend/src/lib/hydration:hydration-overlays", "//devtools/projects/ng-devtools-backend/src/lib/profiling", "//devtools/projects/ng-devtools-backend/src/lib/profiling/cd-analyzer", "//devtools/projects/ng-devtools-backend/src/lib/profiling/profiler", diff --git a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.spec.ts index 63122a21e54..7aa0522da4c 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.spec.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.spec.ts @@ -50,10 +50,6 @@ describe('ClientEventSubscriber', () => { expect(messageBusMock.on).toHaveBeenCalledWith('inspectorStart', jasmine.any(Function)); expect(messageBusMock.on).toHaveBeenCalledWith('inspectorEnd', jasmine.any(Function)); - expect(messageBusMock.on).toHaveBeenCalledWith('createHighlightOverlay', jasmine.any(Function)); - expect(messageBusMock.on).toHaveBeenCalledWith('removeHighlightOverlay', jasmine.any(Function)); - expect(messageBusMock.on).toHaveBeenCalledWith('createHydrationOverlay', jasmine.any(Function)); - expect(messageBusMock.on).toHaveBeenCalledWith('removeHydrationOverlay', jasmine.any(Function)); }); }); diff --git a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts index d5f28c07754..b8c7e9db7be 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts @@ -52,10 +52,7 @@ import { updateState, } from './directive-forest/component-tree/component-tree'; import {getDirectiveForestManager} from './directive-forest/manager'; -import { - highlightHydrationNodes, - removeHydrationHighlights, -} from './hydration/hydration-highlighting'; +import {enableHydrationOverlays, disableHydrationOverlays} from './hydration/hydration-overlays'; import {start as startProfiling, stop as stopProfiling} from './profiling/capture'; import { disableCdDataStream, @@ -410,8 +407,8 @@ const setupInspector = (messageBus: MessageBus): ComponentInspector => { }); messageBus.on('removeHighlightOverlay', () => inspector.unhighlight()); - messageBus.on('createHydrationOverlay', highlightHydrationNodes); - messageBus.on('removeHydrationOverlay', removeHydrationHighlights); + messageBus.on('enableHydrationOverlays', enableHydrationOverlays); + messageBus.on('disableHydrationOverlays', disableHydrationOverlays); return inspector; }; diff --git a/devtools/projects/ng-devtools-backend/src/lib/hydration/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/hydration/BUILD.bazel index 4079dfa58b0..d9cef3416ed 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hydration/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/hydration/BUILD.bazel @@ -3,13 +3,16 @@ load("//devtools/tools:defaults.bzl", "ts_project") package(default_visibility = ["//devtools:__subpackages__"]) ts_project( - name = "hydration-highlighting", - srcs = ["hydration-highlighting.ts"], + name = "hydration-overlays", + srcs = ["hydration-overlays.ts"], deps = [ + "//:node_modules/rxjs", "//devtools/projects/ng-devtools-backend/src/lib/directive-forest:manager", + "//devtools/projects/ng-devtools-backend/src/lib/profiling/profiler", "//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces", "//devtools/projects/ng-devtools-backend/src/lib/shared/highlighter", "//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error", + "//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-highlighting.ts b/devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-overlays.ts similarity index 77% rename from devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-highlighting.ts rename to devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-overlays.ts index 3b13f855f72..a64ca0522c1 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-highlighting.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hydration/hydration-overlays.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ +import {debounceTime, Subscription} from 'rxjs'; import {HydrationStatus} from '../../../../protocol'; import {getDirectiveForestManager} from '../directive-forest/manager'; +import {getProfiler} from '../profiling/profiler'; import {highlightElement, removeHighlightsByType} from '../shared/highlighter'; import { HighlightTemplate, @@ -18,8 +20,35 @@ import { } from '../shared/highlighter/highlights'; import {ComponentTreeNode} from '../shared/interfaces'; import {AngularDevtoolsError} from '../shared/utils/error'; +import {runOutsideAngular} from '../shared/utils/general'; -export function highlightHydrationNodes(): void { +let hydrationOverlaysEnabled = false; +let profilerSubs: Subscription | undefined; + +export function enableHydrationOverlays() { + if (hydrationOverlaysEnabled) { + return; + } + + hydrationOverlaysEnabled = true; + highlightHydrationNodes(); + + runOutsideAngular(() => { + // We are throttling CD events due to the + // possiblity of scroll-induced event flood. + profilerSubs = getProfiler() + .changeDetection$.pipe(debounceTime(250)) + .subscribe(() => refresh()); + }); +} + +export function disableHydrationOverlays() { + removeHydrationHighlights(); + profilerSubs?.unsubscribe(); + hydrationOverlaysEnabled = false; +} + +function highlightHydrationNodes(): void { const forest: ComponentTreeNode[] = getDirectiveForestManager().getDirectiveForest(); // drop the root nodes, we don't want to highlight it @@ -39,12 +68,17 @@ export function highlightHydrationNodes(): void { } } -export function removeHydrationHighlights() { +function removeHydrationHighlights() { removeHighlightsByType(HighlightType.HydrationCompleted); removeHighlightsByType(HighlightType.HydrationMismatched); removeHighlightsByType(HighlightType.HydrationSkipped); } +function refresh() { + removeHydrationHighlights(); + highlightHydrationNodes(); +} + function highlightHydrationElement(node: Element, {status}: HydrationStatus) { let template: HighlightTemplate; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts index 7818505b7a5..494bf7523cf 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts @@ -169,8 +169,6 @@ export class DirectiveExplorerComponent { const splitElement = this.splitElementRef().nativeElement; const directiveForestSplitArea = this.directiveForestSplitArea().nativeElement; const resizeObserver = new ResizeObserver((entries) => { - this.refreshHydrationNodeHighlightsIfNeeded(); - const resizedEntry = entries[0]; if (resizedEntry.target === splitElement) { this.splitDirection.set( @@ -247,7 +245,6 @@ export class DirectiveExplorerComponent { if (!this._refreshRetryTimeout) { this._refreshRetryTimeout = setTimeout(() => this.refresh(), 500); } - this.refreshHydrationNodeHighlightsIfNeeded(); } viewSource(directiveName: string): void { @@ -381,21 +378,6 @@ export class DirectiveExplorerComponent { } } - createHydrationOverlays() { - this._messageBus.emit('createHydrationOverlay'); - } - - removeHydrationOverlays() { - this._messageBus.emit('removeHydrationOverlay'); - } - - private refreshHydrationNodeHighlightsIfNeeded() { - if (untracked(this.settings.showHydrationOverlays)) { - this.removeHydrationOverlays(); - this.createHydrationOverlays(); - } - } - showSignalGraph(node: DevtoolsSignalGraphNode | null) { if (node) { // We want to trigger an update each time we intercept an update. diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts index ca424b2c58d..3bcfc94940a 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts @@ -252,16 +252,6 @@ describe('DirectiveExplorerComponent', () => { }); }); - describe('hydration', () => { - it('should highlight hydration nodes', () => { - comp.createHydrationOverlays(); - expect(messageBusMock.emit).toHaveBeenCalledWith('createHydrationOverlay'); - - comp.removeHydrationOverlays(); - expect(messageBusMock.emit).toHaveBeenCalledWith('removeHydrationOverlay'); - }); - }); - describe('highlight', () => { it('should create a highlight overlay for directive-only nodes', () => { comp.highlight({ diff --git a/devtools/projects/ng-devtools/src/lib/devtools.component.ts b/devtools/projects/ng-devtools/src/lib/devtools.component.ts index 912c84be432..df11684af3c 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools.component.ts @@ -127,9 +127,9 @@ export class DevToolsComponent implements OnDestroy { // Keep BE in sync with hydration visualization. effect(() => { if (this.settings.showHydrationOverlays()) { - this.messageBus.emit('createHydrationOverlay'); + this.messageBus.emit('enableHydrationOverlays'); } else { - this.messageBus.emit('removeHydrationOverlay'); + this.messageBus.emit('disableHydrationOverlays'); } }); diff --git a/devtools/projects/protocol/src/lib/messages.ts b/devtools/projects/protocol/src/lib/messages.ts index 0d7e41ba6b0..c833bf094cc 100644 --- a/devtools/projects/protocol/src/lib/messages.ts +++ b/devtools/projects/protocol/src/lib/messages.ts @@ -434,8 +434,8 @@ export interface Events { createHighlightOverlay: (position: ElementPosition) => void; removeHighlightOverlay: () => void; - createHydrationOverlay: () => void; - removeHydrationOverlay: () => void; + enableHydrationOverlays: () => void; + disableHydrationOverlays: () => void; highlightComponent: (id: number) => void; selectComponent: (id: number) => void;