From efe78d556502ec392cef33d49603c025970a252b Mon Sep 17 00:00:00 2001 From: AleksanderBodurri Date: Fri, 5 Apr 2024 18:24:43 -0400 Subject: [PATCH] fix(devtools): allow DevTools to fail gracefully for unsupported versions of Angular. (#55233) Angular DevTools depends on many modern Angular features in order to function. As a result, at present the last officially supported version is v12. Angular DevTools may function for some Angular 9, 10 and 11 applications, but they are not officially supported. This commit fixes an issue where DevTools would not inject a backend script into an Angular application if it detected it was below version 12. This backend script is important because it's used to inform the DevTools panel that the inspected application is in fact Angular, but that it is not on a supported version. Angular 9, 10 and 11 applications that successfully have Angular DevTools initialize will now have a red highlight and tooltip on their version number, informing the user that they are using Angular DevTools on a version of Angular that is no longer supported. Angular DevTools for applications that are below version 9 will continue to display the "Angular Devtools supports Angular versions 12 and above" message. PR Close #55233 --- .../devtools-tabs.component.html | 23 ++++++++-- .../devtools-tabs.component.scss | 8 ++++ .../devtools-tabs/devtools-tabs.component.ts | 12 +++++- .../src/lib/devtools.component.html | 4 +- .../ng-devtools/src/lib/devtools.component.ts | 43 ++++++++++++------- .../ng-devtools/src/lib/devtools_spec.ts | 8 ++-- .../projects/protocol/src/lib/messages.ts | 2 +- .../shell-browser/src/app/content-script.ts | 8 ---- 8 files changed, 72 insertions(+), 36 deletions(-) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html index 40fc1a1097c..ac811dad7f6 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html @@ -30,12 +30,27 @@ {{ tab }} } - @if (angularVersion) { + @if (angularVersion()) {
Angular version: - - {{ angularVersion }} - + + @if (majorAngularVersion() > 12 || majorAngularVersion() == 0) { + + {{ angularVersion() }} + + } @else { + + {{ angularVersion() }} (unsupported) + + } + @if (latestSHA) { | DevTools SHA: {{ latestSHA }} } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss index f3983078a91..da5e0dd9809 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss @@ -67,6 +67,10 @@ ng-injector-tree.hidden { -webkit-user-select: text; -ms-user-select: text; user-select: text; + + &.unsupported-version { + color: red; + } } mat-icon { @@ -89,6 +93,10 @@ mat-icon { :host-context(.dark-theme) { #version-number { color: #5caace; + + &.unsupported-version { + color: red; + } } .inspector-active { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts index 2136331c758..019b6a2a2d7 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts @@ -9,8 +9,10 @@ import { AfterViewInit, Component, + computed, EventEmitter, inject, + input, Input, OnInit, Output, @@ -59,7 +61,6 @@ type Tabs = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree'; providers: [TabUpdate], }) export class DevToolsTabsComponent implements OnInit, AfterViewInit { - @Input() angularVersion: string | undefined = undefined; @Input() isHydrationEnabled = false; @Output() frameSelected = new EventEmitter(); @@ -80,6 +81,15 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit { TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID; + angularVersion = input(undefined); + majorAngularVersion = computed(() => { + const version = this.angularVersion(); + if (!version) { + return -1; + } + return parseInt(version.toString().split('.')[0], 10); + }); + constructor( public tabUpdate: TabUpdate, public themeService: ThemeService, diff --git a/devtools/projects/ng-devtools/src/lib/devtools.component.html b/devtools/projects/ng-devtools/src/lib/devtools.component.html index 4fb5b38217b..eacfda77901 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools.component.html @@ -2,9 +2,9 @@ @switch (angularStatus) { @case (AngularStatus.EXISTS) { @if (angularIsInDevMode) { - @if (supportedVersion) { + @if (supportedVersion()) {
- +
} @else {

diff --git a/devtools/projects/ng-devtools/src/lib/devtools.component.ts b/devtools/projects/ng-devtools/src/lib/devtools.component.ts index f0fc48f6eae..034d41d12d4 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools.component.ts @@ -9,7 +9,15 @@ import {animate, style, transition, trigger} from '@angular/animations'; import {Platform} from '@angular/cdk/platform'; import {DOCUMENT} from '@angular/common'; -import {Component, inject, OnDestroy, OnInit} from '@angular/core'; +import { + Component, + computed, + inject, + OnDestroy, + OnInit, + signal, + WritableSignal, +} from '@angular/core'; import {Events, MessageBus} from 'protocol'; import {interval} from 'rxjs'; @@ -40,6 +48,8 @@ enum AngularStatus { EXISTS, } +const LAST_SUPPORTED_VERSION = 9; + @Component({ selector: 'ng-devtools', templateUrl: './devtools.component.html', @@ -56,10 +66,22 @@ enum AngularStatus { export class DevToolsComponent implements OnInit, OnDestroy { AngularStatus = AngularStatus; angularStatus: AngularStatus = AngularStatus.UNKNOWN; - angularVersion: string | boolean | undefined = undefined; + angularVersion: WritableSignal = signal(undefined); angularIsInDevMode = true; hydration: boolean = false; - ivy!: boolean; + ivy: WritableSignal = signal(undefined); + + supportedVersion = computed(() => { + const version = this.angularVersion(); + if (!version) { + return false; + } + const majorVersion = parseInt(version.toString().split('.')[0], 10); + + // Check that major version is either greater or equal to the last supported version + // or that the major version is 0 for the (0.0.0-PLACEHOLDER) dev build case. + return (majorVersion >= LAST_SUPPORTED_VERSION || majorVersion === 0) && this.ivy(); + }); private readonly _firefoxStyleName = 'firefox_styles.css'; private readonly _chromeStyleName = 'chrome_styles.css'; @@ -85,9 +107,9 @@ export class DevToolsComponent implements OnInit, OnDestroy { this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => { this.angularStatus = version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST; - this.angularVersion = version; + this.angularVersion.set(version); this.angularIsInDevMode = devMode; - this.ivy = ivy; + this.ivy.set(ivy); this._interval$.unsubscribe(); this.hydration = hydration; }); @@ -98,17 +120,6 @@ export class DevToolsComponent implements OnInit, OnDestroy { this._loadStyle(browserStyleName); } - get majorAngularVersion(): number { - if (!this.angularVersion) { - return -1; - } - return parseInt(this.angularVersion.toString().split('.')[0], 10); - } - - get supportedVersion(): boolean { - return (this.majorAngularVersion >= 9 || this.majorAngularVersion === 0) && this.ivy; - } - /** Add a style file in header based on fileName */ private _loadStyle(styleName: string) { const head = this._document.getElementsByTagName('head')[0]; diff --git a/devtools/projects/ng-devtools/src/lib/devtools_spec.ts b/devtools/projects/ng-devtools/src/lib/devtools_spec.ts index 6c17a3968db..be70cad7d4a 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools_spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FrameManager} from './frame_manager'; import {DevToolsComponent} from './devtools.component'; @@ -44,8 +44,8 @@ describe('DevtoolsComponent', () => { it('should render ng devtools tabs when Angular Status is EXISTS and is in dev mode and is supported version', () => { component.angularStatus = component.AngularStatus.EXISTS; component.angularIsInDevMode = true; - component.angularVersion = '0.0.0'; - component.ivy = true; + component.angularVersion = signal('0.0.0'); + component.ivy = signal(true); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('ng-devtools-tabs')).toBeTruthy(); }); @@ -62,7 +62,7 @@ describe('DevtoolsComponent', () => { it('should render version support message when Angular Status is EXISTS and angular version is not supported', () => { component.angularStatus = component.AngularStatus.EXISTS; component.angularIsInDevMode = true; - component.angularVersion = '1.0.0'; + component.angularVersion = signal('1.0.0'); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('.devtools').textContent).toContain( 'Angular Devtools only supports Angular versions 12 and above', diff --git a/devtools/projects/protocol/src/lib/messages.ts b/devtools/projects/protocol/src/lib/messages.ts index 5ae4c73a47e..6a053c6ad86 100644 --- a/devtools/projects/protocol/src/lib/messages.ts +++ b/devtools/projects/protocol/src/lib/messages.ts @@ -238,7 +238,7 @@ export interface Events { shutdown: () => void; queryNgAvailability: () => void; ngAvailability: (config: { - version: string | undefined | boolean; + version: string | undefined; devMode: boolean; ivy: boolean; hydration: boolean; diff --git a/devtools/projects/shell-browser/src/app/content-script.ts b/devtools/projects/shell-browser/src/app/content-script.ts index a9b6ada8d8c..8b168fbb933 100644 --- a/devtools/projects/shell-browser/src/app/content-script.ts +++ b/devtools/projects/shell-browser/src/app/content-script.ts @@ -46,14 +46,6 @@ detectAngularMessageBus.on('detectAngular', (detectionResult) => { return; } - if (detectionResult.isDebugMode !== true) { - return; - } - - if (detectionResult.isSupportedAngularVersion !== true) { - return; - } - // Defensive check against non html page. Realistically this should never happen. if (document.contentType !== 'text/html') { return;