Bug 1662147: Add AXControlSearchKey to rotor r=eeejay

Differential Revision: https://phabricator.services.mozilla.com/D89073
This commit is contained in:
Morgan Reschenberg 2020-09-14 21:22:50 +00:00
Родитель f74cb16fd0
Коммит b99c8c44b6
10 изменённых файлов: 526 добавлений и 5 удалений

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

@ -70,7 +70,7 @@ class AccessibleOrProxy {
* Return the child object either an accessible or a proxied accessible at
* the given index.
*/
AccessibleOrProxy ChildAt(uint32_t aIdx) {
AccessibleOrProxy ChildAt(uint32_t aIdx) const {
if (IsProxy()) {
return AsProxy()->ChildAt(aIdx);
}

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

@ -214,7 +214,7 @@ MARKUPMAP(
}
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::time, eIgnoreCase)) {
return new HTMLDateTimeAccessible<roles::GROUPING>(
return new HTMLDateTimeAccessible<roles::TIME_EDITOR>(
aElement, aContext->Document());
}
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,

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

@ -1066,7 +1066,12 @@ enum Role {
*/
CODE = 182,
LAST_ROLE = CODE
/**
* Represents control whose purpose is to allow a user to edit a time.
*/
TIME_EDITOR = 183,
LAST_ROLE = TIME_EDITOR
};
} // namespace roles

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

@ -711,7 +711,7 @@ ROLE(COLOR_CHOOSER,
ROLE(DATE_EDITOR,
"date editor",
ATK_ROLE_DATE_EDITOR,
NSAccessibilityUnknownRole,
@"AXDateField",
NSAccessibilityUnknownSubrole,
USE_ROLE_STRING,
IA2_ROLE_DATE_EDITOR,
@ -1856,4 +1856,14 @@ ROLE(CODE,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
eNoNameRule)
ROLE(TIME_EDITOR,
"time editor",
ATK_ROLE_PANEL,
@"AXTimeField",
NSAccessibilityUnknownSubrole,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
eNameFromSubtreeIfReqRule)
// clang-format on

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

@ -1060,4 +1060,9 @@ interface nsIAccessibleRole : nsISupports
*/
const unsigned long ROLE_CODE = 182;
/**
* Represents control whose purpose is to allow a user to edit a time.
*/
const unsigned long ROLE_TIME_EDITOR = 183;
};

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

@ -76,7 +76,6 @@ using namespace mozilla::a11y;
MOXSearchInfo* search =
[[MOXSearchInfo alloc] initWithParameters:searchPredicate
andRoot:mGeckoAccessible];
return [search performSearch];
}
@ -205,6 +204,13 @@ using namespace mozilla::a11y;
: RotorLandmarkRule();
[matches addObjectsFromArray:[self getMatchesForRule:rule]];
}
if ([key isEqualToString:@"AXControlSearchKey"]) {
RotorControlRule rule = mImmediateDescendantsOnly
? RotorControlRule(mStartElem)
: RotorControlRule();
[matches addObjectsFromArray:[self getMatchesForRule:rule]];
}
}
return matches;

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

