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/. */
5
6 #include "mozilla/a11y/SelectionManager.h"
7
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"
16
17 #include "mozilla/PresShell.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/Selection.h"
20 #include "mozilla/dom/Element.h"
21
22 using namespace mozilla;
23 using namespace mozilla::a11y;
24 using mozilla::dom::Selection;
25
26 struct mozilla::a11y::SelData final {
SelDatamozilla::a11y::SelData27 SelData(Selection* aSel, int32_t aReason) : mSel(aSel), mReason(aReason) {}
28
29 RefPtr<Selection> mSel;
30 int16_t mReason;
31
32 NS_INLINE_DECL_REFCOUNTING(SelData)
33
34 private:
35 // Private destructor, to discourage deletion outside of Release():
~SelDatamozilla::a11y::SelData36 ~SelData() {}
37 };
38
SelectionManager()39 SelectionManager::SelectionManager()
40 : mCaretOffset(-1), mAccWithCaret(nullptr) {}
41
ClearControlSelectionListener()42 void SelectionManager::ClearControlSelectionListener() {
43 // Remove 'this' registered as selection listener for the normal selection.
44 if (mCurrCtrlNormalSel) {
45 mCurrCtrlNormalSel->RemoveSelectionListener(this);
46 mCurrCtrlNormalSel = nullptr;
47 }
48
49 // Remove 'this' registered as selection listener for the spellcheck
50 // selection.
51 if (mCurrCtrlSpellSel) {
52 mCurrCtrlSpellSel->RemoveSelectionListener(this);
53 mCurrCtrlSpellSel = nullptr;
54 }
55 }
56
SetControlSelectionListener(dom::Element * aFocusedElm)57 void SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) {
58 // When focus moves such that the caret is part of a new frame selection
59 // this removes the old selection listener and attaches a new one for
60 // the current focus.
61 ClearControlSelectionListener();
62
63 nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame();
64 if (!controlFrame) return;
65
66 const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection();
67 NS_ASSERTION(frameSel, "No frame selection for focused element!");
68 if (!frameSel) return;
69
70 // Register 'this' as selection listener for the normal selection.
71 Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
72 normalSel->AddSelectionListener(this);
73 mCurrCtrlNormalSel = normalSel;
74
75 // Register 'this' as selection listener for the spell check selection.
76 Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
77 spellSel->AddSelectionListener(this);
78 mCurrCtrlSpellSel = spellSel;
79 }
80
AddDocSelectionListener(PresShell * aPresShell)81 void SelectionManager::AddDocSelectionListener(PresShell* aPresShell) {
82 const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
83
84 // Register 'this' as selection listener for the normal selection.
85 Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
86 normalSel->AddSelectionListener(this);
87
88 // Register 'this' as selection listener for the spell check selection.
89 Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
90 spellSel->AddSelectionListener(this);
91 }
92
RemoveDocSelectionListener(PresShell * aPresShell)93 void SelectionManager::RemoveDocSelectionListener(PresShell* aPresShell) {
94 const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
95
96 // Remove 'this' registered as selection listener for the normal selection.
97 Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
98 normalSel->RemoveSelectionListener(this);
99
100 // Remove 'this' registered as selection listener for the spellcheck
101 // selection.
102 Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
103 spellSel->RemoveSelectionListener(this);
104
105 if (mCurrCtrlNormalSel) {
106 if (mCurrCtrlNormalSel->GetPresShell() == aPresShell) {
107 // Remove 'this' registered as selection listener for the normal selection
108 // if we are removing listeners for its PresShell.
109 mCurrCtrlNormalSel->RemoveSelectionListener(this);
110 mCurrCtrlNormalSel = nullptr;
111 }
112 }
113
114 if (mCurrCtrlSpellSel) {
115 if (mCurrCtrlSpellSel->GetPresShell() == aPresShell) {
116 // Remove 'this' registered as selection listener for the spellcheck
117 // selection if we are removing listeners for its PresShell.
118 mCurrCtrlSpellSel->RemoveSelectionListener(this);
119 mCurrCtrlSpellSel = nullptr;
120 }
121 }
122 }
123
ProcessTextSelChangeEvent(AccEvent * aEvent)124 void SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) {
125 // Fire selection change event if it's not pure caret-move selection change,
126 // i.e. the accessible has or had not collapsed selection.
127 AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
128 if (!event->IsCaretMoveOnly()) nsEventShell::FireEvent(aEvent);
129
130 // Fire caret move event if there's a caret in the selection.
131 nsINode* caretCntrNode = nsCoreUtils::GetDOMNodeFromDOMPoint(
132 event->mSel->GetFocusNode(), event->mSel->FocusOffset());
133 if (!caretCntrNode) return;
134
135 HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode);
136 NS_ASSERTION(
137 caretCntr,
138 "No text container for focus while there's one for common ancestor?!");
139 if (!caretCntr) return;
140
141 Selection* selection = caretCntr->DOMSelection();
142
143 // XXX Sometimes we can't get a selection for caretCntr, in that case assume
144 // event->mSel is correct.
145 if (!selection) selection = event->mSel;
146
147 mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(),
148 selection->FocusOffset());
149 mAccWithCaret = caretCntr;
150 if (mCaretOffset != -1) {
151 RefPtr<AccCaretMoveEvent> caretMoveEvent =
152 new AccCaretMoveEvent(caretCntr, mCaretOffset, selection->IsCollapsed(),
153 aEvent->FromUserInput());
154 nsEventShell::FireEvent(caretMoveEvent);
155 }
156 }
157
158 NS_IMETHODIMP
NotifySelectionChanged(dom::Document * aDocument,Selection * aSelection,int16_t aReason)159 SelectionManager::NotifySelectionChanged(dom::Document* aDocument,
160 Selection* aSelection,
161 int16_t aReason) {
162 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
163 return NS_ERROR_INVALID_ARG;
164 }
165
166 DocAccessible* document = GetAccService()->GetDocAccessible(aDocument);
167
168 #ifdef A11Y_LOG
169 if (logging::IsEnabled(logging::eSelection)) {
170 logging::SelChange(aSelection, document, aReason);
171 }
172 #endif
173
174 if (document) {
175 // Selection manager has longer lifetime than any document accessible,
176 // so that we are guaranteed that the notification is processed before
177 // the selection manager is destroyed.
178 RefPtr<SelData> selData = new SelData(aSelection, aReason);
179 document->HandleNotification<SelectionManager, SelData>(
180 this, &SelectionManager::ProcessSelectionChanged, selData);
181 }
182
183 return NS_OK;
184 }
185
ProcessSelectionChanged(SelData * aSelData)186 void SelectionManager::ProcessSelectionChanged(SelData* aSelData) {
187 Selection* selection = aSelData->mSel;
188 if (!selection->GetPresShell()) return;
189
190 const nsRange* range = selection->GetAnchorFocusRange();
191 nsINode* cntrNode = nullptr;
192 if (range) {
193 cntrNode = range->GetClosestCommonInclusiveAncestor();
194 }
195
196 if (!cntrNode) {
197 cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
198 if (!cntrNode) {
199 cntrNode = selection->GetPresShell()->GetDocument();
200 NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() ==
201 selection->GetFrameSelection(),
202 "Wrong selection container was used!");
203 }
204 }
205
206 HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode);
207 if (!text) {
208 // FIXME bug 1126649
209 NS_ERROR("We must reach document accessible implementing text interface!");
210 return;
211 }
212
213 if (selection->GetType() == SelectionType::eNormal) {
214 RefPtr<AccEvent> event =
215 new AccTextSelChangeEvent(text, selection, aSelData->mReason);
216 text->Document()->FireDelayedEvent(event);
217
218 } else if (selection->GetType() == SelectionType::eSpellCheck) {
219 // XXX: fire an event for container accessible of the focus/anchor range
220 // of the spelcheck selection.
221 text->Document()->FireDelayedEvent(
222 nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, text);
223 }
224 }
225
226 SelectionManager::~SelectionManager() = default;
227