Skip to content

Commit 367d486

Browse files
pfaffeDevtools-frontend LUCI CQ
authored andcommitted
Presenter-based NetworkThrottlingSelector
Bug: 40434685 Change-Id: Ib4fce35f5c9bba9688cb72d978a0d7652af75dd0 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6822159 Auto-Submit: Philip Pfaffe <[email protected]> Commit-Queue: Philip Pfaffe <[email protected]> Reviewed-by: Danil Somsikov <[email protected]>
1 parent 5be3965 commit 367d486

File tree

6 files changed

+213
-175
lines changed

6 files changed

+213
-175
lines changed

front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts

Lines changed: 186 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44

55
import * as Common from '../../core/common/common.js';
66
import * as i18n from '../../core/i18n/i18n.js';
7+
import * as Platform from '../../core/platform/platform.js';
78
import * as SDK from '../../core/sdk/sdk.js';
9+
import * as Lit from '../../ui/lit/lit.js';
10+
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
811

9-
import {ThrottlingPresets, type NetworkThrottlingConditionsGroup} from './ThrottlingPresets.js';
12+
import {ThrottlingManager} from './ThrottlingManager.js';
13+
import {type NetworkThrottlingConditionsGroup, ThrottlingPresets} from './ThrottlingPresets.js';
14+
15+
const {render, html, Directives} = Lit;
1016

