HTMLElementAccessibles.cpp (10016B)
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 "HTMLElementAccessibles.h" 7 8 #include "CacheConstants.h" 9 #include "nsCoreUtils.h" 10 #include "nsTextEquivUtils.h" 11 #include "Relation.h" 12 #include "mozilla/a11y/Role.h" 13 #include "States.h" 14 15 #include "mozilla/dom/HTMLLabelElement.h" 16 #include "mozilla/dom/HTMLDetailsElement.h" 17 #include "mozilla/dom/HTMLSummaryElement.h" 18 19 using namespace mozilla::a11y; 20 21 //////////////////////////////////////////////////////////////////////////////// 22 // HTMLHRAccessible 23 //////////////////////////////////////////////////////////////////////////////// 24 25 role HTMLHRAccessible::NativeRole() const { return roles::SEPARATOR; } 26 27 //////////////////////////////////////////////////////////////////////////////// 28 // HTMLBRAccessible 29 //////////////////////////////////////////////////////////////////////////////// 30 31 role HTMLBRAccessible::NativeRole() const { return roles::WHITESPACE; } 32 33 uint64_t HTMLBRAccessible::NativeState() const { return states::READONLY; } 34 35 ENameValueFlag HTMLBRAccessible::NativeName(nsString& aName) const { 36 aName = static_cast<char16_t>('\n'); // Newline char 37 return eNameOK; 38 } 39 40 //////////////////////////////////////////////////////////////////////////////// 41 // HTMLLabelAccessible 42 //////////////////////////////////////////////////////////////////////////////// 43 44 ENameValueFlag HTMLLabelAccessible::NativeName(nsString& aName) const { 45 return eNameOK; 46 } 47 48 Relation HTMLLabelAccessible::RelationByType(RelationType aType) const { 49 Relation rel = AccessibleWrap::RelationByType(aType); 50 if (aType == RelationType::LABEL_FOR) { 51 dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromNode(mContent); 52 rel.AppendTarget(mDoc, label->GetControl()); 53 } 54 55 return rel; 56 } 57 58 void HTMLLabelAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 59 nsAtom* aAttribute, 60 AttrModType aModType, 61 const nsAttrValue* aOldValue, 62 uint64_t aOldState) { 63 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 64 aOldValue, aOldState); 65 66 if (aAttribute == nsGkAtoms::_for) { 67 mDoc->QueueCacheUpdate(this, CacheDomain::Relations | CacheDomain::Actions); 68 } 69 } 70 71 bool HTMLLabelAccessible::HasPrimaryAction() const { 72 return nsCoreUtils::IsLabelWithControl(mContent); 73 } 74 75 void HTMLLabelAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 76 if (aIndex == 0) { 77 if (HasPrimaryAction()) { 78 aName.AssignLiteral("click"); 79 } 80 } 81 } 82 83 //////////////////////////////////////////////////////////////////////////////// 84 // nsHTMLOuputAccessible 85 //////////////////////////////////////////////////////////////////////////////// 86 87 Relation HTMLOutputAccessible::RelationByType(RelationType aType) const { 88 Relation rel = AccessibleWrap::RelationByType(aType); 89 if (aType == RelationType::CONTROLLED_BY) { 90 rel.AppendIter( 91 new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::_for)); 92 } 93 94 return rel; 95 } 96 97 void HTMLOutputAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 98 nsAtom* aAttribute, 99 AttrModType aModType, 100 const nsAttrValue* aOldValue, 101 uint64_t aOldState) { 102 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 103 aOldValue, aOldState); 104 105 if (aAttribute == nsGkAtoms::_for) { 106 mDoc->QueueCacheUpdate(this, CacheDomain::Relations); 107 } 108 } 109 110 //////////////////////////////////////////////////////////////////////////////// 111 // HTMLSummaryAccessible 112 //////////////////////////////////////////////////////////////////////////////// 113 114 HTMLSummaryAccessible::HTMLSummaryAccessible(nsIContent* aContent, 115 DocAccessible* aDoc) 116 : HyperTextAccessible(aContent, aDoc) { 117 mGenericTypes |= eButton; 118 } 119 120 bool HTMLSummaryAccessible::HasPrimaryAction() const { return true; } 121 122 void HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 123 if (aIndex != eAction_Click) { 124 return; 125 } 126 127 dom::HTMLSummaryElement* summary = 128 dom::HTMLSummaryElement::FromNode(mContent); 129 if (!summary) { 130 return; 131 } 132 133 dom::HTMLDetailsElement* details = summary->GetDetails(); 134 if (!details) { 135 return; 136 } 137 138 if (details->Open()) { 139 aName.AssignLiteral("collapse"); 140 } else { 141 aName.AssignLiteral("expand"); 142 } 143 } 144 145 uint64_t HTMLSummaryAccessible::NativeState() const { 146 uint64_t state = HyperTextAccessible::NativeState(); 147 148 dom::HTMLSummaryElement* summary = 149 dom::HTMLSummaryElement::FromNode(mContent); 150 if (!summary) { 151 return state; 152 } 153 154 dom::HTMLDetailsElement* details = summary->GetDetails(); 155 if (!details) { 156 return state; 157 } 158 159 state |= states::EXPANDABLE; 160 161 if (details->Open()) { 162 state |= states::EXPANDED; 163 } 164 165 return state; 166 } 167 168 HTMLSummaryAccessible* HTMLSummaryAccessible::FromDetails( 169 LocalAccessible* details) { 170 if (!dom::HTMLDetailsElement::FromNodeOrNull(details->GetContent())) { 171 return nullptr; 172 } 173 174 HTMLSummaryAccessible* summaryAccessible = nullptr; 175 for (uint32_t i = 0; i < details->ChildCount(); i++) { 176 // Iterate through the children of our details accessible to locate main 177 // summary. This iteration includes the anonymous summary if the details 178 // element was not explicitly created with one. 179 LocalAccessible* child = details->LocalChildAt(i); 180 auto* summary = 181 mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child->GetContent()); 182 if (summary && summary->IsMainSummary()) { 183 summaryAccessible = static_cast<HTMLSummaryAccessible*>(child); 184 break; 185 } 186 } 187 188 return summaryAccessible; 189 } 190 191 //////////////////////////////////////////////////////////////////////////////// 192 // HTMLSummaryAccessible: Widgets 193 194 bool HTMLSummaryAccessible::IsWidget() const { return true; } 195 196 //////////////////////////////////////////////////////////////////////////////// 197 // HTMLHeaderOrFooterAccessible 198 //////////////////////////////////////////////////////////////////////////////// 199 200 role HTMLHeaderOrFooterAccessible::NativeRole() const { 201 // Only map header and footer if they are direct descendants of the body tag. 202 // If other sectioning or sectioning root elements, they become sections. 203 nsIContent* parent = mContent->GetParent(); 204 while (parent) { 205 if (parent->IsAnyOfHTMLElements( 206 nsGkAtoms::article, nsGkAtoms::aside, nsGkAtoms::nav, 207 nsGkAtoms::section, nsGkAtoms::main, nsGkAtoms::blockquote, 208 nsGkAtoms::details, nsGkAtoms::dialog, nsGkAtoms::fieldset, 209 nsGkAtoms::figure, nsGkAtoms::td)) { 210 break; 211 } 212 parent = parent->GetParent(); 213 } 214 215 // No sectioning or sectioning root elements found. 216 if (!parent) { 217 return roles::LANDMARK; 218 } 219 220 return roles::SECTION; 221 } 222 223 //////////////////////////////////////////////////////////////////////////////// 224 // HTMLAsideAccessible 225 //////////////////////////////////////////////////////////////////////////////// 226 227 role HTMLAsideAccessible::NativeRole() const { 228 // Per the HTML-AAM spec, there are two cases for aside elements: 229 // 1. scoped to body or main elements -> 'complementary' role 230 // 2. scoped to sectioning content elements 231 // -> if the element has an accessible name, 'complementary' role 232 // -> otherwise, 'generic' role 233 // To implement this, walk ancestors until we find a sectioning content 234 // element, or a body/main element, then take actions based on the rules 235 // above. 236 nsIContent* parent = mContent->GetParent(); 237 while (parent) { 238 if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::aside, 239 nsGkAtoms::nav, nsGkAtoms::section)) { 240 return !NameIsEmpty() ? roles::LANDMARK : roles::SECTION; 241 } 242 if (parent->IsAnyOfHTMLElements(nsGkAtoms::main, nsGkAtoms::body)) { 243 return roles::LANDMARK; 244 } 245 parent = parent->GetParent(); 246 } 247 248 // Fall back to landmark, though we always expect to find a body element. 249 return roles::LANDMARK; 250 } 251 252 //////////////////////////////////////////////////////////////////////////////// 253 // HTMLSectionAccessible 254 //////////////////////////////////////////////////////////////////////////////// 255 256 role HTMLSectionAccessible::NativeRole() const { 257 return NameIsEmpty() ? roles::SECTION : roles::REGION; 258 } 259 260 //////////////////////////////////////////////////////////////////////////////// 261 // HTMLAbbreviationAccessible 262 //////////////////////////////////////////////////////////////////////////////// 263 264 ENameValueFlag HTMLAbbreviationAccessible::NativeName(nsString& aName) const { 265 if (mContent->AsElement()->GetAttr(nsGkAtoms::title, aName)) { 266 // "title" tag takes priority 267 return eNameOK; 268 } 269 270 return HyperTextAccessible::NativeName(aName); 271 } 272 273 void HTMLAbbreviationAccessible::DOMAttributeChanged( 274 int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType, 275 const nsAttrValue* aOldValue, uint64_t aOldState) { 276 if (aAttribute == nsGkAtoms::title) { 277 nsAutoString name; 278 ARIAName(name); 279 if (name.IsEmpty()) { 280 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); 281 return; 282 } 283 284 if (!mContent->AsElement()->HasAttr(nsGkAtoms::aria_describedby)) { 285 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, 286 this); 287 } 288 289 return; 290 } 291 292 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 293 aOldValue, aOldState); 294 }