Skip to content

Commit a094252

Browse files
Lightning00BladeDevtools-frontend LUCI CQ
authored andcommitted
Use Widget mechanism for TabbedPane and CountersGraph
Chromium CL to remove test and temporary disable another one https://crrev.com/c/6935448 Bug: 444247021 Change-Id: If9920aaa5419f95a08e4de316751a5749f5c7252 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6935524 Reviewed-by: Ergün Erdoğmuş <ergunsh@chromium.org> Commit-Queue: Nikolay Vitkov <nvitkov@chromium.org>
1 parent da48008 commit a094252

File tree

5 files changed

+120
-28
lines changed

5 files changed

+120
-28
lines changed

front_end/panels/timeline/CountersGraph.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class CountersGraph extends UI.Widget.VBox {
137137
if (event.updateType === 'RESET' || event.updateType === 'VISIBLE_WINDOW') {
138138
const newWindow = event.state.milli.timelineTraceWindow;
139139
this.calculator.setWindow(newWindow.min, newWindow.max);
140-
this.#scheduleRefresh();
140+
this.requestUpdate();
141141
}
142142
}
143143

@@ -153,7 +153,7 @@ export class CountersGraph extends UI.Widget.VBox {
153153
this.counters[i].reset();
154154
this.counterUI[i].reset();
155155
}
156-
this.#scheduleRefresh();
156+
this.requestUpdate();
157157
let counterEventsFound = 0;
158158
for (let i = 0; i < events.length; ++i) {
159159
const event = events[i];
@@ -208,8 +208,8 @@ export class CountersGraph extends UI.Widget.VBox {
208208
this.refresh();
209209
}
210210

211-
#scheduleRefresh(): void {
212-
UI.UIUtils.invokeOnceAfterBatchUpdate(this, this.refresh);
211+
override performUpdate(): Promise<void>|void {
212+
this.refresh();
213213
}
214214

215215
draw(): void {

front_end/ui/legacy/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ ts_library("unittests") {
192192
"ShortcutRegistry.test.ts",
193193
"SplitWidget.test.ts",
194194
"SuggestBox.test.ts",
195+
"TabbedPane.test.ts",
195196
"Toolbar.test.ts",
196197
"Treeoutline.test.ts",
197198
"UIUtils.test.ts",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Platform from '../../core/platform/platform.js';
6+
import {doubleRaf, raf, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
7+
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
8+
9+
import * as UI from './legacy.js';
10+
11+
describeWithEnvironment('TabbedPane', () => {
12+
let tabbedPane: UI.TabbedPane.TabbedPane;
13+
14+
/**
15+
* A simple widget class that can receive focus.
16+
*/
17+
class FocusableWidget extends UI.Widget.Widget {
18+
constructor(name: string) {
19+
super();
20+
this.element.tabIndex = -1; // Make it focusable
21+
this.element.textContent = name;
22+
this.setDefaultFocusedElement(this.element);
23+
}
24+
}
25+
26+
/**
27+
* This hook runs before each test case (`it(...)`).
28+
* It sets up a new TabbedPane and populates it with 10 tabs.
29+
*/
30+
beforeEach(async () => {
31+
tabbedPane = new UI.TabbedPane.TabbedPane();
32+
tabbedPane.markAsRoot();
33+
34+
for (let i = 0; i < 10; i++) {
35+
tabbedPane.appendTab(i.toString(), `Tab ${i}`, new FocusableWidget(`Widget ${i}`));
36+
}
37+
renderElementIntoDOM(tabbedPane);
38+
await doubleRaf();
39+
});
40+
41+
const dispatchKeyEvent = (key: string) => {
42+
const activeElement = Platform.DOMUtilities.deepActiveElement(document);
43+
if (activeElement) {
44+
activeElement.dispatchEvent(new KeyboardEvent('keydown', {key, bubbles: true, cancelable: true}));
45+
}
46+
};
47+
48+
const getFocusedElementText = () => {
49+
const activeElement = Platform.DOMUtilities.deepActiveElement(document);
50+
return activeElement ? activeElement.textContent : null;
51+
};
52+
53+
it('should navigate between tabs using arrow keys and wrap around', async () => {
54+
// Focus the first tab to start the test.
55+
tabbedPane.selectTab('0');
56+
tabbedPane.focusSelectedTabHeader();
57+
await raf();
58+
59+
assert.strictEqual(getFocusedElementText(), 'Tab 0', 'Initial focus should be on Tab 0');
60+
61+
// Move right to the next tab.
62+
dispatchKeyEvent('ArrowRight');
63+
assert.strictEqual(getFocusedElementText(), 'Tab 1', 'Focus should move to Tab 1');
64+
65+
// Move to the last tab.
66+
for (let i = 2; i <= 9; i++) {
67+
dispatchKeyEvent('ArrowRight');
68+
}
69+
assert.strictEqual(getFocusedElementText(), 'Tab 9', 'Focus should be on the last tab');
70+
71+
// Wrap around to the first tab when moving right from the last tab.
72+
dispatchKeyEvent('ArrowRight');
73+
assert.strictEqual(getFocusedElementText(), 'Tab 0', 'Focus should wrap around to Tab 0');
74+
75+
// Wrap around to the last tab when moving left from the first tab.
76+
dispatchKeyEvent('ArrowLeft');
77+
assert.strictEqual(getFocusedElementText(), 'Tab 9', 'Focus should wrap around to Tab 9 on left arrow');
78+
});
79+
80+
it('should focus the widget content when Enter is pressed on a tab', () => {
81+
// Start with the second tab focused.
82+
tabbedPane.selectTab('1');
83+
tabbedPane.focusSelectedTabHeader();
84+
assert.strictEqual(getFocusedElementText(), 'Tab 1', 'Initial focus should be on Tab 1');
85+
86+
// Press 'Enter' to focus the widget inside the tab's view.
87+
dispatchKeyEvent('Enter');
88+
assert.strictEqual(getFocusedElementText(), 'Widget 1', 'Focus should move to Widget 1');
89+
90+
// For the next step, manually re-focus the tab element, as the user would via Shift+Tab.
91+
tabbedPane.focusSelectedTabHeader();
92+
assert.strictEqual(getFocusedElementText(), 'Tab 1', 'Focus returned to Tab 1');
93+
94+
// Move left to Tab 0.
95+
dispatchKeyEvent('ArrowLeft');
96+
assert.strictEqual(getFocusedElementText(), 'Tab 0', 'Focus should move to Tab 0');
97+
98+
// Press 'Enter' to focus the widget in the first tab.
99+
dispatchKeyEvent('Enter');
100+
assert.strictEqual(getFocusedElementText(), 'Widget 0', 'Focus should move to Widget 0');
101+
});
102+
});

front_end/ui/legacy/TabbedPane.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {ContextMenu} from './ContextMenu.js';
1919
import tabbedPaneStyles from './tabbedPane.css.js';
2020
import type {Toolbar} from './Toolbar.js';
2121
import {Tooltip} from './Tooltip.js';
22-
import {installDragHandle, invokeOnceAfterBatchUpdate} from './UIUtils.js';
22+
import {installDragHandle} from './UIUtils.js';
2323
import {VBox, type Widget} from './Widget.js';
2424
import {Events as ZoomManagerEvents, ZoomManager} from './ZoomManager.js';
2525

@@ -228,7 +228,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
228228
if (this.tabsHistory[0] === tab && this.isShowing()) {
229229
this.selectTab(tab.id, userGesture);
230230
}
231-
this.updateTabElements();
231+
this.requestUpdate();
232232
}
233233

234234
closeTab(id: string, userGesture?: boolean): void {
@@ -244,7 +244,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
244244
for (let i = 0; i < ids.length; ++i) {
245245
this.innerCloseTab(ids[i], userGesture);
246246
}
247-
this.updateTabElements();
247+
this.requestUpdate();
248248
if (this.tabsHistory.length) {
249249
this.selectTab(this.tabsHistory[0].id, false);
250250
}
@@ -346,7 +346,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
346346
this.tabsHistory.splice(this.tabsHistory.indexOf(tab), 1);
347347
this.tabsHistory.splice(0, 0, tab);
348348

349-
this.updateTabElements();
349+
this.requestUpdate();
350350
if (focused || forceFocus) {
351351
this.focus();
352352
}
@@ -396,7 +396,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
396396
return;
397397
}
398398
tab.setIcon(icon);
399-
this.updateTabElements();
399+
this.requestUpdate();
400400
}
401401

402402
setTrailingTabIcon(id: string, icon: IconButton.Icon.Icon|null): void {
@@ -413,7 +413,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
413413
return;
414414
}
415415
tab.setSuffixElement(suffixElement);
416-
this.updateTabElements();
416+
this.requestUpdate();
417417
}
418418

419419
setBadge(id: string, content: string|null): void {
@@ -443,7 +443,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
443443
private zoomChanged(): void {
444444
this.clearMeasuredWidths();
445445
if (this.isShowing()) {
446-
this.updateTabElements();
446+
this.requestUpdate();
447447
}
448448
}
449449

@@ -461,7 +461,7 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
461461
if (tab && tab.title !== tabTitle) {
462462
tab.title = tabTitle;
463463
ARIAUtils.setLabel(tab.tabElement, tabTitle);
464-
this.updateTabElements();
464+
this.requestUpdate();
465465
}
466466
}
467467

@@ -493,19 +493,19 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
493493
this.clearMeasuredWidths();
494494
this.currentDevicePixelRatio = window.devicePixelRatio;
495495
}
496-
this.updateTabElements();
496+
this.requestUpdate();
497497
}
498498

