mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Bug 1506787
: Support tabindex attribute (including value -1) on non-control XUL elements. r=smaug
Previously, the tabindex attribute wasn't supported on non-control XUL elements at all. The only way to make those focusable was to use -moz-user-focus: normal. However, that caused the element to be included in the tab order; there was no way to make it focusable but not tabbable. This can now be achieved using tabindex="-1". This will primarily be useful for buttons on toolbars, which will be grouped under a single tab stop for efficiency. For consistency, this also changes the behaviour of tabindex="-1" with -moz-user-focus: ignore on XUL controls. Previously, -moz-user-focus: ignore would override tabindex="-1", making the element unfocusable. Now, the tabindex attribute always overrides if explicitly specified. Differential Revision: https://phabricator.services.mozilla.com/D12000 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
29161b173a
commit
9e968bb929
@ -7,10 +7,9 @@
|
||||
|
||||
interface nsIControllers;
|
||||
|
||||
[scriptable, uuid(ea7f92d0-b379-4107-91b4-1e69bdd771e3)]
|
||||
[scriptable, uuid(bdc1d047-6d22-4813-bc50-638ccb349c7d)]
|
||||
interface nsIDOMXULControlElement : nsISupports {
|
||||
attribute boolean disabled;
|
||||
attribute long tabIndex;
|
||||
|
||||
// XXX defined in XULElement, but should be defined here
|
||||
// readonly attribute nsIControllers controllers;
|
||||
|
@ -1637,7 +1637,7 @@ SimpleTest.waitForFocus(startTest);
|
||||
</hbox>
|
||||
<hbox>
|
||||
<button id="o2" accesskey="o" style="-moz-user-focus: ignore;" label="no tabindex"/>
|
||||
<button id="o4" style="-moz-user-focus: ignore;" label="tabindex = -1" tabindex="-1"/>
|
||||
<button id="o4" style="-moz-user-focus: ignore;" label="no tabindex"/>
|
||||
<button id="t6" style="-moz-user-focus: ignore;" label="tabindex = 0" tabindex="0"/>
|
||||
<button id="t2" style="-moz-user-focus: ignore;" label="tabindex = 2" tabindex="2"/>
|
||||
</hbox>
|
||||
@ -1663,17 +1663,17 @@ SimpleTest.waitForFocus(startTest);
|
||||
<vbox>
|
||||
<hbox>
|
||||
<dropmarker id="o6" value="no tabindex"/>
|
||||
<dropmarker id="o8" value="tabindex = -1" tabindex="-1"/>
|
||||
<dropmarker id="o10" value="tabindex = 0" tabindex="0"/>
|
||||
<dropmarker id="o12" value="tabindex = 2" tabindex="2"/>
|
||||
<dropmarker id="o8" value="no tabindex"/>
|
||||
<dropmarker id="o10" value="no tabindx"/>
|
||||
<dropmarker id="o12" value="no tabindex"/>
|
||||
<dropmarker id="t9" accesskey="r" style="-moz-user-focus: normal;" value="no tabindex" />
|
||||
<dropmarker id="t10" style="-moz-user-focus: normal;" value="tabindex = -1" tabindex="-1" />
|
||||
<dropmarker id="t10" style="-moz-user-focus: normal;" value="no tabindex"/>
|
||||
<dropmarker id="t11" style="-moz-user-focus: normal;" value="tabindex = 0" tabindex="0" />
|
||||
<dropmarker id="t12" style="-moz-user-focus: normal;" value="tabindex = 2" tabindex="2" />
|
||||
<dropmarker id="t12" style="-moz-user-focus: normal;" value="no tabindex"/>
|
||||
<dropmarker id="o14" style="-moz-user-focus: ignore;" value="no tabindex"/>
|
||||
<dropmarker id="o16" style="-moz-user-focus: ignore;" value="tabindex = -1" tabindex="-1"/>
|
||||
<dropmarker id="n1" style="-moz-user-focus: none;" value="tabindex = 0" tabindex="0"/>
|
||||
<dropmarker id="n3" style="-moz-user-focus: none;" value="tabindex = 2" tabindex="2"/>
|
||||
<dropmarker id="o16" style="-moz-user-focus: ignore;" value="no tabindex"/>
|
||||
<dropmarker id="n1" style="-moz-user-focus: none;" value="no tabindex"/>
|
||||
<dropmarker id="n3" style="-moz-user-focus: none;" value="no tabindex"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<browser id="childframe" type="content" src="child_focus_frame.html" width="300" height="195"/>
|
||||
|
@ -75,6 +75,8 @@ interface XULElement : Element {
|
||||
[Throws]
|
||||
readonly attribute BoxObject? boxObject;
|
||||
|
||||
[SetterThrows]
|
||||
attribute long tabIndex;
|
||||
[Throws]
|
||||
void focus();
|
||||
[Throws]
|
||||
|
@ -439,16 +439,15 @@ nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
|
||||
* For controls, the element cannot be focused and is not part of the tab
|
||||
* order if it is disabled.
|
||||
*
|
||||
* Controls (those that implement nsIDOMXULControlElement):
|
||||
* -moz-user-focus is overridden if a tabindex (even -1) is specified.
|
||||
*
|
||||
* Specifically, the behaviour for all XUL elements is as follows:
|
||||
* *aTabIndex = -1 no tabindex Not focusable or tabbable
|
||||
* *aTabIndex = -1 tabindex="-1" Not focusable or tabbable
|
||||
* *aTabIndex = -1 tabindex="-1" Focusable but not tabbable
|
||||
* *aTabIndex = -1 tabindex=">=0" Focusable and tabbable
|
||||
* *aTabIndex >= 0 no tabindex Focusable and tabbable
|
||||
* *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable
|
||||
* *aTabIndex >= 0 tabindex=">=0" Focusable and tabbable
|
||||
* Non-controls:
|
||||
* *aTabIndex = -1 Not focusable or tabbable
|
||||
* *aTabIndex >= 0 Focusable and tabbable
|
||||
*
|
||||
* If aTabIndex is null, then the tabindex is not computed, and
|
||||
* true is returned for non-disabled controls and false otherwise.
|
||||
@ -483,36 +482,32 @@ nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
|
||||
}
|
||||
|
||||
if (aTabIndex) {
|
||||
if (xulControl) {
|
||||
if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
|
||||
// if either the aTabIndex argument or a specified tabindex is non-negative,
|
||||
// the element becomes focusable.
|
||||
int32_t tabIndex = 0;
|
||||
xulControl->GetTabIndex(&tabIndex);
|
||||
shouldFocus = *aTabIndex >= 0 || tabIndex >= 0;
|
||||
*aTabIndex = tabIndex;
|
||||
} else {
|
||||
// otherwise, if there is no tabindex attribute, just use the value of
|
||||
// *aTabIndex to indicate focusability. Reset any supplied tabindex to 0.
|
||||
shouldFocus = *aTabIndex >= 0;
|
||||
if (shouldFocus)
|
||||
*aTabIndex = 0;
|
||||
}
|
||||
|
||||
if (shouldFocus && sTabFocusModelAppliesToXUL &&
|
||||
!(sTabFocusModel & eTabFocus_formElementsMask)) {
|
||||
// By default, the tab focus model doesn't apply to xul element on any system but OS X.
|
||||
// on OS X we're following it for UI elements (XUL) as sTabFocusModel is based on
|
||||
// "Full Keyboard Access" system setting (see mac/nsILookAndFeel).
|
||||
// both textboxes and list elements (i.e. trees and list) should always be focusable
|
||||
// (textboxes are handled as html:input)
|
||||
// For compatibility, we only do this for controls, otherwise elements like <browser>
|
||||
// cannot take this focus.
|
||||
if (IsNonList(mNodeInfo))
|
||||
*aTabIndex = -1;
|
||||
}
|
||||
if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
|
||||
// The tabindex attribute was specified, so the element becomes
|
||||
// focusable.
|
||||
shouldFocus = true;
|
||||
*aTabIndex = TabIndex();
|
||||
} else {
|
||||
// otherwise, if there is no tabindex attribute, just use the value of
|
||||
// *aTabIndex to indicate focusability. Reset any supplied tabindex to 0.
|
||||
shouldFocus = *aTabIndex >= 0;
|
||||
if (shouldFocus) {
|
||||
*aTabIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (xulControl && shouldFocus && sTabFocusModelAppliesToXUL &&
|
||||
!(sTabFocusModel & eTabFocus_formElementsMask)) {
|
||||
// By default, the tab focus model doesn't apply to xul element on any system but OS X.
|
||||
// on OS X we're following it for UI elements (XUL) as sTabFocusModel is based on
|
||||
// "Full Keyboard Access" system setting (see mac/nsILookAndFeel).
|
||||
// both textboxes and list elements (i.e. trees and list) should always be focusable
|
||||
// (textboxes are handled as html:input)
|
||||
// For compatibility, we only do this for controls, otherwise elements like <browser>
|
||||
// cannot take this focus.
|
||||
if (IsNonList(mNodeInfo)) {
|
||||
*aTabIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1035,6 +1030,11 @@ nsXULElement::ParseAttribute(int32_t aNamespaceID,
|
||||
nsIPrincipal* aMaybeScriptedPrincipal,
|
||||
nsAttrValue& aResult)
|
||||
{
|
||||
if (aNamespaceID == kNameSpaceID_None &&
|
||||
aAttribute == nsGkAtoms::tabindex) {
|
||||
return aResult.ParseIntValue(aValue);
|
||||
}
|
||||
|
||||
// Parse into a nsAttrValue
|
||||
if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
||||
aMaybeScriptedPrincipal, aResult)) {
|
||||
@ -1960,6 +1960,10 @@ nsXULPrototypeElement::SetAttrAt(uint32_t aPos, const nsAString& aValue,
|
||||
return NS_OK;
|
||||
}
|
||||
// Don't abort if parsing failed, it could just be malformed css.
|
||||
} else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) {
|
||||
mAttributes[aPos].mValue.ParseIntValue(aValue);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
|
||||
|
@ -13,51 +13,48 @@
|
||||
Elements are navigated in the following order:
|
||||
1. tabindex > 0 in tree order
|
||||
2. tabindex = 0 in tree order
|
||||
Elements with tabindex = -1 are not in the tab order
|
||||
Elements with tabindex = -1 are focusable, but not in the tab order
|
||||
-->
|
||||
<hbox>
|
||||
<button id="t5" label="One"/>
|
||||
<checkbox id="no1" label="Two" tabindex="-1"/>
|
||||
<button id="t6" label="Three" tabindex="0"/>
|
||||
<button id="t7" label="One"/>
|
||||
<checkbox id="f1" label="Two" tabindex="-1"/>
|
||||
<button id="t8" label="Three" tabindex="0"/>
|
||||
<checkbox id="t1" label="Four" tabindex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<textbox id="t7" idmod="t3" size="3"/>
|
||||
<textbox id="no2" size="3" tabindex="-1"/>
|
||||
<textbox id="t8" idmod="t4" size="3" tabindex="0"/>
|
||||
<textbox id="t9" idmod="t5" size="3"/>
|
||||
<textbox id="f2" size="3" tabindex="-1"/>
|
||||
<textbox id="t10" idmod="t6" size="3" tabindex="0"/>
|
||||
<textbox id="t2" idmod="t1" size="3" tabindex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<button id="no3" style="-moz-user-focus: ignore;" label="One"/>
|
||||
<checkbox id="no4" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/>
|
||||
<button id="t9" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/>
|
||||
<button id="n1" style="-moz-user-focus: ignore;" label="One"/>
|
||||
<checkbox id="f3" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/>
|
||||
<button id="t11" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/>
|
||||
<checkbox id="t3" style="-moz-user-focus: ignore;" label="Four" tabindex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<textbox id="t10" idmod="t5" style="-moz-user-focus: ignore;" size="3"/>
|
||||
<textbox id="no5" style="-moz-user-focus: ignore;" size="3" tabindex="-1"/>
|
||||
<textbox id="t11" idmod="t6" style="-moz-user-focus: ignore;" size="3" tabindex="0"/>
|
||||
<textbox id="t12" idmod="t7" style="-moz-user-focus: ignore;" size="3"/>
|
||||
<textbox id="f4" style="-moz-user-focus: ignore;" size="3" tabindex="-1"/>
|
||||
<textbox id="t13" idmod="t8" style="-moz-user-focus: ignore;" size="3" tabindex="0"/>
|
||||
<textbox id="t4" idmod="t2" style="-moz-user-focus: ignore;" size="3" tabindex="1"/>
|
||||
</hbox>
|
||||
<richlistbox id="t12" idmod="t7">
|
||||
<richlistbox id="t14" idmod="t9">
|
||||
<richlistitem><label value="Item One"/></richlistitem>
|
||||
</richlistbox>
|
||||
|
||||
<hbox>
|
||||
<!-- the tabindex attribute does not apply to non-controls, so it
|
||||
should be treated as -1 for non-focusable dropmarkers, and 0
|
||||
for focusable dropmarkers. Thus, the first four dropmarkers
|
||||
are not in the tab order, and the last four dropmarkers should
|
||||
be in the tab order just after the listbox above.
|
||||
<!-- the tabindex attribute applies to non-controls as well. They are not
|
||||
focusable unless tabindex is explicitly specified.
|
||||
-->
|
||||
<dropmarker id="no6"/>
|
||||
<dropmarker id="no7" tabindex="-1"/>
|
||||
<dropmarker id="no8" tabindex="0"/>
|
||||
<dropmarker id="no9" tabindex="1"/>
|
||||
<dropmarker id="t13" style="-moz-user-focus: normal;"/>
|
||||
<dropmarker id="t14" style="-moz-user-focus: normal;" tabindex="-1"/>
|
||||
<dropmarker id="t15" style="-moz-user-focus: normal;" tabindex="0"/>
|
||||
<dropmarker id="t16" style="-moz-user-focus: normal;" tabindex="1"/>
|
||||
<dropmarker id="n2"/>
|
||||
<dropmarker id="f5" tabindex="-1"/>
|
||||
<dropmarker id="t15" tabindex="0"/>
|
||||
<dropmarker id="t5" idmod="t3" tabindex="1"/>
|
||||
<dropmarker id="t16" style="-moz-user-focus: normal;"/>
|
||||
<dropmarker id="f6" style="-moz-user-focus: normal;" tabindex="-1"/>
|
||||
<dropmarker id="t17" style="-moz-user-focus: normal;" tabindex="0"/>
|
||||
<dropmarker id="t6" idmod="t4" style="-moz-user-focus: normal;" tabindex="1"/>
|
||||
</hbox>
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
@ -73,19 +70,36 @@
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function checkFocusability(aId, aFocusable)
|
||||
{
|
||||
document.activeElement.blur();
|
||||
let testNode = document.getElementById(aId);
|
||||
testNode.focus();
|
||||
let newFocus = document.activeElement;
|
||||
if (newFocus.localName == "input") {
|
||||
newFocus = document.getBindingParent(newFocus);
|
||||
}
|
||||
let check = aFocusable ? is : isnot;
|
||||
let focusableText = aFocusable ? "focusable " : "unfocusable ";
|
||||
check(newFocus, testNode,
|
||||
".focus() call on " + focusableText + aId);
|
||||
}
|
||||
|
||||
var gAdjustedTabFocusModel = false;
|
||||
var gTestCount = 16;
|
||||
var gTestCount = 17;
|
||||
var gTestsOccurred = 0;
|
||||
|
||||
let gFocusableNotTabbableCount = 6;
|
||||
let gNotFocusableCount = 2;
|
||||
|
||||
function runTests()
|
||||
{
|
||||
var t;
|
||||
window.addEventListener("focus", function (event) {
|
||||
function onFocus(event) {
|
||||
if (t == 1 && event.target.id == "t2") {
|
||||
// looks to be using the MacOSX Full Keyboard Access set to Textboxes
|
||||
// and lists only so use the idmod attribute instead
|
||||
gAdjustedTabFocusModel = true;
|
||||
gTestCount = 7;
|
||||
gTestCount = 9;
|
||||
}
|
||||
|
||||
var attrcompare = gAdjustedTabFocusModel ? "idmod" : "id";
|
||||
@ -102,12 +116,23 @@ function runTests()
|
||||
if (event.target.localName != "textbox")
|
||||
gTestsOccurred++;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
window.addEventListener("focus", onFocus, true);
|
||||
|
||||
for (t = 1; t <= gTestCount; t++)
|
||||
synthesizeKey("KEY_Tab");
|
||||
|
||||
is(gTestsOccurred, gTestCount, "test count");
|
||||
window.removeEventListener("focus", onFocus, true);
|
||||
|
||||
for (let i = 1; i <= gFocusableNotTabbableCount; ++i) {
|
||||
checkFocusability("f" + i, true);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= gNotFocusableCount; ++i) {
|
||||
checkFocusability("n" + i, false);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user