Bug 1949920: Expose the UIA Text pattern on text leaves. Don't expose it on the root. r=nlapre

We need to support the Text pattern on text leaves to fix issues with Narrator.
Removing support on the root isn't strictly necessary, but the Text pattern isn't normally supported on a UIA Window and Chromium doesn't do this, so I think it's best to follow.

This change necessitated moving UiaText so that it is no longer inherited by ia2AccessibleHypertext.
I initially tried to inherit UiaText into uiaRawElmProvider, but that doesn't work because ITextProvider and ISelectionProvider both have a GetSelection method with the same signature.
This meant that the ISelectionProvider implementation of GetSelection in uiaRawElmProvider was overriding the implementation in UiaText.
Instead, UiaText has now been split into a separate object.

Differential Revision: https://phabricator.services.mozilla.com/D239811
This commit is contained in:
James Teh
2025-03-03 03:18:42 +00:00
parent 233d72858e
commit ef30c65925
6 changed files with 67 additions and 18 deletions

View File

@@ -21,6 +21,7 @@ addUiaTask(
<div><input id="input" value="input"></div>
<textarea id="textarea">textarea</textarea>
<div id="contentEditable" contenteditable><p>content</p><p>editable</p></div>
<p id="p">p</p>
<a id="link" href="#">link</a>
`,
async function testTextDocumentRange() {
@@ -31,7 +32,7 @@ addUiaTask(
if (gIsUiaEnabled) {
is(
await runPython(`pattern.DocumentRange.GetText(-1)`),
"inputtextareacontenteditablelink",
"inputtextareacontenteditableplink",
"document DocumentRange Text correct"
);
}
@@ -67,7 +68,39 @@ addUiaTask(
);
}
await testPatternAbsent("p", "Text");
// The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
if (gIsUiaEnabled) {
await runPython(`
global pLeaf
p = findUiaByDomId(doc, "p")
pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
`);
await definePyVar("pattern", `getUiaPattern(pLeaf, "Text")`);
ok(await runPython(`bool(pattern)`), "pLeaf has Text pattern");
is(
await runPython(`pattern.DocumentRange.GetText(-1)`),
"p",
"pLeaf DocumentRange Text correct"
);
}
await testPatternAbsent("link", "Text");
// The IA2 -> UIA proxy doesn't expose this text leaf at all.
if (gIsUiaEnabled) {
await runPython(`
global linkLeaf
link = findUiaByDomId(doc, "link")
linkLeaf = uiaClient.RawViewWalker.GetFirstChildElement(link)
`);
await definePyVar("pattern", `getUiaPattern(linkLeaf, "Text")`);
ok(await runPython(`bool(pattern)`), "linkLeaf has Text pattern");
is(
await runPython(`pattern.DocumentRange.GetText(-1)`),
"link",
"linkLeaf DocumentRange Text correct"
);
}
}
);

View File

@@ -41,8 +41,6 @@ ia2AccessibleHypertext::QueryInterface(REFIID aIID, void** aInstancePtr) {
*aInstancePtr = static_cast<IAccessibleEditableText*>(this);
} else if (aIID == IID_IAccessibleTextSelectionContainer) {
*aInstancePtr = static_cast<IAccessibleTextSelectionContainer*>(this);
} else if (aIID == IID_ITextProvider) {
*aInstancePtr = static_cast<ITextProvider*>(this);
}
if (*aInstancePtr) {

View File

@@ -16,7 +16,6 @@
#include "AccessibleHypertext2.h"
#include "IUnknownImpl.h"
#include "MsaaAccessible.h"
#include "UiaText.h"
namespace mozilla {
namespace a11y {
@@ -26,13 +25,8 @@ class ia2AccessibleHypertext : public ia2AccessibleText,
public IAccessibleHypertext2,
public ia2AccessibleEditableText,
public ia2AccessibleTextSelectionContainer,
public UiaText,
public MsaaAccessible {
public:
// UiaText has a private Acc() method. Explicitly specify what
// ia2AccessibleHypertext::Acc should use.
using MsaaAccessible::Acc;
// IUnknown
DECL_IUNKNOWN_INHERITED
IMPL_IUNKNOWN_REFCOUNTING_INHERITED(MsaaAccessible)

View File

@@ -6,7 +6,7 @@
#include "UiaText.h"
#include "ia2AccessibleHypertext.h"
#include "MsaaAccessible.h"
#include "mozilla/a11y/States.h"
#include "TextLeafRange.h"
#include "UiaTextRange.h"
@@ -34,12 +34,14 @@ static SAFEARRAY* TextLeafRangesToUiaRanges(
return uiaRanges;
}
// IUnknown
IMPL_IUNKNOWN1(UiaText, ITextProvider)
// UiaText
Accessible* UiaText::Acc() const {
auto* hyp = static_cast<const ia2AccessibleHypertext*>(this);
return hyp->Acc();
}
UiaText::UiaText(MsaaAccessible* aMsaa) : mMsaa(aMsaa) {}
Accessible* UiaText::Acc() const { return mMsaa->Acc(); }
// ITextProvider methods

View File

@@ -10,13 +10,24 @@
#include "objbase.h"
#include "uiautomation.h"
#include "IUnknownImpl.h"
#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
namespace mozilla::a11y {
class Accessible;
class MsaaAccessible;
/**
* ITextProvider implementation.
*/
class UiaText : public ITextProvider {
public:
explicit UiaText(MsaaAccessible* aMsaa);
// IUnknown
DECL_IUNKNOWN
// ITextProvider
virtual HRESULT STDMETHODCALLTYPE GetSelection(
/* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
@@ -39,7 +50,10 @@ class UiaText : public ITextProvider {
/* [retval][out] */ __RPC__out enum SupportedTextSelection* aRetVal);
private:
virtual ~UiaText() = default;
Accessible* Acc() const;
RefPtr<MsaaAccessible> mMsaa;
};
} // namespace mozilla::a11y

View File

@@ -29,6 +29,7 @@
#include "Relation.h"
#include "RootAccessible.h"
#include "TextLeafRange.h"
#include "UiaText.h"
#include "UiaTextRange.h"
using namespace mozilla;
@@ -100,10 +101,17 @@ static void MaybeRaiseUiaLiveRegionEvent(Accessible* aAcc,
}
static bool HasTextPattern(Accessible* aAcc) {
// Only documents and editable text controls should have the Text pattern.
// The Text pattern must be supported for documents and editable text controls
// on the web:
// https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview#webpage-and-text-input-controls-in-edge
// It is also recommended that the Text pattern be supported for the Text
// control type:
// https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-about-text-and-textrange-patterns#control-types
// If we don't support this for the Text control type, when Narrator is
// continuously reading a document, it doesn't respect the starting position
// of the cursor and doesn't move the cursor as it reads. See bug 1949920.
constexpr uint64_t editableRootStates = states::EDITABLE | states::FOCUSABLE;
return aAcc->IsDoc() ||
return aAcc->IsText() || (aAcc->IsDoc() && !aAcc->IsRoot()) ||
(aAcc->IsHyperText() &&
(aAcc->State() & editableRootStates) == editableRootStates);
}
@@ -463,8 +471,8 @@ uiaRawElmProvider::GetPatternProvider(
return S_OK;
case UIA_TextPatternId:
if (HasTextPattern(acc)) {
auto text =
GetPatternFromDerived<ia2AccessibleHypertext, ITextProvider>();
RefPtr<ITextProvider> text =
new UiaText(static_cast<MsaaAccessible*>(this));
text.forget(aPatternProvider);
}
return S_OK;