Bug 1713050 - P3: Add more granularities to AXSelectedTextChanged events. r=morgan

Differential Revision: https://phabricator.services.mozilla.com/D139747
This commit is contained in:
Eitan Isaacson 2022-03-16 05:56:26 +00:00
parent a479c8f191
commit ce4771430d
12 changed files with 124 additions and 39 deletions

View File

@ -111,7 +111,8 @@ void a11y::ProxyStateChangeEvent(RemoteAccessible* aTarget, uint64_t aState,
}
void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed) {
bool aIsSelectionCollapsed,
int32_t aGranularity) {
RefPtr<SessionAccessibility> sessionAcc =
SessionAccessibility::GetInstanceFor(aTarget);

View File

@ -1372,7 +1372,8 @@ void a11y::ProxyStateChangeEvent(RemoteAccessible* aTarget, uint64_t aState,
}
void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed) {
bool aIsSelectionCollapsed,
int32_t aGranularity) {
AtkObject* wrapper = GetWrapperFor(aTarget);
g_signal_emit_by_name(wrapper, "text_caret_moved", aOffset);
}

View File

@ -104,10 +104,11 @@ void ProxyStateChangeEvent(RemoteAccessible* aTarget, uint64_t aState,
void ProxyFocusEvent(RemoteAccessible* aTarget,
const LayoutDeviceIntRect& aCaretRect);
void ProxyCaretMoveEvent(RemoteAccessible* aTarget,
const LayoutDeviceIntRect& aCaretRect);
const LayoutDeviceIntRect& aCaretRect,
int32_t aGranularity);
#else
void ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed);
bool aIsSelectionCollapsed, int32_t aGranularity);
#endif
void ProxyTextChangeEvent(RemoteAccessible* aTarget, const nsString& aStr,
int32_t aStart, uint32_t aLen, bool aIsInsert,

View File

@ -364,9 +364,9 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
}
#if defined(XP_WIN)
ProxyCaretMoveEvent(proxy, aCaretRect);
ProxyCaretMoveEvent(proxy, aCaretRect, aGranularity);
#else
ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed);
ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed, aGranularity);
#endif
if (!nsCoreUtils::AccEventObserversExist()) {

View File

@ -226,7 +226,9 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
int32_t caretOffset = event->GetCaretOffset();
MOXTextMarkerDelegate* delegate =
[MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
[delegate setCaretOffset:eventTarget at:caretOffset];
[delegate setCaretOffset:eventTarget
at:caretOffset
moveGranularity:event->GetGranularity()];
if (event->IsSelectionCollapsed()) {
// If the selection is collapsed, invalidate our text selection cache.
[delegate setSelectionFrom:eventTarget

View File

@ -15,6 +15,7 @@
AXTextMarkerRangeRef mSelection;
AXTextMarkerRef mCaret;
AXTextMarkerRef mPrevCaret;
int32_t mCaretMoveGranularity;
}
+ (id)getOrCreateForDoc:(mozilla::a11y::Accessible*)aDoc;
@ -30,7 +31,9 @@
to:(mozilla::a11y::Accessible*)endContainer
at:(int32_t)endOffset;
- (void)setCaretOffset:(mozilla::a11y::Accessible*)container at:(int32_t)offset;
- (void)setCaretOffset:(mozilla::a11y::Accessible*)container
at:(int32_t)offset
moveGranularity:(int32_t)granularity;
- (NSDictionary*)selectionChangeInfo;

View File

@ -12,6 +12,7 @@
#include "mozAccessible.h"
#include "mozilla/Preferences.h"
#include "nsISelectionListener.h"
using namespace mozilla::a11y;
@ -52,6 +53,8 @@ static nsTHashMap<nsPtrHashKey<mozilla::a11y::Accessible>,
mGeckoDocAccessible = aDoc;
}
mCaretMoveGranularity = nsISelectionListener::NO_AMOUNT;
return self;
}
@ -76,11 +79,14 @@ static nsTHashMap<nsPtrHashKey<mozilla::a11y::Accessible>,
}
- (void)setCaretOffset:(mozilla::a11y::Accessible*)container
at:(int32_t)offset {
at:(int32_t)offset
moveGranularity:(int32_t)granularity {
GeckoTextMarker caretMarker(container, offset);
mPrevCaret = mCaret;
mCaret = caretMarker.CreateAXTextMarker();
mCaretMoveGranularity = granularity;
CFRetain(mCaret);
}
@ -146,21 +152,43 @@ static nsTHashMap<nsPtrHashKey<mozilla::a11y::Accessible>,
}
bool isForward = prevCaretMarker < caretMarker;
uint32_t deltaLength =
GeckoTextMarkerRange(isForward ? prevCaretMarker : caretMarker,
isForward ? caretMarker : prevCaretMarker)
.Length();
int direction = isForward ? AXTextSelectionDirectionNext
: AXTextSelectionDirectionPrevious;
int32_t granularity = AXTextSelectionGranularityUnknown;
switch (mCaretMoveGranularity) {
case nsISelectionListener::CHARACTER_AMOUNT:
case nsISelectionListener::CLUSTER_AMOUNT:
granularity = AXTextSelectionGranularityCharacter;
break;
case nsISelectionListener::WORD_AMOUNT:
case nsISelectionListener::WORDNOSPACE_AMOUNT:
granularity = AXTextSelectionGranularityWord;
break;
case nsISelectionListener::LINE_AMOUNT:
granularity = AXTextSelectionGranularityLine;
break;
case nsISelectionListener::BEGINLINE_AMOUNT:
direction = AXTextSelectionDirectionBeginning;
granularity = AXTextSelectionGranularityLine;
break;
case nsISelectionListener::ENDLINE_AMOUNT:
direction = AXTextSelectionDirectionEnd;
granularity = AXTextSelectionGranularityLine;
break;
case nsISelectionListener::PARAGRAPH_AMOUNT:
granularity = AXTextSelectionGranularityParagraph;
break;
default:
break;
}
// Determine selection direction with marker comparison.
// If the delta between the two markers is more than one, consider it
// a word. Not accurate, but good enough for VO.
[info addEntriesFromDictionary:@{
@"AXTextSelectionDirection" : isForward
? @(AXTextSelectionDirectionNext)
: @(AXTextSelectionDirectionPrevious),
@"AXTextSelectionGranularity" : deltaLength == 1
? @(AXTextSelectionGranularityCharacter)
: @(AXTextSelectionGranularityWord)
@"AXTextSelectionDirection" : @(direction),
@"AXTextSelectionGranularity" : @(granularity)
}];
return info;

