forked from mirrors/gecko-dev
Bug 1887786 part 2: Implement the UIA SelectionItem pattern. r=morgan
Differential Revision: https://phabricator.services.mozilla.com/D208435
This commit is contained in:
parent
eeb9689caf
commit
330728d677
5 changed files with 240 additions and 2 deletions
|
|
@ -77,6 +77,29 @@ async function testSelectionProps(id, selection, multiple, required) {
|
|||
}
|
||||
}
|
||||
|
||||
async function testSelectionItemProps(id, selected, container) {
|
||||
await assignPyVarToUiaWithId(id);
|
||||
await definePyVar("pattern", `getUiaPattern(${id}, "SelectionItem")`);
|
||||
ok(await runPython(`bool(pattern)`), `${id} has SelectionItem pattern`);
|
||||
is(
|
||||
!!(await runPython(`pattern.CurrentIsSelected`)),
|
||||
selected,
|
||||
`${id} has correct IsSelected`
|
||||
);
|
||||
if (container) {
|
||||
is(
|
||||
await runPython(`pattern.CurrentSelectionContainer.CurrentAutomationId`),
|
||||
container,
|
||||
`${id} has correct SelectionContainer`
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!(await runPython(`bool(pattern.CurrentSelectionContainer)`)),
|
||||
`${id} has no SelectionContainer`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the Selection pattern.
|
||||
*/
|
||||
|
|
@ -125,3 +148,79 @@ addUiaTask(SNIPPET, async function testSelection(browser) {
|
|||
|
||||
await testPatternAbsent("button", "Selection");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test the SelectionItem pattern.
|
||||
*/
|
||||
addUiaTask(SNIPPET, async function testSelection() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
await testPatternAbsent("selectList", "SelectionItem");
|
||||
await testSelectionItemProps("sl1", true, "selectList");
|
||||
await testSelectionItemProps("sl2", false, "selectList");
|
||||
info("Calling Select on sl2");
|
||||
await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "sl2");
|
||||
await runPython(`pattern.Select()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "sl2 got ElementSelected event");
|
||||
await testSelectionItemProps("sl1", false, "selectList");
|
||||
await testSelectionItemProps("sl2", true, "selectList");
|
||||
|
||||
await testSelectionItemProps("sr1", false, "selectRequired");
|
||||
|
||||
await testSelectionItemProps("sm1", true, "selectMulti");
|
||||
await testSelectionItemProps("sm2", false, "selectMulti");
|
||||
info("Calling AddToSelection on sm2");
|
||||
await setUpWaitForUiaEvent("SelectionItem_ElementAddedToSelection", "sm2");
|
||||
await runPython(`pattern.AddToSelection()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "sm2 got ElementAddedToSelection event");
|
||||
await testSelectionItemProps("sm2", true, "selectMulti");
|
||||
await testSelectionItemProps("sm3", true, "selectMulti");
|
||||
info("Calling RemoveFromSelection on sm3");
|
||||
await setUpWaitForUiaEvent(
|
||||
"SelectionItem_ElementRemovedFromSelection",
|
||||
"sm3"
|
||||
);
|
||||
await runPython(`pattern.RemoveFromSelection()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "sm3 got ElementRemovedFromSelection event");
|
||||
await testSelectionItemProps("sm3", false, "selectMulti");
|
||||
|
||||
await testSelectionItemProps("t1", false, "tablist");
|
||||
await testSelectionItemProps("t2", true, "tablist");
|
||||
|
||||
// The IA2 -> UIA proxy doesn't expose the SelectionItem pattern on grid
|
||||
// cells.
|
||||
if (gIsUiaEnabled) {
|
||||
await testSelectionItemProps("g1", false, "grid");
|
||||
await testSelectionItemProps("g2", true, "grid");
|
||||
}
|
||||
|
||||
await testSelectionItemProps("r1", true, null);
|
||||
await testSelectionItemProps("r2", false, null);
|
||||
// The IA2 -> UIA proxy doesn't fire correct events for radio buttons.
|
||||
if (gIsUiaEnabled) {
|
||||
info("Calling Select on r2");
|
||||
await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "r2");
|
||||
await runPython(`pattern.Select()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "r2 got ElementSelected event");
|
||||
await testSelectionItemProps("r1", false, null);
|
||||
await testSelectionItemProps("r2", true, null);
|
||||
info("Calling RemoveFromSelection on r2");
|
||||
await testPythonRaises(
|
||||
`pattern.RemoveFromSelection()`,
|
||||
"RemoveFromSelection failed on r2"
|
||||
);
|
||||
}
|
||||
|
||||
await testPatternAbsent("m1", "SelectionItem");
|
||||
// The IA2 -> UIA proxy doesn't expose the SelectionItem pattern for radio
|
||||
// menu items.
|
||||
if (gIsUiaEnabled) {
|
||||
await testSelectionItemProps("m2", false, null);
|
||||
await testSelectionItemProps("m3", true, null);
|
||||
}
|
||||
|
||||
await testPatternAbsent("button", "SelectionItem");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ addUiaTask(
|
|||
<button id="button">button</button>
|
||||
<p id="p">p</p>
|
||||
<input id="checkbox" type="checkbox">
|
||||
<input id="radio" type="radio">
|
||||
`,
|
||||
async function testInvoke() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
|
|
@ -54,6 +55,8 @@ addUiaTask(
|
|||
// Check boxes expose the Toggle pattern, so they should not expose the
|
||||
// Invoke pattern.
|
||||
await testPatternAbsent("checkbox", "Invoke");
|
||||
// Ditto for radio buttons.
|
||||
await testPatternAbsent("radio", "Invoke");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ void a11y::PlatformShowHideEvent(Accessible* aTarget, Accessible*, bool aInsert,
|
|||
void a11y::PlatformSelectionEvent(Accessible* aTarget, Accessible*,
|
||||
uint32_t aType) {
|
||||
MsaaAccessible::FireWinEvent(aTarget, aType);
|
||||
uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aType);
|
||||
}
|
||||
|
||||
static bool GetInstantiatorExecutable(const DWORD aPid,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ static ExpandCollapseState ToExpandCollapseState(uint64_t aState) {
|
|||
return ExpandCollapseState_LeafNode;
|
||||
}
|
||||
|
||||
static bool IsRadio(Accessible* aAcc) {
|
||||
role r = aAcc->Role();
|
||||
return r == roles::RADIOBUTTON || r == roles::RADIO_MENU_ITEM;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// uiaRawElmProvider
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -87,6 +92,17 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
|||
case nsIAccessibleEvent::EVENT_NAME_CHANGE:
|
||||
property = UIA_NamePropertyId;
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_SELECTION:
|
||||
::UiaRaiseAutomationEvent(uia, UIA_SelectionItem_ElementSelectedEventId);
|
||||
return;
|
||||
case nsIAccessibleEvent::EVENT_SELECTION_ADD:
|
||||
::UiaRaiseAutomationEvent(
|
||||
uia, UIA_SelectionItem_ElementAddedToSelectionEventId);
|
||||
return;
|
||||
case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
|
||||
::UiaRaiseAutomationEvent(
|
||||
uia, UIA_SelectionItem_ElementRemovedFromSelectionEventId);
|
||||
return;
|
||||
case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
|
||||
::UiaRaiseAutomationEvent(uia, UIA_Selection_InvalidatedEventId);
|
||||
return;
|
||||
|
|
@ -129,6 +145,13 @@ void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc,
|
|||
_variant_t newVal;
|
||||
switch (aState) {
|
||||
case states::CHECKED:
|
||||
if (aEnabled && IsRadio(aAcc)) {
|
||||
::UiaRaiseAutomationEvent(uia,
|
||||
UIA_SelectionItem_ElementSelectedEventId);
|
||||
return;
|
||||
}
|
||||
// For other checkable things, the Toggle pattern is used.
|
||||
[[fallthrough]];
|
||||
case states::MIXED:
|
||||
case states::PRESSED:
|
||||
property = UIA_ToggleToggleStatePropertyId;
|
||||
|
|
@ -177,6 +200,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
|
|||
*aInterface = static_cast<IRangeValueProvider*>(this);
|
||||
} else if (aIid == IID_IScrollItemProvider) {
|
||||
*aInterface = static_cast<IScrollItemProvider*>(this);
|
||||
} else if (aIid == IID_ISelectionItemProvider) {
|
||||
*aInterface = static_cast<ISelectionItemProvider*>(this);
|
||||
} else if (aIid == IID_ISelectionProvider) {
|
||||
*aInterface = static_cast<ISelectionProvider*>(this);
|
||||
} else if (aIid == IID_IToggleProvider) {
|
||||
|
|
@ -302,7 +327,7 @@ uiaRawElmProvider::GetPatternProvider(
|
|||
// the same behavior is not exposed through another control pattern
|
||||
// provider".
|
||||
if (acc->ActionCount() > 0 && !HasTogglePattern() &&
|
||||
!HasExpandCollapsePattern()) {
|
||||
!HasExpandCollapsePattern() && !HasSelectionItemPattern()) {
|
||||
RefPtr<IInvokeProvider> invoke = this;
|
||||
invoke.forget(aPatternProvider);
|
||||
}
|
||||
|
|
@ -318,6 +343,12 @@ uiaRawElmProvider::GetPatternProvider(
|
|||
scroll.forget(aPatternProvider);
|
||||
return S_OK;
|
||||
}
|
||||
case UIA_SelectionItemPatternId:
|
||||
if (HasSelectionItemPattern()) {
|
||||
RefPtr<ISelectionItemProvider> item = this;
|
||||
item.forget(aPatternProvider);
|
||||
}
|
||||
return S_OK;
|
||||
case UIA_SelectionPatternId:
|
||||
// According to the UIA documentation, radio button groups should support
|
||||
// the Selection pattern. However:
|
||||
|
|
@ -989,6 +1020,86 @@ uiaRawElmProvider::get_IsSelectionRequired(__RPC__out BOOL* aRetVal) {
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// ISelectionItemProvider methods
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::Select() {
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
if (IsRadio(acc)) {
|
||||
acc->DoAction(0);
|
||||
} else {
|
||||
acc->TakeSelection();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::AddToSelection() {
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
if (IsRadio(acc)) {
|
||||
acc->DoAction(0);
|
||||
} else {
|
||||
acc->SetSelected(true);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::RemoveFromSelection() {
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
if (IsRadio(acc)) {
|
||||
return UIA_E_INVALIDOPERATION;
|
||||
}
|
||||
acc->SetSelected(false);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::get_IsSelected(__RPC__out BOOL* aRetVal) {
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
if (IsRadio(acc)) {
|
||||
*aRetVal = acc->State() & states::CHECKED;
|
||||
} else {
|
||||
*aRetVal = acc->State() & states::SELECTED;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::get_SelectionContainer(
|
||||
__RPC__deref_out_opt IRawElementProviderSimple** aRetVal) {
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
*aRetVal = nullptr;
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
Accessible* container = nsAccUtils::GetSelectableContainer(acc, acc->State());
|
||||
if (!container) {
|
||||
return E_FAIL;
|
||||
}
|
||||
RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(container);
|
||||
uia.forget(aRetVal);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
bool uiaRawElmProvider::IsControl() {
|
||||
|
|
@ -1105,6 +1216,14 @@ RefPtr<Interface> uiaRawElmProvider::GetPatternFromDerived() {
|
|||
return derived;
|
||||
}
|
||||
|
||||
bool uiaRawElmProvider::HasSelectionItemPattern() {
|
||||
Accessible* acc = Acc();
|
||||
MOZ_ASSERT(acc);
|
||||
// In UIA, radio buttons and radio menu items are exposed as selected or
|
||||
// unselected.
|
||||
return acc->State() & states::SELECTABLE || IsRadio(acc);
|
||||
}
|
||||
|
||||
SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) {
|
||||
if (aAccs.IsEmpty()) {
|
||||
// The UIA documentation is unclear about this, but the UIA client
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
public IScrollItemProvider,
|
||||
public IValueProvider,
|
||||
public IRangeValueProvider,
|
||||
public ISelectionProvider {
|
||||
public ISelectionProvider,
|
||||
public ISelectionItemProvider {
|
||||
public:
|
||||
static constexpr enum ProviderOptions kProviderOptions =
|
||||
static_cast<enum ProviderOptions>(ProviderOptions_ServerSideProvider |
|
||||
|
|
@ -161,6 +162,20 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(
|
||||
/* [retval][out] */ __RPC__out BOOL* aRetVal);
|
||||
|
||||
// ISelectionItemProvider methods
|
||||
virtual HRESULT STDMETHODCALLTYPE Select(void);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE AddToSelection(void);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection(void);
|
||||
|
||||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelected(
|
||||
/* [retval][out] */ __RPC__out BOOL* aRetVal);
|
||||
|
||||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SelectionContainer(
|
||||
/* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple**
|
||||
aRetVal);
|
||||
|
||||
private:
|
||||
Accessible* Acc() const;
|
||||
bool IsControl();
|
||||
|
|
@ -170,6 +185,7 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
bool HasValuePattern() const;
|
||||
template <class Derived, class Interface>
|
||||
RefPtr<Interface> GetPatternFromDerived();
|
||||
bool HasSelectionItemPattern();
|
||||
};
|
||||
|
||||
SAFEARRAY* AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs);
|
||||
|
|
|
|||
Loading…
Reference in a new issue