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;
|
interface nsIControllers;
|
||||||
|
|
||||||
[scriptable, uuid(ea7f92d0-b379-4107-91b4-1e69bdd771e3)]
|
[scriptable, uuid(bdc1d047-6d22-4813-bc50-638ccb349c7d)]
|
||||||
interface nsIDOMXULControlElement : nsISupports {
|
interface nsIDOMXULControlElement : nsISupports {
|
||||||
attribute boolean disabled;
|
attribute boolean disabled;
|
||||||
attribute long tabIndex;
|
|
||||||
|
|
||||||
// XXX defined in XULElement, but should be defined here
|
// XXX defined in XULElement, but should be defined here
|
||||||
// readonly attribute nsIControllers controllers;
|
// readonly attribute nsIControllers controllers;
|
||||||
|
@ -1637,7 +1637,7 @@ SimpleTest.waitForFocus(startTest);
|
|||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<button id="o2" accesskey="o" style="-moz-user-focus: ignore;" label="no tabindex"/>
|
<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="t6" style="-moz-user-focus: ignore;" label="tabindex = 0" tabindex="0"/>
|
||||||
<button id="t2" style="-moz-user-focus: ignore;" label="tabindex = 2" tabindex="2"/>
|
<button id="t2" style="-moz-user-focus: ignore;" label="tabindex = 2" tabindex="2"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
@ -1663,17 +1663,17 @@ SimpleTest.waitForFocus(startTest);
|
|||||||
<vbox>
|
<vbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<dropmarker id="o6" value="no tabindex"/>
|
<dropmarker id="o6" value="no tabindex"/>
|
||||||
<dropmarker id="o8" value="tabindex = -1" tabindex="-1"/>
|
<dropmarker id="o8" value="no tabindex"/>
|
||||||
<dropmarker id="o10" value="tabindex = 0" tabindex="0"/>
|
<dropmarker id="o10" value="no tabindx"/>
|
||||||
<dropmarker id="o12" value="tabindex = 2" tabindex="2"/>
|
<dropmarker id="o12" value="no tabindex"/>
|
||||||
<dropmarker id="t9" accesskey="r" style="-moz-user-focus: normal;" 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="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="o14" style="-moz-user-focus: ignore;" value="no tabindex"/>
|
||||||
<dropmarker id="o16" style="-moz-user-focus: ignore;" value="tabindex = -1" tabindex="-1"/>
|
<dropmarker id="o16" style="-moz-user-focus: ignore;" value="no tabindex"/>
|
||||||
<dropmarker id="n1" style="-moz-user-focus: none;" value="tabindex = 0" tabindex="0"/>
|
<dropmarker id="n1" style="-moz-user-focus: none;" value="no tabindex"/>
|
||||||
<dropmarker id="n3" style="-moz-user-focus: none;" value="tabindex = 2" tabindex="2"/>
|
<dropmarker id="n3" style="-moz-user-focus: none;" value="no tabindex"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
<browser id="childframe" type="content" src="child_focus_frame.html" width="300" height="195"/>
|
<browser id="childframe" type="content" src="child_focus_frame.html" width="300" height="195"/>
|
||||||
|
@ -75,6 +75,8 @@ interface XULElement : Element {
|
|||||||
[Throws]
|
[Throws]
|
||||||
readonly attribute BoxObject? boxObject;
|
readonly attribute BoxObject? boxObject;
|
||||||
|
|
||||||
|
[SetterThrows]
|
||||||
|
attribute long tabIndex;
|
||||||
[Throws]
|
[Throws]
|
||||||
void focus();
|
void focus();
|
||||||
[Throws]
|
[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
|
* For controls, the element cannot be focused and is not part of the tab
|
||||||
* order if it is disabled.
|
* 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 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 = -1 tabindex=">=0" Focusable and tabbable
|
||||||
* *aTabIndex >= 0 no tabindex Focusable and tabbable
|
* *aTabIndex >= 0 no tabindex Focusable and tabbable
|
||||||
* *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable
|
* *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable
|
||||||
* *aTabIndex >= 0 tabindex=">=0" Focusable and 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
|
* If aTabIndex is null, then the tabindex is not computed, and
|
||||||
* true is returned for non-disabled controls and false otherwise.
|
* true is returned for non-disabled controls and false otherwise.
|
||||||
@ -483,36 +482,32 @@ nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (aTabIndex) {
|
if (aTabIndex) {
|
||||||
if (xulControl) {
|
if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
|
||||||
if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
|
// The tabindex attribute was specified, so the element becomes
|
||||||
// if either the aTabIndex argument or a specified tabindex is non-negative,
|
// focusable.
|
||||||
// the element becomes focusable.
|
shouldFocus = true;
|
||||||
int32_t tabIndex = 0;
|
*aTabIndex = TabIndex();
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else {
|
} 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;
|
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,
|
nsIPrincipal* aMaybeScriptedPrincipal,
|
||||||
nsAttrValue& aResult)
|
nsAttrValue& aResult)
|
||||||
{
|
{
|
||||||
|
if (aNamespaceID == kNameSpaceID_None &&
|
||||||
|
aAttribute == nsGkAtoms::tabindex) {
|
||||||
|
return aResult.ParseIntValue(aValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse into a nsAttrValue
|
// Parse into a nsAttrValue
|
||||||
if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
||||||
aMaybeScriptedPrincipal, aResult)) {
|
aMaybeScriptedPrincipal, aResult)) {
|
||||||
@ -1960,6 +1960,10 @@ nsXULPrototypeElement::SetAttrAt(uint32_t aPos, const nsAString& aValue,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
// Don't abort if parsing failed, it could just be malformed css.
|
// 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);
|
mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
|
||||||
|
@ -13,51 +13,48 @@
|
|||||||
Elements are navigated in the following order:
|
Elements are navigated in the following order:
|
||||||
1. tabindex > 0 in tree order
|
1. tabindex > 0 in tree order
|
||||||
2. 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>
|
<hbox>
|
||||||
<button id="t5" label="One"/>
|
<button id="t7" label="One"/>
|
||||||
<checkbox id="no1" label="Two" tabindex="-1"/>
|
<checkbox id="f1" label="Two" tabindex="-1"/>
|
||||||
<button id="t6" label="Three" tabindex="0"/>
|
<button id="t8" label="Three" tabindex="0"/>
|
||||||
<checkbox id="t1" label="Four" tabindex="1"/>
|
<checkbox id="t1" label="Four" tabindex="1"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<textbox id="t7" idmod="t3" size="3"/>
|
<textbox id="t9" idmod="t5" size="3"/>
|
||||||
<textbox id="no2" size="3" tabindex="-1"/>
|
<textbox id="f2" size="3" tabindex="-1"/>
|
||||||
<textbox id="t8" idmod="t4" size="3" tabindex="0"/>
|
<textbox id="t10" idmod="t6" size="3" tabindex="0"/>
|
||||||
<textbox id="t2" idmod="t1" size="3" tabindex="1"/>
|
<textbox id="t2" idmod="t1" size="3" tabindex="1"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<button id="no3" style="-moz-user-focus: ignore;" label="One"/>
|
<button id="n1" style="-moz-user-focus: ignore;" label="One"/>
|
||||||
<checkbox id="no4" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/>
|
<checkbox id="f3" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/>
|
||||||
<button id="t9" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/>
|
<button id="t11" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/>
|
||||||
<checkbox id="t3" style="-moz-user-focus: ignore;" label="Four" tabindex="1"/>
|
<checkbox id="t3" style="-moz-user-focus: ignore;" label="Four" tabindex="1"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<textbox id="t10" idmod="t5" style="-moz-user-focus: ignore;" size="3"/>
|
<textbox id="t12" idmod="t7" style="-moz-user-focus: ignore;" size="3"/>
|
||||||
<textbox id="no5" style="-moz-user-focus: ignore;" size="3" tabindex="-1"/>
|
<textbox id="f4" 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="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"/>
|
<textbox id="t4" idmod="t2" style="-moz-user-focus: ignore;" size="3" tabindex="1"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
<richlistbox id="t12" idmod="t7">
|
<richlistbox id="t14" idmod="t9">
|
||||||
<richlistitem><label value="Item One"/></richlistitem>
|
<richlistitem><label value="Item One"/></richlistitem>
|
||||||
</richlistbox>
|
</richlistbox>
|
||||||
|
|
||||||
<hbox>
|
<hbox>
|
||||||
<!-- the tabindex attribute does not apply to non-controls, so it
|
<!-- the tabindex attribute applies to non-controls as well. They are not
|
||||||
should be treated as -1 for non-focusable dropmarkers, and 0
|
focusable unless tabindex is explicitly specified.
|
||||||
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.
|
|
||||||
-->
|
-->
|
||||||
<dropmarker id="no6"/>
|
<dropmarker id="n2"/>
|
||||||
<dropmarker id="no7" tabindex="-1"/>
|
<dropmarker id="f5" tabindex="-1"/>
|
||||||
<dropmarker id="no8" tabindex="0"/>
|
<dropmarker id="t15" tabindex="0"/>
|
||||||
<dropmarker id="no9" tabindex="1"/>
|
<dropmarker id="t5" idmod="t3" tabindex="1"/>
|
||||||
<dropmarker id="t13" style="-moz-user-focus: normal;"/>
|
<dropmarker id="t16" style="-moz-user-focus: normal;"/>
|
||||||
<dropmarker id="t14" style="-moz-user-focus: normal;" tabindex="-1"/>
|
<dropmarker id="f6" style="-moz-user-focus: normal;" tabindex="-1"/>
|
||||||
<dropmarker id="t15" style="-moz-user-focus: normal;" tabindex="0"/>
|
<dropmarker id="t17" style="-moz-user-focus: normal;" tabindex="0"/>
|
||||||
<dropmarker id="t16" style="-moz-user-focus: normal;" tabindex="1"/>
|
<dropmarker id="t6" idmod="t4" style="-moz-user-focus: normal;" tabindex="1"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
||||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||||
@ -73,19 +70,36 @@
|
|||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
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 gAdjustedTabFocusModel = false;
|
||||||
var gTestCount = 16;
|
var gTestCount = 17;
|
||||||
var gTestsOccurred = 0;
|
var gTestsOccurred = 0;
|
||||||
|
let gFocusableNotTabbableCount = 6;
|
||||||
|
let gNotFocusableCount = 2;
|
||||||
|
|
||||||
function runTests()
|
function runTests()
|
||||||
{
|
{
|
||||||
var t;
|
var t;
|
||||||
window.addEventListener("focus", function (event) {
|
function onFocus(event) {
|
||||||
if (t == 1 && event.target.id == "t2") {
|
if (t == 1 && event.target.id == "t2") {
|
||||||
// looks to be using the MacOSX Full Keyboard Access set to Textboxes
|
// looks to be using the MacOSX Full Keyboard Access set to Textboxes
|
||||||
// and lists only so use the idmod attribute instead
|
// and lists only so use the idmod attribute instead
|
||||||
gAdjustedTabFocusModel = true;
|
gAdjustedTabFocusModel = true;
|
||||||
gTestCount = 7;
|
gTestCount = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrcompare = gAdjustedTabFocusModel ? "idmod" : "id";
|
var attrcompare = gAdjustedTabFocusModel ? "idmod" : "id";
|
||||||
@ -102,12 +116,23 @@ function runTests()
|
|||||||
if (event.target.localName != "textbox")
|
if (event.target.localName != "textbox")
|
||||||
gTestsOccurred++;
|
gTestsOccurred++;
|
||||||
}
|
}
|
||||||
}, true);
|
}
|
||||||
|
window.addEventListener("focus", onFocus, true);
|
||||||
|
|
||||||
for (t = 1; t <= gTestCount; t++)
|
for (t = 1; t <= gTestCount; t++)
|
||||||
synthesizeKey("KEY_Tab");
|
synthesizeKey("KEY_Tab");
|
||||||
|
|
||||||
is(gTestsOccurred, gTestCount, "test count");
|
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();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user