Bug 1545766: PanelMultiView: Don't override keyboard navigation in embedded documents. r=Gijs

Extension panels contain embedded documents; i.e. a <browser> element.
We want users to be able to tab to these and we want them to be focused automatically if a subview is opened from the keyboard, so treat them as tabbable.
However, once an embedded document is focused, we can't manage keyboard navigation inside it, so don't try.
Previously, we tried, which meant keys were overridden even though they didn't do anything, breaking keyboard navigation in extensions altogether.

Differential Revision: https://phabricator.services.mozilla.com/D28442

--HG--
extra : moz-landing-system : lando
This commit is contained in:
James Teh 2019-05-02 10:35:49 +00:00
parent 762dbd77f4
commit 32a5b927ef
2 changed files with 74 additions and 18 deletions

View File

@ -1411,7 +1411,9 @@ var PanelView = class extends AssociatedToNode {
_isNavigableWithTabOnly(element) {
let tag = element.localName;
return tag == "menulist" || tag == "textbox" || tag == "input"
|| tag == "textarea";
|| tag == "textarea"
// Allow tab to reach embedded documents in extension panels.
|| tag == "browser";
}
/**
@ -1565,6 +1567,24 @@ var PanelView = class extends AssociatedToNode {
return;
}
let focus = this.document.activeElement;
// Make sure the focus is actually inside the panel. (It might not be if
// the panel was opened with the mouse.) If it isn't, we don't care
// about it for our purposes.
// We use Node.compareDocumentPosition because Node.contains doesn't
// behave as expected for anonymous content; e.g. the input inside a
// textbox.
if (focus && !(this.node.compareDocumentPosition(focus)
& Node.DOCUMENT_POSITION_CONTAINED_BY)) {
focus = null;
}
// Extension panels contain embedded documents. We can't manage
// keyboard navigation within those.
if (focus && focus.tagName == "browser") {
return;
}
let stop = () => {
event.stopPropagation();
event.preventDefault();
@ -1578,20 +1598,7 @@ var PanelView = class extends AssociatedToNode {
// We use the real focus rather than this.selectedElement because focus
// might have been moved without keyboard navigation (e.g. mouse click)
// and this.selectedElement is only updated for keyboard navigation.
let focus = this.document.activeElement;
if (!focus) {
return false;
}
// Make sure the focus is actually inside the panel.
// (It might not be if the panel was opened with the mouse.)
// We use Node.compareDocumentPosition because Node.contains doesn't
// behave as expected for anonymous content; e.g. the input inside a
// textbox.
if (!(this.node.compareDocumentPosition(focus)
& Node.DOCUMENT_POSITION_CONTAINED_BY)) {
return false;
}
return this._isNavigableWithTabOnly(focus);
return focus && this._isNavigableWithTabOnly(focus);
};
let keyCode = event.code;

View File

@ -23,6 +23,8 @@ let gMainArrowOrder;
let gSubView;
let gSubButton;
let gSubTextarea;
let gDocView;
let gDocBrowser;
async function openPopup() {
let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
@ -36,9 +38,10 @@ async function hidePopup() {
await hidden;
}
async function showSubView() {
let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown");
gPanelMultiView.showSubView(gSubView);
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;
}
@ -74,6 +77,8 @@ add_task(async function setup() {
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);
@ -111,6 +116,18 @@ add_task(async function setup() {
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();
@ -270,3 +287,35 @@ add_task(async function testActivation() {
checkActivated(gMainButton1, () => EventUtils.synthesizeKey("KEY_Enter", {code: "NumpadEnter"}), "pressing numpad enter");
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();
});