chrome-devtools-frontend
Version:
Chrome DevTools UI
172 lines (150 loc) • 5.65 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as Root from '../../core/root/root.js';
import type {DisabledReason} from './AiUtils.js';
export const enum Events {
CHANGED = 'Changed',
}
export interface EventTypes {
[Events.CHANGED]: void;
}
/**
* Wrapper around a plain {@link Common.Settings.Setting} for AI features whose availability
* can change dynamically over the lifetime of a DevTools session (e.g., due to changes in
* AIDA availability, network connectivity, authentication, or enterprise policy).
*
* This wrapper listens to {@link Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED} and emits
* {@link Events.CHANGED} when either the underlying setting value changes or when AIDA status
* changes, allowing UI components to dynamically react without polluting the plain `Setting`
* class with dynamic availability state.
*/
export class AiSetting<ValueT> extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
#setting?: Common.Settings.Setting<ValueT>;
readonly #descriptor: Common.Settings.ConditionalSettingDescriptor<ValueT, DisabledReason[]>;
readonly #hostConfigTracker: Host.AidaClient.HostConfigTracker;
readonly #settings: Common.Settings.Settings;
readonly #boundOnSettingChanged = this.#onSettingChanged.bind(this);
readonly #boundOnAidaAvailabilityChanged = this.#onAidaAvailabilityChanged.bind(this);
#isSubscribed = false;
constructor(
descriptor: Common.Settings.ConditionalSettingDescriptor<ValueT, DisabledReason[]>,
hostConfigTracker: Host.AidaClient.HostConfigTracker,
settings: Common.Settings.Settings,
) {
super();
this.#descriptor = descriptor;
this.#hostConfigTracker = hostConfigTracker;
this.#settings = settings;
this.#tryResolveSetting();
}
override addEventListener<T extends keyof EventTypes>(
eventType: T,
listener: Common.EventTarget.EventListener<EventTypes, T>,
thisObject?: Object,
): Common.EventTarget.EventDescriptor<EventTypes, T> {
const isFirst = !this.hasEventListeners(eventType);
const descriptor = super.addEventListener(eventType, listener, thisObject);
if (isFirst) {
this.#subscribe();
}
return descriptor;
}
override removeEventListener<T extends keyof EventTypes>(
eventType: T,
listener: Common.EventTarget.EventListener<EventTypes, T>,
thisObject?: Object,
): void {
super.removeEventListener(eventType, listener, thisObject);
if (!this.hasEventListeners(eventType)) {
this.#unsubscribe();
}
}
#subscribe(): void {
if (this.#isSubscribed) {
return;
}
this.#tryResolveSetting();
this.#isSubscribed = true;
this.#hostConfigTracker.addEventListener(
Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED,
this.#boundOnAidaAvailabilityChanged,
);
this.#setting?.addChangeListener(this.#boundOnSettingChanged);
}
#unsubscribe(): void {
if (!this.#isSubscribed) {
return;
}
this.#isSubscribed = false;
this.#hostConfigTracker.removeEventListener(
Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED,
this.#boundOnAidaAvailabilityChanged,
);
this.#setting?.removeChangeListener(this.#boundOnSettingChanged);
}
#tryResolveSetting(): void {
const result = this.#settings.maybeResolve(this.#descriptor as
Common.Settings.ConditionalSettingDescriptor<unknown, DisabledReason[]>);
if ('setting' in result) {
if (this.#setting !== result.setting) {
if (this.#setting && this.#isSubscribed) {
this.#setting.removeChangeListener(this.#boundOnSettingChanged);
}
this.#setting = result.setting as Common.Settings.Setting<ValueT>;
if (this.#isSubscribed) {
this.#setting.addChangeListener(this.#boundOnSettingChanged);
}
}
} else if (this.#setting) {
if (this.#isSubscribed) {
this.#setting.removeChangeListener(this.#boundOnSettingChanged);
}
this.#setting = undefined;
}
}
get unavailable(): boolean {
const availability = this.#descriptor.isAvailable(Root.Runtime.hostConfig);
return availability.status === Common.Settings.SettingAvailability.UNAVAILABLE;
}
get disabled(): boolean {
const availability = this.#descriptor.isAvailable(Root.Runtime.hostConfig);
return availability.status === Common.Settings.SettingAvailability.DISABLED;
}
get disabledReasons(): DisabledReason[] {
const availability = this.#descriptor.isAvailable(Root.Runtime.hostConfig);
if (availability.status === Common.Settings.SettingAvailability.DISABLED) {
return availability.reason;
}
return [];
}
getIfNotDisabled(): ValueT|undefined {
if (this.disabled || this.unavailable) {
return undefined;
}
this.#tryResolveSetting();
return this.#setting?.get();
}
setIfNotDisabled(value: ValueT): void {
if (this.disabled || this.unavailable) {
return;
}
this.#tryResolveSetting();
this.#setting?.set(value);
}
get(): ValueT|undefined {
return this.getIfNotDisabled();
}
set(value: ValueT): void {
this.setIfNotDisabled(value);
}
#onSettingChanged(): void {
this.dispatchEventToListeners(Events.CHANGED);
}
#onAidaAvailabilityChanged(): void {
this.#tryResolveSetting();
this.dispatchEventToListeners(Events.CHANGED);
}
}