зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1686995 - Add AXTextSelectionDirection and AXTextSelectionGranularity to text selection events. r=morgan
.. and also add AXTextSelectionChangedFocus and AXTextStateSync when needed. Differential Revision: https://phabricator.services.mozilla.com/D102509
This commit is contained in:
Родитель
c738dc904e
Коммит
135c36619d
|
@ -216,11 +216,12 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
|
|||
|
||||
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
|
||||
AccCaretMoveEvent* event = downcast_accEvent(aEvent);
|
||||
if (event->IsSelectionCollapsed()) {
|
||||
// If the selection is collapsed, invalidate our text selection cache.
|
||||
int32_t caretOffset = event->GetCaretOffset();
|
||||
MOXTextMarkerDelegate* delegate =
|
||||
[MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
|
||||
int32_t caretOffset = event->GetCaretOffset();
|
||||
[delegate setCaretOffset:eventTarget at:caretOffset];
|
||||
if (event->IsSelectionCollapsed()) {
|
||||
// If the selection is collapsed, invalidate our text selection cache.
|
||||
[delegate setSelectionFrom:eventTarget
|
||||
at:caretOffset
|
||||
to:eventTarget
|
||||
|
|
|
@ -48,6 +48,10 @@ class GeckoTextMarker final {
|
|||
|
||||
bool operator<(const GeckoTextMarker& aPoint) const;
|
||||
|
||||
bool operator==(const GeckoTextMarker& aPoint) const {
|
||||
return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset;
|
||||
}
|
||||
|
||||
AccessibleOrProxy mContainer;
|
||||
int32_t mOffset;
|
||||
|
||||
|
|
|
@ -81,7 +81,8 @@ GeckoTextMarker::GeckoTextMarker(AccessibleOrProxy aDoc,
|
|||
AXTextMarkerRef aTextMarker) {
|
||||
MOZ_ASSERT(!aDoc.IsNull());
|
||||
OpaqueGeckoTextMarker opaqueMarker;
|
||||
if (AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
|
||||
if (aTextMarker &&
|
||||
AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
|
||||
memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker),
|
||||
sizeof(OpaqueGeckoTextMarker));
|
||||
if (DocumentExists(aDoc, opaqueMarker.mDoc)) {
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
@interface MOXTextMarkerDelegate : NSObject <MOXTextMarkerSupport> {
|
||||
mozilla::a11y::AccessibleOrProxy mGeckoDocAccessible;
|
||||
id mSelection;
|
||||
id mCaret;
|
||||
id mPrevCaret;
|
||||
}
|
||||
|
||||
+ (id)getOrCreateForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
|
||||
|
@ -30,12 +32,15 @@
|
|||
to:(mozilla::a11y::AccessibleOrProxy)endContainer
|
||||
at:(int32_t)endOffset;
|
||||
|
||||
- (void)setCaretOffset:(mozilla::a11y::AccessibleOrProxy)container
|
||||
at:(int32_t)offset;
|
||||
|
||||
- (NSDictionary*)selectionChangeInfo;
|
||||
|
||||
- (void)invalidateSelection;
|
||||
|
||||
- (mozilla::a11y::GeckoTextMarkerRange)selection;
|
||||
|
||||
- (BOOL)selectionIsCollapsed;
|
||||
|
||||
// override
|
||||
- (id)moxStartTextMarker;
|
||||
|
||||
|
@ -107,3 +112,47 @@
|
|||
- (void)moxSetSelectedTextMarkerRange:(id)textMarkerRange;
|
||||
|
||||
@end
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
enum AXTextEditType {
|
||||
AXTextEditTypeUnknown,
|
||||
AXTextEditTypeDelete,
|
||||
AXTextEditTypeInsert,
|
||||
AXTextEditTypeTyping,
|
||||
AXTextEditTypeDictation,
|
||||
AXTextEditTypeCut,
|
||||
AXTextEditTypePaste,
|
||||
AXTextEditTypeAttributesChange
|
||||
};
|
||||
|
||||
enum AXTextStateChangeType {
|
||||
AXTextStateChangeTypeUnknown,
|
||||
AXTextStateChangeTypeEdit,
|
||||
AXTextStateChangeTypeSelectionMove,
|
||||
AXTextStateChangeTypeSelectionExtend
|
||||
};
|
||||
|
||||
enum AXTextSelectionDirection {
|
||||
AXTextSelectionDirectionUnknown,
|
||||
AXTextSelectionDirectionBeginning,
|
||||
AXTextSelectionDirectionEnd,
|
||||
AXTextSelectionDirectionPrevious,
|
||||
AXTextSelectionDirectionNext,
|
||||
AXTextSelectionDirectionDiscontiguous
|
||||
};
|
||||
|
||||
enum AXTextSelectionGranularity {
|
||||
AXTextSelectionGranularityUnknown,
|
||||
AXTextSelectionGranularityCharacter,
|
||||
AXTextSelectionGranularityWord,
|
||||
AXTextSelectionGranularityLine,
|
||||
AXTextSelectionGranularitySentence,
|
||||
AXTextSelectionGranularityParagraph,
|
||||
AXTextSelectionGranularityPage,
|
||||
AXTextSelectionGranularityDocument,
|
||||
AXTextSelectionGranularityAll
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,97 @@ static nsDataHashtable<nsUint64HashKey, MOXTextMarkerDelegate*> sDelegates;
|
|||
mSelection = [selection.CreateAXTextMarkerRange() retain];
|
||||
}
|
||||
|
||||
- (void)setCaretOffset:(mozilla::a11y::AccessibleOrProxy)container
|
||||
at:(int32_t)offset {
|
||||
GeckoTextMarker caretMarker(container, offset);
|
||||
|
||||
mPrevCaret = mCaret;
|
||||
mCaret = [caretMarker.CreateAXTextMarker() retain];
|
||||
}
|
||||
|
||||
// This returns an info object to pass with AX SelectedTextChanged events.
|
||||
// It uses the current and previous caret position to make decisions
|
||||
// regarding which attributes to add to the info object.
|
||||
- (NSDictionary*)selectionChangeInfo {
|
||||
GeckoTextMarkerRange selectedGeckoRange =
|
||||
GeckoTextMarkerRange(mGeckoDocAccessible, mSelection);
|
||||
|
||||
// This is the base info object, includes the selected marker range and
|
||||
// the change type depending on the collapsed state of the selection.
|
||||
NSMutableDictionary* info = [@{
|
||||
@"AXSelectedTextMarkerRange" : selectedGeckoRange.IsValid() ? mSelection
|
||||
: [NSNull null],
|
||||
@"AXTextStateChangeType" :
|
||||
selectedGeckoRange.mStart == selectedGeckoRange.mEnd
|
||||
? @(AXTextStateChangeTypeSelectionMove)
|
||||
: @(AXTextStateChangeTypeSelectionExtend)
|
||||
} mutableCopy];
|
||||
|
||||
GeckoTextMarker caretMarker(mGeckoDocAccessible, mCaret);
|
||||
GeckoTextMarker prevCaretMarker(mGeckoDocAccessible, mPrevCaret);
|
||||
if (!caretMarker.IsValid()) {
|
||||
// If the current caret is invalid, stop here and return base info.
|
||||
return info;
|
||||
}
|
||||
|
||||
mozAccessible* caretEditable =
|
||||
[GetNativeFromGeckoAccessible(caretMarker.mContainer)
|
||||
moxEditableAncestor];
|
||||
|
||||
if (!caretEditable) {
|
||||
// If we are not in an editable, VO expects AXTextStateSync to be present
|
||||
// and true.
|
||||
info[@"AXTextStateSync"] = @YES;
|
||||
}
|
||||
|
||||
if (!prevCaretMarker.IsValid() || caretMarker == prevCaretMarker) {
|
||||
// If we have no stored previous marker, stop here.
|
||||
return info;
|
||||
}
|
||||
|
||||
mozAccessible* prevCaretEditable =
|
||||
[GetNativeFromGeckoAccessible(prevCaretMarker.mContainer)
|
||||
moxEditableAncestor];
|
||||
|
||||
if (prevCaretEditable != caretEditable) {
|
||||
// If the caret goes in or out of an editable, consider the
|
||||
// move direction "discontiguous".
|
||||
info[@"AXTextSelectionDirection"] =
|
||||
@(AXTextSelectionDirectionDiscontiguous);
|
||||
if ([[caretEditable moxFocused] boolValue]) {
|
||||
// If the caret is in a new focused editable, VO expects this attribute to
|
||||
// be present and to be true.
|
||||
info[@"AXTextSelectionChangedFocus"] = @YES;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool isForward = prevCaretMarker < caretMarker;
|
||||
uint32_t deltaLength =
|
||||
GeckoTextMarkerRange(isForward ? prevCaretMarker : caretMarker,
|
||||
isForward ? caretMarker : prevCaretMarker)
|
||||
.Length();
|
||||
|
||||
// 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)
|
||||
}];
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
- (void)invalidateSelection {
|
||||
[mSelection release];
|
||||
[mCaret release];
|
||||
[mPrevCaret release];
|
||||
mSelection = nil;
|
||||
}
|
||||
|
||||
|
@ -79,13 +168,6 @@ static nsDataHashtable<nsUint64HashKey, MOXTextMarkerDelegate*> sDelegates;
|
|||
return mozilla::a11y::GeckoTextMarkerRange(mGeckoDocAccessible, mSelection);
|
||||
}
|
||||
|
||||
- (BOOL)selectionIsCollapsed {
|
||||
GeckoTextMarkerRange range(mGeckoDocAccessible, mSelection);
|
||||
|
||||
return range.mStart.mContainer == range.mEnd.mContainer &&
|
||||
range.mStart.mOffset == range.mEnd.mOffset;
|
||||
}
|
||||
|
||||
- (id)moxStartTextMarker {
|
||||
GeckoTextMarker geckoTextPoint(mGeckoDocAccessible, 0);
|
||||
return geckoTextPoint.CreateAXTextMarker();
|
||||
|
|
|
@ -110,10 +110,11 @@ void ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t aState,
|
|||
void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset,
|
||||
bool aIsSelectionCollapsed) {
|
||||
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
|
||||
if (aIsSelectionCollapsed) {
|
||||
// If selection is collapsed, invalidate selection.
|
||||
MOXTextMarkerDelegate* delegate =
|
||||
[MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
|
||||
[delegate setCaretOffset:aTarget at:aOffset];
|
||||
if (aIsSelectionCollapsed) {
|
||||
// If selection is collapsed, invalidate selection.
|
||||
[delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];
|
||||
}
|
||||
|
||||
|
|
|
@ -1021,18 +1021,11 @@ struct RoleDescrComparator {
|
|||
// We consider any caret move event to be a selected text change event.
|
||||
// So dispatching an event for EVENT_TEXT_SELECTION_CHANGED would be
|
||||
// reduntant.
|
||||
id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate];
|
||||
id selectedRange = [delegate moxSelectedTextMarkerRange];
|
||||
BOOL isCollapsed =
|
||||
[static_cast<MOXTextMarkerDelegate*>(delegate) selectionIsCollapsed];
|
||||
NSDictionary* userInfo = @{
|
||||
@"AXTextChangeElement" : self,
|
||||
@"AXSelectedTextMarkerRange" :
|
||||
(selectedRange ? selectedRange : [NSNull null]),
|
||||
@"AXTextStateChangeType" : isCollapsed
|
||||
? @(AXTextStateChangeTypeSelectionMove)
|
||||
: @(AXTextStateChangeTypeSelectionExtend)
|
||||
};
|
||||
MOXTextMarkerDelegate* delegate =
|
||||
static_cast<MOXTextMarkerDelegate*>([self moxTextMarkerDelegate]);
|
||||
NSMutableDictionary* userInfo =
|
||||
[[delegate selectionChangeInfo] mutableCopy];
|
||||
userInfo[@"AXTextChangeElement"] = self;
|
||||
|
||||
mozAccessible* webArea = [self topWebArea];
|
||||
[webArea
|
||||
|
|
|
@ -111,27 +111,3 @@
|
|||
- (NSValue*)moxBoundsForRange:(NSValue*)range;
|
||||
|
||||
@end
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
enum AXTextEditType {
|
||||
AXTextEditTypeUnknown,
|
||||
AXTextEditTypeDelete,
|
||||
AXTextEditTypeInsert,
|
||||
AXTextEditTypeTyping,
|
||||
AXTextEditTypeDictation,
|
||||
AXTextEditTypeCut,
|
||||
AXTextEditTypePaste,
|
||||
AXTextEditTypeAttributesChange
|
||||
};
|
||||
|
||||
enum AXTextStateChangeType {
|
||||
AXTextStateChangeTypeUnknown,
|
||||
AXTextStateChangeTypeEdit,
|
||||
AXTextStateChangeTypeSelectionMove,
|
||||
AXTextStateChangeTypeSelectionExtend
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,43 +57,67 @@ function testValueChangedEventData(
|
|||
is(str, expectedWordAtLeft);
|
||||
}
|
||||
|
||||
function matchWebArea(expectedId, expectedStateChangeType) {
|
||||
// Return true if the first given object a subset of the second
|
||||
function isSubset(subset, superset) {
|
||||
if (typeof subset != "object" || typeof superset != "object") {
|
||||
return superset == subset;
|
||||
}
|
||||
|
||||
for (let [prop, val] of Object.entries(subset)) {
|
||||
if (!isSubset(val, superset[prop])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function matchWebArea(expectedId, expectedInfo) {
|
||||
return (iface, data) => {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let textChangeElemID = data.AXTextChangeElement.getAttributeValue(
|
||||
"AXDOMIdentifier"
|
||||
);
|
||||
|
||||
return (
|
||||
iface.getAttributeValue("AXRole") == "AXWebArea" &&
|
||||
!!data &&
|
||||
data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier") ==
|
||||
expectedId &&
|
||||
data.AXTextStateChangeType == expectedStateChangeType
|
||||
textChangeElemID == expectedId &&
|
||||
isSubset(expectedInfo, data)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function matchInput(expectedId, expectedStateChangeType) {
|
||||
return (iface, data) =>
|
||||
function matchInput(expectedId, expectedInfo) {
|
||||
return (iface, data) => {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
iface.getAttributeValue("AXDOMIdentifier") == expectedId &&
|
||||
!!data &&
|
||||
data.AXTextStateChangeType == expectedStateChangeType;
|
||||
isSubset(expectedInfo, data)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
async function synthKeyAndTestSelectionChanged(
|
||||
synthKey,
|
||||
synthEvent,
|
||||
expectedId,
|
||||
expectedSelectionString
|
||||
expectedSelectionString,
|
||||
expectedSelectionInfo
|
||||
) {
|
||||
// If the expected string is empty, it is a caret move/collapse
|
||||
let expectedStateChangeType = expectedSelectionString
|
||||
? AXTextStateChangeTypeSelectionExtend
|
||||
: AXTextStateChangeTypeSelectionMove;
|
||||
let selectionChangedEvents = Promise.all([
|
||||
waitForMacEventWithInfo(
|
||||
"AXSelectedTextChanged",
|
||||
matchWebArea(expectedId, expectedStateChangeType)
|
||||
matchWebArea(expectedId, expectedSelectionInfo)
|
||||
),
|
||||
waitForMacEventWithInfo(
|
||||
"AXSelectedTextChanged",
|
||||
matchInput(expectedId, expectedStateChangeType)
|
||||
matchInput(expectedId, expectedSelectionInfo)
|
||||
),
|
||||
]);
|
||||
|
||||
|
@ -141,16 +165,42 @@ async function synthKeyAndTestValueChanged(
|
|||
expectedWordAtLeft
|
||||
) {
|
||||
let valueChangedEvents = Promise.all([
|
||||
waitForMacEventWithInfo(
|
||||
waitForMacEvent(
|
||||
"AXSelectedTextChanged",
|
||||
matchWebArea(expectedTextSelectionId, AXTextStateChangeTypeSelectionMove)
|
||||
matchWebArea(expectedTextSelectionId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
||||
})
|
||||
),
|
||||
waitForMacEvent(
|
||||
"AXSelectedTextChanged",
|
||||
matchInput(expectedTextSelectionId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
||||
})
|
||||
),
|
||||
waitForMacEventWithInfo(
|
||||
"AXSelectedTextChanged",
|
||||
matchInput(expectedTextSelectionId, AXTextStateChangeTypeSelectionMove)
|
||||
"AXValueChanged",
|
||||
matchWebArea(expectedId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeEdit,
|
||||
AXTextChangeValues: [
|
||||
{
|
||||
AXTextChangeValue: expectedChangeValue,
|
||||
AXTextEditType: expectedEditType,
|
||||
},
|
||||
],
|
||||
})
|
||||
),
|
||||
waitForMacEventWithInfo(
|
||||
"AXValueChanged",
|
||||
matchInput(expectedId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeEdit,
|
||||
AXTextChangeValues: [
|
||||
{
|
||||
AXTextChangeValue: expectedChangeValue,
|
||||
AXTextEditType: expectedEditType,
|
||||
},
|
||||
],
|
||||
})
|
||||
),
|
||||
waitForMacEventWithInfo("AXValueChanged", matchWebArea(expectedId, 1)),
|
||||
waitForMacEventWithInfo("AXValueChanged", matchInput(expectedId, 1)),
|
||||
]);
|
||||
|
||||
EventUtils.synthesizeKey(synthKey, synthEvent);
|
||||
|
@ -186,11 +236,15 @@ async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
|
|||
),
|
||||
waitForMacEventWithInfo(
|
||||
"AXSelectedTextChanged",
|
||||
matchWebArea(selectionId, AXTextStateChangeTypeSelectionMove)
|
||||
matchWebArea(selectionId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
||||
})
|
||||
),
|
||||
waitForMacEventWithInfo(
|
||||
"AXSelectedTextChanged",
|
||||
matchInput(selectionId, AXTextStateChangeTypeSelectionMove)
|
||||
matchInput(selectionId, {
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
||||
})
|
||||
),
|
||||
]);
|
||||
input.setAttributeValue("AXFocused", true);
|
||||
|
@ -242,32 +296,74 @@ async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
|
|||
await testTextDelete("d", "worl");
|
||||
await testTextDelete("l", "wor");
|
||||
|
||||
await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, selectionId, "");
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowLeft",
|
||||
{ shiftKey: true },
|
||||
null,
|
||||
selectionId,
|
||||
"o"
|
||||
"",
|
||||
{
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
||||
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
||||
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
||||
}
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowLeft",
|
||||
{ shiftKey: true },
|
||||
selectionId,
|
||||
"wo"
|
||||
"o",
|
||||
{
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
||||
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
||||
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
||||
}
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowLeft",
|
||||
{ shiftKey: true },
|
||||
selectionId,
|
||||
"wo",
|
||||
{
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
||||
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
||||
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
||||
}
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowLeft",
|
||||
null,
|
||||
selectionId,
|
||||
"",
|
||||
{ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, selectionId, "");
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_Home",
|
||||
{ shiftKey: true },
|
||||
selectionId,
|
||||
"hello "
|
||||
"hello ",
|
||||
{
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
||||
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
||||
AXTextSelectionGranularity: AXTextSelectionGranularityWord,
|
||||
}
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowLeft",
|
||||
null,
|
||||
selectionId,
|
||||
"",
|
||||
{ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
|
||||
);
|
||||
await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, selectionId, "");
|
||||
await synthKeyAndTestSelectionChanged(
|
||||
"KEY_ArrowRight",
|
||||
{ shiftKey: true, altKey: true },
|
||||
selectionId,
|
||||
"hello"
|
||||
"hello",
|
||||
{
|
||||
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
||||
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
||||
AXTextSelectionGranularity: AXTextSelectionGranularityWord,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ addAccessibleTask(`<p id="p">Hello World</p>`, async (browser, accDoc) => {
|
|||
|
||||
let evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
|
||||
return (
|
||||
info.AXTextStateSync &&
|
||||
info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
|
||||
elem.getAttributeValue("AXRole") == "AXWebArea"
|
||||
);
|
||||
|
@ -48,6 +49,7 @@ addAccessibleTask(`<p id="p">Hello World</p>`, async (browser, accDoc) => {
|
|||
|
||||
evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
|
||||
return (
|
||||
info.AXTextStateSync &&
|
||||
info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
|
||||
elem.getAttributeValue("AXRole") == "AXWebArea"
|
||||
);
|
||||
|
@ -60,6 +62,7 @@ addAccessibleTask(`<p id="p">Hello World</p>`, async (browser, accDoc) => {
|
|||
// Collapse selection
|
||||
evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
|
||||
return (
|
||||
info.AXTextStateSync &&
|
||||
info.AXTextStateChangeType == AXTextStateChangeTypeSelectionMove &&
|
||||
elem.getAttributeValue("AXRole") == "AXWebArea"
|
||||
);
|
||||
|
@ -130,3 +133,55 @@ addAccessibleTask(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Test text selection with focus change
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`<p id="p">Hello <input id="input"></p>`,
|
||||
async (browser, accDoc) => {
|
||||
let macDoc = accDoc.nativeInterface.QueryInterface(
|
||||
Ci.nsIAccessibleMacInterface
|
||||
);
|
||||
|
||||
let evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
|
||||
return (
|
||||
info.AXTextStateSync &&
|
||||
info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
|
||||
elem.getAttributeValue("AXRole") == "AXWebArea"
|
||||
);
|
||||
});
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
let p = content.document.getElementById("p");
|
||||
let r = new content.Range();
|
||||
r.setStart(p.firstChild, 1);
|
||||
r.setEnd(p.firstChild, 3);
|
||||
|
||||
let s = content.getSelection();
|
||||
s.addRange(r);
|
||||
});
|
||||
await evt;
|
||||
|
||||
let range = macDoc.getAttributeValue("AXSelectedTextMarkerRange");
|
||||
is(stringForRange(macDoc, range), "el");
|
||||
|
||||
let events = Promise.all([
|
||||
waitForMacEvent("AXFocusedUIElementChanged"),
|
||||
waitForMacEventWithInfo("AXSelectedTextChanged"),
|
||||
]);
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.getElementById("input").focus();
|
||||
});
|
||||
let [, { data }] = await events;
|
||||
ok(
|
||||
data.AXTextSelectionChangedFocus,
|
||||
"have AXTextSelectionChangedFocus in event info"
|
||||
);
|
||||
ok(!data.AXTextStateSync, "no AXTextStateSync in editables");
|
||||
is(
|
||||
data.AXTextSelectionDirection,
|
||||
AXTextSelectionDirectionDiscontiguous,
|
||||
"discontigous direction"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
/* exported getNativeInterface, waitForMacEventWithInfo, waitForMacEvent,
|
||||
NSRange, NSDictionary, stringForRange, AXTextStateChangeTypeEdit,
|
||||
AXTextEditTypeDelete, AXTextEditTypeTyping, AXTextStateChangeTypeSelectionMove,
|
||||
AXTextStateChangeTypeSelectionExtend */
|
||||
AXTextStateChangeTypeSelectionExtend, AXTextSelectionDirectionUnknown,
|
||||
AXTextSelectionDirectionPrevious, AXTextSelectionDirectionNext,
|
||||
AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown,
|
||||
AXTextSelectionGranularityCharacter, AXTextSelectionGranularityWord */
|
||||
|
||||
// Load the shared-head file first.
|
||||
/* import-globals-from ../shared-head.js */
|
||||
|
@ -32,6 +35,17 @@ const AXTextStateChangeTypeSelectionExtend = 3;
|
|||
const AXTextEditTypeDelete = 1;
|
||||
const AXTextEditTypeTyping = 3;
|
||||
|
||||
// AXTextSelectionDirection enum values
|
||||
const AXTextSelectionDirectionUnknown = 0;
|
||||
const AXTextSelectionDirectionPrevious = 3;
|
||||
const AXTextSelectionDirectionNext = 4;
|
||||
const AXTextSelectionDirectionDiscontiguous = 5;
|
||||
|
||||
// AXTextSelectionGranularity enum values
|
||||
const AXTextSelectionGranularityUnknown = 0;
|
||||
const AXTextSelectionGranularityCharacter = 1;
|
||||
const AXTextSelectionGranularityWord = 2;
|
||||
|
||||
function getNativeInterface(accDoc, id) {
|
||||
return findAccessibleChildByID(accDoc, id).nativeInterface.QueryInterface(
|
||||
Ci.nsIAccessibleMacInterface
|
||||
|
|
Загрузка…
Ссылка в новой задаче