AccessibleWrap.mm (8714B)
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 #include "nsUnicharUtils.h" 12 13 #include "LocalAccessible-inl.h" 14 #include "nsAccUtils.h" 15 #include "mozilla/a11y/Role.h" 16 #include "TextRange.h" 17 #include "gfxPlatform.h" 18 19 #import "MOXLandmarkAccessibles.h" 20 #import "MOXMathAccessibles.h" 21 #import "MOXOuterDoc.h" 22 #import "MOXTextMarkerDelegate.h" 23 #import "MOXWebAreaAccessible.h" 24 #import "mozAccessible.h" 25 #import "mozActionElements.h" 26 #import "mozHTMLAccessible.h" 27 #import "mozSelectableElements.h" 28 #import "mozTableAccessible.h" 29 #import "mozTextAccessible.h" 30 31 using namespace mozilla; 32 using namespace mozilla::a11y; 33 34 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) 35 : LocalAccessible(aContent, aDoc), 36 mNativeObject(nil), 37 mNativeInited(false) { 38 if (aContent && aDoc && IsLiveRegion(aContent)) { 39 // Check if this accessible is a live region and queue it 40 // it for dispatching an event after it has been inserted. 41 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc); 42 doc->QueueNewLiveRegion(this); 43 } 44 } 45 46 AccessibleWrap::~AccessibleWrap() {} 47 48 mozAccessible* AccessibleWrap::GetNativeObject() { 49 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 50 51 if (!mNativeInited && !mNativeObject) { 52 // We don't creat OSX accessibles for xul tooltips, defunct accessibles, 53 // <br> (whitespace) elements, or pruned children. 54 // 55 // To maintain a scripting environment where the XPCOM accessible hierarchy 56 // look the same on all platforms, we still let the C++ objects be created 57 // though. 58 if (!IsXULTooltip() && !IsDefunct() && Role() != roles::WHITESPACE) { 59 mNativeObject = [[GetNativeType() alloc] initWithAccessible:this]; 60 } 61 } 62 63 mNativeInited = true; 64 65 return mNativeObject; 66 67 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 68 } 69 70 void AccessibleWrap::GetNativeInterface(void** aOutInterface) { 71 *aOutInterface = static_cast<void*>(GetNativeObject()); 72 } 73 74 // overridden in subclasses to create the right kind of object. by default we 75 // create a generic 'mozAccessible' node. 76 Class AccessibleWrap::GetNativeType() { 77 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 78 79 if (IsXULTabpanels()) { 80 return [mozPaneAccessible class]; 81 } 82 83 if (IsTable()) { 84 return [mozTableAccessible class]; 85 } 86 87 if (IsTableRow()) { 88 return [mozTableRowAccessible class]; 89 } 90 91 if (IsTableCell()) { 92 return [mozTableCellAccessible class]; 93 } 94 95 if (IsDoc()) { 96 return [MOXWebAreaAccessible class]; 97 } 98 99 if (IsOuterDoc()) { 100 return [MOXOuterDoc class]; 101 } 102 103 return GetTypeFromRole(Role()); 104 105 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 106 } 107 108 // this method is very important. it is fired when an accessible object "dies". 109 // after this point the object might still be around (because some 3rd party 110 // still has a ref to it), but it is in fact 'dead'. 111 void AccessibleWrap::Shutdown() { 112 // this ensure we will not try to re-create the native object. 113 mNativeInited = true; 114 115 // we really intend to access the member directly. 116 if (mNativeObject) { 117 [mNativeObject expire]; 118 [mNativeObject release]; 119 mNativeObject = nil; 120 } 121 122 LocalAccessible::Shutdown(); 123 } 124 125 nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { 126 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 127 128 nsresult rv = LocalAccessible::HandleAccEvent(aEvent); 129 NS_ENSURE_SUCCESS(rv, rv); 130 131 if (IsDefunct()) { 132 // The accessible can become defunct after their events are handled. 133 return NS_OK; 134 } 135 136 uint32_t eventType = aEvent->GetEventType(); 137 138 if (eventType == nsIAccessibleEvent::EVENT_SHOW) { 139 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document()); 140 doc->ProcessNewLiveRegions(); 141 } 142 143 if (((eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED || 144 eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED) && 145 !aEvent->FromUserInput()) || 146 eventType == nsIAccessibleEvent::EVENT_NAME_CHANGE) { 147 for (LocalAccessible* container = aEvent->GetAccessible(); container; 148 container = container->LocalParent()) { 149 if (container->HasOwnContent() && IsLiveRegion(container->GetContent())) { 150 // We rely on EventQueue::CoalesceEvents to remove duplicates 151 Document()->FireDelayedEvent( 152 nsIAccessibleEvent::EVENT_LIVE_REGION_CHANGED, container); 153 } 154 } 155 } 156 157 return NS_OK; 158 159 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 160 } 161 162 bool AccessibleWrap::IsLiveRegion(nsIContent* aContent) { 163 if (!aContent || !aContent->IsElement()) { 164 return false; 165 } 166 167 static const dom::Element::AttrValuesArray sLiveRegionValues[] = { 168 nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr}; 169 int32_t attrValue = nsAccUtils::FindARIAAttrValueIn( 170 aContent->AsElement(), nsGkAtoms::aria_live, sLiveRegionValues, 171 eIgnoreCase); 172 if (attrValue == 0) { 173 // aria-live is "off", do nothing. 174 } else if (attrValue > 0) { 175 // aria-live attribute is polite or assertive. It's live! 176 return true; 177 } else if (const nsRoleMapEntry* roleMap = 178 aria::GetRoleMap(aContent->AsElement())) { 179 // aria role defines it as a live region. It's live! 180 if (roleMap->liveAttRule == ePoliteLiveAttr || 181 roleMap->liveAttRule == eAssertiveLiveAttr) { 182 return true; 183 } 184 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute( 185 aContent, nsGkAtoms::aria_live)) { 186 // HTML element defines it as a live region. It's live! 187 if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) { 188 return true; 189 } 190 } 191 192 return false; 193 } 194 195 //////////////////////////////////////////////////////////////////////////////// 196 // AccessibleWrap protected 197 198 Class a11y::GetTypeFromRole(roles::Role aRole) { 199 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 200 201 switch (aRole) { 202 case roles::COMBOBOX: 203 return [mozPopupButtonAccessible class]; 204 205 case roles::PUSHBUTTON: 206 return [mozButtonAccessible class]; 207 208 case roles::PAGETAB: 209 return [mozTabAccessible class]; 210 211 case roles::DATE_EDITOR: 212 return [mozDatePickerAccessible class]; 213 214 case roles::CHECKBUTTON: 215 case roles::TOGGLE_BUTTON: 216 case roles::SWITCH: 217 case roles::CHECK_MENU_ITEM: 218 return [mozCheckboxAccessible class]; 219 220 case roles::RADIOBUTTON: 221 case roles::RADIO_MENU_ITEM: 222 return [mozRadioButtonAccessible class]; 223 224 case roles::PROGRESSBAR: 225 return [mozRangeAccessible class]; 226 227 case roles::METER: 228 return [mozMeterAccessible class]; 229 230 case roles::SPINBUTTON: 231 case roles::SLIDER: 232 return [mozIncrementableAccessible class]; 233 234 case roles::HEADING: 235 return [mozHeadingAccessible class]; 236 237 case roles::PAGETABLIST: 238 return [mozTabGroupAccessible class]; 239 240 case roles::TEXT_LEAF: 241 case roles::STATICTEXT: 242 return [mozTextLeafAccessible class]; 243 244 case roles::LANDMARK: 245 return [MOXLandmarkAccessible class]; 246 247 case roles::LINK: 248 return [mozLinkAccessible class]; 249 250 case roles::LISTBOX: 251 return [mozListboxAccessible class]; 252 253 case roles::LISTITEM: 254 return [MOXListItemAccessible class]; 255 256 case roles::OPTION: { 257 return [mozOptionAccessible class]; 258 } 259 260 case roles::RICH_OPTION: { 261 return [mozSelectableChildAccessible class]; 262 } 263 264 case roles::COMBOBOX_LIST: 265 case roles::MENUBAR: 266 case roles::MENUPOPUP: { 267 return [mozMenuAccessible class]; 268 } 269 270 case roles::COMBOBOX_OPTION: 271 case roles::PARENT_MENUITEM: 272 case roles::MENUITEM: { 273 return [mozMenuItemAccessible class]; 274 } 275 276 case roles::MATHML_ROOT: 277 return [MOXMathRootAccessible class]; 278 279 case roles::MATHML_SQUARE_ROOT: 280 return [MOXMathSquareRootAccessible class]; 281 282 case roles::MATHML_FRACTION: 283 return [MOXMathFractionAccessible class]; 284 285 case roles::MATHML_SUB: 286 case roles::MATHML_SUP: 287 case roles::MATHML_SUB_SUP: 288 return [MOXMathSubSupAccessible class]; 289 290 case roles::MATHML_UNDER: 291 case roles::MATHML_OVER: 292 case roles::MATHML_UNDER_OVER: 293 return [MOXMathUnderOverAccessible class]; 294 295 case roles::OUTLINE: 296 case roles::TREE_TABLE: 297 return [mozOutlineAccessible class]; 298 299 case roles::OUTLINEITEM: 300 return [mozOutlineRowAccessible class]; 301 302 case roles::LABEL: 303 return [MOXLabelAccessible class]; 304 305 default: 306 return [mozAccessible class]; 307 } 308 309 return nil; 310 311 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 312 }