diff --git a/accessible/tests/browser/python_runner_wsh.py b/accessible/tests/browser/python_runner_wsh.py
index 488051240f71..2686967cfb7e 100644
--- a/accessible/tests/browser/python_runner_wsh.py
+++ b/accessible/tests/browser/python_runner_wsh.py
@@ -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())
diff --git a/accessible/tests/browser/windows/uia/browser_simplePatterns.js b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
index f464db0e1394..b2326ae3a825 100644
--- a/accessible/tests/browser/windows/uia/browser_simplePatterns.js
+++ b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
@@ -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(
+ `
+
+
+
+
+
slider
+
+ `,
+ 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");
+ }
+);
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
index 47b81b13890d..46936cfd8aa0 100644
--- a/accessible/windows/uia/uiaRawElmProvider.cpp
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -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(this);
} else if (aIid == IID_IInvokeProvider) {
*aInterface = static_cast(this);
+ } else if (aIid == IID_IRangeValueProvider) {
+ *aInterface = static_cast(this);
} else if (aIid == IID_IScrollItemProvider) {
*aInterface = static_cast(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 value = this;
+ value.forget(aPatternProvider);
+ }
+ return S_OK;
case UIA_ScrollItemPatternId: {
RefPtr 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();
+ } 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() {
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
index 4a133a28566e..ad8f0557be78 100644
--- a/accessible/windows/uia/uiaRawElmProvider.h
+++ b/accessible/windows/uia/uiaRawElmProvider.h
@@ -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(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();