Bug 1720334: Represent checked/unchecked state with AXValue for treeitems r=eeejay

Differential Revision: https://phabricator.services.mozilla.com/D121215
This commit is contained in:
Morgan Reschenberg 2021-08-19 20:50:55 +00:00
Родитель ace5987168
Коммит 224e6f9293
6 изменённых файлов: 161 добавлений и 14 удалений

Просмотреть файл

@ -111,7 +111,7 @@ using namespace mozilla::a11y;
static const uint64_t kCachedStates = static const uint64_t kCachedStates =
states::CHECKED | states::PRESSED | states::MIXED | states::EXPANDED | states::CHECKED | states::PRESSED | states::MIXED | states::EXPANDED |
states::CURRENT | states::SELECTED | states::TRAVERSED | states::LINKED | states::CURRENT | states::SELECTED | states::TRAVERSED | states::LINKED |
states::HASPOPUP | states::BUSY | states::MULTI_LINE; states::HASPOPUP | states::BUSY | states::MULTI_LINE | states::CHECKABLE;
static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63; static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63;
- (uint64_t)state { - (uint64_t)state {

Просмотреть файл

@ -174,6 +174,9 @@
// override // override
- (NSString*)moxLabel; - (NSString*)moxLabel;
// override
- (id)moxValue;
// override // override
- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled; - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled;

Просмотреть файл

@ -622,16 +622,53 @@ enum CachedBool { eCachedBoolMiss, eCachedTrue, eCachedFalse };
return nsCocoaUtils::ToNSString(title); return nsCocoaUtils::ToNSString(title);
} }
enum CheckedState {
kUncheckable = -1,
kUnchecked = 0,
kChecked = 1,
kMixed = 2
};
- (int)checkedValue {
uint64_t state = [self
stateWithMask:(states::CHECKABLE | states::CHECKED | states::MIXED)];
if (state & states::CHECKABLE) {
if (state & states::CHECKED) {
return kChecked;
}
if (state & states::MIXED) {
return kMixed;
}
return kUnchecked;
}
return kUncheckable;
}
- (id)moxValue {
int checkedValue = [self checkedValue];
return checkedValue >= 0 ? @(checkedValue) : nil;
}
- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled { - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
[super stateChanged:state isEnabled:enabled]; [super stateChanged:state isEnabled:enabled];
if (state == states::EXPANDED) { if (state & states::EXPANDED) {
// If the EXPANDED state is updated, fire appropriate events on the // If the EXPANDED state is updated, fire appropriate events on the
// outline row. // outline row.
[self moxPostNotification:(enabled [self moxPostNotification:(enabled
? NSAccessibilityRowExpandedNotification ? NSAccessibilityRowExpandedNotification
: NSAccessibilityRowCollapsedNotification)]; : NSAccessibilityRowCollapsedNotification)];
} }
if (state & (states::CHECKED | states::CHECKABLE | states::MIXED)) {
// If the MIXED, CHECKED or CHECKABLE state changes, update the value we
// expose for the row, which communicates checked status.
[self moxPostNotification:NSAccessibilityValueChangedNotification];
}
} }
@end @end

Просмотреть файл

