Bug 1647483 - Add a TextRange getter to text selection change events. r=Jamie

This includes 3 changes:
1. Add a lazy ranges getter to AccTextSelChangeEvent.
2. Create an XPCOM interface for testing purposes.
3. Add IPDL bindings for passing ranges in e10s.

Differential Revision: https://phabricator.services.mozilla.com/D80556
This commit is contained in:
Eitan Isaacson 2020-06-23 18:52:42 +00:00
Родитель 70c8aeee0b
Коммит ccdf272301
16 изменённых файлов: 175 добавлений и 32 удалений

Просмотреть файл

@ -10,11 +10,15 @@
#include "DocAccessible.h"
#include "xpcAccEvents.h"
#include "States.h"
#include "TextRange.h"
#include "xpcAccessibleDocument.h"
#include "xpcAccessibleTextRange.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/UserActivation.h"
#include "nsIMutableArray.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -134,6 +138,11 @@ bool AccTextSelChangeEvent::IsCaretMoveOnly() const {
nsISelectionListener::COLLAPSETOEND_REASON)) == 0);
}
void AccTextSelChangeEvent::SelectionRanges(
nsTArray<TextRange>* aRanges) const {
TextRange::TextRangesFromSelection(mSel, aRanges);
}
////////////////////////////////////////////////////////////////////////////////
// AccSelChangeEvent
////////////////////////////////////////////////////////////////////////////////
@ -235,6 +244,24 @@ already_AddRefed<nsIAccessibleEvent> a11y::MakeXPCEvent(AccEvent* aEvent) {
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eTextSelChangeEvent)) {
AccTextSelChangeEvent* tsc = downcast_accEvent(aEvent);
AutoTArray<TextRange, 1> ranges;
tsc->SelectionRanges(&ranges);
nsCOMPtr<nsIMutableArray> xpcRanges =
do_CreateInstance(NS_ARRAY_CONTRACTID);
uint32_t len = ranges.Length();
for (uint32_t idx = 0; idx < len; idx++) {
xpcRanges->AppendElement(
new xpcAccessibleTextRange(std::move(ranges[idx])));
}
xpEvent = new xpcAccTextSelectionChangeEvent(
type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, xpcRanges);
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) {
AccVCChangeEvent* vcc = downcast_accEvent(aEvent);
xpEvent = new xpcAccVirtualCursorChangeEvent(

Просмотреть файл

@ -21,6 +21,7 @@ namespace a11y {
class DocAccessible;
class EventQueue;
class TextRange;
// Constants used to point whether the event is from user input.
enum EIsFromUserInput {
@ -380,6 +381,11 @@ class AccTextSelChangeEvent : public AccEvent {
*/
bool IsCaretMoveOnly() const;
/**
* Return selection ranges in document/control.
*/
void SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const;
private:
RefPtr<dom::Selection> mSel;
int32_t mReason;

Просмотреть файл

@ -7,6 +7,7 @@
#include "TextRange-inl.h"
#include "Accessible-inl.h"
#include "mozilla/dom/Selection.h"
#include "nsAccUtils.h"
namespace mozilla {
@ -233,6 +234,37 @@ void TextRange::Select() const {}
void TextRange::ScrollIntoView(EHowToAlign aHow) const {}
void TextRange::TextRangesFromSelection(dom::Selection* aSelection,
nsTArray<TextRange>* aRanges) {
MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
aRanges->SetCapacity(aSelection->RangeCount());
for (uint32_t idx = 0; idx < aSelection->RangeCount(); idx++) {
const nsRange* DOMRange = aSelection->GetRangeAt(idx);
HyperTextAccessible* startContainer =
nsAccUtils::GetTextContainer(DOMRange->GetStartContainer());
HyperTextAccessible* endContainer =
nsAccUtils::GetTextContainer(DOMRange->GetEndContainer());
HyperTextAccessible* commonAncestor = nsAccUtils::GetTextContainer(
DOMRange->GetClosestCommonInclusiveAncestor());
if (!startContainer || !endContainer) {
continue;
}
int32_t startOffset = startContainer->DOMPointToOffset(
DOMRange->GetStartContainer(), DOMRange->StartOffset(), false);
int32_t endOffset = endContainer->DOMPointToOffset(
DOMRange->GetEndContainer(), DOMRange->EndOffset(), true);
TextRange tr(commonAncestor && commonAncestor->IsTextField()
? commonAncestor
: startContainer->Document(),
startContainer, startOffset, endContainer, endOffset);
*(aRanges->AppendElement()) = std::move(tr);
}
}
////////////////////////////////////////////////////////////////////////////////
// pivate

Просмотреть файл

@ -214,6 +214,9 @@ class TextRange final {
mStartOffset = aOffset;
}
static void TextRangesFromSelection(dom::Selection* aSelection,
nsTArray<TextRange>* aRanges);
private:
TextRange(const TextRange& aRange) = delete;
TextRange& operator=(const TextRange& aRange) = delete;

Просмотреть файл

@ -25,6 +25,7 @@
#include "RootAccessible.h"
#include "States.h"
#include "StyleInfo.h"
#include "TextRange.h"
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "TreeWalker.h"
@ -894,6 +895,27 @@ nsresult Accessible::HandleAccEvent(AccEvent* aEvent) {
announcementEvent->Priority());
break;
}
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
AutoTArray<TextRange, 1> ranges;
textSelChangeEvent->SelectionRanges(&ranges);
nsTArray<TextRangeData> textRangeData(ranges.Length());
for (size_t i = 0; i < ranges.Length(); i++) {
const TextRange& range = ranges.ElementAt(i);
Accessible* start = range.StartContainer();
Accessible* end = range.EndContainer();
textRangeData.AppendElement(TextRangeData(
start->IsDoc() && start->AsDoc()->IPCDoc()
? 0
: reinterpret_cast<uint64_t>(start->UniqueID()),
end->IsDoc() && end->AsDoc()->IPCDoc()
? 0
: reinterpret_cast<uint64_t>(end->UniqueID()),
range.StartOffset(), range.EndOffset()));
}
ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
break;
}
#endif
default:
ipcDoc->SendEvent(id, aEvent->GetEventType());

Просмотреть файл

@ -1703,32 +1703,12 @@ void HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const {
void HyperTextAccessible::SelectionRanges(
nsTArray<a11y::TextRange>* aRanges) const {
MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
dom::Selection* sel = DOMSelection();
if (!sel) return;
aRanges->SetCapacity(sel->RangeCount());
for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
const nsRange* DOMRange = sel->GetRangeAt(idx);
HyperTextAccessible* startContainer =
nsAccUtils::GetTextContainer(DOMRange->GetStartContainer());
HyperTextAccessible* endContainer =
nsAccUtils::GetTextContainer(DOMRange->GetEndContainer());
if (!startContainer || !endContainer) {
continue;
}
int32_t startOffset = startContainer->DOMPointToOffset(
DOMRange->GetStartContainer(), DOMRange->StartOffset(), false);
int32_t endOffset = endContainer->DOMPointToOffset(
DOMRange->GetEndContainer(), DOMRange->EndOffset(), true);
TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
startContainer, startOffset, endContainer, endOffset);
*(aRanges->AppendElement()) = std::move(tr);
if (!sel) {
return;
}
TextRange::TextRangesFromSelection(sel, aRanges);
}
void HyperTextAccessible::VisibleRanges(

Просмотреть файл

@ -36,6 +36,7 @@ XPIDL_SOURCES += [
'nsIAccessibleText.idl',
'nsIAccessibleTextChangeEvent.idl',
'nsIAccessibleTextRange.idl',
'nsIAccessibleTextSelectionChangeEvent.idl',
'nsIAccessibleTypes.idl',
'nsIAccessibleValue.idl',
'nsIAccessibleVirtualCursorChangeEvent.idl',

Просмотреть файл

@ -0,0 +1,21 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIAccessibleEvent.idl"
interface nsIArray;
/**
* Fired when the caret changes position in text.
*/
[scriptable, builtinclass, uuid(011f98e2-2beb-4ec3-97a5-f154f624e112)]
interface nsIAccessibleTextSelectionChangeEvent: nsIAccessibleEvent
{
/**
* Return an array of disjoint ranges for selected text within the
* source of this event.
*/
readonly attribute nsIArray selectionRanges;
};

Просмотреть файл

@ -11,7 +11,7 @@
#include "xpcAccessibleDocument.h"
#include "xpcAccEvents.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "TextRange.h"
#if defined(XP_WIN)
# include "AccessibleWrap.h"
@ -499,6 +499,15 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
// XXX: nsIAccessibleTextRange is not e10s friendly, so don't bother
// supporting nsIAccessibleTextSelectionChangeEvent for now.
// This is a placeholder for potential platform support.
return RecvEvent(aID, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
}
#endif
mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(

Просмотреть файл

@ -140,6 +140,9 @@ class DocAccessibleParent : public ProxyAccessible,
virtual mozilla::ipc::IPCResult RecvAnnouncementEvent(
const uint64_t& aID, const nsString& aAnnouncement,
const uint16_t& aPriority) override;
virtual mozilla::ipc::IPCResult RecvTextSelectionChangeEvent(
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) override;
#endif
mozilla::ipc::IPCResult RecvRoleChangedEvent(const a11y::role& aRole) final;

Просмотреть файл

@ -69,6 +69,14 @@ struct RelationTargets
uint64_t[] Targets;
};
struct TextRangeData
{
uint64_t StartID;
uint64_t EndID;
int32_t StartOffset;
int32_t EndOffset;
};
nested(upto inside_sync) sync protocol PDocAccessible
{
manager PBrowser;
@ -104,6 +112,7 @@ parent:
async AnnouncementEvent(uint64_t aID,
nsString aAnnouncement,
uint16_t aPriority);
async TextSelectionChangeEvent(uint64_t aID, TextRangeData[] aSelection);
/*
* Tell the parent document to bind the existing document as a new child

Просмотреть файл

@ -22,6 +22,8 @@ const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent;
const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent;
const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent;
const nsIAccessibleTextSelectionChangeEvent =
Ci.nsIAccessibleTextSelectionChangeEvent;
const nsIAccessibleVirtualCursorChangeEvent =
Ci.nsIAccessibleVirtualCursorChangeEvent;
const nsIAccessibleObjectAttributeChangedEvent =

Просмотреть файл

@ -2045,7 +2045,15 @@ function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) {
/**
* Text selection change checker.
*/
function textSelectionChecker(aID, aStartOffset, aEndOffset) {
function textSelectionChecker(
aID,
aStartOffset,
aEndOffset,
aRangeStartContainer,
aRangeStartOffset,
aRangeEndContainer,
aRangeEndOffset
) {
this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
this.check = function textSelectionChecker_check(aEvent) {
@ -2053,6 +2061,24 @@ function textSelectionChecker(aID, aStartOffset, aEndOffset) {
ok(true, "Collapsed selection triggered text selection change event.");
} else {
testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
// Test selection test range
let selectionRanges = aEvent.QueryInterface(
nsIAccessibleTextSelectionChangeEvent
).selectionRanges;
let range = selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
is(
range.startContainer,
getAccessible(aRangeStartContainer),
"correct range start container"
);
is(range.startOffset, aRangeStartOffset, "correct range start offset");
is(range.endOffset, aRangeEndOffset, "correct range end offset");
is(
range.endContainer,
getAccessible(aRangeEndContainer),
"correct range end container"
);
}
};
}

Просмотреть файл

@ -34,15 +34,15 @@
gQueue = new eventQueue();
gQueue.push(new synthClick("c1_p1", getOnclickSeq("c1_p1")));
gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1), { shiftKey: true }));
gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2), { shiftKey: true }));
gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1, "c1_p1", 0, "c1_p2", 0), { shiftKey: true }));
gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2, "c1_p1", 0, "c1_p2", 9), { shiftKey: true }));
gQueue.push(new synthClick("ta1", getOnclickSeq("ta1")));
gQueue.push(new synthRightKey("ta1",
new textSelectionChecker("ta1", 0, 1),
new textSelectionChecker("ta1", 0, 1, "ta1", 0, "ta1", 1),
{ shiftKey: true }));
gQueue.push(new synthLeftKey("ta1",
[new textSelectionChecker("ta1", 0, 0),
[new textSelectionChecker("ta1", 0, 0, "ta1", 0, "ta1", 0),
new caretMoveChecker(0, "ta1")]));
gQueue.invoke(); // Will call SimpleTest.finish();

Просмотреть файл

@ -10,6 +10,7 @@ simple_events = [
'Event',
'StateChangeEvent',
'TextChangeEvent',
'TextSelectionChangeEvent',
'HideEvent',
'CaretMoveEvent',
'ObjectAttributeChangedEvent',

Просмотреть файл

@ -27,6 +27,9 @@ class TextRange;
class xpcAccessibleTextRange final : public nsIAccessibleTextRange {
public:
explicit xpcAccessibleTextRange(TextRange&& aRange)
: mRange(std::forward<TextRange>(aRange)) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(xpcAccessibleTextRange)
@ -61,8 +64,6 @@ class xpcAccessibleTextRange final : public nsIAccessibleTextRange {
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLETEXTRANGE_IMPL_IID)
private:
explicit xpcAccessibleTextRange(TextRange&& aRange)
: mRange(std::forward<TextRange>(aRange)) {}
xpcAccessibleTextRange() {}
~xpcAccessibleTextRange() {}