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();