@ -7,18 +7,6 @@
/* import-globals-from ../../mochitest/states.js */ /* import-globals-from ../../mochitest/states.js */
loadScripts({ name: "states.js", dir: MOCHITESTS_DIR }); loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
function waitForStateChange(id, state, isEnabled) {
return waitForEvent(EVENT_STATE_CHANGE, e => {
e.QueryInterface(nsIAccessibleStateChangeEvent);
return (
e.state == state &&
!e.isExtraState &&
isEnabled == e.isEnabled &&
id == getAccessibleDOMNodeID(e.accessible)
);
});
}
// Test aria-expanded on a button // Test aria-expanded on a button
addAccessibleTask( addAccessibleTask(
`hello world<br> `hello world<br>

Просмотреть файл

@ -4,6 +4,9 @@
"use strict"; "use strict";
/* import-globals-from ../../mochitest/states.js */
loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
/** /**
* Test outline, outline rows with computed properties * Test outline, outline rows with computed properties
*/ */
@ -457,3 +460,107 @@ addAccessibleTask(
is(treeItems[1].getAttributeValue("AXDisclosing"), 1); is(treeItems[1].getAttributeValue("AXDisclosing"), 1);
} }
); );
// Test outline rows correctly expose checkable, checked/unchecked/mixed status
addAccessibleTask(
`
<div role="tree" id="tree">
<div role="treeitem" aria-checked="false" id="l1">
Leaf 1
</div>
<div role="treeitem" aria-checked="true" id="l2">
Leaf 2
</div>
<div role="treeitem" id="l3">
Leaf 3
</div>
<div role="treeitem" aria-checked="mixed" id="l4">
Leaf 4
</div>
</div>
`,
async (browser, accDoc) => {
const tree = getNativeInterface(accDoc, "tree");
const treeItems = tree.getAttributeValue("AXChildren");
is(treeItems.length, 4, "Outline has four direct children");
is(
treeItems[0].getAttributeValue("AXValue"),
0,
"Child one is not checked"
);
is(treeItems[1].getAttributeValue("AXValue"), 1, "Child two is checked");
is(
treeItems[2].getAttributeValue("AXValue"),
null,
"Child three is not checkable and has no val"
);
is(treeItems[3].getAttributeValue("AXValue"), 2, "Child four is mixed");
let stateChanged = Promise.all([
waitForMacEvent("AXValueChanged", "l1"),
waitForStateChange("l1", STATE_CHECKED, true),
]);
// We should get a state change event for checked.
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("l1")
.setAttribute("aria-checked", "true");
});
await stateChanged;
is(treeItems[0].getAttributeValue("AXValue"), 1, "Child one is checked");
stateChanged = Promise.all([
waitForMacEvent("AXValueChanged", "l2"),
waitForMacEvent("AXValueChanged", "l2"),
waitForStateChange("l2", STATE_CHECKED, false),
waitForStateChange("l2", STATE_CHECKABLE, false),
]);
// We should get a state change event for both checked and checkable,
// and value changes for both.
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("l2").removeAttribute("aria-checked");
});
await stateChanged;
is(
treeItems[1].getAttributeValue("AXValue"),
null,
"Child two is not checkable and has no val"
);
stateChanged = Promise.all([
waitForMacEvent("AXValueChanged", "l3"),
waitForMacEvent("AXValueChanged", "l3"),
waitForStateChange("l3", STATE_CHECKED, true),
waitForStateChange("l3", STATE_CHECKABLE, true),
]);
// We should get a state change event for both checked and checkable,
// and value changes for each.
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("l3")
.setAttribute("aria-checked", "true");
});
await stateChanged;
is(treeItems[2].getAttributeValue("AXValue"), 1, "Child three is checked");
stateChanged = Promise.all([
waitForMacEvent("AXValueChanged", "l4"),
waitForMacEvent("AXValueChanged", "l4"),
waitForStateChange("l4", STATE_MIXED, false),
waitForStateChange("l4", STATE_CHECKABLE, false),
]);
// We should get a state change event for both mixed and checkable,
// and value changes for each.
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("l4").removeAttribute("aria-checked");
});
await stateChanged;
is(
treeItems[3].getAttributeValue("AXValue"),
null,
"Child four is not checkable and has no value"
);
}
);

Просмотреть файл

@ -127,3 +127,15 @@ function stringForRange(macDoc, range) {
return str; return str;
} }
function waitForStateChange(id, state, isEnabled) {
return waitForEvent(EVENT_STATE_CHANGE, e => {
e.QueryInterface(nsIAccessibleStateChangeEvent);
return (
e.state == state &&
!e.isExtraState &&
isEnabled == e.isEnabled &&
id == getAccessibleDOMNodeID(e.accessible)
);
});
}