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