forked from mirrors/gecko-dev
Bug 1887780 part 2: Implement the UIA Value pattern. r=morgan
Differential Revision: https://phabricator.services.mozilla.com/D206449
This commit is contained in:
parent
0d51bb4e69
commit
e84478d977
3 changed files with 258 additions and 4 deletions
|
|
@ -283,3 +283,163 @@ addUiaTask(
|
|||
testStates(button, 0, 0, STATE_OFFSCREEN);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Test the Value pattern.
|
||||
*/
|
||||
addUiaTask(
|
||||
`
|
||||
<input id="text" value="before">
|
||||
<input id="textRo" readonly value="textRo">
|
||||
<input id="textDis" disabled value="textDis">
|
||||
<select id="select"><option selected>a</option><option>b</option></select>
|
||||
<progress id="progress" value="0.5"></progress>
|
||||
<input id="range" type="range" aria-valuetext="02:00:00">
|
||||
<a id="link" href="https://example.com/">Link</a>
|
||||
<div id="ariaTextbox" contenteditable role="textbox">before</div>
|
||||
<button id="button">button</button>
|
||||
`,
|
||||
async function testValue() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
await assignPyVarToUiaWithId("text");
|
||||
await definePyVar("pattern", `getUiaPattern(text, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "text has Value pattern");
|
||||
ok(
|
||||
!(await runPython(`pattern.CurrentIsReadOnly`)),
|
||||
"text has IsReadOnly false"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"before",
|
||||
"text has correct Value"
|
||||
);
|
||||
info("SetValue on text");
|
||||
await setUpWaitForUiaPropEvent("ValueValue", "text");
|
||||
await runPython(`pattern.SetValue("after")`);
|
||||
await waitForUiaEvent();
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"after",
|
||||
"text has correct Value"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("textRo");
|
||||
await definePyVar("pattern", `getUiaPattern(textRo, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "textRo has Value pattern");
|
||||
ok(
|
||||
await runPython(`pattern.CurrentIsReadOnly`),
|
||||
"textRo has IsReadOnly true"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"textRo",
|
||||
"textRo has correct Value"
|
||||
);
|
||||
info("SetValue on textRo");
|
||||
await testPythonRaises(
|
||||
`pattern.SetValue("after")`,
|
||||
"SetValue on textRo failed"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("textDis");
|
||||
await definePyVar("pattern", `getUiaPattern(textDis, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "textDis has Value pattern");
|
||||
ok(
|
||||
!(await runPython(`pattern.CurrentIsReadOnly`)),
|
||||
"textDis has IsReadOnly false"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"textDis",
|
||||
"textDis has correct Value"
|
||||
);
|
||||
// The IA2 -> UIA proxy doesn't fail SetValue for a disabled element.
|
||||
if (gIsUiaEnabled) {
|
||||
info("SetValue on textDis");
|
||||
await testPythonRaises(
|
||||
`pattern.SetValue("after")`,
|
||||
"SetValue on textDis failed"
|
||||
);
|
||||
}
|
||||
|
||||
await assignPyVarToUiaWithId("select");
|
||||
await definePyVar("pattern", `getUiaPattern(select, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "select has Value pattern");
|
||||
ok(
|
||||
!(await runPython(`pattern.CurrentIsReadOnly`)),
|
||||
"select has IsReadOnly false"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"a",
|
||||
"select has correct Value"
|
||||
);
|
||||
info("SetValue on select");
|
||||
await testPythonRaises(
|
||||
`pattern.SetValue("b")`,
|
||||
"SetValue on select failed"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("progress");
|
||||
await definePyVar("pattern", `getUiaPattern(progress, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "progress has Value pattern");
|
||||
// Gecko a11y doesn't treat progress bars as read only, but it probably
|
||||
// should.
|
||||
todo(
|
||||
await runPython(`pattern.CurrentIsReadOnly`),
|
||||
"progress has IsReadOnly true"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"50%",
|
||||
"progress has correct Value"
|
||||
);
|
||||
info("SetValue on progress");
|
||||
await testPythonRaises(
|
||||
`pattern.SetValue("60%")`,
|
||||
"SetValue on progress failed"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("range");
|
||||
await definePyVar("pattern", `getUiaPattern(range, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "range has Value pattern");
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"02:00:00",
|
||||
"range has correct Value"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("link");
|
||||
await definePyVar("pattern", `getUiaPattern(link, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "link has Value pattern");
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"https://example.com/",
|
||||
"link has correct Value"
|
||||
);
|
||||
|
||||
await assignPyVarToUiaWithId("ariaTextbox");
|
||||
await definePyVar("pattern", `getUiaPattern(ariaTextbox, "Value")`);
|
||||
ok(await runPython(`bool(pattern)`), "ariaTextbox has Value pattern");
|
||||
ok(
|
||||
!(await runPython(`pattern.CurrentIsReadOnly`)),
|
||||
"ariaTextbox has IsReadOnly false"
|
||||
);
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"before",
|
||||
"ariaTextbox has correct Value"
|
||||
);
|
||||
info("SetValue on ariaTextbox");
|
||||
await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
|
||||
await runPython(`pattern.SetValue("after")`);
|
||||
await waitForUiaEvent();
|
||||
is(
|
||||
await runPython(`pattern.CurrentValue`),
|
||||
"after",
|
||||
"ariaTextbox has correct Value"
|
||||
);
|
||||
|
||||
await testPatternAbsent("button", "Value");
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
|||
return;
|
||||
}
|
||||
PROPERTYID property = 0;
|
||||
_variant_t newVal;
|
||||
bool gotNewVal = false;
|
||||
// For control pattern properties, we can't use GetPropertyValue. In those
|
||||
// cases, we must set newVal appropriately and set gotNewVal to true.
|
||||
switch (aGeckoEvent) {
|
||||
case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
|
||||
property = UIA_FullDescriptionPropertyId;
|
||||
|
|
@ -80,13 +84,20 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
|||
case nsIAccessibleEvent::EVENT_NAME_CHANGE:
|
||||
property = UIA_NamePropertyId;
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
|
||||
property = UIA_ValueValuePropertyId;
|
||||
newVal.vt = VT_BSTR;
|
||||
uia->get_Value(&newVal.bstrVal);
|
||||
gotNewVal = true;
|
||||
break;
|
||||
}
|
||||
// Don't pointlessly query the property value if no UIA clients are listening.
|
||||
if (property && ::UiaClientsAreListening()) {
|
||||
// We can't get the old value. Thankfully, clients don't seem to need it.
|
||||
_variant_t oldVal;
|
||||
_variant_t newVal;
|
||||
uia->GetPropertyValue(property, &newVal);
|
||||
if (!gotNewVal) {
|
||||
// This isn't a pattern property, so we can use GetPropertyValue.
|
||||
uia->GetPropertyValue(property, &newVal);
|
||||
}
|
||||
::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +165,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
|
|||
*aInterface = static_cast<IScrollItemProvider*>(this);
|
||||
} else if (aIid == IID_IToggleProvider) {
|
||||
*aInterface = static_cast<IToggleProvider*>(this);
|
||||
} else if (aIid == IID_IValueProvider) {
|
||||
*aInterface = static_cast<IValueProvider*>(this);
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
|
@ -278,6 +291,12 @@ uiaRawElmProvider::GetPatternProvider(
|
|||
toggle.forget(aPatternProvider);
|
||||
}
|
||||
return S_OK;
|
||||
case UIA_ValuePatternId:
|
||||
if (HasValuePattern()) {
|
||||
RefPtr<IValueProvider> value = this;
|
||||
value.forget(aPatternProvider);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
|
@ -678,6 +697,58 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP uiaRawElmProvider::ScrollIntoView() {
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// IValueProvider methods
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::SetValue(__RPC__in LPCWSTR aVal) {
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
HyperTextAccessibleBase* ht = acc->AsHyperTextBase();
|
||||
if (!ht || !acc->IsTextRole()) {
|
||||
return UIA_E_INVALIDOPERATION;
|
||||
}
|
||||
if (acc->State() & (states::READONLY | states::UNAVAILABLE)) {
|
||||
return UIA_E_INVALIDOPERATION;
|
||||
}
|
||||
nsAutoString text(aVal);
|
||||
ht->ReplaceText(text);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::get_Value(__RPC__deref_out_opt BSTR* aRetVal) {
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
*aRetVal = nullptr;
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
nsAutoString value;
|
||||
acc->Value(value);
|
||||
*aRetVal = ::SysAllocStringLen(value.get(), value.Length());
|
||||
if (!*aRetVal) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) {
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
Accessible* acc = Acc();
|
||||
if (!acc) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
*aRetVal = acc->State() & states::READONLY;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
bool uiaRawElmProvider::IsControl() {
|
||||
|
|
@ -772,3 +843,14 @@ bool uiaRawElmProvider::HasExpandCollapsePattern() {
|
|||
MOZ_ASSERT(acc);
|
||||
return acc->State() & (states::EXPANDABLE | states::HASPOPUP);
|
||||
}
|
||||
|
||||
bool uiaRawElmProvider::HasValuePattern() const {
|
||||
Accessible* acc = Acc();
|
||||
MOZ_ASSERT(acc);
|
||||
if (acc->HasNumericValue() || acc->IsCombobox() || acc->IsHTMLLink() ||
|
||||
acc->IsTextField()) {
|
||||
return true;
|
||||
}
|
||||
const nsRoleMapEntry* roleMapEntry = acc->ARIARoleMap();
|
||||
return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
public IInvokeProvider,
|
||||
public IToggleProvider,
|
||||
public IExpandCollapseProvider,
|
||||
public IScrollItemProvider {
|
||||
public IScrollItemProvider,
|
||||
public IValueProvider {
|
||||
public:
|
||||
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
||||
uint32_t aGeckoEvent);
|
||||
|
|
@ -107,12 +108,23 @@ class uiaRawElmProvider : public IAccessibleEx,
|
|||
// IScrollItemProvider
|
||||
virtual HRESULT STDMETHODCALLTYPE ScrollIntoView(void);
|
||||
|
||||
// IValueProvider
|
||||
virtual HRESULT STDMETHODCALLTYPE SetValue(
|
||||
/* [in] */ __RPC__in LPCWSTR val);
|
||||
|
||||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Value(
|
||||
/* [retval][out] */ __RPC__deref_out_opt BSTR* pRetVal);
|
||||
|
||||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsReadOnly(
|
||||
/* [retval][out] */ __RPC__out BOOL* pRetVal);
|
||||
|
||||
private:
|
||||
Accessible* Acc() const;
|
||||
bool IsControl();
|
||||
long GetControlType() const;
|
||||
bool HasTogglePattern();
|
||||
bool HasExpandCollapsePattern();
|
||||
bool HasValuePattern() const;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
|||
Loading…
Reference in a new issue