ia2AccessibleText.cpp (14597B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:expandtab:shiftwidth=2:tabstop=2: 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "ia2Accessible.h" 9 #include "ia2AccessibleHypertext.h" 10 #include "ia2AccessibleText.h" 11 12 #include "AccessibleText_i.c" 13 14 #include "mozilla/a11y/Compatibility.h" 15 #include "mozilla/a11y/HyperTextAccessibleBase.h" 16 #include "mozilla/ClearOnShutdown.h" 17 18 using namespace mozilla::a11y; 19 20 HyperTextAccessibleBase* ia2AccessibleText::sLastTextChangeAcc = nullptr; 21 mozilla::StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString; 22 uint32_t ia2AccessibleText::sLastTextChangeStart = 0; 23 uint32_t ia2AccessibleText::sLastTextChangeEnd = 0; 24 bool ia2AccessibleText::sLastTextChangeWasInsert = false; 25 26 HyperTextAccessibleBase* ia2AccessibleText::TextAcc() { 27 auto hyp = static_cast<ia2AccessibleHypertext*>(this); 28 Accessible* acc = hyp->Acc(); 29 return acc ? acc->AsHyperTextBase() : nullptr; 30 } 31 32 // IAccessibleText 33 34 STDMETHODIMP 35 ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset) { 36 HyperTextAccessibleBase* textAcc = TextAcc(); 37 if (!textAcc) { 38 return CO_E_OBJNOTCONNECTED; 39 } 40 41 return textAcc->AddToSelection(aStartOffset, aEndOffset) ? S_OK 42 : E_INVALIDARG; 43 } 44 45 STDMETHODIMP 46 ia2AccessibleText::get_attributes(long aOffset, long* aStartOffset, 47 long* aEndOffset, BSTR* aTextAttributes) { 48 if (!aStartOffset || !aEndOffset || !aTextAttributes) return E_INVALIDARG; 49 50 *aStartOffset = 0; 51 *aEndOffset = 0; 52 *aTextAttributes = nullptr; 53 54 int32_t startOffset = 0, endOffset = 0; 55 HyperTextAccessibleBase* textAcc = TextAcc(); 56 if (!textAcc) { 57 return CO_E_OBJNOTCONNECTED; 58 } 59 60 RefPtr<AccAttributes> attributes = 61 textAcc->TextAttributes(true, aOffset, &startOffset, &endOffset); 62 63 HRESULT hr = 64 ia2Accessible::ConvertToIA2Attributes(attributes, aTextAttributes); 65 if (FAILED(hr)) return hr; 66 67 *aStartOffset = startOffset; 68 *aEndOffset = endOffset; 69 70 return S_OK; 71 } 72 73 STDMETHODIMP 74 ia2AccessibleText::get_caretOffset(long* aOffset) { 75 if (!aOffset) return E_INVALIDARG; 76 77 *aOffset = -1; 78 79 HyperTextAccessibleBase* textAcc = TextAcc(); 80 if (!textAcc) { 81 return CO_E_OBJNOTCONNECTED; 82 } 83 84 *aOffset = textAcc->CaretOffset(); 85 86 return *aOffset != -1 ? S_OK : S_FALSE; 87 } 88 89 STDMETHODIMP 90 ia2AccessibleText::get_characterExtents(long aOffset, 91 enum IA2CoordinateType aCoordType, 92 long* aX, long* aY, long* aWidth, 93 long* aHeight) { 94 if (!aX || !aY || !aWidth || !aHeight) return E_INVALIDARG; 95 *aX = *aY = *aWidth = *aHeight = 0; 96 97 uint32_t geckoCoordType = 98 (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) 99 ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE 100 : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; 101 LayoutDeviceIntRect rect; 102 auto textAcc = TextAcc(); 103 if (!textAcc) { 104 return CO_E_OBJNOTCONNECTED; 105 } 106 107 rect = textAcc->CharBounds(aOffset, geckoCoordType); 108 109 // Can't use GetRect() because of long vs. int32_t mismatch 110 *aX = rect.X(); 111 *aY = rect.Y(); 112 *aWidth = rect.Width(); 113 *aHeight = rect.Height(); 114 return S_OK; 115 } 116 117 STDMETHODIMP 118 ia2AccessibleText::get_nSelections(long* aNSelections) { 119 if (!aNSelections) return E_INVALIDARG; 120 *aNSelections = 0; 121 122 HyperTextAccessibleBase* textAcc = TextAcc(); 123 if (!textAcc) { 124 return CO_E_OBJNOTCONNECTED; 125 } 126 127 *aNSelections = textAcc->SelectionCount(); 128 if (*aNSelections == 0 && 129 (Compatibility::A11ySuppressionReasons() & 130 SuppressionReasons::Clipboard) && 131 static_cast<ia2AccessibleHypertext*>(this)->Acc()->IsDoc()) { 132 // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 133 // 22H2) might walk the document a11y tree using UIA whenever anything 134 // is copied to the clipboard. This causes an unacceptable hang. It walks 135 // using IAccessibleText/IAccessibleHyperText if the document reports no 136 // selection, so we lie here and say that there is a selection even though 137 // there isn't. It will subsequently call get_selection, which will fail, 138 // but this hack here seems to be enough to avoid further text calls. 139 *aNSelections = 1; 140 } 141 142 return S_OK; 143 } 144 145 STDMETHODIMP 146 ia2AccessibleText::get_offsetAtPoint(long aX, long aY, 147 enum IA2CoordinateType aCoordType, 148 long* aOffset) { 149 if (!aOffset) return E_INVALIDARG; 150 *aOffset = 0; 151 152 uint32_t geckoCoordType = 153 (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) 154 ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE 155 : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; 156 157 HyperTextAccessibleBase* textAcc = TextAcc(); 158 if (!textAcc) { 159 return CO_E_OBJNOTCONNECTED; 160 } 161 162 *aOffset = textAcc->OffsetAtPoint(aX, aY, geckoCoordType); 163 164 return *aOffset == -1 ? S_FALSE : S_OK; 165 } 166 167 STDMETHODIMP 168 ia2AccessibleText::get_selection(long aSelectionIndex, long* aStartOffset, 169 long* aEndOffset) { 170 if (!aStartOffset || !aEndOffset) return E_INVALIDARG; 171 *aStartOffset = *aEndOffset = 0; 172 173 int32_t startOffset = 0, endOffset = 0; 174 HyperTextAccessibleBase* textAcc = TextAcc(); 175 if (!textAcc) { 176 return CO_E_OBJNOTCONNECTED; 177 } 178 179 if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) { 180 return E_INVALIDARG; 181 } 182 183 *aStartOffset = startOffset; 184 *aEndOffset = endOffset; 185 return S_OK; 186 } 187 188 STDMETHODIMP 189 ia2AccessibleText::get_text(long aStartOffset, long aEndOffset, BSTR* aText) { 190 if (!aText) return E_INVALIDARG; 191 192 *aText = nullptr; 193 194 nsAutoString text; 195 HyperTextAccessibleBase* textAcc = TextAcc(); 196 if (!textAcc) { 197 return CO_E_OBJNOTCONNECTED; 198 } 199 200 if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) { 201 return E_INVALIDARG; 202 } 203 204 textAcc->TextSubstring(aStartOffset, aEndOffset, text); 205 206 if (text.IsEmpty()) return S_FALSE; 207 208 *aText = ::SysAllocStringLen(text.get(), text.Length()); 209 return *aText ? S_OK : E_OUTOFMEMORY; 210 } 211 212 STDMETHODIMP 213 ia2AccessibleText::get_textBeforeOffset(long aOffset, 214 enum IA2TextBoundaryType aBoundaryType, 215 long* aStartOffset, long* aEndOffset, 216 BSTR* aText) { 217 if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; 218 219 *aStartOffset = *aEndOffset = 0; 220 *aText = nullptr; 221 222 HyperTextAccessibleBase* textAcc = TextAcc(); 223 if (!textAcc) { 224 return CO_E_OBJNOTCONNECTED; 225 } 226 227 if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; 228 229 nsAutoString text; 230 int32_t startOffset = 0, endOffset = 0; 231 232 if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { 233 startOffset = 0; 234 endOffset = textAcc->CharacterCount(); 235 textAcc->TextSubstring(startOffset, endOffset, text); 236 } else { 237 AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); 238 if (boundaryType == -1) return S_FALSE; 239 240 textAcc->TextBeforeOffset(aOffset, boundaryType, &startOffset, &endOffset, 241 text); 242 } 243 244 *aStartOffset = startOffset; 245 *aEndOffset = endOffset; 246 247 if (text.IsEmpty()) return S_FALSE; 248 249 *aText = ::SysAllocStringLen(text.get(), text.Length()); 250 return *aText ? S_OK : E_OUTOFMEMORY; 251 } 252 253 STDMETHODIMP 254 ia2AccessibleText::get_textAfterOffset(long aOffset, 255 enum IA2TextBoundaryType aBoundaryType, 256 long* aStartOffset, long* aEndOffset, 257 BSTR* aText) { 258 if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; 259 260 *aStartOffset = 0; 261 *aEndOffset = 0; 262 *aText = nullptr; 263 264 HyperTextAccessibleBase* textAcc = TextAcc(); 265 if (!textAcc) { 266 return CO_E_OBJNOTCONNECTED; 267 } 268 269 if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; 270 271 nsAutoString text; 272 int32_t startOffset = 0, endOffset = 0; 273 274 if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { 275 startOffset = 0; 276 endOffset = textAcc->CharacterCount(); 277 textAcc->TextSubstring(startOffset, endOffset, text); 278 } else { 279 AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); 280 if (boundaryType == -1) return S_FALSE; 281 textAcc->TextAfterOffset(aOffset, boundaryType, &startOffset, &endOffset, 282 text); 283 } 284 285 *aStartOffset = startOffset; 286 *aEndOffset = endOffset; 287 288 if (text.IsEmpty()) return S_FALSE; 289 290 *aText = ::SysAllocStringLen(text.get(), text.Length()); 291 return *aText ? S_OK : E_OUTOFMEMORY; 292 } 293 294 STDMETHODIMP 295 ia2AccessibleText::get_textAtOffset(long aOffset, 296 enum IA2TextBoundaryType aBoundaryType, 297 long* aStartOffset, long* aEndOffset, 298 BSTR* aText) { 299 if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; 300 301 *aStartOffset = *aEndOffset = 0; 302 *aText = nullptr; 303 304 HyperTextAccessibleBase* textAcc = TextAcc(); 305 if (!textAcc) return CO_E_OBJNOTCONNECTED; 306 307 if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; 308 309 nsAutoString text; 310 int32_t startOffset = 0, endOffset = 0; 311 if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { 312 startOffset = 0; 313 endOffset = textAcc->CharacterCount(); 314 textAcc->TextSubstring(startOffset, endOffset, text); 315 } else { 316 AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); 317 if (boundaryType == -1) return S_FALSE; 318 textAcc->TextAtOffset(aOffset, boundaryType, &startOffset, &endOffset, 319 text); 320 } 321 322 *aStartOffset = startOffset; 323 *aEndOffset = endOffset; 324 325 if (text.IsEmpty()) return S_FALSE; 326 327 *aText = ::SysAllocStringLen(text.get(), text.Length()); 328 return *aText ? S_OK : E_OUTOFMEMORY; 329 } 330 331 STDMETHODIMP 332 ia2AccessibleText::removeSelection(long aSelectionIndex) { 333 HyperTextAccessibleBase* textAcc = TextAcc(); 334 if (!textAcc) { 335 return CO_E_OBJNOTCONNECTED; 336 } 337 338 return textAcc->RemoveFromSelection(aSelectionIndex) ? S_OK : E_INVALIDARG; 339 } 340 341 STDMETHODIMP 342 ia2AccessibleText::setCaretOffset(long aOffset) { 343 HyperTextAccessibleBase* textAcc = TextAcc(); 344 if (!textAcc) { 345 return CO_E_OBJNOTCONNECTED; 346 } 347 348 if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; 349 350 textAcc->SetCaretOffset(aOffset); 351 return S_OK; 352 } 353 354 STDMETHODIMP 355 ia2AccessibleText::setSelection(long aSelectionIndex, long aStartOffset, 356 long aEndOffset) { 357 HyperTextAccessibleBase* textAcc = TextAcc(); 358 if (!textAcc) { 359 return CO_E_OBJNOTCONNECTED; 360 } 361 362 return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset, 363 aEndOffset) 364 ? S_OK 365 : E_INVALIDARG; 366 } 367 368 STDMETHODIMP 369 ia2AccessibleText::get_nCharacters(long* aNCharacters) { 370 if (!aNCharacters) return E_INVALIDARG; 371 *aNCharacters = 0; 372 373 HyperTextAccessibleBase* textAcc = TextAcc(); 374 if (!textAcc) return CO_E_OBJNOTCONNECTED; 375 376 *aNCharacters = textAcc->CharacterCount(); 377 return S_OK; 378 } 379 380 STDMETHODIMP 381 ia2AccessibleText::scrollSubstringTo(long aStartIndex, long aEndIndex, 382 enum IA2ScrollType aScrollType) { 383 HyperTextAccessibleBase* textAcc = TextAcc(); 384 if (!textAcc) { 385 return CO_E_OBJNOTCONNECTED; 386 } 387 388 if (!textAcc->IsValidRange(aStartIndex, aEndIndex)) return E_INVALIDARG; 389 390 textAcc->ScrollSubstringTo(aStartIndex, aEndIndex, aScrollType); 391 return S_OK; 392 } 393 394 STDMETHODIMP 395 ia2AccessibleText::scrollSubstringToPoint(long aStartIndex, long aEndIndex, 396 enum IA2CoordinateType aCoordType, 397 long aX, long aY) { 398 HyperTextAccessibleBase* textAcc = TextAcc(); 399 if (!textAcc) { 400 return CO_E_OBJNOTCONNECTED; 401 } 402 if (!textAcc->IsValidRange(aStartIndex, aEndIndex)) { 403 return E_INVALIDARG; 404 } 405 uint32_t geckoCoordType = 406 (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) 407 ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE 408 : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; 409 textAcc->ScrollSubstringToPoint(aStartIndex, aEndIndex, geckoCoordType, aX, 410 aY); 411 return S_OK; 412 } 413 414 STDMETHODIMP 415 ia2AccessibleText::get_newText(IA2TextSegment* aNewText) { 416 return GetModifiedText(true, aNewText); 417 } 418 419 STDMETHODIMP 420 ia2AccessibleText::get_oldText(IA2TextSegment* aOldText) { 421 return GetModifiedText(false, aOldText); 422 } 423 424 // ia2AccessibleText 425 426 HRESULT 427 ia2AccessibleText::GetModifiedText(bool aGetInsertedText, 428 IA2TextSegment* aText) { 429 if (!aText) return E_INVALIDARG; 430 431 if (!sLastTextChangeAcc) return S_OK; 432 433 if (aGetInsertedText != sLastTextChangeWasInsert) return S_OK; 434 435 if (sLastTextChangeAcc != TextAcc()) return S_OK; 436 437 aText->start = sLastTextChangeStart; 438 aText->end = sLastTextChangeEnd; 439 440 if (sLastTextChangeString->IsEmpty()) return S_FALSE; 441 442 aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), 443 sLastTextChangeString->Length()); 444 return aText->text ? S_OK : E_OUTOFMEMORY; 445 } 446 447 AccessibleTextBoundary ia2AccessibleText::GetGeckoTextBoundary( 448 enum IA2TextBoundaryType aBoundaryType) { 449 switch (aBoundaryType) { 450 case IA2_TEXT_BOUNDARY_CHAR: 451 return nsIAccessibleText::BOUNDARY_CLUSTER; 452 case IA2_TEXT_BOUNDARY_WORD: 453 return nsIAccessibleText::BOUNDARY_WORD_START; 454 case IA2_TEXT_BOUNDARY_LINE: 455 return nsIAccessibleText::BOUNDARY_LINE_START; 456 case IA2_TEXT_BOUNDARY_PARAGRAPH: 457 return nsIAccessibleText::BOUNDARY_PARAGRAPH; 458 // case IA2_TEXT_BOUNDARY_SENTENCE: 459 // XXX: not implemented 460 default: 461 return -1; 462 } 463 } 464 465 void ia2AccessibleText::InitTextChangeData() { 466 ClearOnShutdown(&sLastTextChangeString); 467 } 468 469 void ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleBase* aAcc, 470 bool aInsert, 471 const nsAString& aStr, 472 int32_t aStart, uint32_t aLen) { 473 if (!sLastTextChangeString) sLastTextChangeString = new nsString(); 474 475 sLastTextChangeAcc = aAcc; 476 sLastTextChangeStart = aStart; 477 sLastTextChangeEnd = aStart + aLen; 478 sLastTextChangeWasInsert = aInsert; 479 *sLastTextChangeString = aStr; 480 }