View File

@ -111,11 +111,11 @@ void ProxyStateChangeEvent(RemoteAccessible* aProxy, uint64_t aState,
}
void ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed) {
bool aIsSelectionCollapsed, int32_t aGranularity) {
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
MOXTextMarkerDelegate* delegate =
[MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
[delegate setCaretOffset:aTarget at:aOffset];
[delegate setCaretOffset:aTarget at:aOffset moveGranularity:aGranularity];
if (aIsSelectionCollapsed) {
// If selection is collapsed, invalidate selection.
[delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];

View File

@ -22,7 +22,8 @@ void a11y::ProxyEvent(RemoteAccessible*, uint32_t) {}
void a11y::ProxyStateChangeEvent(RemoteAccessible*, uint64_t, bool) {}
void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed) {}
bool aIsSelectionCollapsed,
int32_t aGranularity) {}
void a11y::ProxyTextChangeEvent(RemoteAccessible*, const nsString&, int32_t,
uint32_t, bool, bool) {}

View File

@ -224,7 +224,7 @@ async function synthKeyAndTestValueChanged(
);
}
async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
async function focusIntoInput(accDoc, inputId, innerContainerId) {
let selectionId = innerContainerId ? innerContainerId : inputId;
let input = getNativeInterface(accDoc, inputId);
ok(!input.getAttributeValue("AXFocused"), "input is not focused");
@ -249,6 +249,11 @@ async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
]);
input.setAttributeValue("AXFocused", true);
await events;
}
async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
let selectionId = innerContainerId ? innerContainerId : inputId;
await focusIntoInput(accDoc, inputId, innerContainerId);
async function testTextInput(
synthKey,
@ -337,14 +342,14 @@ async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
{ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
);
await synthKeyAndTestSelectionChanged(
"KEY_Home",
{ shiftKey: true },
"KEY_ArrowLeft",
{ shiftKey: true, metaKey: true },
selectionId,
"hello ",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
AXTextSelectionGranularity: AXTextSelectionGranularityWord,
AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
await synthKeyAndTestSelectionChanged(
@ -372,7 +377,8 @@ addAccessibleTask(
`<a href="#">link</a> <input id="input">`,
async (browser, accDoc) => {
await focusIntoInputAndType(accDoc, "input");
}
},
{ topLevel: true, iframe: true, remoteIframe: true }
);
// Test content editable
@ -390,15 +396,6 @@ addAccessibleTask(
}
);
// Test text input in iframe
addAccessibleTask(
`<a href="#">link</a> <input id="input">`,
async (browser, accDoc) => {
await focusIntoInputAndType(accDoc, "input");
},
{ iframe: true }
);
// Test input that gets role::EDITCOMBOBOX
addAccessibleTask(`<input type="text" id="box">`, async (browser, accDoc) => {
const box = getNativeInterface(accDoc, "box");
@ -410,3 +407,48 @@ addAccessibleTask(`<input type="text" id="box">`, async (browser, accDoc) => {
);
await focusIntoInputAndType(accDoc, "box");
});
// Test multiline caret control in a text area
addAccessibleTask(
`<textarea id="input" cols="15">one two three four five six seven eight</textarea>`,
async (browser, accDoc) => {
await focusIntoInput(accDoc, "input");
await synthKeyAndTestSelectionChanged("KEY_ArrowRight", null, "input", "", {
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
});
await synthKeyAndTestSelectionChanged("KEY_ArrowDown", null, "input", "", {
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
});
await synthKeyAndTestSelectionChanged(
"KEY_ArrowLeft",
{ metaKey: true },
"input",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
await synthKeyAndTestSelectionChanged(
"KEY_ArrowRight",
{ metaKey: true },
"input",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionEnd,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
},
{ topLevel: true, iframe: true, remoteIframe: true }
);

View File

@ -10,7 +10,9 @@
AXTextStateChangeTypeSelectionExtend, AXTextSelectionDirectionUnknown,
AXTextSelectionDirectionPrevious, AXTextSelectionDirectionNext,
AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown,
AXTextSelectionGranularityCharacter, AXTextSelectionGranularityWord */
AXTextSelectionDirectionBeginning, AXTextSelectionDirectionEnd,
AXTextSelectionGranularityCharacter, AXTextSelectionGranularityWord,
AXTextSelectionGranularityLine */
// Load the shared-head file first.
/* import-globals-from ../shared-head.js */
@ -37,6 +39,8 @@ const AXTextEditTypeTyping = 3;
// AXTextSelectionDirection enum values
const AXTextSelectionDirectionUnknown = 0;
const AXTextSelectionDirectionBeginning = 1;
const AXTextSelectionDirectionEnd = 2;
const AXTextSelectionDirectionPrevious = 3;
const AXTextSelectionDirectionNext = 4;
const AXTextSelectionDirectionDiscontiguous = 5;
@ -45,6 +49,7 @@ const AXTextSelectionDirectionDiscontiguous = 5;
const AXTextSelectionGranularityUnknown = 0;
const AXTextSelectionGranularityCharacter = 1;
const AXTextSelectionGranularityWord = 2;
const AXTextSelectionGranularityLine = 3;
function getNativeInterface(accDoc, id) {
return findAccessibleChildByID(accDoc, id).nativeInterface.QueryInterface(

View File

@ -172,7 +172,8 @@ void a11y::ProxyFocusEvent(RemoteAccessible* aTarget,
}
void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget,
const LayoutDeviceIntRect& aCaretRect) {
const LayoutDeviceIntRect& aCaretRect,
int32_t aGranularity) {
AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect);
MsaaAccessible::FireWinEvent(aTarget,
nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED);