Bug 1886709 part 2: Implement the UIA Invoke pattern. r=nlapre

Differential Revision: https://phabricator.services.mozilla.com/D205432
This commit is contained in:
James Teh 2024-03-26 04:25:41 +00:00
parent 116d79df30
commit bf353f3ed5
5 changed files with 83 additions and 2 deletions

View File

@ -13,4 +13,6 @@ support-files = ["head.js"]
["browser_focus.js"]
["browser_generalProps.js"]
["browser_simplePatterns.js"]
["browser_tree.js"]

View File

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Test the Invoke pattern.
*/
addUiaTask(
`
<button id="button">button</button>
<p id="p">p</p>
`,
async function testInvoke() {
await definePyVar("doc", `getDocUia()`);
await assignPyVarToUiaWithId("button");
await definePyVar("pattern", `getUiaPattern(button, "Invoke")`);
ok(await runPython(`bool(pattern)`), "button has Invoke pattern");
info("Calling Invoke on button");
// The button will get focus when it is clicked.
let focused = waitForEvent(EVENT_FOCUS, "button");
// The UIA -> IA2 proxy doesn't fire the Invoked event.
if (gIsUiaEnabled) {
await setUpWaitForUiaEvent("Invoke_Invoked", "button");
}
await runPython(`pattern.Invoke()`);
await focused;
ok(true, "button got focus");
if (gIsUiaEnabled) {
await waitForUiaEvent();
ok(true, "button got Invoked event");
}
let hasPattern = await runPython(`
p = findUiaByDomId(doc, "p")
return bool(getUiaPattern(p, "Invoke"))
`);
ok(!hasPattern, "p doesn't have Invoke pattern");
}
);

View File

@ -146,6 +146,10 @@ class MsaaAccessible : public ia2Accessible,
EXCEPINFO* pExcepInfo,
UINT* puArgErr) override;
// UIA's IInvokeProvider has a method called Invoke too, but it's fine because
// it accepts very different parameters.
using uiaRawElmProvider::Invoke;
protected:
explicit MsaaAccessible(Accessible* aAcc);
virtual ~MsaaAccessible();

View File

@ -73,6 +73,8 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
*aInterface = static_cast<IRawElementProviderSimple*>(this);
} else if (aIid == IID_IRawElementProviderFragment) {
*aInterface = static_cast<IRawElementProviderFragment*>(this);
} else if (aIid == IID_IInvokeProvider) {
*aInterface = static_cast<IInvokeProvider*>(this);
} else {
return E_NOINTERFACE;
}
@ -164,8 +166,19 @@ STDMETHODIMP
uiaRawElmProvider::GetPatternProvider(
PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) {
if (!aPatternProvider) return E_INVALIDARG;
*aPatternProvider = nullptr;
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
switch (aPatternId) {
case UIA_InvokePatternId:
if (acc->ActionCount() > 0) {
RefPtr<IInvokeProvider> invoke = this;
invoke.forget(aPatternProvider);
}
return S_OK;
}
return S_OK;
}
@ -453,6 +466,23 @@ uiaRawElmProvider::get_FragmentRoot(
return S_OK;
}
// IInvokeProvider methods
STDMETHODIMP
uiaRawElmProvider::Invoke() {
Accessible* acc = Acc();
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
if (acc->DoAction(0)) {
// We don't currently have a way to notify when the action was actually
// handled. The UIA documentation says it's okay to fire this immediately if
// it "is not possible or practical to wait until the action is complete".
::UiaRaiseAutomationEvent(this, UIA_Invoke_InvokedEventId);
}
return S_OK;
}
// Private methods
bool uiaRawElmProvider::IsControl() {

View File

@ -21,7 +21,8 @@ class Accessible;
*/
class uiaRawElmProvider : public IAccessibleEx,
public IRawElementProviderSimple,
public IRawElementProviderFragment {
public IRawElementProviderFragment,
public IInvokeProvider {
public:
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
uint32_t aGeckoEvent);
@ -81,6 +82,9 @@ class uiaRawElmProvider : public IAccessibleEx,
/* [retval][out] */ __RPC__deref_out_opt IRawElementProviderFragmentRoot**
aRetVal);
// IInvokeProvider
virtual HRESULT STDMETHODCALLTYPE Invoke(void);
private:
Accessible* Acc() const;
bool IsControl();