XULFormControlAccessible.cpp (15792B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "XULFormControlAccessible.h" 7 8 #include "LocalAccessible-inl.h" 9 #include "HTMLFormControlAccessible.h" 10 #include "nsAccUtils.h" 11 #include "DocAccessible.h" 12 #include "Relation.h" 13 #include "mozilla/a11y/Role.h" 14 #include "States.h" 15 #include "TreeWalker.h" 16 #include "XULMenuAccessible.h" 17 18 #include "nsIDOMXULButtonElement.h" 19 #include "nsIDOMXULMenuListElement.h" 20 #include "nsIDOMXULRadioGroupElement.h" 21 #include "nsIDOMXULSelectCntrlItemEl.h" 22 #include "nsIFrame.h" 23 #include "nsMenuPopupFrame.h" 24 #include "nsNameSpaceManager.h" 25 #include "mozilla/dom/Element.h" 26 27 using namespace mozilla::a11y; 28 29 //////////////////////////////////////////////////////////////////////////////// 30 // XULButtonAccessible 31 //////////////////////////////////////////////////////////////////////////////// 32 33 XULButtonAccessible::XULButtonAccessible(nsIContent* aContent, 34 DocAccessible* aDoc) 35 : AccessibleWrap(aContent, aDoc) { 36 if (ContainsMenu()) { 37 mGenericTypes |= eMenuButton; 38 } else { 39 mGenericTypes |= eButton; 40 } 41 } 42 43 XULButtonAccessible::~XULButtonAccessible() {} 44 45 //////////////////////////////////////////////////////////////////////////////// 46 // XULButtonAccessible: nsISupports 47 48 //////////////////////////////////////////////////////////////////////////////// 49 // XULButtonAccessible: nsIAccessible 50 51 bool XULButtonAccessible::HasPrimaryAction() const { return true; } 52 53 void XULButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 54 if (aIndex == eAction_Click) aName.AssignLiteral("press"); 55 } 56 57 //////////////////////////////////////////////////////////////////////////////// 58 // XULButtonAccessible: LocalAccessible 59 60 role XULButtonAccessible::NativeRole() const { 61 // Buttons can be checked; they simply appear pressed in rather than checked. 62 // In this case, we must expose them as toggle buttons. 63 nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement = Elm()->AsXULButton(); 64 if (xulButtonElement) { 65 nsAutoString type; 66 xulButtonElement->GetType(type); 67 if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) { 68 return roles::TOGGLE_BUTTON; 69 } 70 } 71 return roles::PUSHBUTTON; 72 } 73 74 uint64_t XULButtonAccessible::NativeState() const { 75 // Possible states: focused, focusable, unavailable(disabled). 76 77 // get focus and disable status from base class 78 uint64_t state = LocalAccessible::NativeState(); 79 80 nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement = Elm()->AsXULButton(); 81 if (xulButtonElement) { 82 // Some buttons can have their checked state set without being of type 83 // checkbox or radio. Expose the pressed state unconditionally. 84 bool checked = false; 85 xulButtonElement->GetChecked(&checked); 86 if (checked) { 87 state |= states::PRESSED; 88 } 89 } 90 91 if (ContainsMenu()) state |= states::HASPOPUP; 92 93 if (mContent->AsElement()->HasAttr(nsGkAtoms::_default)) { 94 state |= states::DEFAULT; 95 } 96 97 return state; 98 } 99 100 bool XULButtonAccessible::AttributeChangesState(nsAtom* aAttribute) { 101 if (aAttribute == nsGkAtoms::checked) { 102 return true; 103 } 104 return AccessibleWrap::AttributeChangesState(aAttribute); 105 } 106 107 void XULButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 108 nsAtom* aAttribute, 109 AttrModType aModType, 110 const nsAttrValue* aOldValue, 111 uint64_t aOldState) { 112 AccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 113 aOldValue, aOldState); 114 if (aAttribute == nsGkAtoms::label) { 115 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); 116 } 117 } 118 119 //////////////////////////////////////////////////////////////////////////////// 120 // XULButtonAccessible: Widgets 121 122 bool XULButtonAccessible::IsWidget() const { return true; } 123 124 bool XULButtonAccessible::IsActiveWidget() const { 125 return FocusMgr()->HasDOMFocus(mContent); 126 } 127 128 bool XULButtonAccessible::AreItemsOperable() const { 129 if (IsMenuButton()) { 130 LocalAccessible* menuPopup = mChildren.SafeElementAt(0, nullptr); 131 if (menuPopup) { 132 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame()); 133 return menuPopupFrame->IsOpen(); 134 } 135 } 136 return false; // no items 137 } 138 139 bool XULButtonAccessible::IsAcceptableChild(nsIContent* aEl) const { 140 // In general XUL buttons should not have accessible children. However: 141 return 142 // menu buttons can have popup accessibles (@type="menu" or 143 // columnpicker). 144 aEl->IsXULElement(nsGkAtoms::menupopup) || 145 // A XUL button can be labelled by a direct child text node, so we need to 146 // allow that as a child so it will be picked up when computing name from 147 // subtree. 148 (aEl->IsText() && aEl->GetParent() == mContent); 149 } 150 151 //////////////////////////////////////////////////////////////////////////////// 152 // XULButtonAccessible protected 153 154 bool XULButtonAccessible::ContainsMenu() const { 155 return mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 156 nsGkAtoms::menu, eCaseMatters); 157 } 158 159 //////////////////////////////////////////////////////////////////////////////// 160 // XULDropmarkerAccessible 161 //////////////////////////////////////////////////////////////////////////////// 162 163 XULDropmarkerAccessible::XULDropmarkerAccessible(nsIContent* aContent, 164 DocAccessible* aDoc) 165 : LeafAccessible(aContent, aDoc) {} 166 167 bool XULDropmarkerAccessible::HasPrimaryAction() const { return true; } 168 169 bool XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen) const { 170 bool isOpen = false; 171 172 nsIContent* parent = mContent->GetFlattenedTreeParent(); 173 174 while (parent) { 175 nsCOMPtr<nsIDOMXULButtonElement> parentButtonElement = 176 parent->AsElement()->AsXULButton(); 177 if (parentButtonElement) { 178 parentButtonElement->GetOpen(&isOpen); 179 if (aToggleOpen) parentButtonElement->SetOpen(!isOpen); 180 return isOpen; 181 } 182 183 nsCOMPtr<nsIDOMXULMenuListElement> parentMenuListElement = 184 parent->AsElement()->AsXULMenuList(); 185 if (parentMenuListElement) { 186 parentMenuListElement->GetOpen(&isOpen); 187 if (aToggleOpen) parentMenuListElement->SetOpen(!isOpen); 188 return isOpen; 189 } 190 parent = parent->GetFlattenedTreeParent(); 191 } 192 193 return isOpen; 194 } 195 196 void XULDropmarkerAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 197 aName.Truncate(); 198 if (aIndex == eAction_Click) { 199 if (DropmarkerOpen(false)) { 200 aName.AssignLiteral("close"); 201 } else { 202 aName.AssignLiteral("open"); 203 } 204 } 205 } 206 207 bool XULDropmarkerAccessible::DoAction(uint8_t index) const { 208 if (index == eAction_Click) { 209 DropmarkerOpen(true); // Reverse the open attribute 210 return true; 211 } 212 return false; 213 } 214 215 role XULDropmarkerAccessible::NativeRole() const { return roles::PUSHBUTTON; } 216 217 uint64_t XULDropmarkerAccessible::NativeState() const { 218 return DropmarkerOpen(false) ? states::PRESSED : 0; 219 } 220 221 //////////////////////////////////////////////////////////////////////////////// 222 // XULGroupboxAccessible 223 //////////////////////////////////////////////////////////////////////////////// 224 225 XULGroupboxAccessible::XULGroupboxAccessible(nsIContent* aContent, 226 DocAccessible* aDoc) 227 : AccessibleWrap(aContent, aDoc) {} 228 229 role XULGroupboxAccessible::NativeRole() const { return roles::GROUPING; } 230 231 ENameValueFlag XULGroupboxAccessible::NativeName(nsString& aName) const { 232 // XXX: we use the first related accessible only. 233 LocalAccessible* label = 234 RelationByType(RelationType::LABELLED_BY).LocalNext(); 235 if (label) { 236 label->Name(aName); 237 return eNameFromRelations; 238 } 239 240 return eNameOK; 241 } 242 243 Relation XULGroupboxAccessible::RelationByType(RelationType aType) const { 244 Relation rel = AccessibleWrap::RelationByType(aType); 245 246 // The label for xul:groupbox is generated from the first xul:label 247 if (aType == RelationType::LABELLED_BY && ChildCount() > 0) { 248 LocalAccessible* childAcc = LocalChildAt(0); 249 if (childAcc->Role() == roles::LABEL && 250 childAcc->GetContent()->IsXULElement(nsGkAtoms::label)) { 251 rel.AppendTarget(childAcc); 252 } 253 } 254 255 return rel; 256 } 257 258 //////////////////////////////////////////////////////////////////////////////// 259 // XULRadioButtonAccessible 260 //////////////////////////////////////////////////////////////////////////////// 261 262 XULRadioButtonAccessible::XULRadioButtonAccessible(nsIContent* aContent, 263 DocAccessible* aDoc) 264 : RadioButtonAccessible(aContent, aDoc) {} 265 266 uint64_t XULRadioButtonAccessible::NativeState() const { 267 uint64_t state = LeafAccessible::NativeState(); 268 state |= states::CHECKABLE; 269 270 nsCOMPtr<nsIDOMXULSelectControlItemElement> radioButton = 271 Elm()->AsXULSelectControlItem(); 272 if (radioButton) { 273 bool selected = false; // Radio buttons can be selected 274 radioButton->GetSelected(&selected); 275 if (selected) { 276 state |= states::CHECKED; 277 } 278 } 279 280 return state; 281 } 282 283 uint64_t XULRadioButtonAccessible::NativeInteractiveState() const { 284 return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE; 285 } 286 287 //////////////////////////////////////////////////////////////////////////////// 288 // XULRadioButtonAccessible: Widgets 289 290 LocalAccessible* XULRadioButtonAccessible::ContainerWidget() const { 291 return mParent; 292 } 293 294 //////////////////////////////////////////////////////////////////////////////// 295 // XULRadioGroupAccessible 296 //////////////////////////////////////////////////////////////////////////////// 297 298 /** 299 * XUL Radio Group 300 * The Radio Group proxies for the Radio Buttons themselves. The Group gets 301 * focus whereas the Buttons do not. So we only have an accessible object for 302 * this for the purpose of getting the proper RadioButton. Need this here to 303 * avoid circular reference problems when navigating the accessible tree and 304 * for getting to the radiobuttons. 305 */ 306 307 XULRadioGroupAccessible::XULRadioGroupAccessible(nsIContent* aContent, 308 DocAccessible* aDoc) 309 : XULSelectControlAccessible(aContent, aDoc) {} 310 311 role XULRadioGroupAccessible::NativeRole() const { return roles::RADIO_GROUP; } 312 313 uint64_t XULRadioGroupAccessible::NativeInteractiveState() const { 314 // The radio group is not focusable. Sometimes the focus controller will 315 // report that it is focused. That means that the actual selected radio button 316 // should be considered focused. 317 return NativelyUnavailable() ? states::UNAVAILABLE : 0; 318 } 319 320 //////////////////////////////////////////////////////////////////////////////// 321 // XULRadioGroupAccessible: Widgets 322 323 bool XULRadioGroupAccessible::IsWidget() const { return true; } 324 325 bool XULRadioGroupAccessible::IsActiveWidget() const { 326 return FocusMgr()->HasDOMFocus(mContent); 327 } 328 329 bool XULRadioGroupAccessible::AreItemsOperable() const { return true; } 330 331 LocalAccessible* XULRadioGroupAccessible::CurrentItem() const { 332 if (!mSelectControl) { 333 return nullptr; 334 } 335 336 RefPtr<dom::Element> currentItemElm; 337 nsCOMPtr<nsIDOMXULRadioGroupElement> group = 338 mSelectControl->AsXULRadioGroup(); 339 if (group) { 340 group->GetFocusedItem(getter_AddRefs(currentItemElm)); 341 } 342 343 if (currentItemElm) { 344 DocAccessible* document = Document(); 345 if (document) { 346 return document->GetAccessible(currentItemElm); 347 } 348 } 349 350 return nullptr; 351 } 352 353 void XULRadioGroupAccessible::SetCurrentItem(const LocalAccessible* aItem) { 354 if (!mSelectControl) { 355 return; 356 } 357 358 nsCOMPtr<dom::Element> itemElm = aItem->Elm(); 359 nsCOMPtr<nsIDOMXULRadioGroupElement> group = 360 mSelectControl->AsXULRadioGroup(); 361 if (group) { 362 group->SetFocusedItem(itemElm); 363 } 364 } 365 366 //////////////////////////////////////////////////////////////////////////////// 367 // XULStatusBarAccessible 368 //////////////////////////////////////////////////////////////////////////////// 369 370 XULStatusBarAccessible::XULStatusBarAccessible(nsIContent* aContent, 371 DocAccessible* aDoc) 372 : AccessibleWrap(aContent, aDoc) {} 373 374 role XULStatusBarAccessible::NativeRole() const { return roles::STATUSBAR; } 375 376 //////////////////////////////////////////////////////////////////////////////// 377 // XULToolbarButtonAccessible 378 //////////////////////////////////////////////////////////////////////////////// 379 380 XULToolbarButtonAccessible::XULToolbarButtonAccessible(nsIContent* aContent, 381 DocAccessible* aDoc) 382 : XULButtonAccessible(aContent, aDoc) {} 383 384 void XULToolbarButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet, 385 int32_t* aSetSize) { 386 int32_t setSize = 0; 387 int32_t posInSet = 0; 388 389 LocalAccessible* parent = LocalParent(); 390 if (!parent) return; 391 392 uint32_t childCount = parent->ChildCount(); 393 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { 394 LocalAccessible* child = parent->LocalChildAt(childIdx); 395 if (IsSeparator(child)) { // end of a group of buttons 396 if (posInSet) break; // we've found our group, so we're done 397 398 setSize = 0; // not our group, so start a new group 399 400 } else { 401 setSize++; // another button in the group 402 403 if (child == this) posInSet = setSize; // we've found our button 404 } 405 } 406 407 *aPosInSet = posInSet; 408 *aSetSize = setSize; 409 } 410 411 bool XULToolbarButtonAccessible::IsSeparator(LocalAccessible* aAccessible) { 412 nsIContent* content = aAccessible->GetContent(); 413 return content && content->IsAnyOfXULElements(nsGkAtoms::toolbarseparator, 414 nsGkAtoms::toolbarspacer, 415 nsGkAtoms::toolbarspring); 416 } 417 418 //////////////////////////////////////////////////////////////////////////////// 419 // XULToolbarButtonAccessible: Widgets 420 421 bool XULToolbarButtonAccessible::IsAcceptableChild(nsIContent* aEl) const { 422 return XULButtonAccessible::IsAcceptableChild(aEl) || 423 // In addition to the children allowed by buttons, toolbarbuttons can 424 // have labels as children, but only if the label attribute is not 425 // present. 426 (aEl->IsXULElement(nsGkAtoms::label) && 427 !mContent->AsElement()->HasAttr(nsGkAtoms::label)); 428 } 429 430 //////////////////////////////////////////////////////////////////////////////// 431 // XULToolbarAccessible 432 //////////////////////////////////////////////////////////////////////////////// 433 434 XULToolbarAccessible::XULToolbarAccessible(nsIContent* aContent, 435 DocAccessible* aDoc) 436 : AccessibleWrap(aContent, aDoc) {} 437 438 role XULToolbarAccessible::NativeRole() const { return roles::TOOLBAR; } 439 440 ENameValueFlag XULToolbarAccessible::NativeName(nsString& aName) const { 441 if (mContent->AsElement()->GetAttr(nsGkAtoms::toolbarname, aName)) { 442 aName.CompressWhitespace(); 443 } 444 445 return eNameOK; 446 } 447 448 //////////////////////////////////////////////////////////////////////////////// 449 // XULToolbarAccessible 450 //////////////////////////////////////////////////////////////////////////////// 451 452 XULToolbarSeparatorAccessible::XULToolbarSeparatorAccessible( 453 nsIContent* aContent, DocAccessible* aDoc) 454 : LeafAccessible(aContent, aDoc) {} 455 456 role XULToolbarSeparatorAccessible::NativeRole() const { 457 return roles::SEPARATOR; 458 } 459 460 uint64_t XULToolbarSeparatorAccessible::NativeState() const { return 0; }