1117
const UIStrings = {
1218
/**
@@ -21,66 +27,198 @@ const UIStrings = {
2127
* @description Text in Network Throttling Selector of the Network panel
2228
*/
2329
custom: 'Custom',
30+
/**
31+
*@description Text with two placeholders separated by a colon
32+
*@example {Node removed} PH1
33+
*@example {div#id1} PH2
34+
*/
35+
sS: '{PH1}: {PH2}',
36+
/**
37+
*@description Accessibility label for custom add network throttling option
38+
*@example {Custom} PH1
39+
*/
40+
addS: 'Add {PH1}',
41+
/**
42+
*@description Text in Throttling Manager of the Network panel
43+
*/
44+
add: 'Add…',
45+
/**
46+
* @description Text label for a selection box showing that a specific option is recommended for CPU or Network throttling.
47+
* @example {Fast 4G} PH1
48+
* @example {4x slowdown} PH1
49+
*/
50+
recommendedThrottling: '{PH1} – recommended',
2451
} as const;
2552
const str_ = i18n.i18n.registerUIStrings('panels/mobile_throttling/NetworkThrottlingSelector.ts', UIStrings);
2653
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
27-
export class NetworkThrottlingSelector {
28-
private populateCallback: (arg0: NetworkThrottlingConditionsGroup[]) => Array<SDK.NetworkManager.Conditions|null>;
29-
private readonly selectCallback: (arg0: number) => void;
30-
private readonly customNetworkConditionsSetting: Common.Settings.Setting<SDK.NetworkManager.Conditions[]>;
31-
private options!: Array<SDK.NetworkManager.Conditions|null>;
3254

33-
constructor(
34-
populateCallback: (arg0: NetworkThrottlingConditionsGroup[]) => Array<SDK.NetworkManager.Conditions|null>,
35-
selectCallback: (arg0: number) => void,
36-
customNetworkConditionsSetting: Common.Settings.Setting<SDK.NetworkManager.Conditions[]>) {
37-
this.populateCallback = populateCallback;
38-
this.selectCallback = selectCallback;
39-
this.customNetworkConditionsSetting = customNetworkConditionsSetting;
40-
this.customNetworkConditionsSetting.addChangeListener(this.populateOptions, this);
55+
interface ViewInput {
56+
recommendedConditions: SDK.NetworkManager.Conditions|null;
57+
selectedConditions: SDK.NetworkManager.Conditions;
58+
throttlingGroups: NetworkThrottlingConditionsGroup[];
59+
customConditionsGroup: NetworkThrottlingConditionsGroup;
60+
jslogContext: string;
61+
title: string;
62+
onSelect: (conditions: SDK.NetworkManager.Conditions) => void;
63+
onAddCustomConditions: () => void;
64+
}
65+
export type ViewFunction = (input: ViewInput, output: object, target: HTMLElement) => void;
66+
67+
export const DEFAULT_VIEW: ViewFunction = (input, output, target) => {
68+
// The title is usually an i18nLazyString except for custom values that are stored in the local storage in the form of a string.
69+
const title = (conditions: SDK.NetworkManager.Conditions): string =>
70+
typeof conditions.title === 'function' ? conditions.title() : conditions.title;
71+
const jslog = (group: NetworkThrottlingConditionsGroup, condition: SDK.NetworkManager.Conditions): string =>
72+
`${VisualLogging.item(Platform.StringUtilities.toKebabCase(condition.i18nTitleKey || title(condition))).track({
73+
click: true
74+
})}`;
75+
const optionsMap = new WeakMap<HTMLOptionElement, SDK.NetworkManager.Conditions>();
76+
let selectedConditions = input.selectedConditions;
77+
function onSelect(event: Event): void {
78+
const element = (event.target as HTMLSelectElement | null);
79+
if (!element) {
80+
return;
81+
}
82+
const option = element.selectedOptions[0];
83+
if (!option) {
84+
return;
85+
}
86+
if (option === element.options[element.options.length - 1]) {
87+
input.onAddCustomConditions();
88+
event.consume(true);
89+
element.value = title(selectedConditions);
90+
} else {
91+
const conditions = optionsMap.get(option);
92+
if (conditions) {
93+
selectedConditions = conditions;
94+
input.onSelect(conditions);
95+
}
96+
}
97+
}
98+
render(
99+
// clang-format off
100+
html`<select
101+
aria-label=${input.title}
102+
jslog=${VisualLogging.dropDown().track({change: true}).context(input.jslogContext)}
103+
@change=${onSelect}>
104+
${input.throttlingGroups.map(
105+
group =>
106+
html`<optgroup
107+
label=${group.title}>
108+
${group.items.map(condition => html`<option
109+
${Directives.ref(option => option && optionsMap.set(option as HTMLOptionElement, condition))}
110+
?selected=${SDK.NetworkManager.networkConditionsEqual(condition, selectedConditions)}
111+
value=${title(condition)}
112+
aria-label=${i18nString(UIStrings.sS, {PH1: group.title, PH2: title(condition)})}
113+
jslog=${jslog(group, condition)}>
114+
${condition === input.recommendedConditions ?
115+
i18nString(UIStrings.recommendedThrottling, {PH1: title(condition)}):
116+
title(condition)}
117+
</option>`)}
118+
</optgroup>`)}
119+
<optgroup label=${input.customConditionsGroup.title}>
120+
${input.customConditionsGroup.items.map(condition => html`<option
121+
${Directives.ref(option => option && optionsMap.set(option as HTMLOptionElement, condition))}
122+
?selected=${SDK.NetworkManager.networkConditionsEqual(condition, selectedConditions)}
123+
value=${title(condition)}
124+
aria-label=${i18nString(UIStrings.sS, {PH1: input.customConditionsGroup.title, PH2: title(condition)})}
125+
jslog=${VisualLogging.item('custom-network-throttling-item').track({click: true})}>
126+
${condition === input.recommendedConditions ?
127+
i18nString(UIStrings.recommendedThrottling, {PH1: title(condition)}):
128+
title(condition)}
129+
</option>`)}
130+
<option
131+
value=${i18nString(UIStrings.add)}
132+
aria-label=${i18nString(UIStrings.addS, {PH1: input.customConditionsGroup.title})}
133+
jslog=${VisualLogging.action('add').track({click: true})}>
134+
${i18nString(UIStrings.add)}
135+
</option>
136+
</optgroup>
137+
</select>`, // clang-format on
138+
target);
139+
};
140+
141+
export const enum Events {
142+
CONDITIONS_CHANGED = 'conditionsChanged',
143+
}
144+
145+
export interface EventTypes {
146+
[Events.CONDITIONS_CHANGED]: SDK.NetworkManager.Conditions;
147+
}
148+
149+
export class NetworkThrottlingSelect extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
150+
#recommendedConditions: SDK.NetworkManager.Conditions|null = null;
151+
readonly #element: HTMLElement;
152+
readonly #jslogContext: string;
153+
#currentConditions: SDK.NetworkManager.Conditions;
154+
readonly #title: string;
155+
readonly #view: ViewFunction;
156+
157+
static createForGlobalConditions(element: HTMLElement, title: string): NetworkThrottlingSelect {
158+
ThrottlingManager.instance(); // Instantiate the throttling manager to connect network manager with the setting
159+
const select = new NetworkThrottlingSelect(
160+
element, title, SDK.NetworkManager.activeNetworkThrottlingKeySetting().name,
161+
SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions());
162+
select.addEventListener(
163+
Events.CONDITIONS_CHANGED,
164+
ev => SDK.NetworkManager.MultitargetNetworkManager.instance().setNetworkConditions(ev.data));
41165
SDK.NetworkManager.MultitargetNetworkManager.instance().addEventListener(
42166
SDK.NetworkManager.MultitargetNetworkManager.Events.CONDITIONS_CHANGED, () => {
43-
this.networkConditionsChanged();
44-
}, this);
45-
this.populateOptions();
167+
select.currentConditions = SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions();
168+
});
169+
return select;
46170
}
47171

