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 "nsAccessibilityService.h"
12 #include "nsAccUtils.h"
13 #include "nsCoreUtils.h"
14 #include "nsEventShell.h"
15 #include "nsFrameSelection.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/Selection.h"
20 #include "mozilla/dom/Element.h"
22 using namespace mozilla
;
23 using namespace mozilla::a11y
;
24 using mozilla::dom::Selection
;
26 struct mozilla::a11y::SelData final
{
27 SelData(Selection
* aSel
, int32_t aReason
, int32_t aGranularity
)
28 : mSel(aSel
), mReason(aReason
), mGranularity(aGranularity
) {}
30 RefPtr
<Selection
> mSel
;
34 NS_INLINE_DECL_REFCOUNTING(SelData
)
37 // Private destructor, to discourage deletion outside of Release():
41 SelectionManager::SelectionManager()
42 : mCaretOffset(-1), mAccWithCaret(nullptr) {}
44 void SelectionManager::ClearControlSelectionListener() {
45 // Remove 'this' registered as selection listener for the normal selection.
46 if (mCurrCtrlNormalSel
) {
47 mCurrCtrlNormalSel
->RemoveSelectionListener(this);
48 mCurrCtrlNormalSel
= nullptr;
51 // Remove 'this' registered as selection listener for the spellcheck
53 if (mCurrCtrlSpellSel
) {
54 mCurrCtrlSpellSel
->RemoveSelectionListener(this);
55 mCurrCtrlSpellSel
= nullptr;
59 void SelectionManager::SetControlSelectionListener(dom::Element
* aFocusedElm
) {
60 // When focus moves such that the caret is part of a new frame selection
61 // this removes the old selection listener and attaches a new one for
63 ClearControlSelectionListener();
65 nsIFrame
* controlFrame
= aFocusedElm
->GetPrimaryFrame();
66 if (!controlFrame
) return;
68 const nsFrameSelection
* frameSel
= controlFrame
->GetConstFrameSelection();
69 NS_ASSERTION(frameSel
, "No frame selection for focused element!");
70 if (!frameSel
) return;
72 // Register 'this' as selection listener for the normal selection.
73 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
74 normalSel
->AddSelectionListener(this);
75 mCurrCtrlNormalSel
= normalSel
;
77 // Register 'this' as selection listener for the spell check selection.
78 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
79 spellSel
->AddSelectionListener(this);
80 mCurrCtrlSpellSel
= spellSel
;
83 void SelectionManager::AddDocSelectionListener(PresShell
* aPresShell
) {
84 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
86 // Register 'this' as selection listener for the normal selection.
87 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
88 normalSel
->AddSelectionListener(this);
90 // Register 'this' as selection listener for the spell check selection.
91 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
92 spellSel
->AddSelectionListener(this);
95 void SelectionManager::RemoveDocSelectionListener(PresShell
* aPresShell
) {
96 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
98 // Remove 'this' registered as selection listener for the normal selection.
99 Selection
* normalSel
= frameSel
->GetSelection(SelectionType::eNormal
);
100 normalSel
->RemoveSelectionListener(this);
102 // Remove 'this' registered as selection listener for the spellcheck
104 Selection
* spellSel
= frameSel
->GetSelection(SelectionType::eSpellCheck
);
105 spellSel
->RemoveSelectionListener(this);
107 if (mCurrCtrlNormalSel
) {
108 if (mCurrCtrlNormalSel
->GetPresShell() == aPresShell
) {
109 // Remove 'this' registered as selection listener for the normal selection
110 // if we are removing listeners for its PresShell.
111 mCurrCtrlNormalSel
->RemoveSelectionListener(this);
112 mCurrCtrlNormalSel
= nullptr;
116 if (mCurrCtrlSpellSel
) {
117 if (mCurrCtrlSpellSel
->GetPresShell() == aPresShell
) {
118 // Remove 'this' registered as selection listener for the spellcheck
119 // selection if we are removing listeners for its PresShell.
120 mCurrCtrlSpellSel
->RemoveSelectionListener(this);
121 mCurrCtrlSpellSel
= nullptr;
126 void SelectionManager::ProcessTextSelChangeEvent(AccEvent
* aEvent
) {
127 // Fire selection change event if it's not pure caret-move selection change,
128 // i.e. the accessible has or had not collapsed selection.
129 AccTextSelChangeEvent
* event
= downcast_accEvent(aEvent
);
130 if (!event
->IsCaretMoveOnly()) nsEventShell::FireEvent(aEvent
);
132 // Fire caret move event if there's a caret in the selection.
133 nsINode
* caretCntrNode
= nsCoreUtils::GetDOMNodeFromDOMPoint(
134 event
->mSel
->GetFocusNode(), event
->mSel
->FocusOffset());
135 if (!caretCntrNode
) return;
137 HyperTextAccessible
* caretCntr
= nsAccUtils::GetTextContainer(caretCntrNode
);
140 "No text container for focus while there's one for common ancestor?!");
141 if (!caretCntr
) return;
143 Selection
* selection
= caretCntr
->DOMSelection();
145 // XXX Sometimes we can't get a selection for caretCntr, in that case assume
146 // event->mSel is correct.
147 if (!selection
) selection
= event
->mSel
;
149 mCaretOffset
= caretCntr
->DOMPointToOffset(selection
->GetFocusNode(),
150 selection
->FocusOffset());
151 mAccWithCaret
= caretCntr
;
152 if (mCaretOffset
!= -1) {
153 RefPtr
<AccCaretMoveEvent
> caretMoveEvent
=
154 new AccCaretMoveEvent(caretCntr
, mCaretOffset
, selection
->IsCollapsed(),
155 caretCntr
->IsCaretAtEndOfLine(),
156 event
->GetGranularity(), aEvent
->FromUserInput());
157 nsEventShell::FireEvent(caretMoveEvent
);
162 SelectionManager::NotifySelectionChanged(dom::Document
* aDocument
,
163 Selection
* aSelection
, int16_t aReason
,
165 if (NS_WARN_IF(!aDocument
) || NS_WARN_IF(!aSelection
)) {
166 return NS_ERROR_INVALID_ARG
;
169 DocAccessible
* document
= GetAccService()->GetDocAccessible(aDocument
);
172 if (logging::IsEnabled(logging::eSelection
)) {
173 logging::SelChange(aSelection
, document
, aReason
);
178 // Selection manager has longer lifetime than any document accessible,
179 // so that we are guaranteed that the notification is processed before
180 // the selection manager is destroyed.
181 RefPtr
<SelData
> selData
= new SelData(aSelection
, aReason
, aAmount
);
182 document
->HandleNotification
<SelectionManager
, SelData
>(
183 this, &SelectionManager::ProcessSelectionChanged
, selData
);
189 void SelectionManager::ProcessSelectionChanged(SelData
* aSelData
) {
190 Selection
* selection
= aSelData
->mSel
;
191 if (!selection
->GetPresShell()) return;
193 const nsRange
* range
= selection
->GetAnchorFocusRange();
194 nsINode
* cntrNode
= nullptr;
196 cntrNode
= range
->GetClosestCommonInclusiveAncestor();
200 cntrNode
= selection
->GetFrameSelection()->GetAncestorLimiter();
202 cntrNode
= selection
->GetPresShell()->GetDocument();
203 NS_ASSERTION(aSelData
->mSel
->GetPresShell()->ConstFrameSelection() ==
204 selection
->GetFrameSelection(),
205 "Wrong selection container was used!");
209 HyperTextAccessible
* text
= nsAccUtils::GetTextContainer(cntrNode
);
212 NS_ERROR("We must reach document accessible implementing text interface!");
216 if (selection
->GetType() == SelectionType::eNormal
) {
217 RefPtr
<AccEvent
> event
= new AccTextSelChangeEvent(
218 text
, selection
, aSelData
->mReason
, aSelData
->mGranularity
);
219 text
->Document()->FireDelayedEvent(event
);
221 } else if (selection
->GetType() == SelectionType::eSpellCheck
) {
222 // XXX: fire an event for container accessible of the focus/anchor range
223 // of the spelcheck selection.
224 text
->Document()->FireDelayedEvent(
225 nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
, text
);
229 SelectionManager::~SelectionManager() = default;