Bug 1887785: Implement UIA RangeValue pattern. r=nlapre

Differential Revision: https://phabricator.services.mozilla.com/D207950
This commit is contained in:
James Teh 2024-04-20 05:28:09 +00:00
parent 42042620f1
commit 56f7d50bd9
4 changed files with 228 additions and 1 deletions

View file

@ -9,6 +9,7 @@ It is intended to be called from JS browser tests.
"""
import json
import math
import os
import sys
import traceback
@ -83,6 +84,9 @@ def web_socket_transfer_data(request):
exec(code, namespace)
# Run the function we just defined.
ret = namespace["run"]()
if isinstance(ret, float) and math.isnan(ret):
# NaN can't be serialized by JSON.
ret = None
send("return", ret)
except Exception:
send("exception", traceback.format_exc())

View file

@ -317,6 +317,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "text");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
ok(true, "Got ValueValue prop change event on text");
is(
await runPython(`pattern.CurrentValue`),
"after",
@ -434,6 +435,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
ok(true, "Got ValueValue prop change event on ariaTextbox");
is(
await runPython(`pattern.CurrentValue`),
"after",
@ -443,3 +445,95 @@ addUiaTask(
await testPatternAbsent("button", "Value");
}
);
async function testRangeValueProps(id, ro, val, min, max, small, large) {
await assignPyVarToUiaWithId(id);
await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`);
ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`);
is(
!!(await runPython(`pattern.CurrentIsReadOnly`)),
ro,
`${id} has IsReadOnly ${ro}`
);
is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`);
is(
await runPython(`pattern.CurrentMinimum`),
min,
`${id} has correct Minimum`
);
is(
await runPython(`pattern.CurrentMaximum`),
max,
`${id} has correct Maximum`
);
// IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't
// either.
if (gIsUiaEnabled) {
is(
await runPython(`pattern.CurrentSmallChange`),
small,
`${id} has correct SmallChange`
);
is(
await runPython(`pattern.CurrentLargeChange`),
large,
`${id} has correct LargeChange`
);
}
}
/**
* Test the RangeValue pattern.
*/
addUiaTask(
`
<input id="range" type="range">
<input id="rangeBig" type="range" max="1000">
<progress id="progress" value="0.5"></progress>
<input id="numberRo" type="number" min="0" max="10" value="5" readonly>
<div id="ariaSlider" role="slider">slider</div>
<button id="button">button</button>
`,
async function testRangeValue(browser) {
await definePyVar("doc", `getDocUia()`);
await testRangeValueProps("range", false, 50, 0, 100, 1, 10);
info("SetValue on range");
await setUpWaitForUiaPropEvent("RangeValueValue", "range");
await runPython(`pattern.SetValue(20)`);
await waitForUiaEvent();
ok(true, "Got RangeValueValue prop change event on range");
is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value");
await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100);
// Gecko a11y doesn't expose progress bars as read only, but it probably
// should.
await testRangeValueProps("progress", false, 0.5, 0, 1, 0, 0.1);
info("Calling SetValue on progress");
await testPythonRaises(
`pattern.SetValue(0.6)`,
"SetValue on progress failed"
);
await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1);
info("Calling SetValue on numberRo");
await testPythonRaises(
`pattern.SetValue(6)`,
"SetValue on numberRo failed"
);
await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null);
info("Setting aria-valuenow on ariaSlider");
await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider");
await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60");
await waitForUiaEvent();
ok(true, "Got RangeValueValue prop change event on ariaSlider");
is(
await runPython(`pattern.CurrentValue`),
60,
"ariaSlider has correct Value"
);
await testPatternAbsent("button", "RangeValue");
}
);

View file

@ -91,6 +91,12 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
uia->get_Value(&newVal.bstrVal);
gotNewVal = true;
break;
case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
property = UIA_RangeValueValuePropertyId;
newVal.vt = VT_R8;
uia->get_Value(&newVal.dblVal);
gotNewVal = true;
break;
}
if (property && ::UiaClientsAreListening()) {
// We can't get the old value. Thankfully, clients don't seem to need it.
@ -162,6 +168,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
*aInterface = static_cast<IExpandCollapseProvider*>(this);
} else if (aIid == IID_IInvokeProvider) {
*aInterface = static_cast<IInvokeProvider*>(this);
} else if (aIid == IID_IRangeValueProvider) {
*aInterface = static_cast<IRangeValueProvider*>(this);
} else if (aIid == IID_IScrollItemProvider) {
*aInterface = static_cast<IScrollItemProvider*>(this);
} else if (aIid == IID_IToggleProvider) {
@ -279,6 +287,12 @@ uiaRawElmProvider::GetPatternProvider(
invoke.forget(aPatternProvider);
}
return S_OK;
case UIA_RangeValuePatternId:
if (acc->HasNumericValue()) {
RefPtr<IValueProvider> value = this;
value.forget(aPatternProvider);
}
return S_OK;
case UIA_ScrollItemPatternId: {
RefPtr<IScrollItemProvider> scroll = this;
scroll.forget(aPatternProvider);
@ -786,6 +800,99 @@ uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) {
return S_OK;
}
// IRangeValueProvider methods
STDMETHODIMP
uiaRawElmProvider::SetValue(double aVal) {
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
if (!acc->SetCurValue(aVal)) {
return UIA_E_INVALIDOPERATION;
}
return S_OK;
}
STDMETHODIMP
uiaRawElmProvider::get_Value(__RPC__out double* aRetVal) {
if (!aRetVal) {
return E_INVALIDARG;
}
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
*aRetVal = acc->CurValue();
return S_OK;
}
STDMETHODIMP
uiaRawElmProvider::get_Maximum(__RPC__out double* aRetVal) {
if (!aRetVal) {
return E_INVALIDARG;
}
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
*aRetVal = acc->MaxValue();
return S_OK;
}
STDMETHODIMP
uiaRawElmProvider::get_Minimum(
/* [retval][out] */ __RPC__out double* aRetVal) {
if (!aRetVal) {
return E_INVALIDARG;
}
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
*aRetVal = acc->MinValue();
return S_OK;
}
STDMETHODIMP
uiaRawElmProvider::get_LargeChange(
/* [retval][out] */ __RPC__out double* aRetVal) {
if (!aRetVal) {
return E_INVALIDARG;
}
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
// We want the change that would occur if the user pressed page up or page
// down. For HTML input elements, this is 10% of the total range unless step
// is larger. See:
// https://searchfox.org/mozilla-central/rev/c7df16ffad1f12a19c81c16bce0b65e4a15304d0/dom/html/HTMLInputElement.cpp#3878
double step = acc->Step();
double min = acc->MinValue();
double max = acc->MaxValue();
if (std::isnan(step) || std::isnan(min) || std::isnan(max)) {
*aRetVal = UnspecifiedNaN<double>();
} else {
*aRetVal = std::max(step, (max - min) / 10);
}
return S_OK;
}
STDMETHODIMP
uiaRawElmProvider::get_SmallChange(
/* [retval][out] */ __RPC__out double* aRetVal) {
if (!aRetVal) {
return E_INVALIDARG;
}
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
*aRetVal = acc->Step();
return S_OK;
}
// Private methods
bool uiaRawElmProvider::IsControl() {

View file

@ -26,7 +26,8 @@ class uiaRawElmProvider : public IAccessibleEx,
public IToggleProvider,
public IExpandCollapseProvider,
public IScrollItemProvider,
public IValueProvider {
public IValueProvider,
public IRangeValueProvider {
public:
static constexpr enum ProviderOptions kProviderOptions =
static_cast<enum ProviderOptions>(ProviderOptions_ServerSideProvider |
@ -123,6 +124,27 @@ class uiaRawElmProvider : public IAccessibleEx,
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsReadOnly(
/* [retval][out] */ __RPC__out BOOL* pRetVal);
// IRangeValueProvider
virtual HRESULT STDMETHODCALLTYPE SetValue(
/* [in] */ double aVal);
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Value(
/* [retval][out] */ __RPC__out double* aRetVal);
// get_IsReadOnly is shared with IValueProvider.
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Maximum(
/* [retval][out] */ __RPC__out double* aRetVal);
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Minimum(
/* [retval][out] */ __RPC__out double* aRetVal);
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_LargeChange(
/* [retval][out] */ __RPC__out double* aRetVal);
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SmallChange(
/* [retval][out] */ __RPC__out double* aRetVal);
private:
Accessible* Acc() const;
bool IsControl();