@ -39,6 +39,17 @@ class RotorLandmarkRule final : public PivotRoleRule {
explicit RotorLandmarkRule(AccessibleOrProxy& aDirectDescendantsFrom);
};
class RotorControlRule final : public PivotRule {
public:
explicit RotorControlRule(AccessibleOrProxy& aDirectDescendantsFrom);
explicit RotorControlRule();
virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
private:
AccessibleOrProxy mDirectDescendantsFrom;
};
/**
* This rule matches all accessibles, filtering out non-direct
* descendants if necessary.

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

@ -34,6 +34,74 @@ RotorLandmarkRule::RotorLandmarkRule() : PivotRoleRule(roles::LANDMARK) {}
RotorLandmarkRule::RotorLandmarkRule(AccessibleOrProxy& aDirectDescendantsFrom)
: PivotRoleRule(roles::LANDMARK, aDirectDescendantsFrom) {}
RotorControlRule::RotorControlRule(AccessibleOrProxy& aDirectDescendantsFrom)
: mDirectDescendantsFrom(aDirectDescendantsFrom) {}
RotorControlRule::RotorControlRule() : mDirectDescendantsFrom(nullptr) {}
uint16_t RotorControlRule::Match(const AccessibleOrProxy& aAccOrProxy) {
uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
if (nsAccUtils::MustPrune(aAccOrProxy)) {
result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
}
if (!mDirectDescendantsFrom.IsNull() &&
(aAccOrProxy != mDirectDescendantsFrom)) {
result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
}
switch (aAccOrProxy.Role()) {
case roles::PUSHBUTTON:
case roles::SPINBUTTON:
case roles::DETAILS:
case roles::CHECKBUTTON:
case roles::COLOR_CHOOSER:
case roles::BUTTONDROPDOWNGRID: // xul colorpicker
case roles::LISTBOX:
case roles::COMBOBOX:
case roles::EDITCOMBOBOX:
case roles::RADIOBUTTON:
case roles::RADIO_GROUP:
case roles::PAGETAB:
case roles::SLIDER:
case roles::SWITCH:
case roles::ENTRY:
case roles::OUTLINE:
case roles::PASSWORD_TEXT:
result |= nsIAccessibleTraversalRule::FILTER_MATCH;
break;
case roles::GROUPING: {
// Groupings are sometimes used (like radio groups) to denote
// sets of controls. If that's the case, we want to surface
// them. We also want to surface grouped time and date controls.
for (unsigned int i = 0; i < aAccOrProxy.ChildCount(); i++) {
AccessibleOrProxy currChild = aAccOrProxy.ChildAt(i);
if (currChild.Role() == roles::CHECKBUTTON ||
currChild.Role() == roles::SWITCH ||
currChild.Role() == roles::SPINBUTTON ||
currChild.Role() == roles::RADIOBUTTON) {
result |= nsIAccessibleTraversalRule::FILTER_MATCH;
break;
}
}
break;
}
case roles::DATE_EDITOR:
case roles::TIME_EDITOR:
result |= nsIAccessibleTraversalRule::FILTER_MATCH;
result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
break;
default:
break;
}
return result;
}
// Match All Rule
RotorAllRule::RotorAllRule(AccessibleOrProxy& aDirectDescendantsFrom)

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

@ -399,3 +399,418 @@ addAccessibleTask(
);
}
);
/**
* Test rotor with heading
*/
addAccessibleTask(
`<h1 id="hello">hello</h1><br><h2 id="world">world</h2><br>goodbye`,
async (browser, accDoc) => {
const searchPred = {
AXSearchKey: "AXHeadingSearchKey",
AXImmediateDescendants: 1,
AXResultsLimit: -1,
AXDirection: "AXDirectionNext",
};
const webArea = accDoc.nativeInterface.QueryInterface(
Ci.nsIAccessibleMacInterface
);
is(
webArea.getAttributeValue("AXRole"),
"AXWebArea",
"Got web area accessible"
);
const headingCount = webArea.getParameterizedAttributeValue(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
is(2, headingCount, "Found two headings");
const headings = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
NSDictionary(searchPred)
);
const hello = getNativeInterface(accDoc, "hello");
const world = getNativeInterface(accDoc, "world");
is(
hello.getAttributeValue("AXTitle"),
headings[0].getAttributeValue("AXTitle"),
"Found correct first heading"
);
is(
world.getAttributeValue("AXTitle"),
headings[1].getAttributeValue("AXTitle"),
"Found correct second heading"
);
}
);
/**
* Test rotor with buttons
*/
addAccessibleTask(
`
<form>
<h2>input[type=button]</h2>
<input type="button" value="apply" id="button1">
<h2>input[type=submit]</h2>
<input type="submit" value="submit now" id="submit">
<h2>input[type=image]</h2>
<input type="image" src="sample.jpg" alt="submit image" id="image">
<h2>input[type=reset]</h2>
<input type="reset" value="reset now" id="reset">
<h2>button element</h2>
<button id="button2">Submit button</button>
</form>
`,
async (browser, accDoc) => {
const searchPred = {
AXSearchKey: "AXControlSearchKey",
AXImmediateDescendants: 1,
AXResultsLimit: -1,
AXDirection: "AXDirectionNext",
};
const webArea = accDoc.nativeInterface.QueryInterface(
Ci.nsIAccessibleMacInterface
);
is(
webArea.getAttributeValue("AXRole"),
"AXWebArea",
"Got web area accessible"
);
const controlsCount = webArea.getParameterizedAttributeValue(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
is(5, controlsCount, "Found 5 controls");
const controls = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
NSDictionary(searchPred)
);
const button1 = getNativeInterface(accDoc, "button1");
const submit = getNativeInterface(accDoc, "submit");
const image = getNativeInterface(accDoc, "image");
const reset = getNativeInterface(accDoc, "reset");
const button2 = getNativeInterface(accDoc, "button2");
is(
button1.getAttributeValue("AXTitle"),
controls[0].getAttributeValue("AXTitle"),
"Found correct first control"
);
is(
submit.getAttributeValue("AXTitle"),
controls[1].getAttributeValue("AXTitle"),
"Found correct second control"
);
is(
image.getAttributeValue("AXTitle"),
controls[2].getAttributeValue("AXTitle"),
"Found correct third control"
);
is(
reset.getAttributeValue("AXTitle"),
controls[3].getAttributeValue("AXTitle"),
"Found correct third control"
);
is(
button2.getAttributeValue("AXTitle"),
controls[4].getAttributeValue("AXTitle"),
"Found correct third control"
);
}
);
/**
* Test rotor with inputs
*/
addAccessibleTask(
`
<input type="text" value="I'm a text field." id="text"><br>
<input type="text" value="me too" id="implText"><br>
<textarea id="textarea">this is some text in a text area</textarea><br>
<input type="tel" value="0000000000" id="tel"><br>
<input type="url" value="https://example.com" id="url"><br>
<input type="email" value="hi@example.com" id="email"><br>
<input type="password" value="blah" id="password"><br>
<input type="month" value="2020-01" id="month"><br>
<input type="week" value="2020-W01" id="week"><br>
<input type="number" value="12" id="number"><br>
<input type="range" value="12" min="0" max="20" id="range"><br>
<input type="date" value="2020-01-01" id="date"><br>
<input type="time" value="10:10:10" id="time"><br>
`,
async (browser, accDoc) => {
const searchPred = {
AXSearchKey: "AXControlSearchKey",
AXImmediateDescendants: 1,
AXResultsLimit: -1,
AXDirection: "AXDirectionNext",
};
const webArea = accDoc.nativeInterface.QueryInterface(
Ci.nsIAccessibleMacInterface
);
is(
webArea.getAttributeValue("AXRole"),
"AXWebArea",
"Got web area accessible"
);
const controlsCount = webArea.getParameterizedAttributeValue(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
is(13, controlsCount, "Found 13 controls");
// the extra controls here come from our time control
// we can't filter out its internal buttons/incrementors
// like we do with the date entry because the time entry
// doesn't have its own specific role -- its just a grouping.
const controls = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
NSDictionary(searchPred)
);
const text = getNativeInterface(accDoc, "text");
const implText = getNativeInterface(accDoc, "implText");
const textarea = getNativeInterface(accDoc, "textarea");
const tel = getNativeInterface(accDoc, "tel");
const url = getNativeInterface(accDoc, "url");
const email = getNativeInterface(accDoc, "email");
const password = getNativeInterface(accDoc, "password");
const month = getNativeInterface(accDoc, "month");
const week = getNativeInterface(accDoc, "week");
const number = getNativeInterface(accDoc, "number");
const range = getNativeInterface(accDoc, "range");
const toCheck = [
text,
implText,
textarea,
tel,
url,
email,
password,
month,
week,
number,
range,
];
for (let i = 0; i < toCheck.length; i++) {
is(
toCheck[i].getAttributeValue("AXValue"),
controls[i].getAttributeValue("AXValue"),
"Found correct input control"
);
}
const date = getNativeInterface(accDoc, "date");
const time = getNativeInterface(accDoc, "time");
is(
date.getAttributeValue("AXRole"),
controls[11].getAttributeValue("AXRole"),
"Found corrent date editor"
);
is(
time.getAttributeValue("AXRole"),
controls[12].getAttributeValue("AXRole"),
"Found corrent time editor"
);
}
);
/**
* Test rotor with groupings
*/
addAccessibleTask(
`
<fieldset>
<legend>Radios</legend>
<div role="radiogroup" id="radios">
<input id="radio1" type="radio" name="g1" checked="checked"> Radio 1
<input id="radio2" type="radio" name="g1"> Radio 2
</div>
</fieldset>
<fieldset id="checkboxes">
<legend>Checkboxes</legend>
<input id="checkbox1" type="checkbox" name="g2"> Checkbox 1
<input id="checkbox2" type="checkbox" name="g2" checked="checked">Checkbox 2
</fieldset>
<fieldset id="switches">
<legend>Switches</legend>
<input id="switch1" name="g3" role="switch" type="checkbox">Switch 1
<input checked="checked" id="switch2" name="g3" role="switch" type="checkbox">Switch 2
</fieldset>
`,
async (browser, accDoc) => {
const searchPred = {
AXSearchKey: "AXControlSearchKey",
AXImmediateDescendants: 1,
AXResultsLimit: -1,
AXDirection: "AXDirectionNext",
};
const webArea = accDoc.nativeInterface.QueryInterface(
Ci.nsIAccessibleMacInterface
);
is(
webArea.getAttributeValue("AXRole"),
"AXWebArea",
"Got web area accessible"
);
const controlsCount = webArea.getParameterizedAttributeValue(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
is(9, controlsCount, "Found 9 controls");
const controls = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
NSDictionary(searchPred)
);
const radios = getNativeInterface(accDoc, "radios");
const radio1 = getNativeInterface(accDoc, "radio1");
const radio2 = getNativeInterface(accDoc, "radio2");
is(
radios.getAttributeValue("AXRole"),
controls[0].getAttributeValue("AXRole"),
"Found correct group of radios"
);
is(
radio1.getAttributeValue("AXRole"),
controls[1].getAttributeValue("AXRole"),
"Found correct radio 1"
);
is(
radio2.getAttributeValue("AXRole"),
controls[2].getAttributeValue("AXRole"),
"Found correct radio 2"
);
const checkboxes = getNativeInterface(accDoc, "checkboxes");
const checkbox1 = getNativeInterface(accDoc, "checkbox1");
const checkbox2 = getNativeInterface(accDoc, "checkbox2");
is(
checkboxes.getAttributeValue("AXRole"),
controls[3].getAttributeValue("AXRole"),
"Found correct group of checkboxes"
);
is(
checkbox1.getAttributeValue("AXRole"),
controls[4].getAttributeValue("AXRole"),
"Found correct checkbox 1"
);
is(
checkbox2.getAttributeValue("AXRole"),
controls[5].getAttributeValue("AXRole"),
"Found correct checkbox 2"
);
const switches = getNativeInterface(accDoc, "switches");
const switch1 = getNativeInterface(accDoc, "switch1");
const switch2 = getNativeInterface(accDoc, "switch2");
is(
switches.getAttributeValue("AXRole"),
controls[6].getAttributeValue("AXRole"),
"Found correct group of switches"
);
is(
switch1.getAttributeValue("AXRole"),
controls[7].getAttributeValue("AXRole"),
"Found correct switch 1"
);
is(
switch2.getAttributeValue("AXRole"),
controls[8].getAttributeValue("AXRole"),
"Found correct switch 2"
);
}
);
/**
* Test rotor with misc controls
*/
addAccessibleTask(
`
<input role="spinbutton" id="spinbutton" type="number" value="25">
<details id="details">
<summary>Hello</summary>
world
</details>
<ul role="tree" id="tree">
<li role="treeitem">item1</li>
<li role="treeitem">item1</li>
</ul>
`,
async (browser, accDoc) => {
const searchPred = {
AXSearchKey: "AXControlSearchKey",
AXImmediateDescendants: 1,
AXResultsLimit: -1,
AXDirection: "AXDirectionNext",
};
const webArea = accDoc.nativeInterface.QueryInterface(
Ci.nsIAccessibleMacInterface
);
is(
webArea.getAttributeValue("AXRole"),
"AXWebArea",
"Got web area accessible"
);
const controlsCount = webArea.getParameterizedAttributeValue(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
is(3, controlsCount, "Found 3 controls");
const controls = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
NSDictionary(searchPred)
);
const spin = getNativeInterface(accDoc, "spinbutton");
const details = getNativeInterface(accDoc, "details");
const tree = getNativeInterface(accDoc, "tree");
is(
spin.getAttributeValue("AXRole"),
controls[0].getAttributeValue("AXRole"),
"Found correct spinbutton"
);
is(
details.getAttributeValue("AXRole"),
controls[1].getAttributeValue("AXRole"),
"Found correct details element"
);
is(
tree.getAttributeValue("AXRole"),
controls[2].getAttributeValue("AXRole"),
"Found correct tree"
);
}
);

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

@ -131,6 +131,7 @@ const ROLE_TERM = nsIAccessibleRole.ROLE_TERM;
const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT;
const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF;
const ROLE_TIME_EDITOR = nsIAccessibleRole.ROLE_TIME_EDITOR;
const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR;
const ROLE_TOOLTIP = nsIAccessibleRole.ROLE_TOOLTIP;