Bug 1877642 - Disable browser_fullscreen-tab-close-race.js on apple_silicon !debug...
[gecko.git] / accessible / base / SelectionManager.cpp
blob97721bb439e4ca4fd54ca5f1ca96c24760ba4f41
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;
30 int16_t mReason;
31 int32_t mGranularity;
33 NS_INLINE_DECL_REFCOUNTING(SelData)
35 private:
36 // Private destructor, to discourage deletion outside of Release():
37 ~SelData() {}
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
51 // selection.
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
61 // the current focus.
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
102 // selection.
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
130 // selection events.
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);
145 NS_ASSERTION(
146 caretCntr,
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);
168 NS_IMETHODIMP
169 SelectionManager::NotifySelectionChanged(dom::Document* aDocument,
170 Selection* aSelection, int16_t aReason,
171 int32_t aAmount) {
172 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
173 return NS_ERROR_INVALID_ARG;
176 DocAccessible* document = GetAccService()->GetDocAccessible(aDocument);
178 #ifdef A11Y_LOG
179 if (logging::IsEnabled(logging::eSelection)) {
180 logging::SelChange(aSelection, document, aReason);
182 #endif
184 if (document) {
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);
193 return NS_OK;
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;
202 if (range) {
203 cntrNode = range->GetClosestCommonInclusiveAncestor();
206 if (!cntrNode) {
207 cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
208 if (!cntrNode) {
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);
217 if (!text) {
218 // FIXME bug 1126649
219 NS_ERROR("We must reach document accessible implementing text interface!");
220 return;
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();
241 MOZ_ASSERT(doc);
242 TextLeafPoint::UpdateCachedSpellingError(doc, aRange);
246 SelectionManager::~SelectionManager() = default;