Bug 956032 - text selection change event is missed when selected text becomes unselected, r=tbsaunde

This commit is contained in:
Alexander Surkov 2014-02-11 20:18:31 -05:00
parent 7a1a4298ce
commit 4aa0692608
9 changed files with 99 additions and 35 deletions

View File

@ -12,6 +12,7 @@
#include "States.h"
#include "nsEventStateManager.h"
#include "mozilla/Selection.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -126,13 +127,22 @@ AccShowEvent::
////////////////////////////////////////////////////////////////////////////////
AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget,
nsISelection* aSelection) :
Selection* aSelection,
int32_t aReason) :
AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget,
eAutoDetect, eCoalesceTextSelChange),
mSel(aSelection) {}
mSel(aSelection), mReason(aReason) {}
AccTextSelChangeEvent::~AccTextSelChangeEvent() { }
bool
AccTextSelChangeEvent::IsCaretMoveOnly() const
{
return mSel->GetRangeCount() == 1 && mSel->IsCollapsed() &&
((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
nsISelectionListener::COLLAPSETOEND_REASON)) == 0);
}
////////////////////////////////////////////////////////////////////////////////
// AccSelChangeEvent
////////////////////////////////////////////////////////////////////////////////

View File

@ -10,9 +10,10 @@
#include "mozilla/a11y/Accessible.h"
class nsISelection;
namespace mozilla {
class Selection;
namespace a11y {
class DocAccessible;
@ -366,7 +367,8 @@ private:
class AccTextSelChangeEvent : public AccEvent
{
public:
AccTextSelChangeEvent(HyperTextAccessible* aTarget, nsISelection* aSelection);
AccTextSelChangeEvent(HyperTextAccessible* aTarget, Selection* aSelection,
int32_t aReason);
virtual ~AccTextSelChangeEvent();
// AccEvent
@ -376,8 +378,16 @@ public:
return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent);
}
// AccTextSelChangeEvent
/**
* Return true if the text selection change wasn't caused by pure caret move.
*/
bool IsCaretMoveOnly() const;
private:
nsCOMPtr<nsISelection> mSel;
nsRefPtr<Selection> mSel;
int32_t mReason;
friend class EventQueue;
friend class SelectionManager;

View File

@ -582,7 +582,8 @@ logging::FocusDispatched(Accessible* aTarget)
}
void
logging::SelChange(nsISelection* aSelection, DocAccessible* aDocument)
logging::SelChange(nsISelection* aSelection, DocAccessible* aDocument,
int16_t aReason)
{
nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
@ -598,8 +599,10 @@ logging::SelChange(nsISelection* aSelection, DocAccessible* aDocument)
strType = "unknown";
bool isIgnored = !aDocument || !aDocument->IsContentLoaded();
printf("\nSelection changed, selection type: %s, notification %s\n",
strType, (isIgnored ? "ignored" : "pending"));
printf("\nSelection changed, selection type: %s, notification %s, reason: %d\n",
strType, (isIgnored ? "ignored" : "pending"), aReason);
Stack();
}
void

View File

@ -118,7 +118,8 @@ void FocusDispatched(Accessible* aTarget);
/**
* Log the selection change.
*/
void SelChange(nsISelection* aSelection, DocAccessible* aDocument);
void SelChange(nsISelection* aSelection, DocAccessible* aDocument,
int16_t aReason);
/**
* Log the message ('title: text' format) on new line. Print the start and end

View File

@ -14,13 +14,23 @@
#include "nsIAccessibleTypes.h"
#include "nsIDOMDocument.h"
#include "nsIPresShell.h"
#include "nsISelectionPrivate.h"
#include "mozilla/Selection.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::a11y;
struct mozilla::a11y::SelData
{
SelData(Selection* aSel, int32_t aReason) :
mSel(aSel), mReason(aReason) {}
nsRefPtr<Selection> mSel;
int16_t mReason;
NS_INLINE_DECL_REFCOUNTING(SelData);
};
void
SelectionManager::ClearControlSelectionListener()
{
@ -110,17 +120,16 @@ SelectionManager::RemoveDocSelectionListener(nsIPresShell* aPresShell)
void
SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent)
{
// Fire selection change event if it's not pure caret-move selection change,
// i.e. the accessible has or had not collapsed selection.
AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
Selection* sel = static_cast<Selection*>(event->mSel.get());
// Fire selection change event if it's not pure caret-move selection change.
if (sel->GetRangeCount() != 1 || !sel->IsCollapsed())
if (!event->IsCaretMoveOnly())
nsEventShell::FireEvent(aEvent);
// Fire caret move event if there's a caret in the selection.
nsINode* caretCntrNode =
nsCoreUtils::GetDOMNodeFromDOMPoint(sel->GetFocusNode(),
sel->FocusOffset());
nsCoreUtils::GetDOMNodeFromDOMPoint(event->mSel->GetFocusNode(),
event->mSel->FocusOffset());
if (!caretCntrNode)
return;
@ -150,7 +159,7 @@ SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eSelection))
logging::SelChange(aSelection, document);
logging::SelChange(aSelection, document, aReason);
#endif
// Don't fire events until document is loaded.
@ -158,17 +167,19 @@ SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
// Selection manager has longer lifetime than any document accessible,
// so that we are guaranteed that the notification is processed before
// the selection manager is destroyed.
document->HandleNotification<SelectionManager, nsISelection>
(this, &SelectionManager::ProcessSelectionChanged, aSelection);
nsRefPtr<SelData> selData =
new SelData(static_cast<Selection*>(aSelection), aReason);
document->HandleNotification<SelectionManager, SelData>
(this, &SelectionManager::ProcessSelectionChanged, selData);
}
return NS_OK;
}
void
SelectionManager::ProcessSelectionChanged(nsISelection* aSelection)
SelectionManager::ProcessSelectionChanged(SelData* aSelData)
{
Selection* selection = static_cast<Selection*>(aSelection);
Selection* selection = aSelData->mSel;
if (!selection->GetPresShell())
return;
@ -176,11 +187,12 @@ SelectionManager::ProcessSelectionChanged(nsISelection* aSelection)
nsINode* cntrNode = nullptr;
if (range)
cntrNode = range->GetCommonAncestor();
if (!cntrNode) {
cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
if (!cntrNode) {
cntrNode = selection->GetPresShell()->GetDocument();
NS_ASSERTION(selection->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(),
NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(),
"Wrong selection container was used!");
}
}
@ -192,7 +204,8 @@ SelectionManager::ProcessSelectionChanged(nsISelection* aSelection)
}
if (selection->GetType() == nsISelectionController::SELECTION_NORMAL) {
nsRefPtr<AccEvent> event = new AccTextSelChangeEvent(text, aSelection);
nsRefPtr<AccEvent> event =
new AccTextSelChangeEvent(text, selection, aSelData->mReason);
text->Document()->FireDelayedEvent(event);
} else if (selection->GetType() == nsISelectionController::SELECTION_SPELLCHECK) {

View File

@ -38,6 +38,8 @@ class AccEvent;
* selection change events.
*/
struct SelData;
class SelectionManager : public nsISelectionListener
{
public:
@ -83,7 +85,7 @@ protected:
/**
* Process DOM selection change. Fire selection and caret move events.
*/
void ProcessSelectionChanged(nsISelection* aSelection);
void ProcessSelectionChanged(SelData* aSelData);
private:
// Currently focused control.

View File

@ -1212,17 +1212,17 @@ function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
/**
* Left arrow key invoker.
*/
function synthLeftKey(aNodeOrID, aCheckerOrEventSeq)
function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", null, aCheckerOrEventSeq);
this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", aArgs, aCheckerOrEventSeq);
}
/**
* Right arrow key invoker.
*/
function synthRightKey(aNodeOrID, aCheckerOrEventSeq)
function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", null, aCheckerOrEventSeq);
this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", aArgs, aCheckerOrEventSeq);
}
/**
@ -1764,7 +1764,12 @@ function textSelectionChecker(aID, aStartOffset, aEndOffset)
this.check = function textSelectionChecker_check(aEvent)
{
testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
if (aStartOffset == aEndOffset) {
is(getAccessible(aID, [nsIAccessibleText]).caretOffset, aStartOffset,
"Wrong collapsed selection!");
} else {
testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
}
}
}

View File

@ -24,19 +24,30 @@
// gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
function getOnclickSeq(aID)
{
return [
new caretMoveChecker(0, aID),
new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
];
}
function doTests()
{
// test caret move events and caret offsets
gQueue = new eventQueue();
var onclickSeq = [
new caretMoveChecker(0, "c1_p1"),
new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, "c1_p1")
];
gQueue.push(new synthClick("c1_p1", onclickSeq));
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 synthClick("ta1", getOnclickSeq("ta1")));
gQueue.push(new synthRightKey("ta1",
new textSelectionChecker("ta1", 0, 1),
{ shiftKey: true }));
gQueue.push(new synthLeftKey("ta1",
new textSelectionChecker("ta1", 0, 0)));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -52,6 +63,11 @@
title="Text selection change event has a wrong target when selection is spanned through several objects">
Bug 762934
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=956032"
title="Text selection change event missed when selected text becomes unselected">
Bug 956032
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -62,6 +78,8 @@
<p id="c1_p2">paragraph</p>
</div>
<textarea id="ta1">Hello world</textarea>
<div id="eventdump"></div>
</body>
</html>

View File

@ -787,6 +787,7 @@ nsFrameSelection::MoveCaret(uint32_t aKeycode,
{
const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
if (anchorFocusRange) {
PostReason(nsISelectionListener::COLLAPSETOSTART_REASON);
sel->Collapse(anchorFocusRange->GetStartParent(),
anchorFocusRange->StartOffset());
}
@ -802,6 +803,7 @@ nsFrameSelection::MoveCaret(uint32_t aKeycode,
{
const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
if (anchorFocusRange) {
PostReason(nsISelectionListener::COLLAPSETOEND_REASON);
sel->Collapse(anchorFocusRange->GetEndParent(),
anchorFocusRange->EndOffset());
}