499499
headerResized(): void {
500-
this.updateTabElements();
500+
this.requestUpdate();
501501
}
502502

503503
override wasShown(): void {
504504
const effectiveTab = this.currentTab || this.tabsHistory[0];
505505
if (effectiveTab && this.autoSelectFirstItemOnShow) {
506506
this.selectTab(effectiveTab.id);
507507
}
508-
this.updateTabElements();
508+
this.requestUpdate();
509509
this.dispatchEventToListeners(Events.PaneVisibilityChanged, {isVisible: true});
510510
}
511511

@@ -537,10 +537,6 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
537537
return constraints;
538538
}
539539

540-
private updateTabElements(): void {
541-
invokeOnceAfterBatchUpdate(this, this.innerUpdateTabElements);
542-
}
543-
544540
setPlaceholderElement(element: Element, focusedElement?: Element): void {
545541
this.placeholderElement = element;
546542
if (focusedElement) {
@@ -553,10 +549,10 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
553549
}
554550

555551
async waitForTabElementUpdate(): Promise<void> {
556-
this.innerUpdateTabElements();
552+
this.performUpdate();
557553
}
558554

559-
private innerUpdateTabElements(): void {
555+
override performUpdate(): void {
560556
if (!this.isShowing()) {
561557
return;
562558
}

front_end/ui/legacy/UIUtils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -965,13 +965,6 @@ export function endBatchUpdate(): void {
965965
}
966966
}
967967

968-
export function invokeOnceAfterBatchUpdate(object: Object, method: () => void): void {
969-
if (!postUpdateHandlers) {
970-
postUpdateHandlers = new InvokeOnceHandlers(true);
971-
}
972-
postUpdateHandlers.add(object, method);
973-
}
974-
975968
export function animateFunction(
976969
window: Window, func: (...args: any[]) => void, params: Array<{
977970
from: number,

0 commit comments

Comments
 (0)