зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1886711: Implement the UIA Toggle pattern. r=nlapre
Differential Revision: https://phabricator.services.mozilla.com/D205552
This commit is contained in:
Родитель
2f6b638988
Коммит
2ac0262f68
|
@ -4,6 +4,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
const ToggleState_Off = 0;
|
||||
const ToggleState_On = 1;
|
||||
const ToggleState_Indeterminate = 2;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
/**
|
||||
* Test the Invoke pattern.
|
||||
*/
|
||||
|
@ -11,6 +17,7 @@ addUiaTask(
|
|||
`
|
||||
<button id="button">button</button>
|
||||
<p id="p">p</p>
|
||||
<input id="checkbox" type="checkbox">
|
||||
`,
|
||||
async function testInvoke() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
|
@ -32,10 +39,105 @@ addUiaTask(
|
|||
ok(true, "button got Invoked event");
|
||||
}
|
||||
|
||||
let hasPattern = await runPython(`
|
||||
p = findUiaByDomId(doc, "p")
|
||||
return bool(getUiaPattern(p, "Invoke"))
|
||||
`);
|
||||
ok(!hasPattern, "p doesn't have Invoke pattern");
|
||||
await testPatternAbsent("p", "Invoke");
|
||||
// The Microsoft IA2 -> UIA proxy doesn't follow Microsoft's own rules.
|
||||
if (gIsUiaEnabled) {
|
||||
// Check boxes expose the Toggle pattern, so they should not expose the
|
||||
// Invoke pattern.
|
||||
await testPatternAbsent("checkbox", "Invoke");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Test the Toggle pattern.
|
||||
*/
|
||||
addUiaTask(
|
||||
`
|
||||
<input id="checkbox" type="checkbox" checked>
|
||||
<button id="toggleButton" aria-pressed="false">toggle</button>
|
||||
<button id="button">button</button>
|
||||
<p id="p">p</p>
|
||||
|
||||
<script>
|
||||
// When checkbox is clicked and it is not checked, make it indeterminate.
|
||||
document.getElementById("checkbox").addEventListener("click", evt => {
|
||||
// Within the event listener, .checked is reversed and you can't set
|
||||
// .indeterminate. Work around this by deferring and handling the changes
|
||||
// ourselves.
|
||||
evt.preventDefault();
|
||||
const target = evt.target;
|
||||
setTimeout(() => {
|
||||
if (target.checked) {
|
||||
target.checked = false;
|
||||
} else {
|
||||
target.indeterminate = true;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// When toggleButton is clicked, set aria-pressed to true.
|
||||
document.getElementById("toggleButton").addEventListener("click", evt => {
|
||||
evt.target.ariaPressed = "true";
|
||||
});
|
||||
</script>
|
||||
`,
|
||||
async function testToggle() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
await assignPyVarToUiaWithId("checkbox");
|
||||
await definePyVar("pattern", `getUiaPattern(checkbox, "Toggle")`);
|
||||
ok(await runPython(`bool(pattern)`), "checkbox has Toggle pattern");
|
||||
is(
|
||||
await runPython(`pattern.CurrentToggleState`),
|
||||
ToggleState_On,
|
||||
"checkbox has ToggleState_On"
|
||||
);
|
||||
// The IA2 -> UIA proxy doesn't fire ToggleState prop change events.
|
||||
if (gIsUiaEnabled) {
|
||||
info("Calling Toggle on checkbox");
|
||||
await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
|
||||
await runPython(`pattern.Toggle()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "Got ToggleState prop change event on checkbox");
|
||||
is(
|
||||
await runPython(`pattern.CurrentToggleState`),
|
||||
ToggleState_Off,
|
||||
"checkbox has ToggleState_Off"
|
||||
);
|
||||
info("Calling Toggle on checkbox");
|
||||
await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
|
||||
await runPython(`pattern.Toggle()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "Got ToggleState prop change event on checkbox");
|
||||
is(
|
||||
await runPython(`pattern.CurrentToggleState`),
|
||||
ToggleState_Indeterminate,
|
||||
"checkbox has ToggleState_Indeterminate"
|
||||
);
|
||||
}
|
||||
|
||||
await assignPyVarToUiaWithId("toggleButton");
|
||||
await definePyVar("pattern", `getUiaPattern(toggleButton, "Toggle")`);
|
||||
ok(await runPython(`bool(pattern)`), "toggleButton has Toggle pattern");
|
||||
is(
|
||||
await runPython(`pattern.CurrentToggleState`),
|
||||
ToggleState_Off,
|
||||
"toggleButton has ToggleState_Off"
|
||||
);
|
||||
if (gIsUiaEnabled) {
|
||||
info("Calling Toggle on toggleButton");
|
||||
await setUpWaitForUiaPropEvent("ToggleToggleState", "toggleButton");
|
||||
await runPython(`pattern.Toggle()`);
|
||||
await waitForUiaEvent();
|
||||
ok(true, "Got ToggleState prop change event on toggleButton");
|
||||
is(
|
||||
await runPython(`pattern.CurrentToggleState`),
|
||||
ToggleState_On,
|
||||
"toggleButton has ToggleState_Off"
|
||||
);
|
||||
}
|
||||
|
||||
await testPatternAbsent("button", "Toggle");
|
||||
await testPatternAbsent("p", "Toggle");
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent */
|
||||
/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent */
|
||||
|
||||
// Load the shared-head file first.
|
||||
Services.scriptloader.loadSubScript(
|
||||
|
@ -102,3 +102,14 @@ function waitForUiaEvent() {
|
|||
onEvent.wait()
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a UIA element does *not* support the given control pattern.
|
||||
*/
|
||||
async function testPatternAbsent(id, patternName) {
|
||||
const hasPattern = await runPython(`
|
||||
el = findUiaByDomId(doc, "${id}")
|
||||
return bool(getUiaPattern(el, "${patternName}"))
|
||||
`);
|
||||
ok(!hasPattern, `${id} doesn't have ${patternName} pattern`);
|
||||
}
|
||||
|
|
|
@ -72,8 +72,10 @@ void a11y::PlatformEvent(Accessible* aTarget, uint32_t aEventType) {
|
|||
uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aEventType);
|
||||
}
|
||||
|
||||
void a11y::PlatformStateChangeEvent(Accessible* aTarget, uint64_t, bool) {
|
||||
void a11y::PlatformStateChangeEvent(Accessible* aTarget, uint64_t aState,
|
||||
bool aEnabled) {
|
||||
MsaaAccessible::FireWinEvent(aTarget, nsIAccessibleEvent::EVENT_STATE_CHANGE);
|
||||
uiaRawElmProvider::RaiseUiaEventForStateChange(aTarget, aState, aEnabled);
|
||||
}
|
||||
|
||||
void a11y::PlatformFocusEvent(Accessible* aTarget,
|
||||
|
|
|
@ -65,6 +65,38 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
|||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc,
|
||||
uint64_t aState,
|
||||
bool aEnabled) {
|
||||
if (!StaticPrefs::accessibility_uia_enable()) {
|
||||
return;
|
||||
}
|
||||
auto* uia = MsaaAccessible::GetFrom(aAcc);
|
||||
if (!uia) {
|
||||
return;
|
||||
}
|
||||
PROPERTYID property = 0;
|
||||
_variant_t newVal;
|
||||
switch (aState) {
|
||||
case states::CHECKED:
|
||||
case states::MIXED:
|
||||
case states::PRESSED:
|
||||
property = UIA_ToggleToggleStatePropertyId;
|
||||
newVal.vt = VT_I4;
|
||||
newVal.lVal = ToToggleState(aEnabled ? aState : 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(property);
|
||||
if (::UiaClientsAreListening()) {
|
||||
// We can't get the old value. Thankfully, clients don't seem to need it.
|
||||
_variant_t oldVal;
|
||||
::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
|
||||
}
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
|
||||
STDMETHODIMP
|
||||
|
@ -78,6 +110,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
|
|||
*aInterface = static_cast<IRawElementProviderFragment*>(this);
|
||||
} else if (aIid == IID_IInvokeProvider) {
|
||||
*aInterface = static_cast<IInvokeProvider*>(this);
|
||||
} else if (aIid == IID_IToggleProvider) {
|
||||
*aInterface = static_cast<IToggleProvider*>(this);
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
@ -176,11 +210,20 @@ uiaRawElmProvider::GetPatternProvider(
|
|||
}
|
||||
switch (aPatternId) {
|
||||
case UIA_InvokePatternId:
|
||||
if (acc->ActionCount() > 0) {
|
||||
// Per the UIA documentation, we should only expose the Invoke pattern "if
|
||||
// the same behavior is not exposed through another control pattern
|
||||
// provider".
|
||||
if (acc->ActionCount() > 0 && !HasTogglePattern()) {
|
||||
RefPtr<IInvokeProvider> invoke = this;
|
||||
invoke.forget(aPatternProvider);
|
||||
}
|
||||
return S_OK;
|
||||
case UIA_TogglePatternId:
|
||||
if (HasTogglePattern()) {
|
||||
RefPtr<IToggleProvider> toggle = this;
|
||||
toggle.forget(aPatternProvider);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
@ -497,6 +540,31 @@ uiaRawElmProvider::Invoke() {
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// IToggleProvider methods
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::Toggle() {
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
acc->DoAction(0);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::get_ToggleState(__RPC__out enum ToggleState* aRetVal) {
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
*aRetVal = ToToggleState(acc->State());
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
bool uiaRawElmProvider::IsControl() {
|
||||
|
@ -578,3 +646,21 @@ long uiaRawElmProvider::GetControlType() const {
|
|||
MOZ_CRASH("Unknown role.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool uiaRawElmProvider::HasTogglePattern() {
|
||||
Accessible* acc = Acc();
|
||||
MOZ_ASSERT(acc);
|
||||
return acc->State() & states::CHECKABLE ||
|
||||
acc->Role() == roles::TOGGLE_BUTTON;
|
||||
}
|
||||
|
||||
/* static */
|
||||
ToggleState uiaRawElmProvider::ToToggleState(uint64_t aState) {
|
||||
if (aState & states::MIXED) {
|
||||
return ToggleState_Indeterminate;
|
||||
}
|
||||
if (aState & (states::CHECKED | states::PRESSED)) {
|
||||
return ToggleState_On;
|
||||
}
|
||||
return ToggleState_Off;
|
||||
}
|
||||
|
|
|
@ -22,10 +22,13 @@ class Accessible;
|
|||
class uiaRawElmProvider : public IAccessibleEx,
|
||||
public IRawElementProviderSimple,
|
||||
public IRawElementProviderFragment,
|
||||
public IInvokeProvider {
|
||||
public IInvokeProvider,
|
||||
public IToggleProvider {
|
||||
public:
|
||||
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
||||
uint32_t aGeckoEvent);
|
||||
static void RaiseUiaEventForStateChange(Accessible* aAcc, uint64_t aState,
|
||||
bool aEnabled);
|
||||
|
||||
// IUnknown
|
||||
STDMETHODIMP QueryInterface(REFIID aIid, void** aInterface);
|
||||
|
@ -85,10 +88,18 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
// IInvokeProvider
|
||||
virtual HRESULT STDMETHODCALLTYPE Invoke(void);
|
||||
|
||||
// IToggleProvider
|
||||
virtual HRESULT STDMETHODCALLTYPE Toggle(void);
|
||||
|
||||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ToggleState(
|
||||
/* [retval][out] */ __RPC__out enum ToggleState* aRetVal);
|
||||
|
||||
private:
|
||||
Accessible* Acc() const;
|
||||
bool IsControl();
|
||||
long GetControlType() const;
|
||||
bool HasTogglePattern();
|
||||
static ToggleState ToToggleState(uint64_t aState);
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
Загрузка…
Ссылка в новой задаче