// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @devtools/no-lit-render-outside-of-view */ import '../../../ui/kit/kit.js'; import '../../../ui/components/menus/menus.js'; import * as Common from '../../../core/common/common.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * as Platform from '../../../core/platform/platform.js'; import * as SDK from '../../../core/sdk/sdk.js'; import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; import type * as Menus from '../../../ui/components/menus/menus.js'; import * as Lit from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import * as MobileThrottling from '../../mobile_throttling/mobile_throttling.js'; import networkThrottlingSelectorStyles from './networkThrottlingSelector.css.js'; const {html, nothing} = Lit; const UIStrings = { /** * @description Text label for a selection box showing which network throttling option is applied. * @example {No throttling} PH1 */ network: 'Network: {PH1}', /** * @description Text label for a selection box showing which network throttling option is applied. * @example {No throttling} PH1 */ networkThrottling: 'Network throttling: {PH1}', /** * @description Text label for a selection box showing that a specific option is recommended for network throttling. * @example {Fast 4G} PH1 */ recommendedThrottling: '{PH1} – recommended', /** * @description Text for why user should change a throttling setting. */ recommendedThrottlingReason: 'Consider changing setting to simulate real user environments', /** * @description Text label for a menu group that disables network throttling. */ disabled: 'Disabled', /** * @description Text label for a menu group that contains default presets for network throttling. */ presets: 'Presets', /** * @description Text label for a menu group that contains custom presets for network throttling. */ custom: 'Custom', /** * @description Text label for a menu option to add a new custom throttling preset. */ add: 'Add…', } as const; const str_ = i18n.i18n.registerUIStrings('panels/timeline/components/NetworkThrottlingSelector.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); interface ConditionsGroup { name: string; items: SDK.NetworkManager.Conditions[]; showCustomAddOption?: boolean; jslogContext?: string; } export class NetworkThrottlingSelector extends HTMLElement { readonly #shadow = this.attachShadow({mode: 'open'}); #customNetworkConditionsSetting: Common.Settings.Setting; #groups: ConditionsGroup[] = []; #currentConditions: SDK.NetworkManager.Conditions; #recommendedConditions: SDK.NetworkManager.Conditions|null = null; constructor() { super(); this.#customNetworkConditionsSetting = Common.Settings.Settings.instance().moduleSetting('custom-network-conditions'); this.#resetPresets(); this.#currentConditions = SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions(); this.#render(); } set recommendedConditions(recommendedConditions: SDK.NetworkManager.Conditions|null) { this.#recommendedConditions = recommendedConditions; void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); } connectedCallback(): void { SDK.NetworkManager.MultitargetNetworkManager.instance().addEventListener( SDK.NetworkManager.MultitargetNetworkManager.Events.CONDITIONS_CHANGED, this.#onConditionsChanged, this); // Also call onConditionsChanged immediately to make sure we get the // latest snapshot. Otherwise if another panel updated this value and this // component wasn't in the DOM, this component will not update itself // when it is put into the page this.#onConditionsChanged(); this.#customNetworkConditionsSetting.addChangeListener(this.#onSettingChanged, this); } disconnectedCallback(): void { SDK.NetworkManager.MultitargetNetworkManager.instance().removeEventListener( SDK.NetworkManager.MultitargetNetworkManager.Events.CONDITIONS_CHANGED, this.#onConditionsChanged, this); this.#customNetworkConditionsSetting.removeChangeListener(this.#onSettingChanged, this); } #resetPresets(): void { this.#groups = [ { name: i18nString(UIStrings.disabled), items: [ SDK.NetworkManager.NoThrottlingConditions, ], }, { name: i18nString(UIStrings.presets), items: MobileThrottling.ThrottlingPresets.ThrottlingPresets.networkPresets, }, { name: i18nString(UIStrings.custom), items: this.#customNetworkConditionsSetting.get(), showCustomAddOption: true, jslogContext: 'custom-network-throttling-item', }, ]; } #onConditionsChanged(): void { this.#currentConditions = SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions(); void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); } #onMenuItemSelected(event: Menus.SelectMenu.SelectMenuItemSelectedEvent): void { const newConditions = this.#groups.flatMap(g => g.items).find(item => { const keyForItem = this.#keyForNetworkConditions(item); return keyForItem === event.itemValue; }); if (newConditions) { SDK.NetworkManager.MultitargetNetworkManager.instance().setNetworkConditions(newConditions); } } #onSettingChanged(): void { this.#resetPresets(); void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); } #getConditionsTitle(conditions: SDK.NetworkManager.Conditions): string { return conditions.title instanceof Function ? conditions.title() : conditions.title; } #onAddClick(): void { void Common.Revealer.reveal(this.#customNetworkConditionsSetting); } /** * The key that uniquely identifies the condition setting. All the DevTools * presets have the i18nKey, so we rely on that, but for custom user added * ones we fallback to using the title (it wouldn't make sense for a user to * add presets with the same title) */ #keyForNetworkConditions(conditions: SDK.NetworkManager.Conditions): string { return conditions.i18nTitleKey || this.#getConditionsTitle(conditions); } #render = (): void => { const selectionTitle = this.#getConditionsTitle(this.#currentConditions); const selectedConditionsKey = this.#keyForNetworkConditions(this.#currentConditions); let recommendedInfoEl; if (this.#recommendedConditions && this.#currentConditions === SDK.NetworkManager.NoThrottlingConditions) { recommendedInfoEl = html``; } // clang-format off /* eslint-disable @devtools/no-deprecated-component-usages */ const output = html` ${this.#groups.map(group => { return html` ${group.items.map(conditions => { let title = this.#getConditionsTitle(conditions); if (conditions === this.#recommendedConditions) { title = i18nString(UIStrings.recommendedThrottling, {PH1: title}); } const key = this.#keyForNetworkConditions(conditions); const jslogContext = group.jslogContext || Platform.StringUtilities.toKebabCase(conditions.i18nTitleKey || title); return html` ${title} `; })} ${group.showCustomAddOption ? html` ${i18nString(UIStrings.add)} ` : nothing} `; })} ${recommendedInfoEl} `; /* eslint-enable @devtools/no-deprecated-component-usages */ // clang-format on Lit.render(output, this.#shadow, {host: this}); }; } customElements.define('devtools-network-throttling-selector', NetworkThrottlingSelector); declare global { interface HTMLElementTagNameMap { 'devtools-network-throttling-selector': NetworkThrottlingSelector; } }