mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 1564549 - Implement text navigation natively. r=geckoview-reviewers,Jamie,snorp
Differential Revision: https://phabricator.services.mozilla.com/D45600 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
85d6a5f093
commit
ac01020042
@ -301,6 +301,78 @@ void AccessibleWrap::ExploreByTouch(float aX, float aY) {
|
||||
}
|
||||
}
|
||||
|
||||
void AccessibleWrap::NavigateText(int32_t aGranularity, int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward,
|
||||
bool aSelect) {
|
||||
a11y::Pivot pivot(RootAccessible());
|
||||
|
||||
HyperTextAccessible* editable =
|
||||
(State() & states::EDITABLE) != 0 ? AsHyperText() : nullptr;
|
||||
|
||||
int32_t start = aStartOffset, end = aEndOffset;
|
||||
// If the accessible is an editable, set the virtual cursor position
|
||||
// to its caret offset. Otherwise use the document's virtual cursor
|
||||
// position as a starting offset.
|
||||
if (editable) {
|
||||
start = end = editable->CaretOffset();
|
||||
}
|
||||
|
||||
uint16_t pivotGranularity = nsIAccessiblePivot::LINE_BOUNDARY;
|
||||
switch (aGranularity) {
|
||||
case 1: // MOVEMENT_GRANULARITY_CHARACTER
|
||||
pivotGranularity = nsIAccessiblePivot::CHAR_BOUNDARY;
|
||||
break;
|
||||
case 2: // MOVEMENT_GRANULARITY_WORD
|
||||
pivotGranularity = nsIAccessiblePivot::WORD_BOUNDARY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t newOffset;
|
||||
Accessible* newAnchor = nullptr;
|
||||
if (aForward) {
|
||||
newAnchor = pivot.NextText(this, &start, &end, pivotGranularity);
|
||||
newOffset = end;
|
||||
} else {
|
||||
newAnchor = pivot.PrevText(this, &start, &end, pivotGranularity);
|
||||
newOffset = start;
|
||||
}
|
||||
|
||||
if (newAnchor && (start != aStartOffset || end != aEndOffset)) {
|
||||
RefPtr<AccEvent> event = new AccVCChangeEvent(
|
||||
newAnchor->Document(), this, aStartOffset, aEndOffset, newAnchor, start,
|
||||
end, nsIAccessiblePivot::REASON_NONE, pivotGranularity, eFromUserInput);
|
||||
nsEventShell::FireEvent(event);
|
||||
}
|
||||
|
||||
// If we are in an editable, move the caret to the new virtual cursor
|
||||
// offset.
|
||||
if (editable) {
|
||||
if (aSelect) {
|
||||
int32_t anchor = editable->CaretOffset();
|
||||
if (editable->SelectionCount()) {
|
||||
int32_t startSel, endSel;
|
||||
GetSelectionOrCaret(&startSel, &endSel);
|
||||
anchor = startSel == anchor ? endSel : startSel;
|
||||
}
|
||||
editable->SetSelectionBoundsAt(0, anchor, newOffset);
|
||||
} else {
|
||||
editable->SetCaretOffset(newOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset,
|
||||
int32_t* aEndOffset) {
|
||||
*aStartOffset = *aEndOffset = -1;
|
||||
if (HyperTextAccessible* textAcc = AsHyperText()) {
|
||||
if (!textAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) {
|
||||
*aStartOffset = *aEndOffset = textAcc->CaretOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState,
|
||||
uint8_t aActionCount) {
|
||||
uint32_t flags = 0;
|
||||
|
@ -39,6 +39,9 @@ class AccessibleWrap : public Accessible {
|
||||
|
||||
virtual void ExploreByTouch(float aX, float aY);
|
||||
|
||||
virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward, bool aSelect);
|
||||
|
||||
mozilla::java::GeckoBundle::LocalRef ToBundle(bool aSmall = false);
|
||||
|
||||
mozilla::java::GeckoBundle::LocalRef ToBundle(
|
||||
@ -89,6 +92,8 @@ class AccessibleWrap : public Accessible {
|
||||
|
||||
bool HandleLiveRegionEvent(AccEvent* aEvent);
|
||||
|
||||
void GetSelectionOrCaret(int32_t* aStartOffset, int32_t* aEndOffset);
|
||||
|
||||
static void GetRoleDescription(role aRole,
|
||||
nsIPersistentProperties* aAttributes,
|
||||
nsAString& aGeckoRole,
|
||||
|
@ -120,6 +120,13 @@ void ProxyAccessibleWrap::ExploreByTouch(float aX, float aY) {
|
||||
Proxy()->ID(), aX, aY);
|
||||
}
|
||||
|
||||
void ProxyAccessibleWrap::NavigateText(int32_t aGranularity,
|
||||
int32_t aStartOffset, int32_t aEndOffset,
|
||||
bool aForward, bool aSelect) {
|
||||
Unused << Proxy()->Document()->GetPlatformExtension()->SendNavigateText(
|
||||
Proxy()->ID(), aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
|
||||
}
|
||||
|
||||
role ProxyAccessibleWrap::WrapperRole() { return Proxy()->Role(); }
|
||||
|
||||
AccessibleWrap* ProxyAccessibleWrap::WrapperParent() {
|
||||
|
@ -62,6 +62,10 @@ class ProxyAccessibleWrap : public AccessibleWrap {
|
||||
virtual void Pivot(int32_t aGranularity, bool aForward,
|
||||
bool aInclusive) override;
|
||||
|
||||
virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward,
|
||||
bool aSelect) override;
|
||||
|
||||
virtual void ExploreByTouch(float aX, float aY) override;
|
||||
|
||||
virtual void WrapperDOMNodeID(nsString& aDOMNodeID) override;
|
||||
|
@ -124,6 +124,14 @@ void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) {
|
||||
FORWARD_ACTION_TO_ACCESSIBLE(ExploreByTouch, aX, aY);
|
||||
}
|
||||
|
||||
void SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward,
|
||||
bool aSelect) {
|
||||
FORWARD_ACTION_TO_ACCESSIBLE(NavigateText, aGranularity, aStartOffset,
|
||||
aEndOffset, aForward, aSelect);
|
||||
}
|
||||
|
||||
SessionAccessibility* SessionAccessibility::GetInstanceFor(
|
||||
ProxyAccessible* aAccessible) {
|
||||
auto tab =
|
||||
|
@ -56,6 +56,8 @@ class SessionAccessibility final
|
||||
void Click(int32_t aID);
|
||||
void Pivot(int32_t aID, int32_t aGranularity, bool aForward, bool aInclusive);
|
||||
void ExploreByTouch(int32_t aID, float aX, float aY);
|
||||
void NavigateText(int32_t aID, int32_t aGranularity, int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward, bool aSelect);
|
||||
void StartNativeAccessibility();
|
||||
|
||||
// Event methods
|
||||
|
@ -22,10 +22,11 @@ mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvPivot(
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvNavigateText(
|
||||
int32_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset,
|
||||
uint64_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset,
|
||||
bool aForward, bool aSelect) {
|
||||
if (auto acc = IdToAccessibleWrap(aID)) {
|
||||
// XXX: Forward to appropriate wrapper method.
|
||||
acc->NavigateText(aGranularity, aStartOffset, aEndOffset, aForward,
|
||||
aSelect);
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
|
@ -19,7 +19,7 @@ class DocAccessiblePlatformExtChild : public PDocAccessiblePlatformExtChild {
|
||||
mozilla::ipc::IPCResult RecvPivot(uint64_t aID, int32_t aGranularity,
|
||||
bool aForward, bool aInclusive);
|
||||
|
||||
mozilla::ipc::IPCResult RecvNavigateText(int32_t aID, int32_t aGranularity,
|
||||
mozilla::ipc::IPCResult RecvNavigateText(uint64_t aID, int32_t aGranularity,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset, bool aForward,
|
||||
bool aSelect);
|
||||
|
@ -17,7 +17,7 @@ child:
|
||||
|
||||
async Pivot(uint64_t aID, int32_t aGranularity, bool aForward, bool aInclusive);
|
||||
|
||||
async NavigateText(int32_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset, bool aForward, bool aSelect);
|
||||
async NavigateText(uint64_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset, bool aForward, bool aSelect);
|
||||
|
||||
async SetSelection(int32_t aID, int32_t aStart, int32_t aEnd);
|
||||
|
||||
|
@ -318,14 +318,17 @@ class ZZAccessibilityTest : BaseSessionTest() {
|
||||
} while (fromIndex != eventFromIndex || toIndex != eventToIndex)
|
||||
}
|
||||
|
||||
private fun waitUntilTextTraversed(fromIndex: Int, toIndex: Int) {
|
||||
private fun waitUntilTextTraversed(fromIndex: Int, toIndex: Int): Int {
|
||||
var nodeId: Int = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onTextTraversal(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
assertThat("fromIndex matches", event.fromIndex, equalTo(fromIndex))
|
||||
assertThat("toIndex matches", event.toIndex, equalTo(toIndex))
|
||||
}
|
||||
})
|
||||
return nodeId
|
||||
}
|
||||
|
||||
private fun waitUntilClick(checked: Boolean) {
|
||||
@ -414,6 +417,22 @@ class ZZAccessibilityTest : BaseSessionTest() {
|
||||
assertThat("text should be pasted", event.text[0].toString(), equalTo("hello cruel cruel cruel"))
|
||||
}
|
||||
})
|
||||
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArguments(0, 0))
|
||||
waitUntilTextSelectionChanged(0, 0)
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD, true))
|
||||
waitUntilTextSelectionChanged(0, 5)
|
||||
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CUT, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled
|
||||
override fun onTextChanged(event: AccessibilityEvent) {
|
||||
assertThat("text should be cut", event.text[0].toString(), equalTo(" cruel cruel cruel"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testMoveByCharacter() {
|
||||
@ -433,17 +452,17 @@ class ZZAccessibilityTest : BaseSessionTest() {
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
|
||||
waitUntilTextTraversed(0, 1) // "L"
|
||||
nodeId = waitUntilTextTraversed(0, 1) // "L"
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
|
||||
waitUntilTextTraversed(1, 2) // "o"
|
||||
nodeId = waitUntilTextTraversed(1, 2) // "o"
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
|
||||
waitUntilTextTraversed(0, 1) // "L"
|
||||
nodeId = waitUntilTextTraversed(0, 1) // "L"
|
||||
}
|
||||
|
||||
@Test fun testMoveByWord() {
|
||||
@ -463,12 +482,12 @@ class ZZAccessibilityTest : BaseSessionTest() {
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
|
||||
waitUntilTextTraversed(0, 5) // "Lorem"
|
||||
nodeId = waitUntilTextTraversed(0, 5) // "Lorem"
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
|
||||
waitUntilTextTraversed(6, 11) // "ipsum"
|
||||
nodeId = waitUntilTextTraversed(6, 11) // "ipsum"
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
|
||||
@ -496,17 +515,17 @@ class ZZAccessibilityTest : BaseSessionTest() {
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
|
||||
waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
|
||||
nodeId = waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
|
||||
waitUntilTextTraversed(18, 28) // "sit amet, "
|
||||
nodeId = waitUntilTextTraversed(18, 28) // "sit amet, "
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
|
||||
moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
|
||||
waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
|
||||
nodeId = waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
|
||||
}
|
||||
|
||||
@Test fun testHeadings() {
|
||||
|
@ -271,11 +271,7 @@ public class SessionAccessibility {
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", data);
|
||||
} else if (granularity > 0) {
|
||||
boolean extendSelection = arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
|
||||
data = new GeckoBundle(3);
|
||||
data.putString("direction", action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ? "Next" : "Previous");
|
||||
data.putInt("granularity", granularity);
|
||||
data.putBoolean("select", extendSelection);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityByGranularity", data);
|
||||
nativeProvider.navigateText(virtualViewId, granularity, mStartOffset, mEndOffset, action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, extendSelection);
|
||||
}
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_SET_SELECTION:
|
||||
@ -548,6 +544,8 @@ public class SessionAccessibility {
|
||||
private int mAccessibilityFocusedNode = 0;
|
||||
// The current node with focus
|
||||
private int mFocusedNode = 0;
|
||||
private int mStartOffset = -1;
|
||||
private int mEndOffset = -1;
|
||||
// Viewport cache
|
||||
final SparseArray<GeckoBundle> mViewportCache = new SparseArray<>();
|
||||
// Focus cache
|
||||
@ -807,6 +805,8 @@ public class SessionAccessibility {
|
||||
}
|
||||
break;
|
||||
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
|
||||
mStartOffset = -1;
|
||||
mEndOffset = -1;
|
||||
mAccessibilityFocusedNode = sourceId;
|
||||
break;
|
||||
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
|
||||
@ -816,6 +816,10 @@ public class SessionAccessibility {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
|
||||
mStartOffset = event.getFromIndex();
|
||||
mEndOffset = event.getToIndex();
|
||||
break;
|
||||
}
|
||||
|
||||
((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
|
||||
@ -864,6 +868,9 @@ public class SessionAccessibility {
|
||||
@WrapForJNI(dispatchTo = "gecko")
|
||||
public native void exploreByTouch(int id, float x, float y);
|
||||
|
||||
@WrapForJNI(dispatchTo = "gecko")
|
||||
public native void navigateText(int id, int granularity, int startOffset, int endOffset, boolean forward, boolean select);
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko", stubName = "SendEvent")
|
||||
private void sendEventNative(final int eventType, final int sourceId, final int className, final GeckoBundle eventData) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
|
Loading…
Reference in New Issue
Block a user