SelectionManager.cpp (8598B)
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 #include "TextLeafRange.h" 17 18 #include "mozilla/PresShell.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 { 27 SelData(Selection* aSel, int32_t aReason, int32_t aGranularity) 28 : mSel(aSel), mReason(aReason), mGranularity(aGranularity) {} 29 30 RefPtr<Selection> mSel; 31 int16_t mReason; 32 int32_t mGranularity; 33 34 NS_INLINE_DECL_REFCOUNTING(SelData) 35 36 private: 37 // Private destructor, to discourage deletion outside of Release(): 38 ~SelData() {} 39 }; 40 41 SelectionManager::SelectionManager() 42 : mCaretOffset(-1), mAccWithCaret(nullptr) {} 43 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; 49 } 50 } 51 52 void SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) { 53 // When focus moves such that the caret is part of a new frame selection 54 // this removes the old selection listener and attaches a new one for 55 // the current focus. 56 ClearControlSelectionListener(); 57 58 nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame(); 59 if (!controlFrame) return; 60 61 const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection(); 62 NS_ASSERTION(frameSel, "No frame selection for focused element!"); 63 if (!frameSel) return; 64 65 // Register 'this' as selection listener for the normal selection. 66 Selection& normalSel = frameSel->NormalSelection(); 67 normalSel.AddSelectionListener(this); 68 mCurrCtrlNormalSel = &normalSel; 69 } 70 71 void SelectionManager::AddDocSelectionListener(PresShell* aPresShell) { 72 const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); 73 74 // Register 'this' as selection listener for the normal selection. 75 Selection& normalSel = frameSel->NormalSelection(); 76 normalSel.AddSelectionListener(this); 77 } 78 79 void SelectionManager::RemoveDocSelectionListener(PresShell* aPresShell) { 80 const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); 81 82 // Remove 'this' registered as selection listener for the normal selection. 83 Selection& normalSel = frameSel->NormalSelection(); 84 normalSel.RemoveSelectionListener(this); 85 86 if (mCurrCtrlNormalSel) { 87 if (mCurrCtrlNormalSel->GetPresShell() == aPresShell) { 88 // Remove 'this' registered as selection listener for the normal selection 89 // if we are removing listeners for its PresShell. 90 mCurrCtrlNormalSel->RemoveSelectionListener(this); 91 mCurrCtrlNormalSel = nullptr; 92 } 93 } 94 } 95 96 void SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) { 97 // Fire selection change event if it's not pure caret-move selection change, 98 // i.e. the accessible has or had not collapsed selection. Also, it must not 99 // be a collapsed selection on the container of a focused text field, since 100 // the text field has an independent selection and will thus fire its own 101 // selection events. 102 AccTextSelChangeEvent* event = downcast_accEvent(aEvent); 103 if (!event->IsCaretMoveOnly() && 104 !(event->mSel->IsCollapsed() && event->mSel != mCurrCtrlNormalSel && 105 FocusMgr() && FocusMgr()->FocusedLocalAccessible() && 106 FocusMgr()->FocusedLocalAccessible()->IsTextField())) { 107 nsEventShell::FireEvent(aEvent); 108 } 109 110 // Fire caret move event if there's a caret in the selection. 111 nsINode* caretCntrNode = nsCoreUtils::GetDOMNodeFromDOMPoint( 112 event->mSel->GetFocusNode(), event->mSel->FocusOffset()); 113 if (!caretCntrNode) return; 114 115 HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode); 116 NS_ASSERTION( 117 caretCntr, 118 "No text container for focus while there's one for common ancestor?!"); 119 if (!caretCntr) return; 120 121 Selection* selection = caretCntr->DOMSelection(); 122 123 // XXX Sometimes we can't get a selection for caretCntr, in that case assume 124 // event->mSel is correct. 125 if (!selection) selection = event->mSel; 126 127 mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(), 128 selection->FocusOffset()); 129 mAccWithCaret = caretCntr; 130 if (mCaretOffset != -1) { 131 TextLeafPoint caret = TextLeafPoint::GetCaret(caretCntr); 132 RefPtr<AccCaretMoveEvent> caretMoveEvent = 133 new AccCaretMoveEvent(caretCntr, mCaretOffset, selection->IsCollapsed(), 134 caret.mIsEndOfLineInsertionPoint, 135 event->GetGranularity(), aEvent->FromUserInput()); 136 nsEventShell::FireEvent(caretMoveEvent); 137 } 138 } 139 140 NS_IMETHODIMP 141 SelectionManager::NotifySelectionChanged(dom::Document* aDocument, 142 Selection* aSelection, int16_t aReason, 143 int32_t aAmount) { 144 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) { 145 return NS_ERROR_INVALID_ARG; 146 } 147 148 DocAccessible* document = GetAccService()->GetDocAccessible(aDocument); 149 150 #ifdef A11Y_LOG 151 if (logging::IsEnabled(logging::eSelection)) { 152 logging::SelChange(aSelection, document, aReason); 153 } 154 #endif 155 156 if (document) { 157 // Selection manager has longer lifetime than any document accessible, 158 // so that we are guaranteed that the notification is processed before 159 // the selection manager is destroyed. 160 RefPtr<SelData> selData = new SelData(aSelection, aReason, aAmount); 161 document->HandleNotification<SelectionManager, SelData>( 162 this, &SelectionManager::ProcessSelectionChanged, selData); 163 } 164 165 return NS_OK; 166 } 167 168 void SelectionManager::ProcessSelectionChanged(SelData* aSelData) { 169 Selection* selection = aSelData->mSel; 170 if (!selection->GetPresShell()) return; 171 172 const nsRange* range = selection->GetAnchorFocusRange(); 173 nsINode* cntrNode = nullptr; 174 if (range) { 175 cntrNode = range->GetClosestCommonInclusiveAncestor(); 176 } 177 178 if (!cntrNode) { 179 cntrNode = selection->GetFrameSelection()->GetAncestorLimiter(); 180 if (!cntrNode) { 181 cntrNode = selection->GetPresShell()->GetDocument(); 182 NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == 183 selection->GetFrameSelection(), 184 "Wrong selection container was used!"); 185 } 186 } 187 188 HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode); 189 if (!text) { 190 // FIXME bug 1126649 191 NS_ERROR("We must reach document accessible implementing text interface!"); 192 return; 193 } 194 195 if (selection->GetType() == SelectionType::eNormal) { 196 RefPtr<AccEvent> event = new AccTextSelChangeEvent( 197 text, selection, aSelData->mReason, aSelData->mGranularity); 198 text->Document()->FireDelayedEvent(event); 199 } 200 } 201 202 /* static */ 203 bool SelectionManager::SelectionRangeChanged(SelectionType aType, 204 const dom::AbstractRange& aRange) { 205 if (aType != SelectionType::eSpellCheck && 206 aType != SelectionType::eTargetText && 207 aType != SelectionType::eHighlight) { 208 // We don't need to handle range changes for this selection type. 209 return false; 210 } 211 if (!GetAccService()) { 212 return false; 213 } 214 nsINode* start = aRange.GetStartContainer(); 215 if (!start) { 216 // This can happen when the document is being cleaned up. 217 return false; 218 } 219 dom::Document* doc = start->OwnerDoc(); 220 MOZ_ASSERT(doc); 221 nsINode* node = aRange.GetClosestCommonInclusiveAncestor(); 222 if (!node) { 223 // Bug 1954751: This can happen when a Selection is being garbage collected, 224 // but it's unclear exactly what other circumstances are involved. 225 return false; 226 } 227 HyperTextAccessible* acc = nsAccUtils::GetTextContainer(node); 228 if (!acc) { 229 return true; 230 } 231 MOZ_ASSERT(acc->Document()); 232 acc->Document()->FireDelayedEvent( 233 nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, acc); 234 if (IPCAccessibilityActive()) { 235 TextLeafPoint::UpdateCachedTextOffsetAttributes(doc, aRange); 236 } 237 return true; 238 } 239 240 SelectionManager::~SelectionManager() = default;