mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-12 14:20:14 +02:00
Previously, we sent a command event and a click event. Normally, the command event executes the action, then the click event closes the menu. However, in some cases (e.g. the Library button), there is no command event handler and the mousedown event executes the action instead. Differential Revision: https://phabricator.services.mozilla.com/D29151 --HG-- extra : moz-landing-system : lando
335 lines
12 KiB
JavaScript
335 lines
12 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Test the keyboard behavior of PanelViews.
|
|
*/
|
|
|
|
const {PanelMultiView} = ChromeUtils.import("resource:///modules/PanelMultiView.jsm");
|
|
|
|
let gAnchor;
|
|
let gPanel;
|
|
let gPanelMultiView;
|
|
let gMainView;
|
|
let gMainButton1;
|
|
let gMainMenulist;
|
|
let gMainTextbox;
|
|
let gMainButton2;
|
|
let gMainButton3;
|
|
let gMainTabOrder;
|
|
let gMainArrowOrder;
|
|
let gSubView;
|
|
let gSubButton;
|
|
let gSubTextarea;
|
|
let gDocView;
|
|
let gDocBrowser;
|
|
|
|
async function openPopup() {
|
|
let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
|
|
PanelMultiView.openPopup(gPanel, gAnchor, "bottomcenter topright");
|
|
await shown;
|
|
}
|
|
|
|
async function hidePopup() {
|
|
let hidden = BrowserTestUtils.waitForEvent(gPanel, "popuphidden");
|
|
PanelMultiView.hidePopup(gPanel);
|
|
await hidden;
|
|
}
|
|
|
|
async function showSubView(view = gSubView) {
|
|
let shown = BrowserTestUtils.waitForEvent(view, "ViewShown");
|
|
// We must show with an anchor so the Back button is generated.
|
|
gPanelMultiView.showSubView(view, gMainButton1);
|
|
await shown;
|
|
}
|
|
|
|
async function expectFocusAfterKey(aKey, aFocus) {
|
|
let res = aKey.match(/^(Shift\+)?(.+)$/);
|
|
let shift = Boolean(res[1]);
|
|
let key;
|
|
if (res[2].length == 1) {
|
|
key = res[2]; // Character.
|
|
} else {
|
|
key = "KEY_" + res[2]; // Tab, ArrowRight, etc.
|
|
}
|
|
info("Waiting for focus on " + aFocus.id);
|
|
let focused = BrowserTestUtils.waitForEvent(aFocus, "focus");
|
|
EventUtils.synthesizeKey(key, {shiftKey: shift});
|
|
await focused;
|
|
ok(true, aFocus.id + " focused after " + aKey + " pressed");
|
|
}
|
|
|
|
add_task(async function setup() {
|
|
let navBar = document.getElementById("nav-bar");
|
|
gAnchor = document.createXULElement("toolbarbutton");
|
|
navBar.appendChild(gAnchor);
|
|
gPanel = document.createXULElement("panel");
|
|
navBar.appendChild(gPanel);
|
|
gPanelMultiView = document.createXULElement("panelmultiview");
|
|
gPanelMultiView.setAttribute("mainViewId", "testMainView");
|
|
gPanel.appendChild(gPanelMultiView);
|
|
|
|
gMainView = document.createXULElement("panelview");
|
|
gMainView.id = "testMainView";
|
|
gPanelMultiView.appendChild(gMainView);
|
|
gMainButton1 = document.createXULElement("button");
|
|
gMainButton1.id = "gMainButton1";
|
|
gMainView.appendChild(gMainButton1);
|
|
// We use this for anchoring subviews, so it must have a label.
|
|
gMainButton1.setAttribute("label", "gMainButton1");
|
|
gMainMenulist = document.createXULElement("menulist");
|
|
gMainMenulist.id = "gMainMenulist";
|
|
gMainView.appendChild(gMainMenulist);
|
|
let menuPopup = document.createXULElement("menupopup");
|
|
gMainMenulist.appendChild(menuPopup);
|
|
let item = document.createXULElement("menuitem");
|
|
item.setAttribute("value", "1");
|
|
item.setAttribute("selected", "true");
|
|
menuPopup.appendChild(item);
|
|
item = document.createXULElement("menuitem");
|
|
item.setAttribute("value", "2");
|
|
menuPopup.appendChild(item);
|
|
gMainTextbox = document.createXULElement("textbox");
|
|
gMainTextbox.id = "gMainTextbox";
|
|
gMainView.appendChild(gMainTextbox);
|
|
gMainTextbox.setAttribute("value", "value");
|
|
gMainButton2 = document.createXULElement("button");
|
|
gMainButton2.id = "gMainButton2";
|
|
gMainView.appendChild(gMainButton2);
|
|
gMainButton3 = document.createXULElement("button");
|
|
gMainButton3.id = "gMainButton3";
|
|
gMainView.appendChild(gMainButton3);
|
|
gMainTabOrder = [gMainButton1, gMainMenulist, gMainTextbox, gMainButton2,
|
|
gMainButton3];
|
|
gMainArrowOrder = [gMainButton1, gMainButton2, gMainButton3];
|
|
|
|
gSubView = document.createXULElement("panelview");
|
|
gSubView.id = "testSubView";
|
|
gPanelMultiView.appendChild(gSubView);
|
|
gSubButton = document.createXULElement("button");
|
|
gSubView.appendChild(gSubButton);
|
|
gSubTextarea = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"textarea");
|
|
gSubTextarea.id = "gSubTextarea";
|
|
gSubView.appendChild(gSubTextarea);
|
|
gSubTextarea.value = "value";
|
|
|
|
gDocView = document.createXULElement("panelview");
|
|
gDocView.id = "testDocView";
|
|
gPanelMultiView.appendChild(gDocView);
|
|
gDocBrowser = document.createXULElement("browser");
|
|
gDocBrowser.id = "gDocBrowser";
|
|
gDocBrowser.setAttribute("type", "content");
|
|
gDocBrowser.setAttribute("src",
|
|
'data:text/html,<textarea id="docTextarea">value</textarea><button id="docButton"></button>');
|
|
gDocBrowser.setAttribute("width", 100);
|
|
gDocBrowser.setAttribute("height", 100);
|
|
gDocView.appendChild(gDocBrowser);
|
|
|
|
registerCleanupFunction(() => {
|
|
gAnchor.remove();
|
|
gPanel.remove();
|
|
});
|
|
});
|
|
|
|
// Test that the tab key focuses all expected controls.
|
|
add_task(async function testTab() {
|
|
await openPopup();
|
|
for (let elem of gMainTabOrder) {
|
|
await expectFocusAfterKey("Tab", elem);
|
|
}
|
|
// Wrap around.
|
|
await expectFocusAfterKey("Tab", gMainTabOrder[0]);
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that the shift+tab key focuses all expected controls.
|
|
add_task(async function testShiftTab() {
|
|
await openPopup();
|
|
for (let i = gMainTabOrder.length - 1; i >= 0; --i) {
|
|
await expectFocusAfterKey("Shift+Tab", gMainTabOrder[i]);
|
|
}
|
|
// Wrap around.
|
|
await expectFocusAfterKey("Shift+Tab",
|
|
gMainTabOrder[gMainTabOrder.length - 1]);
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that the down arrow key skips menulists and textboxes.
|
|
add_task(async function testDownArrow() {
|
|
await openPopup();
|
|
for (let elem of gMainArrowOrder) {
|
|
await expectFocusAfterKey("ArrowDown", elem);
|
|
}
|
|
// Wrap around.
|
|
await expectFocusAfterKey("ArrowDown", gMainArrowOrder[0]);
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that the up arrow key skips menulists and textboxes.
|
|
add_task(async function testUpArrow() {
|
|
await openPopup();
|
|
for (let i = gMainArrowOrder.length - 1; i >= 0; --i) {
|
|
await expectFocusAfterKey("ArrowUp", gMainArrowOrder[i]);
|
|
}
|
|
// Wrap around.
|
|
await expectFocusAfterKey("ArrowUp",
|
|
gMainArrowOrder[gMainArrowOrder.length - 1]);
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that the home/end keys move to the first/last controls.
|
|
add_task(async function testHomeEnd() {
|
|
await openPopup();
|
|
await expectFocusAfterKey("Home", gMainArrowOrder[0]);
|
|
await expectFocusAfterKey("End",
|
|
gMainArrowOrder[gMainArrowOrder.length - 1]);
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that the up/down arrow keys work as expected in menulists.
|
|
add_task(async function testArrowsMenulist() {
|
|
await openPopup();
|
|
gMainMenulist.focus();
|
|
is(document.activeElement, gMainMenulist, "menulist focused");
|
|
is(gMainMenulist.value, "1", "menulist initial value 1");
|
|
if (AppConstants.platform == "macosx") {
|
|
// On Mac, down/up arrows just open the menulist.
|
|
let popup = gMainMenulist.menupopup;
|
|
for (let key of ["ArrowDown", "ArrowUp"]) {
|
|
let shown = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
|
EventUtils.synthesizeKey("KEY_" + key);
|
|
await shown;
|
|
ok(gMainMenulist.open, "menulist open after " + key);
|
|
let hidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
|
|
EventUtils.synthesizeKey("KEY_Escape");
|
|
await hidden;
|
|
ok(!gMainMenulist.open, "menulist closed after Escape");
|
|
}
|
|
} else {
|
|
// On other platforms, down/up arrows change the value without opening the
|
|
// menulist.
|
|
EventUtils.synthesizeKey("KEY_ArrowDown");
|
|
is(document.activeElement, gMainMenulist,
|
|
"menulist still focused after ArrowDown");
|
|
is(gMainMenulist.value, "2", "menulist value 2 after ArrowDown");
|
|
EventUtils.synthesizeKey("KEY_ArrowUp");
|
|
is(document.activeElement, gMainMenulist,
|
|
"menulist still focused after ArrowUp");
|
|
is(gMainMenulist.value, "1", "menulist value 1 after ArrowUp");
|
|
}
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that pressing space in a textbox inserts a space (instead of trying to
|
|
// activate the control).
|
|
add_task(async function testSpaceTextbox() {
|
|
await openPopup();
|
|
gMainTextbox.focus();
|
|
gMainTextbox.selectionStart = gMainTextbox.selectionEnd = 0;
|
|
EventUtils.synthesizeKey(" ");
|
|
is(gMainTextbox.value, " value", "Space typed into textbox");
|
|
gMainTextbox.value = "value";
|
|
await hidePopup();
|
|
});
|
|
|
|
// Tests that the left arrow key normally moves back to the previous view.
|
|
add_task(async function testLeftArrow() {
|
|
await openPopup();
|
|
await showSubView();
|
|
let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
|
|
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
|
await shown;
|
|
ok("Moved to previous view after ArrowLeft");
|
|
await hidePopup();
|
|
});
|
|
|
|
// Tests that the left arrow key moves the caret in a textarea in a subview
|
|
// (instead of going back to the previous view).
|
|
add_task(async function testLeftArrowTextarea() {
|
|
await openPopup();
|
|
await showSubView();
|
|
gSubTextarea.focus();
|
|
is(document.activeElement, gSubTextarea, "textarea focused");
|
|
EventUtils.synthesizeKey("KEY_End");
|
|
is(gSubTextarea.selectionStart, 5, "selectionStart 5 after End");
|
|
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
|
is(gSubTextarea.selectionStart, 4, "selectionStart 4 after ArrowLeft");
|
|
is(document.activeElement, gSubTextarea, "textarea still focused");
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test navigation to a button which is initially disabled and later enabled.
|
|
add_task(async function testDynamicButton() {
|
|
gMainButton2.disabled = true;
|
|
await openPopup();
|
|
await expectFocusAfterKey("ArrowDown", gMainButton1);
|
|
await expectFocusAfterKey("ArrowDown", gMainButton3);
|
|
gMainButton2.disabled = false;
|
|
await expectFocusAfterKey("ArrowUp", gMainButton2);
|
|
await hidePopup();
|
|
});
|
|
|
|
add_task(async function testActivation() {
|
|
function checkActivated(elem, activationFn, reason) {
|
|
let activated = false;
|
|
elem.onclick = function() { activated = true; };
|
|
activationFn();
|
|
ok(activated, "Should have activated button after " + reason);
|
|
elem.onclick = null;
|
|
}
|
|
await openPopup();
|
|
await expectFocusAfterKey("ArrowDown", gMainButton1);
|
|
checkActivated(gMainButton1, () => EventUtils.synthesizeKey("KEY_Enter"), "pressing enter");
|
|
checkActivated(gMainButton1, () => EventUtils.synthesizeKey(" "), "pressing space");
|
|
checkActivated(gMainButton1, () => EventUtils.synthesizeKey("KEY_Enter", {code: "NumpadEnter"}), "pressing numpad enter");
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that keyboard activation works for buttons responding to mousedown
|
|
// events (instead of command or click). The Library button does this, for
|
|
// example.
|
|
add_task(async function testActivationMousedown() {
|
|
await openPopup();
|
|
await expectFocusAfterKey("ArrowDown", gMainButton1);
|
|
let activated = false;
|
|
gMainButton1.onmousedown = function() { activated = true; };
|
|
EventUtils.synthesizeKey(" ");
|
|
ok(activated, "mousedown activated after space");
|
|
gMainButton1.onmousedown = null;
|
|
await hidePopup();
|
|
});
|
|
|
|
// Test that tab and the arrow keys aren't overridden in embedded documents.
|
|
add_task(async function testTabArrowsBrowser() {
|
|
await openPopup();
|
|
await showSubView(gDocView);
|
|
let backButton = gDocView.querySelector(".subviewbutton-back");
|
|
backButton.id = "docBack";
|
|
await expectFocusAfterKey("Tab", backButton);
|
|
let doc = gDocBrowser.contentDocument;
|
|
// Documents don't have an id property, but expectFocusAfterKey wants one.
|
|
doc.id = "doc";
|
|
await expectFocusAfterKey("Tab", doc);
|
|
// Make sure tab/arrows aren't overridden within the embedded document.
|
|
let textarea = doc.getElementById("docTextarea");
|
|
// Tab should really focus the textarea, but default tab handling seems to
|
|
// skip everything inside the browser element when run in this test. This
|
|
// behaves as expected in real panels, though. Force focus to the textarea
|
|
// and then test from there.
|
|
textarea.focus();
|
|
is(doc.activeElement, textarea, "textarea focused");
|
|
is(textarea.selectionStart, 0, "selectionStart initially 0");
|
|
EventUtils.synthesizeKey("KEY_ArrowRight");
|
|
is(textarea.selectionStart, 1, "selectionStart 1 after ArrowRight");
|
|
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
|
is(textarea.selectionStart, 0, "selectionStart 0 after ArrowLeft");
|
|
is(doc.activeElement, textarea, "textarea still focused");
|
|
let docButton = doc.getElementById("docButton");
|
|
expectFocusAfterKey("Tab", docButton);
|
|
// Make sure tab leaves the document and reaches the Back button.
|
|
expectFocusAfterKey("Tab", backButton);
|
|
await hidePopup();
|
|
});
|