Skip to content

Commit 5084e3d

Browse files
Merge pull request #20675 from calixteman/bug2015916
Fix the keyboard accessibility of the manage button in the thumbnails view (bug 2015916)
2 parents fe44bac + f4a2fd6 commit 5084e3d

3 files changed

Lines changed: 159 additions & 31 deletions

File tree

test/integration/thumbnail_view_spec.mjs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ function waitForThumbnailVisible(page, pageNum) {
1212
);
1313
}
1414

15+
async function waitForMenu(page, buttonSelector, visible = true) {
16+
return page.waitForFunction(
17+
(selector, vis) => {
18+
const button = document.querySelector(selector);
19+
if (!button) {
20+
return false;
21+
}
22+
return button.getAttribute("aria-expanded") === (vis ? "true" : "false");
23+
},
24+
{},
25+
buttonSelector,
26+
visible
27+
);
28+
}
29+
1530
describe("PDF Thumbnail View", () => {
1631
describe("Works without errors", () => {
1732
let pages;
@@ -201,4 +216,105 @@ describe("PDF Thumbnail View", () => {
201216
);
202217
});
203218
});
219+
220+
describe("The manage dropdown menu", () => {
221+
let pages;
222+
223+
beforeEach(async () => {
224+
pages = await loadAndWait(
225+
"tracemonkey.pdf",
226+
"#viewsManagerToggleButton",
227+
null,
228+
null,
229+
{ enableSplitMerge: true }
230+
);
231+
});
232+
233+
afterEach(async () => {
234+
await closePages(pages);
235+
});
236+
237+
async function enableMenuItems(page) {
238+
await page.evaluate(() => {
239+
document
240+
.querySelectorAll("#viewsManagerStatusActionOptions button")
241+
.forEach(button => {
242+
button.disabled = false;
243+
});
244+
});
245+
}
246+
247+
it("should open with Enter key and remain open", async () => {
248+
await Promise.all(
249+
pages.map(async ([browserName, page]) => {
250+
await page.click("#viewsManagerToggleButton");
251+
await waitForThumbnailVisible(page, 1);
252+
253+
await enableMenuItems(page);
254+
255+
// Focus the manage button
256+
await kbFocusNext(page);
257+
await kbFocusNext(page);
258+
await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
259+
visible: true,
260+
});
261+
262+
// Press Enter to open the menu
263+
await page.keyboard.press("Enter");
264+
265+
await waitForMenu(page, "#viewsManagerStatusActionButton");
266+
267+
// Verify first menu item can be focused
268+
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
269+
visible: true,
270+
});
271+
272+
// Close menu with Escape
273+
await page.keyboard.press("Escape");
274+
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
275+
})
276+
);
277+
});
278+
279+
it("should open with Space key and remain open", async () => {
280+
await Promise.all(
281+
pages.map(async ([browserName, page]) => {
282+
await page.click("#viewsManagerToggleButton");
283+
await waitForThumbnailVisible(page, 1);
284+
285+
await enableMenuItems(page);
286+
287+
// Focus the manage button
288+
await kbFocusNext(page);
289+
await kbFocusNext(page);
290+
await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
291+
visible: true,
292+
});
293+
294+
// Press Space to open the menu
295+
await page.keyboard.press(" ");
296+
297+
await waitForMenu(page, "#viewsManagerStatusActionButton");
298+
299+
// Verify first menu item can be focused
300+
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
301+
visible: true,
302+
});
303+
304+
// Navigate menu items with arrow keys
305+
await page.keyboard.press("ArrowDown");
306+
await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
307+
visible: true,
308+
});
309+
310+
// Menu should still be open
311+
await waitForMenu(page, "#viewsManagerStatusActionButton");
312+
313+
// Close menu with Escape
314+
await page.keyboard.press("Escape");
315+
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
316+
})
317+
);
318+
});
319+
});
204320
});

web/menu.js

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,36 @@ class Menu {
7070
this.#lastIndex = -1;
7171
}
7272

