diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts index 435e0ed2107..d3e376b25c3 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts @@ -14,7 +14,6 @@ import { ElementRef, inject, input, - NgZone, signal, untracked, viewChild, @@ -81,7 +80,6 @@ export class InjectorTreeComponent { private readonly elementTree = viewChild('elementTree'); private readonly messageBus = inject>(MessageBus); - private readonly zone = inject(NgZone); protected readonly selectedNode = signal(null); @@ -178,51 +176,49 @@ export class InjectorTreeComponent { * */ updateInjectorTreeVisualization(forestWithInjectorPaths: DevToolsNode[]): void { - this.zone.runOutsideAngular(() => { - // At this point we have a forest of directive trees where each node has a resolution path. - // We want to convert this nested forest into an array of resolution paths. - // Our ultimate goal is to convert this array of resolution paths into a tree structure. - // Directive forest -> Array of resolution paths -> Tree of resolution paths + // At this point we have a forest of directive trees where each node has a resolution path. + // We want to convert this nested forest into an array of resolution paths. + // Our ultimate goal is to convert this array of resolution paths into a tree structure. + // Directive forest -> Array of resolution paths -> Tree of resolution paths - // First, pick out the resolution paths. - let injectorPaths = grabInjectorPathsFromDirectiveForest(forestWithInjectorPaths); + // First, pick out the resolution paths. + let injectorPaths = grabInjectorPathsFromDirectiveForest(forestWithInjectorPaths); - if (this.hideFrameworkInjectors) { - injectorPaths = filterOutAngularInjectors(injectorPaths); - } + if (this.hideFrameworkInjectors) { + injectorPaths = filterOutAngularInjectors(injectorPaths); + } - if (this.hideInjectorsWithNoProviders) { - injectorPaths = filterOutInjectorsWithNoProviders(injectorPaths); - } + if (this.hideInjectorsWithNoProviders) { + injectorPaths = filterOutInjectorsWithNoProviders(injectorPaths); + } - // In Angular we have two types of injectors, element injectors and environment injectors. - // We want to split the resolution paths into two groups, one for each type of injector. - const {elementPaths, environmentPaths, startingElementToEnvironmentPath} = - splitInjectorPathsIntoElementAndEnvironmentPaths(injectorPaths); - this.elementToEnvironmentPath = startingElementToEnvironmentPath; + // In Angular we have two types of injectors, element injectors and environment injectors. + // We want to split the resolution paths into two groups, one for each type of injector. + const {elementPaths, environmentPaths, startingElementToEnvironmentPath} = + splitInjectorPathsIntoElementAndEnvironmentPaths(injectorPaths); + this.elementToEnvironmentPath = startingElementToEnvironmentPath; - // Here for our 2 groups of resolution paths, we want to convert them into a tree structure. - const elementInjectorTree = transformInjectorResolutionPathsIntoTree(elementPaths); - const environmentInjectorTree = transformInjectorResolutionPathsIntoTree(environmentPaths); + // Here for our 2 groups of resolution paths, we want to convert them into a tree structure. + const elementInjectorTree = transformInjectorResolutionPathsIntoTree(elementPaths); + const environmentInjectorTree = transformInjectorResolutionPathsIntoTree(environmentPaths); - this.elementInjectorTreeGraph.render(elementInjectorTree); - this.elementInjectorTreeGraph.onNodeClick((_, node) => { - this.selectInjectorByNode(node); - }); - - this.injectorTreeGraph.render(environmentInjectorTree); - this.injectorTreeGraph.onNodeClick((_, node) => { - this.selectInjectorByNode(node); - }); - - if (this.firstRender) { - this.snapToRoot(this.injectorTreeGraph); - this.snapToRoot(this.elementInjectorTreeGraph); - } - - this.highlightPathFromSelectedInjector(); - this.firstRender = false; + this.elementInjectorTreeGraph.render(elementInjectorTree); + this.elementInjectorTreeGraph.onNodeClick((_, node) => { + this.selectInjectorByNode(node); }); + + this.injectorTreeGraph.render(environmentInjectorTree); + this.injectorTreeGraph.onNodeClick((_, node) => { + this.selectInjectorByNode(node); + }); + + if (this.firstRender) { + this.snapToRoot(this.injectorTreeGraph); + this.snapToRoot(this.elementInjectorTreeGraph); + } + + this.highlightPathFromSelectedInjector(); + this.firstRender = false; } snapToRoot(graph: InjectorTreeVisualizer) { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-visualizer/tree-map-visualizer/tree-map-visualizer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-visualizer/tree-map-visualizer/tree-map-visualizer.component.ts index 873ecf7a4d9..4da45fe906c 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-visualizer/tree-map-visualizer/tree-map-visualizer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-visualizer/tree-map-visualizer/tree-map-visualizer.component.ts @@ -15,7 +15,6 @@ import { ElementRef, inject, input, - NgZone, OnDestroy, viewChild, } from '@angular/core'; @@ -37,14 +36,10 @@ export class TreeMapVisualizerComponent implements OnDestroy { readonly frame = input.required(); - private _ngZone = inject(NgZone); - private resize$ = new Subject(); private _throttledResizeSubscription!: Subscription; - private _resizeObserver: ResizeObserver = new ResizeObserver(() => - this._ngZone.run(() => this.resize$.next()), - ); + private _resizeObserver: ResizeObserver = new ResizeObserver(() => this.resize$.next()); private readonly treeMapRecords = computed(() => { // first element in data is the Application node return this._formatter.formatFrame(this.frame()); diff --git a/devtools/projects/ng-devtools/src/lib/devtools.component.ts b/devtools/projects/ng-devtools/src/lib/devtools.component.ts index 18334ad8caa..af41c519032 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools.component.ts @@ -13,7 +13,6 @@ import { computed, inject, OnDestroy, - OnInit, signal, } from '@angular/core'; import {Events, MessageBus, SupportedApis} from '../../../protocol'; diff --git a/devtools/projects/ng-devtools/src/lib/shared/split/responsive-split.directive.ts b/devtools/projects/ng-devtools/src/lib/shared/split/responsive-split.directive.ts index 164fca46ad9..919423cb440 100644 --- a/devtools/projects/ng-devtools/src/lib/shared/split/responsive-split.directive.ts +++ b/devtools/projects/ng-devtools/src/lib/shared/split/responsive-split.directive.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Directive, ElementRef, inject, input, NgZone, DestroyRef, output} from '@angular/core'; +import {Directive, ElementRef, inject, input, DestroyRef, output} from '@angular/core'; import {WINDOW} from '../../application-providers/window_provider'; import {Debouncer} from '../utils/debouncer'; import {SplitComponent} from './split.component'; @@ -30,7 +30,6 @@ export type ResponsiveSplitConfig = { export class ResponsiveSplitDirective { private readonly host = inject(SplitComponent); private readonly elementRef = inject(ElementRef); - private readonly zone = inject(NgZone); private readonly window = inject(WINDOW); protected readonly config = input.required({ @@ -44,14 +43,10 @@ export class ResponsiveSplitDirective { // We use the ResizeObserver from the injected window object to allow mocking in tests. const resizeObserver = new this.window.ResizeObserver( debouncer.debounce(([entry]) => { - // Since used in a ResizeObserver which is not - // patched by zone.js, run inside a zone. - this.zone.run(() => { - if (entry.contentBoxSize) { - const [{inlineSize, blockSize}] = entry.contentBoxSize; - this.applyDirection(inlineSize, blockSize); - } - }); + if (entry.contentBoxSize) { + const [{inlineSize, blockSize}] = entry.contentBoxSize; + this.applyDirection(inlineSize, blockSize); + } }, RESIZE_DEBOUNCE), ); diff --git a/devtools/projects/ng-devtools/src/lib/shared/split/splitArea.directive.ts b/devtools/projects/ng-devtools/src/lib/shared/split/splitArea.directive.ts index 9697062d4cf..41443be4667 100644 --- a/devtools/projects/ng-devtools/src/lib/shared/split/splitArea.directive.ts +++ b/devtools/projects/ng-devtools/src/lib/shared/split/splitArea.directive.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Directive, ElementRef, Input, NgZone, OnDestroy, OnInit, Renderer2} from '@angular/core'; +import {Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2} from '@angular/core'; import {SplitComponent} from './split.component'; import {getInputBoolean, getInputPositiveNumber} from './utils'; @@ -115,7 +115,6 @@ export class SplitAreaDirective implements OnInit, OnDestroy { private readonly lockListeners: Array = []; constructor( - private ngZone: NgZone, public elRef: ElementRef, private renderer: Renderer2, private split: SplitComponent, @@ -126,18 +125,16 @@ export class SplitAreaDirective implements OnInit, OnDestroy { public ngOnInit(): void { this.split.addArea(this); - this.ngZone.runOutsideAngular(() => { - this.transitionListener = this.renderer.listen( - this.elRef.nativeElement, - 'transitionend', - (event: TransitionEvent) => { - // Limit only flex-basis transition to trigger the event - if (event.propertyName === 'flex-basis') { - this.split.notify('transitionEnd', -1); - } - }, - ); - }); + this.transitionListener = this.renderer.listen( + this.elRef.nativeElement, + 'transitionend', + (event: TransitionEvent) => { + // Limit only flex-basis transition to trigger the event + if (event.propertyName === 'flex-basis') { + this.split.notify('transitionEnd', -1); + } + }, + ); } public setStyleOrder(value: number): void { @@ -165,14 +162,12 @@ export class SplitAreaDirective implements OnInit, OnDestroy { } public lockEvents(): void { - this.ngZone.runOutsideAngular(() => { - this.lockListeners.push( - this.renderer.listen(this.elRef.nativeElement, 'selectstart', (e: Event) => false), - ); - this.lockListeners.push( - this.renderer.listen(this.elRef.nativeElement, 'dragstart', (e: Event) => false), - ); - }); + this.lockListeners.push( + this.renderer.listen(this.elRef.nativeElement, 'selectstart', (e: Event) => false), + ); + this.lockListeners.push( + this.renderer.listen(this.elRef.nativeElement, 'dragstart', (e: Event) => false), + ); } public unlockEvents(): void { diff --git a/devtools/projects/protocol/src/lib/priority-aware-message-bus.ts b/devtools/projects/protocol/src/lib/priority-aware-message-bus.ts index fd2e83c94c4..e3e32361815 100644 --- a/devtools/projects/protocol/src/lib/priority-aware-message-bus.ts +++ b/devtools/projects/protocol/src/lib/priority-aware-message-bus.ts @@ -53,7 +53,9 @@ export class PriorityAwareMessageBus extends MessageBus { constructor( private _bus: MessageBus, - private _setTimeout: typeof setTimeout = setTimeout, + // Binding is necessary to ensure that `setTimeout` is called in the global context. + // an doesn't throw "Illegal invocation" error. + private _setTimeout: typeof setTimeout = setTimeout.bind(globalThis), ) { super(); } diff --git a/devtools/projects/shell-browser/src/BUILD.bazel b/devtools/projects/shell-browser/src/BUILD.bazel index 774635d5038..76a29cc6d4e 100644 --- a/devtools/projects/shell-browser/src/BUILD.bazel +++ b/devtools/projects/shell-browser/src/BUILD.bazel @@ -111,7 +111,6 @@ filegroup( ":browser_specific_styles", ":index.html", ":shell_common_styles", - "//:node_modules/zone.js", ], ) diff --git a/devtools/projects/shell-browser/src/app/BUILD.bazel b/devtools/projects/shell-browser/src/app/BUILD.bazel index 929523d60cc..c883b070cc9 100644 --- a/devtools/projects/shell-browser/src/app/BUILD.bazel +++ b/devtools/projects/shell-browser/src/app/BUILD.bazel @@ -20,7 +20,7 @@ ng_project( deps = [ ":chrome_application_environment_rjs", ":chrome_application_operations_rjs", - ":zone_aware_chrome_message_bus_rjs", + ":chrome_message_bus_rjs", "//:node_modules/@angular/cdk", "//:node_modules/@angular/core", "//:node_modules/@angular/material", @@ -99,19 +99,6 @@ ts_project( ], ) -ts_project( - name = "zone_aware_chrome_message_bus", - srcs = [ - "zone-aware-chrome-message-bus.ts", - ], - deps = [ - ":chrome_message_bus_rjs", - "//:node_modules/@angular/core", - "//:node_modules/@types/chrome", - "//devtools/projects/protocol:protocol_rjs", - ], -) - ts_project( name = "chrome_message_bus", srcs = [ diff --git a/devtools/projects/shell-browser/src/app/app.config.ts b/devtools/projects/shell-browser/src/app/app.config.ts index f63bcd637eb..702a92ebc3c 100644 --- a/devtools/projects/shell-browser/src/app/app.config.ts +++ b/devtools/projects/shell-browser/src/app/app.config.ts @@ -6,20 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ApplicationConfig, inject, NgZone} from '@angular/core'; +import {ApplicationConfig, provideZonelessChangeDetection} from '@angular/core'; import {provideAnimations} from '@angular/platform-browser/animations'; import {ApplicationEnvironment, ApplicationOperations} from '../../../ng-devtools'; import {ChromeApplicationEnvironment} from './chrome-application-environment'; import {ChromeApplicationOperations} from './chrome-application-operations'; -import {ZoneAwareChromeMessageBus} from './zone-aware-chrome-message-bus'; import {Events, MessageBus, PriorityAwareMessageBus} from '../../../protocol'; import {FrameManager} from '../../../ng-devtools/src/lib/application-services/frame_manager'; import {Platform} from '@angular/cdk/platform'; +import {ChromeMessageBus} from './chrome-message-bus'; export const appConfig: ApplicationConfig = { providers: [ provideAnimations(), + provideZonelessChangeDetection(), {provide: FrameManager, useFactory: () => FrameManager.initialize()}, { provide: ApplicationOperations, @@ -33,12 +34,11 @@ export const appConfig: ApplicationConfig = { { provide: MessageBus, useFactory(): MessageBus { - const ngZone = inject(NgZone); const port = chrome.runtime.connect({ name: '' + chrome.devtools.inspectedWindow.tabId, }); - return new PriorityAwareMessageBus(new ZoneAwareChromeMessageBus(port, ngZone)); + return new PriorityAwareMessageBus(new ChromeMessageBus(port)); }, }, ], diff --git a/devtools/projects/shell-browser/src/app/zone-aware-chrome-message-bus.ts b/devtools/projects/shell-browser/src/app/zone-aware-chrome-message-bus.ts deleted file mode 100644 index c3bc9d14114..00000000000 --- a/devtools/projects/shell-browser/src/app/zone-aware-chrome-message-bus.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @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.dev/license - */ - -/// - -import {NgZone} from '@angular/core'; -import {Events, MessageBus, Parameters} from '../../../protocol'; - -import {ChromeMessageBus} from './chrome-message-bus'; - -export class ZoneAwareChromeMessageBus extends MessageBus { - private _bus: ChromeMessageBus; - constructor( - port: chrome.runtime.Port, - private _ngZone: NgZone, - ) { - super(); - this._bus = new ChromeMessageBus(port); - } - - override on(topic: E, cb: Events[E]): void { - this._bus.on( - topic, - function (this: ZoneAwareChromeMessageBus): void { - this._ngZone.run(() => (cb as any).apply(null, arguments)); - }.bind(this), - ); - } - - override once(topic: E, cb: Events[E]): void { - this._bus.once( - topic, - function (this: ZoneAwareChromeMessageBus): void { - this._ngZone.run(() => (cb as any).apply(null, arguments)); - }.bind(this), - ); - } - - override emit(topic: E, args?: Parameters): boolean { - this._ngZone.run(() => this._bus.emit(topic, args)); - return true; - } - - override destroy(): void { - this._bus.destroy(); - } -} diff --git a/devtools/projects/shell-browser/src/index.html b/devtools/projects/shell-browser/src/index.html index a76f0fef54e..a190b3eee17 100644 --- a/devtools/projects/shell-browser/src/index.html +++ b/devtools/projects/shell-browser/src/index.html @@ -15,7 +15,6 @@ - diff --git a/devtools/src/BUILD.bazel b/devtools/src/BUILD.bazel index faedcf5f076..0649d901560 100644 --- a/devtools/src/BUILD.bazel +++ b/devtools/src/BUILD.bazel @@ -83,7 +83,6 @@ pkg_web( name = "devapp", srcs = [":dev_app_static_files"] + [ ":bundle", - "//:node_modules/zone.js", ], ) diff --git a/devtools/src/app/app.config.ts b/devtools/src/app/app.config.ts index 2282ceafae2..30765243036 100644 --- a/devtools/src/app/app.config.ts +++ b/devtools/src/app/app.config.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ApplicationConfig} from '@angular/core'; +import {ApplicationConfig, provideZonelessChangeDetection} from '@angular/core'; import {provideAnimations} from '@angular/platform-browser/animations'; import {provideRouter} from '@angular/router'; import {ApplicationEnvironment, ApplicationOperations} from '../../projects/ng-devtools'; @@ -17,6 +17,7 @@ import {DemoApplicationOperations} from '../demo-application-operations'; export const appConfig: ApplicationConfig = { providers: [ provideAnimations(), + provideZonelessChangeDetection(), provideRouter([ { path: '', diff --git a/devtools/src/index.html b/devtools/src/index.html index 54d425d2c1b..ec2a9512b15 100644 --- a/devtools/src/index.html +++ b/devtools/src/index.html @@ -18,7 +18,6 @@ - diff --git a/devtools/tools/esbuild/esbuild-base.config.mts b/devtools/tools/esbuild/esbuild-base.config.mts index f97494a7926..5ddfc22e628 100644 --- a/devtools/tools/esbuild/esbuild-base.config.mts +++ b/devtools/tools/esbuild/esbuild-base.config.mts @@ -34,10 +34,6 @@ export default async function createConfig({ mainFields: ['es2020', 'es2015', 'module', 'main'], // `tslib` sets the `module` condition to resolve to ESM. conditions: ['es2020', 'es2015', 'module'], - supported: { - // Downlevel native `async/await` so that ZoneJS can intercept it. - 'async-await': false, - }, define: optimize ? convertObjectToStringDictionary(GLOBAL_DEFS_FOR_TERSER_WITH_AOT) : undefined, plugins: [ await createEsbuildAngularOptimizePlugin({