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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
PROPERTYID property = 0;
|
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) {
|
switch (aGeckoEvent) {
|
||||||
case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
|
case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
|
||||||
property = UIA_FullDescriptionPropertyId;
|
property = UIA_FullDescriptionPropertyId;
|
||||||
|
|
@ -80,13 +84,20 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
||||||
case nsIAccessibleEvent::EVENT_NAME_CHANGE:
|
case nsIAccessibleEvent::EVENT_NAME_CHANGE:
|
||||||
property = UIA_NamePropertyId;
|
property = UIA_NamePropertyId;
|
||||||
break;
|
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()) {
|
if (property && ::UiaClientsAreListening()) {
|
||||||
// We can't get the old value. Thankfully, clients don't seem to need it.
|
// We can't get the old value. Thankfully, clients don't seem to need it.
|
||||||
_variant_t oldVal;
|
_variant_t oldVal;
|
||||||
_variant_t newVal;
|
if (!gotNewVal) {
|
||||||
uia->GetPropertyValue(property, &newVal);
|
// This isn't a pattern property, so we can use GetPropertyValue.
|
||||||
|
uia->GetPropertyValue(property, &newVal);
|
||||||
|
}
|
||||||
::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
|
::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +165,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
|
||||||
*aInterface = static_cast<IScrollItemProvider*>(this);
|
*aInterface = static_cast<IScrollItemProvider*>(this);
|
||||||
} else if (aIid == IID_IToggleProvider) {
|
} else if (aIid == IID_IToggleProvider) {
|
||||||
*aInterface = static_cast<IToggleProvider*>(this);
|
*aInterface = static_cast<IToggleProvider*>(this);
|
||||||
|
} else if (aIid == IID_IValueProvider) {
|
||||||
|
*aInterface = static_cast<IValueProvider*>(this);
|
||||||
} else {
|
} else {
|
||||||
return E_NOINTERFACE;
|
return E_NOINTERFACE;
|
||||||
}
|
}
|
||||||
|
|
@ -278,6 +291,12 @@ uiaRawElmProvider::GetPatternProvider(
|
||||||
toggle.forget(aPatternProvider);
|
toggle.forget(aPatternProvider);
|
||||||
}
|
}
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
case UIA_ValuePatternId:
|
||||||
|
if (HasValuePattern()) {
|
||||||
|
RefPtr<IValueProvider> value = this;
|
||||||
|
value.forget(aPatternProvider);
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
}
|
}
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
@ -678,6 +697,58 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP uiaRawElmProvider::ScrollIntoView() {
|
||||||
return S_OK;
|
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
|
// Private methods
|
||||||
|
|
||||||
bool uiaRawElmProvider::IsControl() {
|
bool uiaRawElmProvider::IsControl() {
|
||||||
|
|
@ -772,3 +843,14 @@ bool uiaRawElmProvider::HasExpandCollapsePattern() {
|
||||||
MOZ_ASSERT(acc);
|
MOZ_ASSERT(acc);
|
||||||
return acc->State() & (states::EXPANDABLE | states::HASPOPUP);
|
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 IInvokeProvider,
|
||||||
public IToggleProvider,
|
public IToggleProvider,
|
||||||
public IExpandCollapseProvider,
|
public IExpandCollapseProvider,
|
||||||
public IScrollItemProvider {
|
public IScrollItemProvider,
|
||||||
|
public IValueProvider {
|
||||||
public:
|
public:
|
||||||
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
|
||||||
uint32_t aGeckoEvent);
|
uint32_t aGeckoEvent);
|
||||||
|
|
@ -107,12 +108,23 @@ class uiaRawElmProvider : public IAccessibleEx,
|
||||||
// IScrollItemProvider
|
// IScrollItemProvider
|
||||||
virtual HRESULT STDMETHODCALLTYPE ScrollIntoView(void);
|
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:
|
private:
|
||||||
Accessible* Acc() const;
|
Accessible* Acc() const;
|
||||||
bool IsControl();
|
bool IsControl();
|
||||||
long GetControlType() const;
|
long GetControlType() const;
|
||||||
bool HasTogglePattern();
|
bool HasTogglePattern();
|
||||||
bool HasExpandCollapsePattern();
|
bool HasExpandCollapsePattern();
|
||||||
|
bool HasValuePattern() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace a11y
|
} // namespace a11y
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue