1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/a11y/SelectionManager.h"
8 #include "DocAccessible-inl.h"
9 #include "HyperTextAccessible.h"
10 #include "HyperTextAccessible-inl.h"
11 #include "nsAccUtils.h"
12 #include "nsCoreUtils.h"
13 #include "nsEventShell.h"
14 #include "nsFrameSelection.h"
15 #include "TextLeafRange.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/dom/Selection.h"
19 #include "mozilla/dom/Element.h"
21 using namespace mozilla
;
22 using namespace mozilla::a11y
;
23 using mozilla::dom::Selection
;
25 struct mozilla::a11y::SelData final
{
26 SelData(Selection
* aSel
, int32_t aReason
, int32_t aGranularity
)
27 : mSel(aSel
), mReason(aReason
), mGranularity(aGranularity
) {}
29 RefPtr
<Selection
> mSel
;
33 NS_INLINE_DECL_REFCOUNTING(SelData
)
36 // Private destructor, to discourage deletion outside of Release():
40 SelectionManager::SelectionManager()
41 : mCaretOffset(-1), mAccWithCaret(nullptr) {}
43 void SelectionManager::ClearControlSelectionListener() {
44 // Remove 'this' registered as selection listener for the normal selection.
45 if (mCurrCtrlNormalSel
) {
46 mCurrCtrlNormalSel
->RemoveSelectionListener(this);
47 mCurrCtrlNormalSel
= nullptr;
50 // Remove 'this' registered as selection listener for the spellcheck
52 if (mCurrCtrlSpellSel
) {
53 mCurrCtrlSpellSel
->RemoveSelectionListener(this);
54 mCurrCtrlSpellSel
= nullptr;
58 void SelectionManager::SetControlSelectionListener(dom::Element
* aFocusedElm
) {
59 // When focus moves such that the caret is part of a new frame selection
60 // this removes the old selection listener and attaches a new one for
62 ClearControlSelectionListener();
64 nsIFrame
* controlFrame
= aFocusedElm
->GetPrimaryFrame();
65 if (!controlFrame
) return;
67 const nsFrameSelection
* frameSel
= controlFrame
->GetConstFrameSelection();
68 NS_ASSERTION(frameSel
, "No frame selection for focused element!");
69 if (!frameSel
) return;
71 // Register 'this' as selection listener for the normal selection.
72 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
73 normalSel
->AddSelectionListener(this);
74 mCurrCtrlNormalSel
= normalSel
;
76 // Register 'this' as selection listener for the spell check selection.
77 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
78 spellSel
->AddSelectionListener(this);
79 mCurrCtrlSpellSel
= spellSel
;
82 void SelectionManager::AddDocSelectionListener(PresShell
* aPresShell
) {
83 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
85 // Register 'this' as selection listener for the normal selection.
86 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
87 normalSel
->AddSelectionListener(this);
89 // Register 'this' as selection listener for the spell check selection.
90 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
91 spellSel
->AddSelectionListener(this);
94 void SelectionManager::RemoveDocSelectionListener(PresShell
* aPresShell
) {
95 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
97 // Remove 'this' registered as selection listener for the normal selection.
98 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
99 normalSel
->RemoveSelectionListener(this);
101 // Remove 'this' registered as selection listener for the spellcheck
103 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
104 spellSel
->RemoveSelectionListener(this);
106 if (mCurrCtrlNormalSel
) {
107 if (mCurrCtrlNormalSel
->GetPresShell() == aPresShell
) {
108 // Remove 'this' registered as selection listener for the normal selection
109 // if we are removing listeners for its PresShell.
110 mCurrCtrlNormalSel
->RemoveSelectionListener(this);
111 mCurrCtrlNormalSel
= nullptr;
115 if (mCurrCtrlSpellSel
) {
116 if (mCurrCtrlSpellSel
->GetPresShell() == aPresShell
) {
117 // Remove 'this' registered as selection listener for the spellcheck
118 // selection if we are removing listeners for its PresShell.
119 mCurrCtrlSpellSel
->RemoveSelectionListener(this);
120 mCurrCtrlSpellSel
= nullptr;
125 void SelectionManager::ProcessTextSelChangeEvent(AccEvent
* aEvent
) {
126 // Fire selection change event if it's not pure caret-move selection change,
127 // i.e. the accessible has or had not collapsed selection. Also, it must not
128 // be a collapsed selection on the container of a focused text field, since
129 // the text field has an independent selection and will thus fire its own
131 AccTextSelChangeEvent
* event
= downcast_accEvent(aEvent
);
132 if (!event
->IsCaretMoveOnly() &&
133 !(event
->mSel
->IsCollapsed() && event
->mSel
!= mCurrCtrlNormalSel
&&
134 FocusMgr() && FocusMgr()->FocusedLocalAccessible() &&
135 FocusMgr()->FocusedLocalAccessible()->IsTextField())) {
136 nsEventShell::FireEvent(aEvent
);
139 // Fire caret move event if there's a caret in the selection.
140 nsINode
* caretCntrNode
= nsCoreUtils::GetDOMNodeFromDOMPoint(
141 event
->mSel
->GetFocusNode(), event
->mSel
->FocusOffset());
142 if (!caretCntrNode
) return;
144 HyperTextAccessible
* caretCntr
= nsAccUtils::GetTextContainer(caretCntrNode
);
147 "No text container for focus while there's one for common ancestor?!");
148 if (!caretCntr
) return;
150 Selection
* selection
= caretCntr
->DOMSelection();
152 // XXX Sometimes we can't get a selection for caretCntr, in that case assume
153 // event->mSel is correct.
154 if (!selection
) selection
= event
->mSel
;
156 mCaretOffset
= caretCntr
->DOMPointToOffset(selection
->GetFocusNode(),
157 selection
->FocusOffset());
158 mAccWithCaret
= caretCntr
;
159 if (mCaretOffset
!= -1) {
160 RefPtr
<AccCaretMoveEvent
> caretMoveEvent
=
161 new AccCaretMoveEvent(caretCntr
, mCaretOffset
, selection
->IsCollapsed(),
162 caretCntr
->IsCaretAtEndOfLine(),
163 event
->GetGranularity(), aEvent
->FromUserInput());
164 nsEventShell::FireEvent(caretMoveEvent
);
169 SelectionManager::NotifySelectionChanged(dom::Document
* aDocument
,
170 Selection
* aSelection
, int16_t aReason
,
172 if (NS_WARN_IF(!aDocument
) || NS_WARN_IF(!aSelection
)) {
173 return NS_ERROR_INVALID_ARG
;
176 DocAccessible
* document
= GetAccService()->GetDocAccessible(aDocument
);
179 if (logging::IsEnabled(logging::eSelection
)) {
180 logging::SelChange(aSelection
, document
, aReason
);
185 // Selection manager has longer lifetime than any document accessible,
186 // so that we are guaranteed that the notification is processed before
187 // the selection manager is destroyed.
188 RefPtr
<SelData
> selData
= new SelData(aSelection
, aReason
, aAmount
);
189 document
->HandleNotification
<SelectionManager
, SelData
>(
190 this, &SelectionManager::ProcessSelectionChanged
, selData
);
196 void SelectionManager::ProcessSelectionChanged(SelData
* aSelData
) {
197 Selection
* selection
= aSelData
->mSel
;
198 if (!selection
->GetPresShell()) return;
200 const nsRange
* range
= selection
->GetAnchorFocusRange();
201 nsINode
* cntrNode
= nullptr;
203 cntrNode
= range
->GetClosestCommonInclusiveAncestor();
207 cntrNode
= selection
->GetFrameSelection()->GetAncestorLimiter();
209 cntrNode
= selection
->GetPresShell()->GetDocument();
210 NS_ASSERTION(aSelData
->mSel
->GetPresShell()->ConstFrameSelection() ==
211 selection
->GetFrameSelection(),
212 "Wrong selection container was used!");
216 HyperTextAccessible
* text
= nsAccUtils::GetTextContainer(cntrNode
);
219 NS_ERROR("We must reach document accessible implementing text interface!");
223 if (selection
->GetType() == SelectionType::eNormal
) {
224 RefPtr
<AccEvent
> event
= new AccTextSelChangeEvent(
225 text
, selection
, aSelData
->mReason
, aSelData
->mGranularity
);
226 text
->Document()->FireDelayedEvent(event
);
228 } else if (selection
->GetType() == SelectionType::eSpellCheck
) {
229 // XXX: fire an event for container accessible of the focus/anchor range
230 // of the spelcheck selection.
231 text
->Document()->FireDelayedEvent(
232 nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
, text
);
236 void SelectionManager::SpellCheckRangeChanged(const nsRange
& aRange
) {
237 // Events are fired in SelectionManager::NotifySelectionChanged. This is only
238 // used to push cache updates.
239 if (IPCAccessibilityActive()) {
240 dom::Document
* doc
= aRange
.GetStartContainer()->OwnerDoc();
242 TextLeafPoint::UpdateCachedSpellingError(doc
, aRange
);
246 SelectionManager::~SelectionManager() = default;