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 "nsAccessibilityService.h"
10 #include "nsAccUtils.h"
11 #include "nsCoreUtils.h"
12 #include "nsIAccessibleEvent.h"
13 #include "RootAccessible.h"
16 #include "nsIDOMDocument.h"
17 #include "nsIDOMHTMLAnchorElement.h"
18 #include "nsIDOMHTMLTextAreaElement.h"
20 #include "nsIPresShell.h"
21 #include "nsISelectionPrivate.h"
22 #include "nsServiceManagerUtils.h"
23 #include "mozilla/Selection.h"
25 using namespace mozilla
;
26 using namespace mozilla::a11y
;
29 SelectionManager::Shutdown()
31 ClearControlSelectionListener();
32 mLastTextAccessible
= nullptr;
33 mLastUsedSelection
= nullptr;
37 SelectionManager::ClearControlSelectionListener()
42 const nsFrameSelection
* frameSel
= mCurrCtrlFrame
->GetConstFrameSelection();
43 NS_ASSERTION(frameSel
, "No frame selection for the element!");
45 mCurrCtrlFrame
= nullptr;
49 // Remove 'this' registered as selection listener for the normal selection.
50 Selection
* normalSel
=
51 frameSel
->GetSelection(nsISelectionController::SELECTION_NORMAL
);
52 normalSel
->RemoveSelectionListener(this);
54 // Remove 'this' registered as selection listener for the spellcheck
57 frameSel
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
);
58 spellSel
->RemoveSelectionListener(this);
62 SelectionManager::SetControlSelectionListener(dom::Element
* aFocusedElm
)
64 // When focus moves such that the caret is part of a new frame selection
65 // this removes the old selection listener and attaches a new one for
67 ClearControlSelectionListener();
69 mLastTextAccessible
= nullptr;
71 mCurrCtrlFrame
= aFocusedElm
->GetPrimaryFrame();
75 const nsFrameSelection
* frameSel
= mCurrCtrlFrame
->GetConstFrameSelection();
76 NS_ASSERTION(frameSel
, "No frame selection for focused element!");
80 // Register 'this' as selection listener for the normal selection.
81 Selection
* normalSel
=
82 frameSel
->GetSelection(nsISelectionController::SELECTION_NORMAL
);
83 normalSel
->AddSelectionListener(this);
85 // Register 'this' as selection listener for the spell check selection.
87 frameSel
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
);
88 spellSel
->AddSelectionListener(this);
92 SelectionManager::AddDocSelectionListener(nsIPresShell
* aPresShell
)
94 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
96 // Register 'this' as selection listener for the normal selection.
97 Selection
* normalSel
=
98 frameSel
->GetSelection(nsISelectionController::SELECTION_NORMAL
);
99 normalSel
->AddSelectionListener(this);
101 // Register 'this' as selection listener for the spell check selection.
102 Selection
* spellSel
=
103 frameSel
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
);
104 spellSel
->AddSelectionListener(this);
108 SelectionManager::RemoveDocSelectionListener(nsIPresShell
* aPresShell
)
110 const nsFrameSelection
* frameSel
= aPresShell
->ConstFrameSelection();
112 // Remove 'this' registered as selection listener for the normal selection.
113 Selection
* normalSel
=
114 frameSel
->GetSelection(nsISelectionController::SELECTION_NORMAL
);
115 normalSel
->RemoveSelectionListener(this);
117 // Remove 'this' registered as selection listener for the spellcheck
119 Selection
* spellSel
=
120 frameSel
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
);
121 spellSel
->RemoveSelectionListener(this);
125 SelectionManager::NotifySelectionChanged(nsIDOMDocument
* aDOMDocument
,
126 nsISelection
* aSelection
,
129 NS_ENSURE_ARG(aDOMDocument
);
131 nsCOMPtr
<nsIDocument
> documentNode(do_QueryInterface(aDOMDocument
));
132 DocAccessible
* document
= GetAccService()->GetDocAccessible(documentNode
);
135 if (logging::IsEnabled(logging::eSelection
))
136 logging::SelChange(aSelection
, document
);
139 // Don't fire events until document is loaded.
140 if (document
&& document
->IsContentLoaded()) {
141 // Selection manager has longer lifetime than any document accessible,
142 // so that we are guaranteed that the notification is processed before
143 // the selection manager is destroyed.
144 document
->HandleNotification
<SelectionManager
, nsISelection
>
145 (this, &SelectionManager::ProcessSelectionChanged
, aSelection
);
152 SelectionManager::ProcessSelectionChanged(nsISelection
* aSelection
)
154 nsCOMPtr
<nsISelectionPrivate
> privSel(do_QueryInterface(aSelection
));
157 privSel
->GetType(&type
);
159 if (type
== nsISelectionController::SELECTION_NORMAL
)
160 NormalSelectionChanged(aSelection
);
162 else if (type
== nsISelectionController::SELECTION_SPELLCHECK
)
163 SpellcheckSelectionChanged(aSelection
);
167 SelectionManager::NormalSelectionChanged(nsISelection
* aSelection
)
169 mLastUsedSelection
= do_GetWeakReference(aSelection
);
171 int32_t rangeCount
= 0;
172 aSelection
->GetRangeCount(&rangeCount
);
173 if (rangeCount
== 0) {
174 mLastTextAccessible
= nullptr;
175 return; // No selection
178 HyperTextAccessible
* textAcc
=
179 nsAccUtils::GetTextAccessibleFromSelection(aSelection
);
183 int32_t caretOffset
= -1;
184 nsresult rv
= textAcc
->GetCaretOffset(&caretOffset
);
188 if (textAcc
== mLastTextAccessible
&& caretOffset
== mLastCaretOffset
) {
189 int32_t selectionCount
= 0;
190 textAcc
->GetSelectionCount(&selectionCount
); // Don't swallow similar events when selecting text
192 return; // Swallow duplicate caret event
195 mLastCaretOffset
= caretOffset
;
196 mLastTextAccessible
= textAcc
;
198 nsRefPtr
<AccEvent
> event
= new AccCaretMoveEvent(mLastTextAccessible
);
199 mLastTextAccessible
->Document()->FireDelayedEvent(event
);
203 SelectionManager::SpellcheckSelectionChanged(nsISelection
* aSelection
)
205 // XXX: fire an event for accessible of focus node of the selection. If
206 // spellchecking is enabled then we will fire the number of events for
207 // the same accessible for newly appended range of the selection (for every
208 // misspelled word). If spellchecking is disabled (for example,
209 // @spellcheck="false" on html:body) then we won't fire any event.
211 HyperTextAccessible
* hyperText
=
212 nsAccUtils::GetTextAccessibleFromSelection(aSelection
);
214 hyperText
->Document()->
215 FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
,
221 SelectionManager::GetCaretRect(nsIWidget
** aWidget
)
224 NS_ENSURE_TRUE(aWidget
, caretRect
);
227 if (!mLastTextAccessible
) {
228 return caretRect
; // Return empty rect
231 nsINode
*lastNodeWithCaret
= mLastTextAccessible
->GetNode();
232 NS_ENSURE_TRUE(lastNodeWithCaret
, caretRect
);
234 nsIPresShell
*presShell
= nsCoreUtils::GetPresShellFor(lastNodeWithCaret
);
235 NS_ENSURE_TRUE(presShell
, caretRect
);
237 nsRefPtr
<nsCaret
> caret
= presShell
->GetCaret();
238 NS_ENSURE_TRUE(caret
, caretRect
);
240 nsCOMPtr
<nsISelection
> caretSelection(do_QueryReferent(mLastUsedSelection
));
241 NS_ENSURE_TRUE(caretSelection
, caretRect
);
244 caret
->GetCaretVisible(&isVisible
);
246 return nsIntRect(); // Return empty rect
250 nsIFrame
* frame
= caret
->GetGeometry(caretSelection
, &rect
);
251 if (!frame
|| rect
.IsEmpty()) {
252 return nsIntRect(); // Return empty rect
256 // Offset from widget origin to the frame origin, which includes chrome
258 *aWidget
= frame
->GetNearestWidget(offset
);
259 NS_ENSURE_TRUE(*aWidget
, nsIntRect());
262 caretRect
= rect
.ToOutsidePixels(frame
->PresContext()->AppUnitsPerDevPixel());
263 // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
264 caretRect
.MoveBy((*aWidget
)->WidgetToScreenOffset() - (*aWidget
)->GetClientOffset());
266 // Correct for character size, so that caret always matches the size of the character
267 // This is important for font size transitions, and is necessary because the Gecko caret uses the
268 // previous character's size as the user moves forward in the text by character.
269 int32_t charX
, charY
, charWidth
, charHeight
;
270 if (NS_SUCCEEDED(mLastTextAccessible
->GetCharacterExtents(mLastCaretOffset
, &charX
, &charY
,
271 &charWidth
, &charHeight
,
272 nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
))) {
273 caretRect
.height
-= charY
- caretRect
.y
;