UiaText.cpp (5617B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "UiaText.h" 8 9 #include "MsaaAccessible.h" 10 #include "mozilla/a11y/States.h" 11 #include "TextLeafRange.h" 12 #include "UiaTextRange.h" 13 14 namespace mozilla::a11y { 15 16 // Helpers 17 18 static SAFEARRAY* TextLeafRangesToUiaRanges( 19 const nsTArray<TextLeafRange>& aRanges) { 20 // The documentation for GetSelection doesn't specify whether we should return 21 // an empty array or null if there are no ranges to return. However, 22 // GetVisibleRanges says that we should return an empty array, never null, so 23 // that's what we do. 24 // https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextprovider-getvisibleranges 25 SAFEARRAY* uiaRanges = SafeArrayCreateVector(VT_UNKNOWN, 0, aRanges.Length()); 26 LONG indices[1] = {0}; 27 for (const TextLeafRange& range : aRanges) { 28 // SafeArrayPutElement calls AddRef on the element, so we use a raw 29 // pointer here. 30 UiaTextRange* uiaRange = new UiaTextRange(range); 31 SafeArrayPutElement(uiaRanges, indices, uiaRange); 32 ++indices[0]; 33 } 34 return uiaRanges; 35 } 36 37 // IUnknown 38 IMPL_IUNKNOWN1(UiaText, ITextProvider) 39 40 // UiaText 41 42 UiaText::UiaText(MsaaAccessible* aMsaa) : mMsaa(aMsaa) {} 43 44 Accessible* UiaText::Acc() const { return mMsaa->Acc(); } 45 46 // ITextProvider methods 47 48 STDMETHODIMP 49 UiaText::GetSelection(__RPC__deref_out_opt SAFEARRAY** aRetVal) { 50 if (!aRetVal) { 51 return E_INVALIDARG; 52 } 53 Accessible* acc = Acc(); 54 if (!acc) { 55 return CO_E_OBJNOTCONNECTED; 56 } 57 AutoTArray<TextLeafRange, 1> ranges; 58 TextLeafRange::GetSelection(acc, ranges); 59 if (ranges.IsEmpty()) { 60 // There is no selection. Check if there is a caret. 61 if (TextLeafPoint caret = TextLeafPoint::GetCaret(acc)) { 62 ranges.EmplaceBack(caret, caret); 63 } 64 } 65 *aRetVal = TextLeafRangesToUiaRanges(ranges); 66 return S_OK; 67 } 68 69 STDMETHODIMP 70 UiaText::GetVisibleRanges(__RPC__deref_out_opt SAFEARRAY** aRetVal) { 71 if (!aRetVal) { 72 return E_INVALIDARG; 73 } 74 Accessible* acc = Acc(); 75 if (!acc) { 76 return CO_E_OBJNOTCONNECTED; 77 } 78 TextLeafRange fullRange = TextLeafRange::FromAccessible(acc); 79 // The most pragmatic way to determine visible text is to walk by line. 80 // XXX TextLeafRange::VisibleLines doesn't correctly handle lines that are 81 // scrolled out where the scroll container is a descendant of acc. See bug 82 // 1945010. 83 nsTArray<TextLeafRange> ranges = fullRange.VisibleLines(acc); 84 *aRetVal = TextLeafRangesToUiaRanges(ranges); 85 return S_OK; 86 } 87 88 STDMETHODIMP 89 UiaText::RangeFromChild(__RPC__in_opt IRawElementProviderSimple* aChildElement, 90 __RPC__deref_out_opt ITextRangeProvider** aRetVal) { 91 if (!aChildElement || !aRetVal) { 92 return E_INVALIDARG; 93 } 94 *aRetVal = nullptr; 95 Accessible* acc = Acc(); 96 if (!acc) { 97 return CO_E_OBJNOTCONNECTED; 98 } 99 Accessible* child = MsaaAccessible::GetAccessibleFrom(aChildElement); 100 if (!child || !acc->IsAncestorOf(child)) { 101 return E_INVALIDARG; 102 } 103 TextLeafRange range = TextLeafRange::FromAccessible(child); 104 RefPtr uiaRange = new UiaTextRange(range); 105 uiaRange.forget(aRetVal); 106 return S_OK; 107 } 108 109 STDMETHODIMP 110 UiaText::RangeFromPoint(struct UiaPoint aPoint, 111 __RPC__deref_out_opt ITextRangeProvider** aRetVal) { 112 if (!aRetVal) { 113 return E_INVALIDARG; 114 } 115 *aRetVal = nullptr; 116 Accessible* acc = Acc(); 117 if (!acc) { 118 return CO_E_OBJNOTCONNECTED; 119 } 120 121 // Find the deepest accessible node at the given screen coordinates. 122 Accessible* child = acc->ChildAtPoint( 123 aPoint.x, aPoint.y, Accessible::EWhichChildAtPoint::DeepestChild); 124 if (!child) { 125 return E_INVALIDARG; 126 } 127 128 // Find the closest point within the entirety of the leaf where the screen 129 // coordinates lie. 130 TextLeafRange leafRange = TextLeafRange::FromAccessible(child); 131 TextLeafPoint closestPoint = 132 leafRange.TextLeafPointAtScreenPoint(aPoint.x, aPoint.y); 133 TextLeafRange range{closestPoint, closestPoint}; 134 RefPtr uiaRange = new UiaTextRange(range); 135 uiaRange.forget(aRetVal); 136 return S_OK; 137 } 138 139 STDMETHODIMP 140 UiaText::get_DocumentRange(__RPC__deref_out_opt ITextRangeProvider** aRetVal) { 141 if (!aRetVal) { 142 return E_INVALIDARG; 143 } 144 Accessible* acc = Acc(); 145 if (!acc) { 146 return CO_E_OBJNOTCONNECTED; 147 } 148 // On the web, the "document range" could either span the entire document or 149 // just a text input control, depending on the element on which the Text 150 // pattern was queried. See: 151 // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview#webpage-and-text-input-controls-in-edge 152 TextLeafRange range = TextLeafRange::FromAccessible(acc); 153 RefPtr uiaRange = new UiaTextRange(range); 154 uiaRange.forget(aRetVal); 155 return S_OK; 156 } 157 158 STDMETHODIMP 159 UiaText::get_SupportedTextSelection( 160 __RPC__out enum SupportedTextSelection* aRetVal) { 161 if (!aRetVal) { 162 return E_INVALIDARG; 163 } 164 Accessible* acc = Acc(); 165 if (!acc) { 166 return CO_E_OBJNOTCONNECTED; 167 } 168 if (!acc->IsHyperText()) { 169 // Currently, the SELECTABLE_TEXT state is only exposed on HyperText 170 // Accessibles. 171 acc = acc->Parent(); 172 } 173 if (acc && acc->State() & states::SELECTABLE_TEXT) { 174 *aRetVal = SupportedTextSelection_Multiple; 175 } else { 176 *aRetVal = SupportedTextSelection_None; 177 } 178 return S_OK; 179 } 180 181 } // namespace mozilla::a11y