48-
revealAndUpdate(): void {
49-
void Common.Revealer.reveal(this.customNetworkConditionsSetting);
50-
this.networkConditionsChanged();
172+
constructor(
173+
element: HTMLElement, title: string, jslogContext: string, currentConditions: SDK.NetworkManager.Conditions,
174+
view = DEFAULT_VIEW) {
175+
super();
176+
SDK.NetworkManager.customUserNetworkConditionsSetting().addChangeListener(this.#performUpdate, this);
177+
this.#element = element;
178+
this.#jslogContext = jslogContext;
179+
this.#currentConditions = currentConditions;
180+
this.#title = title;
181+
this.#view = view;
182+
183+
this.#performUpdate();
51184
}
52185

53-
optionSelected(conditions: SDK.NetworkManager.Conditions): void {
54-
SDK.NetworkManager.MultitargetNetworkManager.instance().setNetworkConditions(conditions);
186+
set recommendedConditions(recommendedConditions: SDK.NetworkManager.Conditions|null) {
187+
this.#recommendedConditions = recommendedConditions;
188+
this.#performUpdate();
55189
}
56190

57-
private populateOptions(): void {
58-
const disabledGroup = {title: i18nString(UIStrings.disabled), items: [SDK.NetworkManager.NoThrottlingConditions]};
59-
const presetsGroup = {title: i18nString(UIStrings.presets), items: ThrottlingPresets.networkPresets};
60-
const customGroup = {title: i18nString(UIStrings.custom), items: this.customNetworkConditionsSetting.get()};
61-
this.options = this.populateCallback([disabledGroup, presetsGroup, customGroup]);
62-
if (!this.networkConditionsChanged()) {
63-
for (let i = this.options.length - 1; i >= 0; i--) {
64-
if (this.options[i]) {
65-
this.optionSelected(this.options[i] as SDK.NetworkManager.Conditions);
66-
break;
67-
}
68-
}
69-
}
191+
set currentConditions(currentConditions: SDK.NetworkManager.Conditions) {
192+
this.#currentConditions = currentConditions;
193+
this.#performUpdate();
70194
}
71195

72-
/**
73-
* returns false if selected condition no longer exists
74-
*/
75-
private networkConditionsChanged(): boolean {
76-
const value = SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions();
77-
for (let index = 0; index < this.options.length; ++index) {
78-
const option = this.options[index];
79-
if (option && SDK.NetworkManager.networkConditionsEqual(value, option)) {
80-
this.selectCallback(index);
81-
return true;
82-
}
83-
}
84-
return false;
196+
#performUpdate(): void {
197+
const customNetworkConditionsSetting = SDK.NetworkManager.customUserNetworkConditionsSetting();
198+
const customNetworkConditions = customNetworkConditionsSetting.get();
199+
const onAddCustomConditions = (): void => {
200+
void Common.Revealer.reveal(SDK.NetworkManager.customUserNetworkConditionsSetting());
201+
};
202+
203+
const onSelect = (conditions: SDK.NetworkManager.Conditions): void => {
204+
this.dispatchEventToListeners(Events.CONDITIONS_CHANGED, conditions);
205+
};
206+
207+
const throttlingGroups = [
208+
{title: i18nString(UIStrings.disabled), items: [SDK.NetworkManager.NoThrottlingConditions]},
209+
{title: i18nString(UIStrings.presets), items: ThrottlingPresets.networkPresets},
210+
];
211+
const customConditionsGroup = {title: i18nString(UIStrings.custom), items: customNetworkConditions};
212+
const viewInput: ViewInput = {
213+
recommendedConditions: this.#recommendedConditions,
214+
selectedConditions: this.#currentConditions,
215+
jslogContext: this.#jslogContext,
216+
title: this.#title,
217+
onSelect,
218+
onAddCustomConditions,
219+
throttlingGroups,
220+
customConditionsGroup,
221+
};
222+
this.#view(viewInput, {}, this.#element);
85223
}
86224
}

0 commit comments

Comments
 (0)