1/* clang-format off */ 2/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 3/* clang-format on */ 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 "DocAccessibleWrap.h" 9#include "nsObjCExceptions.h" 10#include "nsCocoaUtils.h" 11 12#include "LocalAccessible-inl.h" 13#include "nsAccUtils.h" 14#include "Role.h" 15#include "TextRange.h" 16#include "gfxPlatform.h" 17 18#import "MOXLandmarkAccessibles.h" 19#import "MOXMathAccessibles.h" 20#import "MOXTextMarkerDelegate.h" 21#import "MOXWebAreaAccessible.h" 22#import "mozAccessible.h" 23#import "mozActionElements.h" 24#import "mozHTMLAccessible.h" 25#import "mozSelectableElements.h" 26#import "mozTableAccessible.h" 27#import "mozTextAccessible.h" 28 29using namespace mozilla; 30using namespace mozilla::a11y; 31 32AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) 33 : LocalAccessible(aContent, aDoc), 34 mNativeObject(nil), 35 mNativeInited(false) { 36 if (aContent && aContent->IsElement() && aDoc) { 37 // Check if this accessible is a live region and queue it 38 // it for dispatching an event after it has been inserted. 39 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc); 40 static const dom::Element::AttrValuesArray sLiveRegionValues[] = { 41 nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr}; 42 int32_t attrValue = aContent->AsElement()->FindAttrValueIn( 43 kNameSpaceID_None, nsGkAtoms::aria_live, sLiveRegionValues, 44 eIgnoreCase); 45 if (attrValue == 0) { 46 // aria-live is "off", do nothing. 47 } else if (attrValue > 0) { 48 // aria-live attribute is polite or assertive. It's live! 49 doc->QueueNewLiveRegion(this); 50 } else if (const nsRoleMapEntry* roleMap = 51 aria::GetRoleMap(aContent->AsElement())) { 52 // aria role defines it as a live region. It's live! 53 if (roleMap->liveAttRule == ePoliteLiveAttr || 54 roleMap->liveAttRule == eAssertiveLiveAttr) { 55 doc->QueueNewLiveRegion(this); 56 } 57 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute( 58 aContent, nsGkAtoms::aria_live)) { 59 // HTML element defines it as a live region. It's live! 60 if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) { 61 doc->QueueNewLiveRegion(this); 62 } 63 } 64 } 65} 66 67AccessibleWrap::~AccessibleWrap() {} 68 69mozAccessible* AccessibleWrap::GetNativeObject() { 70 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 71 72 if (!mNativeInited && !mNativeObject) { 73 // We don't creat OSX accessibles for xul tooltips, defunct accessibles, 74 // <br> (whitespace) elements, or pruned children. 75 // 76 // To maintain a scripting environment where the XPCOM accessible hierarchy 77 // look the same on all platforms, we still let the C++ objects be created 78 // though. 79 if (!IsXULTooltip() && !IsDefunct() && Role() != roles::WHITESPACE) { 80 mNativeObject = [[GetNativeType() alloc] initWithAccessible:this]; 81 } 82 } 83 84 mNativeInited = true; 85 86 return mNativeObject; 87 88 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 89} 90 91void AccessibleWrap::GetNativeInterface(void** aOutInterface) { 92 *aOutInterface = static_cast<void*>(GetNativeObject()); 93} 94 95// overridden in subclasses to create the right kind of object. by default we 96// create a generic 'mozAccessible' node. 97Class AccessibleWrap::GetNativeType() { 98 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 99 100 if (IsXULTabpanels()) { 101 return [mozPaneAccessible class]; 102 } 103 104 if (IsTable()) { 105 return [mozTableAccessible class]; 106 } 107 108 if (IsTableRow()) { 109 return [mozTableRowAccessible class]; 110 } 111 112 if (IsTableCell()) { 113 return [mozTableCellAccessible class]; 114 } 115 116 if (IsDoc()) { 117 return [MOXWebAreaAccessible class]; 118 } 119 120 return GetTypeFromRole(Role()); 121 122 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 123} 124 125// this method is very important. it is fired when an accessible object "dies". 126// after this point the object might still be around (because some 3rd party 127// still has a ref to it), but it is in fact 'dead'. 128void AccessibleWrap::Shutdown() { 129 // this ensure we will not try to re-create the native object. 130 mNativeInited = true; 131 132 // we really intend to access the member directly. 133 if (mNativeObject) { 134 [mNativeObject expire]; 135 [mNativeObject release]; 136 mNativeObject = nil; 137 } 138 139 LocalAccessible::Shutdown(); 140} 141 142nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { 143 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 144 145 nsresult rv = LocalAccessible::HandleAccEvent(aEvent); 146 NS_ENSURE_SUCCESS(rv, rv); 147 148 uint32_t eventType = aEvent->GetEventType(); 149 150 if (eventType == nsIAccessibleEvent::EVENT_SHOW) { 151 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document()); 152 doc->ProcessNewLiveRegions(); 153 } 154 155 if (IPCAccessibilityActive()) { 156 return NS_OK; 157 } 158 159 LocalAccessible* eventTarget = nullptr; 160 161 switch (eventType) { 162 case nsIAccessibleEvent::EVENT_SELECTION: 163 case nsIAccessibleEvent::EVENT_SELECTION_ADD: 164 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { 165 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent); 166 // The "widget" is the selected widget's container. In OSX 167 // it is the target of the selection changed event. 168 eventTarget = selEvent->Widget(); 169 break; 170 } 171 case nsIAccessibleEvent::EVENT_TEXT_INSERTED: 172 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { 173 LocalAccessible* acc = aEvent->GetAccessible(); 174 // If there is a text input ancestor, use it as the event source. 175 while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) { 176 acc = acc->LocalParent(); 177 } 178 eventTarget = acc ? acc : aEvent->GetAccessible(); 179 break; 180 } 181 default: 182 eventTarget = aEvent->GetAccessible(); 183 break; 184 } 185 186 mozAccessible* nativeAcc = nil; 187 eventTarget->GetNativeInterface((void**)&nativeAcc); 188 if (!nativeAcc) { 189 return NS_ERROR_FAILURE; 190 } 191 192 switch (eventType) { 193 case nsIAccessibleEvent::EVENT_STATE_CHANGE: { 194 AccStateChangeEvent* event = downcast_accEvent(aEvent); 195 [nativeAcc stateChanged:event->GetState() 196 isEnabled:event->IsStateEnabled()]; 197 break; 198 } 199 200 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: { 201 MOXTextMarkerDelegate* delegate = 202 [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()]; 203 AccTextSelChangeEvent* event = downcast_accEvent(aEvent); 204 AutoTArray<TextRange, 1> ranges; 205 event->SelectionRanges(&ranges); 206 207 if (ranges.Length()) { 208 // Cache selection in delegate. 209 [delegate setSelectionFrom:ranges[0].StartContainer() 210 at:ranges[0].StartOffset() 211 to:ranges[0].EndContainer() 212 at:ranges[0].EndOffset()]; 213 } 214 215 [nativeAcc handleAccessibleEvent:eventType]; 216 break; 217 } 218 219 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { 220 AccCaretMoveEvent* event = downcast_accEvent(aEvent); 221 int32_t caretOffset = event->GetCaretOffset(); 222 MOXTextMarkerDelegate* delegate = 223 [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()]; 224 [delegate setCaretOffset:eventTarget at:caretOffset]; 225 if (event->IsSelectionCollapsed()) { 226 // If the selection is collapsed, invalidate our text selection cache. 227 [delegate setSelectionFrom:eventTarget 228 at:caretOffset 229 to:eventTarget 230 at:caretOffset]; 231 } 232 233 if (mozTextAccessible* textAcc = static_cast<mozTextAccessible*>( 234 [nativeAcc moxEditableAncestor])) { 235 [textAcc 236 handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED]; 237 } else { 238 [nativeAcc 239 handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED]; 240 } 241 break; 242 } 243 244 case nsIAccessibleEvent::EVENT_TEXT_INSERTED: 245 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { 246 AccTextChangeEvent* tcEvent = downcast_accEvent(aEvent); 247 [nativeAcc handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString( 248 tcEvent->ModifiedText()) 249 inserted:tcEvent->IsTextInserted() 250 inContainer:aEvent->GetAccessible() 251 at:tcEvent->GetStartOffset()]; 252 break; 253 } 254 255 case nsIAccessibleEvent::EVENT_FOCUS: 256 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: 257 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: 258 case nsIAccessibleEvent::EVENT_MENUPOPUP_START: 259 case nsIAccessibleEvent::EVENT_MENUPOPUP_END: 260 case nsIAccessibleEvent::EVENT_REORDER: 261 case nsIAccessibleEvent::EVENT_SELECTION: 262 case nsIAccessibleEvent::EVENT_SELECTION_ADD: 263 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: 264 case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED: 265 case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED: 266 case nsIAccessibleEvent::EVENT_NAME_CHANGE: 267 case nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED: 268 case nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED: 269 [nativeAcc handleAccessibleEvent:eventType]; 270 break; 271 272 default: 273 break; 274 } 275 276 return NS_OK; 277 278 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 279} 280 281bool AccessibleWrap::ApplyPostFilter(const EWhichPostFilter& aSearchKey, 282 const nsString& aSearchText) { 283 // We currently only support the eContainsText post filter. 284 MOZ_ASSERT(aSearchKey == EWhichPostFilter::eContainsText, 285 "Only search text supported"); 286 nsAutoString name; 287 Name(name); 288 return name.Find(aSearchText, true) != kNotFound; 289} 290 291//////////////////////////////////////////////////////////////////////////////// 292// AccessibleWrap protected 293 294Class a11y::GetTypeFromRole(roles::Role aRole) { 295 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 296 297 switch (aRole) { 298 case roles::COMBOBOX: 299 return [mozPopupButtonAccessible class]; 300 301 case roles::PUSHBUTTON: 302 return [mozButtonAccessible class]; 303 304 case roles::PAGETAB: 305 return [mozTabAccessible class]; 306 307 case roles::CHECKBUTTON: 308 case roles::TOGGLE_BUTTON: 309 return [mozCheckboxAccessible class]; 310 311 case roles::RADIOBUTTON: 312 return [mozRadioButtonAccessible class]; 313 314 case roles::SPINBUTTON: 315 case roles::SLIDER: 316 return [mozIncrementableAccessible class]; 317 318 case roles::HEADING: 319 return [mozHeadingAccessible class]; 320 321 case roles::PAGETABLIST: 322 return [mozTabGroupAccessible class]; 323 324 case roles::ENTRY: 325 case roles::CAPTION: 326 case roles::ACCEL_LABEL: 327 case roles::EDITCOMBOBOX: 328 case roles::PASSWORD_TEXT: 329 // normal textfield (static or editable) 330 return [mozTextAccessible class]; 331 332 case roles::TEXT_LEAF: 333 case roles::STATICTEXT: 334 return [mozTextLeafAccessible class]; 335 336 case roles::LANDMARK: 337 return [MOXLandmarkAccessible class]; 338 339 case roles::LINK: 340 return [mozLinkAccessible class]; 341 342 case roles::LISTBOX: 343 return [mozListboxAccessible class]; 344 345 case roles::LISTITEM: 346 return [MOXListItemAccessible class]; 347 348 case roles::OPTION: { 349 return [mozOptionAccessible class]; 350 } 351 352 case roles::RICH_OPTION: { 353 return [mozSelectableChildAccessible class]; 354 } 355 356 case roles::COMBOBOX_LIST: 357 case roles::MENUBAR: 358 case roles::MENUPOPUP: { 359 return [mozMenuAccessible class]; 360 } 361 362 case roles::COMBOBOX_OPTION: 363 case roles::PARENT_MENUITEM: 364 case roles::MENUITEM: { 365 return [mozMenuItemAccessible class]; 366 } 367 368 case roles::MATHML_ROOT: 369 return [MOXMathRootAccessible class]; 370 371 case roles::MATHML_SQUARE_ROOT: 372 return [MOXMathSquareRootAccessible class]; 373 374 case roles::MATHML_FRACTION: 375 return [MOXMathFractionAccessible class]; 376 377 case roles::MATHML_SUB: 378 case roles::MATHML_SUP: 379 case roles::MATHML_SUB_SUP: 380 return [MOXMathSubSupAccessible class]; 381 382 case roles::MATHML_UNDER: 383 case roles::MATHML_OVER: 384 case roles::MATHML_UNDER_OVER: 385 return [MOXMathUnderOverAccessible class]; 386 387 case roles::SUMMARY: 388 return [MOXSummaryAccessible class]; 389 390 case roles::OUTLINE: 391 case roles::TREE_TABLE: 392 return [mozOutlineAccessible class]; 393 394 case roles::OUTLINEITEM: 395 return [mozOutlineRowAccessible class]; 396 397 default: 398 return [mozAccessible class]; 399 } 400 401 return nil; 402 403 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 404} 405