73+
/**
74+
* Open the menu.
75+
*/
76+
#openMenu() {
77+
if (this.#openMenuAC) {
78+
return;
79+
}
80+
81+
const menu = this.#menu;
82+
this.#triggeringButton.ariaExpanded = "true";
83+
this.#openMenuAC = new AbortController();
84+
const signal = AbortSignal.any([
85+
this.#menuAC.signal,
86+
this.#openMenuAC.signal,
87+
]);
88+
window.addEventListener(
89+
"pointerdown",
90+
({ target }) => {
91+
if (
92+
!this.#triggeringButton.contains(target) &&
93+
!menu.contains(target)
94+
) {
95+
this.#closeMenu();
96+
}
97+
},
98+
{ signal }
99+
);
100+
window.addEventListener("blur", this.#closeMenu.bind(this), { signal });
101+
}
102+
73103
/**
74104
* Set up the menu.
75105
*/
@@ -80,23 +110,7 @@ class Menu {
80110
return;
81111
}
82112

83-
const menu = this.#menu;
84-
this.#triggeringButton.ariaExpanded = "true";
85-
this.#openMenuAC = new AbortController();
86-
const signal = AbortSignal.any([
87-
this.#menuAC.signal,
88-
this.#openMenuAC.signal,
89-
]);
90-
window.addEventListener(
91-
"pointerdown",
92-
({ target }) => {
93-
if (target !== this.#triggeringButton && !menu.contains(target)) {
94-
this.#closeMenu();
95-
}
96-
},
97-
{ signal }
98-
);
99-
window.addEventListener("blur", this.#closeMenu.bind(this), { signal });
113+
this.#openMenu();
100114
});
101115

102116
const { signal } = this.#menuAC;
@@ -110,12 +124,10 @@ class Menu {
110124
stopEvent(e);
111125
break;
112126
case "ArrowDown":
113-
case "Tab":
114127
this.#goToNextItem(e.target, true);
115128
stopEvent(e);
116129
break;
117130
case "ArrowUp":
118-
case "ShiftTab":
119131
this.#goToNextItem(e.target, false);
120132
stopEvent(e);
121133
break;
@@ -124,15 +136,15 @@ class Menu {
124136
.find(
125137
item => !item.disabled && !item.classList.contains("hidden")
126138
)
127-
.focus();
139+
?.focus();
128140
stopEvent(e);
129141
break;
130142
case "End":
131143
this.#menuItems
132144
.findLast(
133145
item => !item.disabled && !item.classList.contains("hidden")
134146
)
135-
.focus();
147+
?.focus();
136148
stopEvent(e);
137149
break;
138150
default:
@@ -159,27 +171,27 @@ class Menu {
159171
case "Enter":
160172
case "ArrowDown":
161173
case "Home":
174+
stopEvent(e);
162175
if (!this.#openMenuAC) {
163-
this.#triggeringButton.click();
176+
this.#openMenu();
164177
}
165178
this.#menuItems
166179
.find(
167180
item => !item.disabled && !item.classList.contains("hidden")
168181
)
169-
.focus();
170-
stopEvent(e);
182+
?.focus();
171183
break;
172184
case "ArrowUp":
173185
case "End":
186+
stopEvent(e);
174187
if (!this.#openMenuAC) {
175-
this.#triggeringButton.click();
188+
this.#openMenu();
176189
}
177190
this.#menuItems
178191
.findLast(
179192
item => !item.disabled && !item.classList.contains("hidden")
180193
)
181-
.focus();
182-
stopEvent(e);
194+
?.focus();
183195
break;
184196
case "Escape":
185197
this.#closeMenu();

web/viewer.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,22 +207,22 @@
207207
</button>
208208
<menu id="viewsManagerStatusActionOptions" class="popupMenu">
209209
<li>
210-
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
210+
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="-1" disabled>
211211
<span data-l10n-id="pdfjs-views-manager-pages-status-copy-button-label"></span>
212212
</button>
213213
</li>
214214
<li>
215-
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
215+
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="-1" disabled>
216216
<span data-l10n-id="pdfjs-views-manager-pages-status-cut-button-label"></span>
217217
</button>
218218
</li>
219219
<li>
220-
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
220+
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="-1" disabled>
221221
<span data-l10n-id="pdfjs-views-manager-pages-status-delete-button-label"></span>
222222
</button>
223223
</li>
224224
<li>
225-
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
225+
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="-1" disabled>
226226
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
227227
</button>
228228
</li>

0 commit comments

Comments
 (0)