ia2Accessible.cpp (17269B)
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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "AccessibleWrap.h" 8 9 #include "Accessible2_i.c" 10 #include "Accessible2_2_i.c" 11 #include "AccessibleRole.h" 12 #include "AccessibleStates.h" 13 14 #include "AccAttributes.h" 15 #include "ApplicationAccessible.h" 16 #include "Compatibility.h" 17 #include "ia2AccessibleRelation.h" 18 #include "IUnknownImpl.h" 19 #include "nsAccUtils.h" 20 #include "nsCoreUtils.h" 21 #include "nsIAccessibleTypes.h" 22 #include "mozilla/a11y/PDocAccessible.h" 23 #include "Relation.h" 24 #include "TextRange-inl.h" 25 #include "nsAccessibilityService.h" 26 27 #include "mozilla/PresShell.h" 28 #include "nsISimpleEnumerator.h" 29 30 using namespace mozilla; 31 using namespace mozilla::a11y; 32 33 //////////////////////////////////////////////////////////////////////////////// 34 // ia2Accessible 35 //////////////////////////////////////////////////////////////////////////////// 36 37 STDMETHODIMP 38 ia2Accessible::QueryInterface(REFIID iid, void** ppv) { 39 if (!ppv) return E_INVALIDARG; 40 41 *ppv = nullptr; 42 43 // NOTE: If any new versions of IAccessible2 are added here, they should 44 // also be added to the IA2 Handler in 45 // /accessible/ipc/win/handler/AccessibleHandler.cpp 46 47 if (IID_IAccessible2_2 == iid) { 48 *ppv = static_cast<IAccessible2_2*>(this); 49 } else if (IID_IAccessible2 == iid) { 50 *ppv = static_cast<IAccessible2*>(this); 51 } 52 53 if (*ppv) { 54 (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); 55 return S_OK; 56 } 57 58 return E_NOINTERFACE; 59 } 60 61 AccessibleWrap* ia2Accessible::LocalAcc() { 62 return static_cast<MsaaAccessible*>(this)->LocalAcc(); 63 } 64 65 Accessible* ia2Accessible::Acc() { 66 return static_cast<MsaaAccessible*>(this)->Acc(); 67 } 68 69 //////////////////////////////////////////////////////////////////////////////// 70 // IAccessible2 71 72 STDMETHODIMP 73 ia2Accessible::get_nRelations(long* aNRelations) { 74 if (!aNRelations) return E_INVALIDARG; 75 *aNRelations = 0; 76 77 Accessible* acc = Acc(); 78 if (!acc) { 79 return CO_E_OBJNOTCONNECTED; 80 } 81 82 for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) { 83 if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; 84 85 Relation rel = acc->RelationByType(sRelationTypePairs[idx].first); 86 if (rel.Next()) (*aNRelations)++; 87 } 88 return S_OK; 89 } 90 91 STDMETHODIMP 92 ia2Accessible::get_relation(long aRelationIndex, 93 IAccessibleRelation** aRelation) { 94 if (!aRelation || aRelationIndex < 0) return E_INVALIDARG; 95 *aRelation = nullptr; 96 97 Accessible* acc = Acc(); 98 if (!acc) { 99 return CO_E_OBJNOTCONNECTED; 100 } 101 102 long relIdx = 0; 103 for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) { 104 if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; 105 106 RelationType relationType = sRelationTypePairs[idx].first; 107 Relation rel = acc->RelationByType(relationType); 108 RefPtr<ia2AccessibleRelation> ia2Relation = 109 new ia2AccessibleRelation(relationType, &rel); 110 if (ia2Relation->HasTargets()) { 111 if (relIdx == aRelationIndex) { 112 ia2Relation.forget(aRelation); 113 return S_OK; 114 } 115 116 relIdx++; 117 } 118 } 119 120 return E_INVALIDARG; 121 } 122 123 STDMETHODIMP 124 ia2Accessible::get_relations(long aMaxRelations, 125 IAccessibleRelation** aRelation, 126 long* aNRelations) { 127 if (!aRelation || !aNRelations || aMaxRelations <= 0) return E_INVALIDARG; 128 *aNRelations = 0; 129 130 Accessible* acc = Acc(); 131 if (!acc) { 132 return CO_E_OBJNOTCONNECTED; 133 } 134 135 for (uint32_t idx = 0; 136 idx < std::size(sRelationTypePairs) && *aNRelations < aMaxRelations; 137 idx++) { 138 if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; 139 140 RelationType relationType = sRelationTypePairs[idx].first; 141 Relation rel = acc->RelationByType(relationType); 142 RefPtr<ia2AccessibleRelation> ia2Rel = 143 new ia2AccessibleRelation(relationType, &rel); 144 if (ia2Rel->HasTargets()) { 145 ia2Rel.forget(aRelation + (*aNRelations)); 146 (*aNRelations)++; 147 } 148 } 149 return S_OK; 150 } 151 152 STDMETHODIMP 153 ia2Accessible::role(long* aRole) { 154 if (!aRole) return E_INVALIDARG; 155 *aRole = 0; 156 157 Accessible* acc = Acc(); 158 if (!acc) return CO_E_OBJNOTCONNECTED; 159 160 #define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \ 161 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \ 162 nameRule) \ 163 case roles::_geckoRole: \ 164 *aRole = ia2Role; \ 165 break; 166 167 a11y::role geckoRole; 168 geckoRole = acc->Role(); 169 switch (geckoRole) { 170 #include "RoleMap.h" 171 default: 172 MOZ_CRASH("Unknown role."); 173 } 174 175 #undef ROLE 176 177 // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call 178 // the IA2 role a ROLE_OUTLINEITEM. 179 if (geckoRole == roles::ROW) { 180 Accessible* xpParent = acc->Parent(); 181 if (xpParent && xpParent->Role() == roles::TREE_TABLE) 182 *aRole = ROLE_SYSTEM_OUTLINEITEM; 183 } 184 185 return S_OK; 186 } 187 188 // XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294. 189 MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP 190 ia2Accessible::scrollTo(enum IA2ScrollType aScrollType) { 191 Accessible* acc = Acc(); 192 if (!acc) { 193 return CO_E_OBJNOTCONNECTED; 194 } 195 196 acc->ScrollTo(aScrollType); 197 return S_OK; 198 } 199 200 STDMETHODIMP 201 ia2Accessible::scrollToPoint(enum IA2CoordinateType aCoordType, long aX, 202 long aY) { 203 Accessible* acc = Acc(); 204 if (!acc) { 205 return CO_E_OBJNOTCONNECTED; 206 } 207 208 uint32_t geckoCoordType = 209 (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) 210 ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE 211 : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; 212 213 acc->ScrollToPoint(geckoCoordType, aX, aY); 214 215 return S_OK; 216 } 217 218 STDMETHODIMP 219 ia2Accessible::get_groupPosition(long* aGroupLevel, long* aSimilarItemsInGroup, 220 long* aPositionInGroup) { 221 if (!aGroupLevel || !aSimilarItemsInGroup || !aPositionInGroup) 222 return E_INVALIDARG; 223 224 *aGroupLevel = 0; 225 *aSimilarItemsInGroup = 0; 226 *aPositionInGroup = 0; 227 228 Accessible* acc = Acc(); 229 if (!acc) { 230 return CO_E_OBJNOTCONNECTED; 231 } 232 233 GroupPos groupPos = acc->GroupPosition(); 234 235 // Group information for accessibles having level only (like html headings 236 // elements) isn't exposed by this method. AT should look for 'level' object 237 // attribute. 238 if (!groupPos.setSize && !groupPos.posInSet) return S_FALSE; 239 240 *aGroupLevel = groupPos.level; 241 *aSimilarItemsInGroup = groupPos.setSize; 242 *aPositionInGroup = groupPos.posInSet; 243 244 return S_OK; 245 } 246 247 STDMETHODIMP 248 ia2Accessible::get_states(AccessibleStates* aStates) { 249 if (!aStates) return E_INVALIDARG; 250 *aStates = 0; 251 252 // XXX: bug 344674 should come with better approach that we have here. 253 254 Accessible* acc = Acc(); 255 if (!acc) { 256 *aStates = IA2_STATE_DEFUNCT; 257 return S_OK; 258 } 259 260 uint64_t state; 261 state = acc->State(); 262 263 if (state & states::INVALID) *aStates |= IA2_STATE_INVALID_ENTRY; 264 if (state & states::REQUIRED) *aStates |= IA2_STATE_REQUIRED; 265 266 // The following IA2 states are not supported by Gecko 267 // IA2_STATE_ARMED 268 // IA2_STATE_MANAGES_DESCENDANTS 269 // IA2_STATE_ICONIFIED 270 // IA2_STATE_INVALID // This is not a state, it is the absence of a state 271 272 if (state & states::ACTIVE) *aStates |= IA2_STATE_ACTIVE; 273 if (state & states::DEFUNCT) *aStates |= IA2_STATE_DEFUNCT; 274 if (state & states::EDITABLE) *aStates |= IA2_STATE_EDITABLE; 275 if (state & states::HORIZONTAL) *aStates |= IA2_STATE_HORIZONTAL; 276 if (state & states::MODAL) *aStates |= IA2_STATE_MODAL; 277 if (state & states::MULTI_LINE) *aStates |= IA2_STATE_MULTI_LINE; 278 if (state & states::OPAQUE1) *aStates |= IA2_STATE_OPAQUE; 279 if (state & states::SELECTABLE_TEXT) *aStates |= IA2_STATE_SELECTABLE_TEXT; 280 if (state & states::SINGLE_LINE) *aStates |= IA2_STATE_SINGLE_LINE; 281 if (state & states::STALE) *aStates |= IA2_STATE_STALE; 282 if (state & states::SUPPORTS_AUTOCOMPLETION) 283 *aStates |= IA2_STATE_SUPPORTS_AUTOCOMPLETION; 284 if (state & states::TRANSIENT) *aStates |= IA2_STATE_TRANSIENT; 285 if (state & states::VERTICAL) *aStates |= IA2_STATE_VERTICAL; 286 if (state & states::CHECKED) *aStates |= IA2_STATE_CHECKABLE; 287 if (state & states::PINNED) *aStates |= IA2_STATE_PINNED; 288 289 return S_OK; 290 } 291 292 STDMETHODIMP 293 ia2Accessible::get_extendedRole(BSTR* aExtendedRole) { 294 if (!aExtendedRole) return E_INVALIDARG; 295 296 *aExtendedRole = nullptr; 297 return E_NOTIMPL; 298 } 299 300 STDMETHODIMP 301 ia2Accessible::get_localizedExtendedRole(BSTR* aLocalizedExtendedRole) { 302 if (!aLocalizedExtendedRole) return E_INVALIDARG; 303 304 *aLocalizedExtendedRole = nullptr; 305 return E_NOTIMPL; 306 } 307 308 STDMETHODIMP 309 ia2Accessible::get_nExtendedStates(long* aNExtendedStates) { 310 if (!aNExtendedStates) return E_INVALIDARG; 311 312 *aNExtendedStates = 0; 313 return E_NOTIMPL; 314 } 315 316 STDMETHODIMP 317 ia2Accessible::get_extendedStates(long aMaxExtendedStates, 318 BSTR** aExtendedStates, 319 long* aNExtendedStates) { 320 if (!aExtendedStates || !aNExtendedStates) return E_INVALIDARG; 321 322 *aExtendedStates = nullptr; 323 *aNExtendedStates = 0; 324 return E_NOTIMPL; 325 } 326 327 STDMETHODIMP 328 ia2Accessible::get_localizedExtendedStates(long aMaxLocalizedExtendedStates, 329 BSTR** aLocalizedExtendedStates, 330 long* aNLocalizedExtendedStates) { 331 if (!aLocalizedExtendedStates || !aNLocalizedExtendedStates) 332 return E_INVALIDARG; 333 334 *aLocalizedExtendedStates = nullptr; 335 *aNLocalizedExtendedStates = 0; 336 return E_NOTIMPL; 337 } 338 339 STDMETHODIMP 340 ia2Accessible::get_uniqueID(long* aUniqueID) { 341 if (!aUniqueID) return E_INVALIDARG; 342 343 Accessible* acc = Acc(); 344 *aUniqueID = MsaaAccessible::GetChildIDFor(acc); 345 return S_OK; 346 } 347 348 STDMETHODIMP 349 ia2Accessible::get_windowHandle(HWND* aWindowHandle) { 350 if (!aWindowHandle) return E_INVALIDARG; 351 *aWindowHandle = 0; 352 353 Accessible* acc = Acc(); 354 if (!acc) return CO_E_OBJNOTCONNECTED; 355 356 *aWindowHandle = MsaaAccessible::GetHWNDFor(acc); 357 if (!*aWindowHandle && !Compatibility::IsUiaEnabled()) { 358 // Bug 1890155: This can happen if a document is detached from its embedder. 359 // The document might be about to die or it might be moving to a different 360 // embedder; e.g. a tab in a different window. The IA2 -> UIA proxy may 361 // crash if we return a null HWND. For now, pick an arbitrary top level 362 // Gecko HWND. This might be wrong, but only briefly, since the document 363 // will either die or move very soon, at which point this method will 364 // return the correct answer. 365 // TODO This hack should be removed once we only use our native UIA 366 // implementation. 367 if (ApplicationAccessible* app = ApplicationAcc()) { 368 if (LocalAccessible* firstRoot = app->LocalFirstChild()) { 369 *aWindowHandle = MsaaAccessible::GetHWNDFor(firstRoot); 370 } 371 } 372 } 373 return S_OK; 374 } 375 376 STDMETHODIMP 377 ia2Accessible::get_indexInParent(long* aIndexInParent) { 378 if (!aIndexInParent) return E_INVALIDARG; 379 *aIndexInParent = -1; 380 381 Accessible* acc = Acc(); 382 if (!acc) return CO_E_OBJNOTCONNECTED; 383 384 *aIndexInParent = acc->IndexInParent(); 385 386 if (*aIndexInParent == -1) return S_FALSE; 387 388 return S_OK; 389 } 390 391 STDMETHODIMP 392 ia2Accessible::get_locale(IA2Locale* aLocale) { 393 if (!aLocale) return E_INVALIDARG; 394 395 // Language codes consist of a primary code and a possibly empty series of 396 // subcodes: language-code = primary-code ( "-" subcode )* 397 // Two-letter primary codes are reserved for [ISO639] language abbreviations. 398 // Any two-letter subcode is understood to be a [ISO3166] country code. 399 400 Accessible* acc = Acc(); 401 if (!acc) { 402 return CO_E_OBJNOTCONNECTED; 403 } 404 405 nsAutoString lang; 406 acc->Language(lang); 407 408 // If primary code consists from two letters then expose it as language. 409 int32_t offset = lang.FindChar('-', 0); 410 if (offset == -1) { 411 if (lang.Length() == 2) { 412 aLocale->language = ::SysAllocString(lang.get()); 413 return S_OK; 414 } 415 } else if (offset == 2) { 416 aLocale->language = ::SysAllocStringLen(lang.get(), 2); 417 418 // If the first subcode consists from two letters then expose it as 419 // country. 420 offset = lang.FindChar('-', 3); 421 if (offset == -1) { 422 if (lang.Length() == 5) { 423 aLocale->country = ::SysAllocString(lang.get() + 3); 424 return S_OK; 425 } 426 } else if (offset == 5) { 427 aLocale->country = ::SysAllocStringLen(lang.get() + 3, 2); 428 } 429 } 430 431 // Expose as a string if primary code or subcode cannot point to language or 432 // country abbreviations or if there are more than one subcode. 433 aLocale->variant = ::SysAllocString(lang.get()); 434 return S_OK; 435 } 436 437 STDMETHODIMP 438 ia2Accessible::get_attributes(BSTR* aAttributes) { 439 if (!aAttributes) return E_INVALIDARG; 440 *aAttributes = nullptr; 441 442 Accessible* acc = Acc(); 443 if (!acc) { 444 return CO_E_OBJNOTCONNECTED; 445 } 446 447 // The format is name:value;name:value; with \ for escaping these 448 // characters ":;=,\". 449 RefPtr<AccAttributes> attributes = acc->Attributes(); 450 if (acc->Role() == roles::HEADING) { 451 // IAccessible2 expects heading level to be exposed as an object attribute. 452 // However, all other group position info is exposed via groupPosition. 453 nsAccUtils::SetAccGroupAttrs(attributes, acc); 454 } 455 return ConvertToIA2Attributes(attributes, aAttributes); 456 } 457 458 //////////////////////////////////////////////////////////////////////////////// 459 // IAccessible2_2 460 461 STDMETHODIMP 462 ia2Accessible::get_attribute(BSTR name, VARIANT* aAttribute) { 463 if (!aAttribute) return E_INVALIDARG; 464 465 return E_NOTIMPL; 466 } 467 468 STDMETHODIMP 469 ia2Accessible::get_accessibleWithCaret(IUnknown** aAccessible, 470 long* aCaretOffset) { 471 if (!aAccessible || !aCaretOffset) return E_INVALIDARG; 472 473 *aAccessible = nullptr; 474 *aCaretOffset = -1; 475 476 if (!Acc()) { 477 return CO_E_OBJNOTCONNECTED; 478 } 479 AccessibleWrap* acc = LocalAcc(); 480 if (!acc) { 481 return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet. 482 } 483 484 int32_t caretOffset = -1; 485 LocalAccessible* accWithCaret = 486 SelectionMgr()->AccessibleWithCaret(&caretOffset); 487 if (!accWithCaret || acc->Document() != accWithCaret->Document()) 488 return S_FALSE; 489 490 LocalAccessible* child = accWithCaret; 491 while (!child->IsDoc() && child != acc) child = child->LocalParent(); 492 493 if (child != acc) return S_FALSE; 494 495 RefPtr<IAccessible2> ia2WithCaret; 496 accWithCaret->GetNativeInterface(getter_AddRefs(ia2WithCaret)); 497 ia2WithCaret.forget(aAccessible); 498 *aCaretOffset = caretOffset; 499 return S_OK; 500 } 501 502 STDMETHODIMP 503 ia2Accessible::get_relationTargetsOfType(BSTR aType, long aMaxTargets, 504 IUnknown*** aTargets, 505 long* aNTargets) { 506 if (!aTargets || !aNTargets || aMaxTargets < 0) return E_INVALIDARG; 507 *aNTargets = 0; 508 509 Maybe<RelationType> relationType; 510 for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) { 511 if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) { 512 relationType.emplace(sRelationTypePairs[idx].first); 513 break; 514 } 515 } 516 if (!relationType) return E_INVALIDARG; 517 518 Accessible* acc = Acc(); 519 if (!acc) { 520 return CO_E_OBJNOTCONNECTED; 521 } 522 523 nsTArray<Accessible*> targets; 524 Relation rel = acc->RelationByType(*relationType); 525 Accessible* target = nullptr; 526 while ( 527 (target = rel.Next()) && 528 (aMaxTargets == 0 || static_cast<long>(targets.Length()) < aMaxTargets)) { 529 targets.AppendElement(target); 530 } 531 532 *aNTargets = targets.Length(); 533 *aTargets = 534 static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets)); 535 if (!*aTargets) return E_OUTOFMEMORY; 536 537 for (int32_t i = 0; i < *aNTargets; i++) { 538 (*aTargets)[i] = MsaaAccessible::NativeAccessible(targets[i]); 539 } 540 541 return S_OK; 542 } 543 544 //////////////////////////////////////////////////////////////////////////////// 545 // Helpers 546 547 static inline void EscapeAttributeChars(nsString& aStr) { 548 int32_t offset = 0; 549 static const char16_t kCharsToEscape[] = u":;=,\\"; 550 while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { 551 aStr.Insert('\\', offset); 552 offset += 2; 553 } 554 } 555 556 HRESULT 557 ia2Accessible::ConvertToIA2Attributes(AccAttributes* aAttributes, 558 BSTR* aIA2Attributes) { 559 *aIA2Attributes = nullptr; 560 561 // The format is name:value;name:value; with \ for escaping these 562 // characters ":;=,\". 563 564 if (!aAttributes) return S_FALSE; 565 566 nsAutoString strAttrs; 567 568 for (auto iter : *aAttributes) { 569 nsAutoString name; 570 iter.NameAsString(name); 571 EscapeAttributeChars(name); 572 573 nsAutoString value; 574 iter.ValueAsString(value); 575 EscapeAttributeChars(value); 576 577 strAttrs.Append(name); 578 strAttrs.Append(':'); 579 strAttrs.Append(value); 580 strAttrs.Append(';'); 581 } 582 583 if (strAttrs.IsEmpty()) return S_FALSE; 584 585 *aIA2Attributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length()); 586 return *aIA2Attributes ? S_OK : E_OUTOFMEMORY; 587 }