Bug 1766546: Implement SetCaretOffset for cached Windows RemoteAccessible. r=morgan

We implement setting of the caret using HyperText rather than TextLeafPoint because caret stuff, including events, still uses HyperText internally for now.

This moves the async IPDL method already used on non-Windows into the base classes so Windows can use it.
We keep the COM implementation for Windows RemoteAccessible without the cache.
SetCaretOffset was moved into HyperTextAccessibleBase and platform methods were updated accordingly.
Finally, I did some drive-by cleanup (no user impact) and changed GetCaretOffset in ATK and XPCOM to use HyperTextAccessibleBase.
GetCaretOffset was moved to the base some time ago, but ATK and XPCOM weren't updated at the time.

Differential Revision: https://phabricator.services.mozilla.com/D147852
This commit is contained in:
James Teh 2022-06-01 19:34:09 +00:00
parent 2be1e8212c
commit 52b0b92e8d
17 changed files with 111 additions and 61 deletions

View File

@ -271,21 +271,17 @@ static gchar* getTextBeforeOffsetCB(AtkText* aText, gint aOffset,
}
static gint getCaretOffsetCB(AtkText* aText) {
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return -1;
}
return static_cast<gint>(text->CaretOffset());
Accessible* acc = GetInternalObj(ATK_OBJECT(aText));
if (!acc) {
return -1;
}
if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return static_cast<gint>(proxy->CaretOffset());
HyperTextAccessibleBase* text = acc->AsHyperTextBase();
if (!text || !acc->IsTextRole()) {
return -1;
}
return -1;
return static_cast<gint>(text->CaretOffset());
}
static AtkAttributeSet* getRunAttributesCB(AtkText* aText, gint aOffset,
@ -540,23 +536,18 @@ static gboolean setTextSelectionCB(AtkText* aText, gint aSelectionNum,
}
static gboolean setCaretOffsetCB(AtkText* aText, gint aOffset) {
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole() || !text->IsValidOffset(aOffset)) {
return FALSE;
}
text->SetCaretOffset(aOffset);
return TRUE;
Accessible* acc = GetInternalObj(ATK_OBJECT(aText));
if (!acc) {
return FALSE;
}
if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->SetCaretOffset(aOffset);
return TRUE;
HyperTextAccessibleBase* text = acc->AsHyperTextBase();
if (!text || !acc->IsTextRole()) {
return FALSE;
}
return FALSE;
text->SetCaretOffset(aOffset);
return TRUE;
}
static gboolean scrollSubstringToCB(AtkText* aText, gint aStartOffset,

View File

@ -79,9 +79,10 @@ class HyperTextAccessibleBase {
virtual uint32_t CharacterCount() const;
/**
* Get caret offset, if no caret then -1.
* Get/set caret offset, if no caret then -1.
*/
virtual int32_t CaretOffset() const;
virtual void SetCaretOffset(int32_t aOffset) = 0;
/**
* Transform magic offset into text offset.

View File

@ -190,7 +190,7 @@ class HyperTextAccessible : public AccessibleWrap,
* Get/set caret offset, if no caret then -1.
*/
virtual int32_t CaretOffset() const override;
void SetCaretOffset(int32_t aOffset);
virtual void SetCaretOffset(int32_t aOffset) override;
/**
* Provide the line number for the caret.

View File

@ -220,6 +220,15 @@ mozilla::ipc::IPCResult DocAccessibleChildBase::RecvDoActionAsync(
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleChildBase::RecvSetCaretOffset(
const uint64_t& aID, const int32_t& aOffset) {
HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) {
acc->SetCaretOffset(aOffset);
}
return IPC_OK();
}
LocalAccessible* DocAccessibleChildBase::IdToAccessible(
const uint64_t& aID) const {
if (!aID) return mDoc;
@ -229,5 +238,11 @@ LocalAccessible* DocAccessibleChildBase::IdToAccessible(
return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
}
HyperTextAccessible* DocAccessibleChildBase::IdToHyperTextAccessible(
const uint64_t& aID) const {
LocalAccessible* acc = IdToAccessible(aID);
return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
}
} // namespace a11y
} // namespace mozilla

View File

@ -75,6 +75,9 @@ class DocAccessibleChildBase : public PDocAccessibleChild {
virtual mozilla::ipc::IPCResult RecvDoActionAsync(
const uint64_t& aID, const uint8_t& aIndex) override;
virtual mozilla::ipc::IPCResult RecvSetCaretOffset(
const uint64_t& aID, const int32_t& aOffset) override;
protected:
static void FlattenTree(LocalAccessible* aRoot,
nsTArray<LocalAccessible*>& aTree);
@ -97,6 +100,7 @@ class DocAccessibleChildBase : public PDocAccessibleChild {
void SetConstructedInParentProcess() { mIsRemoteConstructed = true; }
LocalAccessible* IdToAccessible(const uint64_t& aID) const;
HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
DocAccessible* mDoc;
bool mIsRemoteConstructed;

View File

@ -1080,6 +1080,11 @@ RemoteAccessibleBase<Derived>::GetCachedHyperTextOffsets() const {
return *mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::offset);
}
template <class Derived>
void RemoteAccessibleBase<Derived>::SetCaretOffset(int32_t aOffset) {
Unused << mDoc->SendSetCaretOffset(mID, aOffset);
}
template class RemoteAccessibleBase<RemoteAccessible>;
} // namespace a11y

View File

@ -224,6 +224,7 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
virtual void TakeFocus() const override;
virtual void ScrollTo(uint32_t aHow) const override;
virtual void SetCaretOffset(int32_t aOffset) override;
/**
* Allow the platform to store a pointers worth of data on us.

View File

@ -70,7 +70,6 @@ void Announce(const nsString& aAnnouncement, uint16_t aPriority);
int32_t CaretLineNumber();
virtual int32_t CaretOffset() const override;
void SetCaretOffset(int32_t aOffset);
virtual void TextSubstring(int32_t aStartOffset, int32_t aEndOfset,
nsAString& aText) const override;

View File

@ -39,12 +39,6 @@ LocalAccessible* DocAccessibleChild::IdToAccessibleSelect(
return acc && acc->IsSelect() ? acc : nullptr;
}
HyperTextAccessible* DocAccessibleChild::IdToHyperTextAccessible(
const uint64_t& aID) const {
LocalAccessible* acc = IdToAccessible(aID);
return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
}
TextLeafAccessible* DocAccessibleChild::IdToTextLeafAccessible(
const uint64_t& aID) const {
LocalAccessible* acc = IdToAccessible(aID);
@ -277,15 +271,6 @@ mozilla::ipc::IPCResult DocAccessibleChild::RecvCaretOffset(const uint64_t& aID,
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleChild::RecvSetCaretOffset(
const uint64_t& aID, const int32_t& aOffset) {
HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) {
acc->SetCaretOffset(aOffset);
}
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleChild::RecvCharacterCount(
const uint64_t& aID, int32_t* aCount) {
HyperTextAccessible* acc = IdToHyperTextAccessible(aID);

View File

@ -104,8 +104,6 @@ class DocAccessibleChild : public DocAccessibleChildBase {
const uint64_t& aID, int32_t* aLineNumber) override;
virtual mozilla::ipc::IPCResult RecvCaretOffset(const uint64_t& aID,
int32_t* aOffset) override;
virtual mozilla::ipc::IPCResult RecvSetCaretOffset(
const uint64_t& aID, const int32_t& aOffset) override;
virtual mozilla::ipc::IPCResult RecvCharacterCount(const uint64_t& aID,
int32_t* aCount) override;
@ -465,7 +463,6 @@ class DocAccessibleChild : public DocAccessibleChildBase {
private:
LocalAccessible* IdToAccessibleLink(const uint64_t& aID) const;
LocalAccessible* IdToAccessibleSelect(const uint64_t& aID) const;
HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
TextLeafAccessible* IdToTextLeafAccessible(const uint64_t& aID) const;
ImageAccessible* IdToImageAccessible(const uint64_t& aID) const;
TableCellAccessible* IdToTableCellAccessible(const uint64_t& aID) const;

View File

@ -177,10 +177,6 @@ int32_t RemoteAccessible::CaretOffset() const {
return offset;
}
void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
Unused << mDoc->SendSetCaretOffset(mID, aOffset);
}
uint32_t RemoteAccessible::CharacterCount() const {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
return RemoteAccessibleBase<RemoteAccessible>::CharacterCount();

View File

@ -140,6 +140,8 @@ child:
async DoActionAsync(uint64_t aID, uint8_t aIndex);
async SetCaretOffset(uint64_t aID, int32_t aOffset);
async __delete__();
};

View File

@ -683,6 +683,10 @@ int32_t RemoteAccessible::CaretOffset() const {
}
void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
return RemoteAccessibleBase<RemoteAccessible>::SetCaretOffset(aOffset);
}
RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
if (!acc) {
return;

View File

@ -35,6 +35,7 @@ class RemoteAccessible : public RemoteAccessibleBase<RemoteAccessible> {
#include "mozilla/a11y/RemoteAccessibleShared.h"
virtual void TakeFocus() const override;
virtual void SetCaretOffset(int32_t aOffset) override;
bool GetCOMInterface(void** aOutAccessible) const;
void SetCOMInterface(const RefPtr<IAccessible>& aIAccessible) {

View File

@ -827,6 +827,63 @@ addAccessibleTask(
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);
/**
* Test setting the caret.
*/
addAccessibleTask(
`
<textarea id="textarea">ab\nc</textarea>
<div id="editable" contenteditable>
<p id="p">a<a id="link" href="https://example.com/">b</a></p>
</div>
`,
async function(browser, docAcc) {
const textarea = findAccessibleChildByID(docAcc, "textarea", [
nsIAccessibleText,
]);
info("textarea: Set caret offset to 0");
let focused = waitForEvent(EVENT_FOCUS, textarea);
let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
textarea.caretOffset = 0;
await focused;
await caretMoved;
is(textarea.caretOffset, 0, "textarea caret correct");
// Test setting caret to another line.
info("textarea: Set caret offset to 3");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
textarea.caretOffset = 3;
await caretMoved;
is(textarea.caretOffset, 3, "textarea caret correct");
// Test setting caret to the end.
info("textarea: Set caret offset to 4 (end)");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
textarea.caretOffset = 4;
await caretMoved;
is(textarea.caretOffset, 4, "textarea caret correct");
const editable = findAccessibleChildByID(docAcc, "editable", [
nsIAccessibleText,
]);
focused = waitForEvent(EVENT_FOCUS, editable);
editable.takeFocus();
await focused;
const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
info("p: Set caret offset to 0");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
p.caretOffset = 0;
await focused;
await caretMoved;
is(p.caretOffset, 0, "p caret correct");
const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
info("link: Set caret offset to 0");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, link);
link.caretOffset = 0;
await caretMoved;
is(link.caretOffset, 0, "link caret correct");
},
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);
function waitForSelectionChange(selectionAcc, caretAcc) {
if (!caretAcc) {
caretAcc = selectionAcc;

View File

@ -340,9 +340,9 @@ ia2AccessibleText::removeSelection(long aSelectionIndex) {
STDMETHODIMP
ia2AccessibleText::setCaretOffset(long aOffset) {
auto [textAcc, hr] = LocalTextAcc();
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return hr;
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG;

View File

@ -277,11 +277,7 @@ xpcAccessibleHyperText::GetCaretOffset(int32_t* aCaretOffset) {
if (!mIntl) return NS_ERROR_FAILURE;
if (mIntl->IsLocal()) {
*aCaretOffset = IntlLocal()->CaretOffset();
} else {
*aCaretOffset = mIntl->AsRemote()->CaretOffset();
}
*aCaretOffset = Intl()->CaretOffset();
return NS_OK;
}
@ -289,11 +285,7 @@ NS_IMETHODIMP
xpcAccessibleHyperText::SetCaretOffset(int32_t aCaretOffset) {
if (!mIntl) return NS_ERROR_FAILURE;
if (mIntl->IsLocal()) {
IntlLocal()->SetCaretOffset(aCaretOffset);
} else {
mIntl->AsRemote()->SetCaretOffset(aCaretOffset);
}
Intl()->SetCaretOffset(aCaretOffset);
return NS_OK;
}