Correctly handle tab/page down when on a menu (bug 2016212)

This commit is contained in:
Calixte Denizet 2026-02-17 19:56:54 +01:00
parent 61de56440a
commit 5290534c53
No known key found for this signature in database
GPG Key ID: 0C5442631EE0691F
2 changed files with 90 additions and 2 deletions

View File

@ -355,4 +355,73 @@ describe("PDF Thumbnail View", () => {
); );
}); });
}); });
describe("Menu keyboard navigation with multi-character keys (bug 2016212)", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"page_with_number_and_link.pdf",
"#viewsManagerSelectorButton",
null,
null,
{ enableSplitMerge: true }
);
});
afterEach(async () => {
await closePages(pages);
});
it("must navigate menus with ArrowDown and Tab keys", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#viewsManagerToggleButton");
await waitForThumbnailVisible(page, 1);
// Focus the views manager selector button
await page.waitForSelector("#viewsManagerSelectorButton", {
visible: true,
});
await page.focus("#viewsManagerSelectorButton");
// Open menu with Enter key
await page.keyboard.press("Enter");
// Wait for menu to be expanded
await waitForMenu(page, "#viewsManagerSelectorButton");
// Check that focus moved to the first menu button (pages)
await page.waitForSelector("#thumbnailsViewMenu:focus", {
visible: true,
});
// Press ArrowDown to navigate to second item
await page.keyboard.press("ArrowDown");
// Should now be on outlines button
await page.waitForSelector("#outlinesViewMenu:focus", {
visible: true,
});
// Press Tab to move to the manage button (should close views menu)
await page.keyboard.press("Tab");
// Wait for views manager menu to be collapsed
await waitForMenu(page, "#viewsManagerSelectorButton", false);
// Focus should be on manage button
await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
visible: true,
});
// Open manage menu with Space key
await page.keyboard.press(" ");
// Wait for manage menu to be expanded
await waitForMenu(page, "#viewsManagerStatusActionButton");
})
);
});
});
}); });

View File

@ -28,6 +28,8 @@ class Menu {
#lastIndex = -1; #lastIndex = -1;
#onFocusOutBound = this.#onFocusOut.bind(this);
/** /**
* Create a menu for the given button. * Create a menu for the given button.
* @param {HTMLElement} menuContainer * @param {HTMLElement} menuContainer
@ -97,7 +99,18 @@ class Menu {
}, },
{ signal } { signal }
); );
window.addEventListener("blur", this.#closeMenu.bind(this), { signal }); const closeMenu = this.#closeMenu.bind(this);
window.addEventListener("blur", closeMenu, { signal });
menu.addEventListener("focusout", this.#onFocusOutBound, { signal });
}
#onFocusOut({ relatedTarget }) {
if (
!this.#triggeringButton.contains(relatedTarget) &&
!this.#menu.contains(relatedTarget)
) {
this.#closeMenu();
}
} }
/** /**
@ -112,6 +125,7 @@ class Menu {
this.#openMenu(); this.#openMenu();
}); });
this.#triggeringButton.addEventListener("focusout", this.#onFocusOutBound);
const { signal } = this.#menuAC; const { signal } = this.#menuAC;
@ -148,7 +162,12 @@ class Menu {
stopEvent(e); stopEvent(e);
break; break;
default: default:
const char = e.key.toLocaleLowerCase(); const { key } = e;
if (!/^\p{L}$/u.test(key)) {
// It isn't a single letter, so ignore it.
break;
}
const char = key.toLocaleLowerCase();
this.#goToNextItem(e.target, true, item => this.#goToNextItem(e.target, true, item =>
item.textContent.trim().toLowerCase().startsWith(char) item.textContent.trim().toLowerCase().startsWith(char)
); );