HTMLFormControlAccessible.cpp (36831B)
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 "HTMLFormControlAccessible.h" 7 8 #include "CacheConstants.h" 9 #include "DocAccessible-inl.h" 10 #include "LocalAccessible-inl.h" 11 #include "nsAccUtils.h" 12 #include "nsEventShell.h" 13 #include "nsTextEquivUtils.h" 14 #include "Relation.h" 15 #include "mozilla/a11y/Role.h" 16 #include "States.h" 17 #include "TextLeafAccessible.h" 18 19 #include "nsContentList.h" 20 #include "mozilla/dom/HTMLInputElement.h" 21 #include "mozilla/dom/HTMLMeterElement.h" 22 #include "mozilla/dom/HTMLTextAreaElement.h" 23 #include "mozilla/dom/HTMLFormControlsCollection.h" 24 #include "nsIFormControl.h" 25 26 #include "mozilla/FloatingPoint.h" 27 #include "mozilla/Preferences.h" 28 #include "mozilla/TextEditor.h" 29 30 using namespace mozilla; 31 using namespace mozilla::dom; 32 using namespace mozilla::a11y; 33 34 //////////////////////////////////////////////////////////////////////////////// 35 // HTMLFormAccessible 36 //////////////////////////////////////////////////////////////////////////////// 37 38 role HTMLFormAccessible::NativeRole() const { 39 return NameIsEmpty() ? roles::FORM : roles::FORM_LANDMARK; 40 } 41 42 void HTMLFormAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 43 nsAtom* aAttribute, 44 AttrModType aModType, 45 const nsAttrValue* aOldValue, 46 uint64_t aOldState) { 47 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 48 aOldValue, aOldState); 49 if (aAttribute == nsGkAtoms::autocomplete) { 50 dom::HTMLFormElement* formEl = dom::HTMLFormElement::FromNode(mContent); 51 52 HTMLFormControlsCollection* controls = formEl->Elements(); 53 uint32_t length = controls->Length(); 54 for (uint32_t i = 0; i < length; i++) { 55 if (LocalAccessible* acc = mDoc->GetAccessible(controls->Item(i))) { 56 if (acc->IsTextField() && !acc->IsPassword()) { 57 if (!acc->Elm()->HasAttr(nsGkAtoms::list) && 58 !acc->Elm()->AttrValueIs(kNameSpaceID_None, 59 nsGkAtoms::autocomplete, nsGkAtoms::OFF, 60 eIgnoreCase)) { 61 RefPtr<AccEvent> stateChangeEvent = 62 new AccStateChangeEvent(acc, states::SUPPORTS_AUTOCOMPLETION); 63 mDoc->FireDelayedEvent(stateChangeEvent); 64 } 65 } 66 } 67 } 68 } 69 } 70 71 //////////////////////////////////////////////////////////////////////////////// 72 // HTMLRadioButtonAccessible 73 //////////////////////////////////////////////////////////////////////////////// 74 75 uint64_t HTMLRadioButtonAccessible::NativeState() const { 76 uint64_t state = AccessibleWrap::NativeState(); 77 78 state |= states::CHECKABLE; 79 80 HTMLInputElement* input = HTMLInputElement::FromNode(mContent); 81 if (input && input->Checked()) state |= states::CHECKED; 82 83 return state; 84 } 85 86 void HTMLRadioButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet, 87 int32_t* aSetSize) { 88 (void)ComputeGroupAttributes(aPosInSet, aSetSize); 89 } 90 91 void HTMLRadioButtonAccessible::DOMAttributeChanged( 92 int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType, 93 const nsAttrValue* aOldValue, uint64_t aOldState) { 94 if (aAttribute == nsGkAtoms::name) { 95 // If our name changed, it's possible our MEMBER_OF relation 96 // also changed. Push a cache update for Relations. 97 mDoc->QueueCacheUpdate(this, CacheDomain::Relations); 98 } else { 99 // Otherwise, handle this attribute change the way our parent 100 // class wants us to handle it. 101 RadioButtonAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, 102 aModType, aOldValue, aOldState); 103 } 104 } 105 106 Relation HTMLRadioButtonAccessible::ComputeGroupAttributes( 107 int32_t* aPosInSet, int32_t* aSetSize) const { 108 Relation rel = Relation(); 109 int32_t namespaceId = mContent->NodeInfo()->NamespaceID(); 110 nsAutoString tagName; 111 mContent->NodeInfo()->GetName(tagName); 112 113 nsAutoString type; 114 mContent->AsElement()->GetAttr(nsGkAtoms::type, type); 115 nsAutoString name; 116 mContent->AsElement()->GetAttr(nsGkAtoms::name, name); 117 118 RefPtr<nsContentList> inputElms; 119 120 if (dom::Element* formElm = nsIFormControl::FromNode(mContent)->GetForm()) { 121 inputElms = NS_GetContentList(formElm, namespaceId, tagName); 122 } else { 123 inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); 124 } 125 NS_ENSURE_TRUE(inputElms, rel); 126 127 uint32_t inputCount = inputElms->Length(false); 128 129 // Compute posinset and setsize. 130 int32_t indexOf = 0; 131 int32_t count = 0; 132 133 for (uint32_t index = 0; index < inputCount; index++) { 134 nsIContent* inputElm = inputElms->Item(index, false); 135 if (inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 136 type, eCaseMatters) && 137 inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 138 name, eCaseMatters) && 139 mDoc->HasAccessible(inputElm)) { 140 count++; 141 rel.AppendTarget(mDoc->GetAccessible(inputElm)); 142 if (inputElm == mContent) indexOf = count; 143 } 144 } 145 146 *aPosInSet = indexOf; 147 *aSetSize = count; 148 return rel; 149 } 150 151 Relation HTMLRadioButtonAccessible::RelationByType(RelationType aType) const { 152 if (aType == RelationType::MEMBER_OF) { 153 int32_t unusedPos, unusedSetSize; 154 return ComputeGroupAttributes(&unusedPos, &unusedSetSize); 155 } 156 157 return LocalAccessible::RelationByType(aType); 158 } 159 160 //////////////////////////////////////////////////////////////////////////////// 161 // HTMLButtonAccessible 162 //////////////////////////////////////////////////////////////////////////////// 163 164 HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent, 165 DocAccessible* aDoc) 166 : HyperTextAccessible(aContent, aDoc) { 167 mGenericTypes |= eButton; 168 } 169 170 bool HTMLButtonAccessible::HasPrimaryAction() const { return true; } 171 172 void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 173 if (aIndex == eAction_Click) aName.AssignLiteral("press"); 174 } 175 176 void HTMLButtonAccessible::Value(nsString& aValue) const { 177 if (HTMLInputElement* input = HTMLInputElement::FromNode(mContent)) { 178 if (input->IsInputColor()) { 179 nsAutoString value; 180 input->GetValue(value, CallerType::NonSystem); 181 Maybe<nscolor> maybeColor = HTMLInputElement::ParseSimpleColor(value); 182 if (maybeColor) { 183 const nscolor& color = maybeColor.ref(); 184 Decimal r(static_cast<int>(NS_GET_R(color) / 2.55f)), 185 g(static_cast<int>(NS_GET_G(color) / 2.55f)), 186 b(static_cast<int>(NS_GET_B(color) / 2.55f)); 187 nsAutoString rs(NS_ConvertUTF8toUTF16(r.toString())); 188 nsAutoString gs(NS_ConvertUTF8toUTF16(g.toString())); 189 nsAutoString bs(NS_ConvertUTF8toUTF16(b.toString())); 190 Accessible::TranslateString(u"inputColorValue"_ns, aValue, 191 {rs, gs, bs}); 192 return; 193 } 194 } 195 } 196 197 HyperTextAccessible::Value(aValue); 198 } 199 200 uint64_t HTMLButtonAccessible::NativeState() const { 201 uint64_t state = HyperTextAccessible::NativeState(); 202 203 dom::Element* elm = Elm(); 204 if (auto* popover = elm->GetEffectivePopoverTargetElement()) { 205 LocalAccessible* popoverAcc = mDoc->GetAccessible(popover); 206 if (!popoverAcc || !popoverAcc->IsAncestorOf(this)) { 207 if (popover->IsPopoverOpen()) { 208 state |= states::EXPANDED; 209 } 210 state |= states::EXPANDABLE; 211 } 212 } 213 214 ElementState elmState = mContent->AsElement()->State(); 215 if (elmState.HasState(ElementState::DEFAULT)) state |= states::DEFAULT; 216 217 return state; 218 } 219 220 role HTMLButtonAccessible::NativeRole() const { return roles::PUSHBUTTON; } 221 222 ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const { 223 // No need to check @value attribute for buttons since this attribute results 224 // in native anonymous text node and the name is calculated from subtree. 225 // The same magic works for @alt and @value attributes in case of type="image" 226 // element that has no valid @src (note if input@type="image" has an image 227 // then neither @alt nor @value attributes are used to generate a visual label 228 // and thus we need to obtain the accessible name directly from attribute 229 // value). Also the same algorithm works in case of default labels for 230 // type="submit"/"reset"/"image" elements. 231 232 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); 233 if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) || 234 !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 235 nsGkAtoms::image, eCaseMatters)) { 236 return nameFlag; 237 } 238 239 if (!mContent->AsElement()->GetAttr(nsGkAtoms::alt, aName)) { 240 mContent->AsElement()->GetAttr(nsGkAtoms::value, aName); 241 } 242 243 aName.CompressWhitespace(); 244 return eNameOK; 245 } 246 247 void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 248 nsAtom* aAttribute, 249 AttrModType aModType, 250 const nsAttrValue* aOldValue, 251 uint64_t aOldState) { 252 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 253 aOldValue, aOldState); 254 255 if (aAttribute == nsGkAtoms::value) { 256 dom::Element* elm = Elm(); 257 if (elm->IsHTMLElement(nsGkAtoms::input) || 258 (elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image, 259 eCaseMatters) && 260 !elm->HasAttr(nsGkAtoms::alt))) { 261 if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) && 262 !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) { 263 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); 264 } 265 } 266 } 267 } 268 269 //////////////////////////////////////////////////////////////////////////////// 270 // HTMLButtonAccessible: Widgets 271 272 bool HTMLButtonAccessible::IsWidget() const { return true; } 273 274 //////////////////////////////////////////////////////////////////////////////// 275 // HTMLTextFieldAccessible 276 //////////////////////////////////////////////////////////////////////////////// 277 278 HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent, 279 DocAccessible* aDoc) 280 : HyperTextAccessible(aContent, aDoc) { 281 mType = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 282 nsGkAtoms::password, eIgnoreCase) 283 ? eHTMLTextPasswordFieldType 284 : eHTMLTextFieldType; 285 } 286 287 role HTMLTextFieldAccessible::NativeRole() const { 288 if (mType == eHTMLTextPasswordFieldType) { 289 return roles::PASSWORD_TEXT; 290 } 291 dom::Element* el = mContent->AsElement(); 292 if (el->HasAttr(nsGkAtoms::list)) { 293 return roles::EDITCOMBOBOX; 294 } 295 if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) { 296 RefPtr<nsAtom> inputType = attr->GetAsAtom(); 297 if (inputType == nsGkAtoms::search) { 298 return roles::SEARCHBOX; 299 } 300 } 301 return roles::ENTRY; 302 } 303 304 already_AddRefed<AccAttributes> HTMLTextFieldAccessible::NativeAttributes() { 305 RefPtr<AccAttributes> attributes = HyperTextAccessible::NativeAttributes(); 306 307 // Expose type for text input elements as it gives some useful context, 308 // especially for mobile. 309 if (const nsAttrValue* attr = 310 mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) { 311 RefPtr<nsAtom> inputType = attr->GetAsAtom(); 312 if (inputType) { 313 if (!ARIARoleMap() && inputType == nsGkAtoms::search) { 314 attributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::searchbox); 315 } 316 attributes->SetAttribute(nsGkAtoms::textInputType, inputType); 317 } 318 } 319 // If this element has the placeholder attribute set, 320 // and if that is not identical to the name, expose it as an object attribute. 321 nsString placeholderText; 322 if (mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholderText)) { 323 nsAutoString name; 324 Name(name); 325 if (!name.Equals(placeholderText)) { 326 attributes->SetAttribute(nsGkAtoms::placeholder, 327 std::move(placeholderText)); 328 } 329 } 330 331 return attributes.forget(); 332 } 333 334 ENameValueFlag HTMLTextFieldAccessible::DirectName(nsString& aName) const { 335 ENameValueFlag nameFlag = LocalAccessible::DirectName(aName); 336 if (!aName.IsEmpty()) return nameFlag; 337 338 mContent->AsElement()->GetAttr(nsGkAtoms::title, aName); 339 aName.CompressWhitespace(); 340 if (aName.IsEmpty()) { 341 // text inputs and textareas might have useful placeholder text 342 mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, aName); 343 } 344 345 return eNameOK; 346 } 347 348 void HTMLTextFieldAccessible::Value(nsString& aValue) const { 349 aValue.Truncate(); 350 351 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent); 352 if (textArea) { 353 MOZ_ASSERT(!(NativeState() & states::PROTECTED)); 354 textArea->GetValue(aValue); 355 return; 356 } 357 358 HTMLInputElement* input = HTMLInputElement::FromNode(mContent); 359 if (input) { 360 // Pass NonSystem as the caller type, to be safe. We don't expect to have a 361 // file input here. 362 input->GetValue(aValue, CallerType::NonSystem); 363 364 if (NativeState() & states::PROTECTED) { // Don't return password text! 365 const char16_t mask = TextEditor::PasswordMask(); 366 for (size_t i = 0; i < aValue.Length(); i++) { 367 aValue.SetCharAt(mask, i); 368 } 369 } 370 } 371 } 372 373 bool HTMLTextFieldAccessible::AttributeChangesState(nsAtom* aAttribute) { 374 if (aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::list || 375 aAttribute == nsGkAtoms::autocomplete) { 376 return true; 377 } 378 379 return LocalAccessible::AttributeChangesState(aAttribute); 380 } 381 382 void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const { 383 HyperTextAccessible::ApplyARIAState(aState); 384 aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); 385 } 386 387 uint64_t HTMLTextFieldAccessible::NativeState() const { 388 uint64_t state = HyperTextAccessible::NativeState(); 389 390 // Text fields are always editable, even if they are also read only or 391 // disabled. 392 state |= states::EDITABLE; 393 394 // can be focusable, focused, protected. readonly, unavailable, selected 395 if (mContent->IsHTMLElement(nsGkAtoms::input) && 396 mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 397 nsGkAtoms::password, eIgnoreCase)) { 398 state |= states::PROTECTED; 399 } 400 401 if (mContent->AsElement()->HasAttr(nsGkAtoms::readonly)) { 402 state |= states::READONLY; 403 } 404 405 // Is it an <input> or a <textarea> ? 406 HTMLInputElement* input = HTMLInputElement::FromNode(mContent); 407 state |= input && input->IsSingleLineTextControl() ? states::SINGLE_LINE 408 : states::MULTI_LINE; 409 410 if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY | 411 states::UNAVAILABLE)) { 412 return state; 413 } 414 415 // Expose autocomplete state if it has associated autocomplete list. 416 if (mContent->AsElement()->HasAttr(nsGkAtoms::list)) { 417 return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP; 418 } 419 420 if (Preferences::GetBool("browser.formfill.enable")) { 421 // Check to see if autocompletion is allowed on this input. We don't expose 422 // it for password fields even though the entire password can be remembered 423 // for a page if the user asks it to be. However, the kind of autocomplete 424 // we're talking here is based on what the user types, where a popup of 425 // possible choices comes up. 426 nsAutoString autocomplete; 427 mContent->AsElement()->GetAttr(nsGkAtoms::autocomplete, autocomplete); 428 429 if (!autocomplete.LowerCaseEqualsLiteral("off")) { 430 Element* formElement = input->GetForm(); 431 if (formElement) { 432 formElement->GetAttr(nsGkAtoms::autocomplete, autocomplete); 433 } 434 435 if (!formElement || !autocomplete.LowerCaseEqualsLiteral("off")) { 436 state |= states::SUPPORTS_AUTOCOMPLETION; 437 } 438 } 439 } 440 441 return state; 442 } 443 444 bool HTMLTextFieldAccessible::HasPrimaryAction() const { return true; } 445 446 void HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 447 if (aIndex == eAction_Click) aName.AssignLiteral("activate"); 448 } 449 450 bool HTMLTextFieldAccessible::DoAction(uint8_t aIndex) const { 451 if (aIndex != 0) return false; 452 453 if (FocusMgr()->IsFocused(this)) { 454 // This already has focus, so TakeFocus()will do nothing. However, the user 455 // might be activating this element because they dismissed a touch keyboard 456 // and want to bring it back. 457 DoCommand(); 458 } else { 459 TakeFocus(); 460 } 461 return true; 462 } 463 464 already_AddRefed<EditorBase> HTMLTextFieldAccessible::GetEditor() const { 465 RefPtr<TextControlElement> textControlElement = 466 TextControlElement::FromNodeOrNull(mContent); 467 if (!textControlElement) { 468 return nullptr; 469 } 470 RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor(); 471 return textEditor.forget(); 472 } 473 474 void HTMLTextFieldAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 475 nsAtom* aAttribute, 476 AttrModType aModType, 477 const nsAttrValue* aOldValue, 478 uint64_t aOldState) { 479 if (aAttribute == nsGkAtoms::placeholder) { 480 mDoc->QueueCacheUpdate(this, CacheDomain::NameAndDescription); 481 return; 482 } 483 HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 484 aOldValue, aOldState); 485 } 486 487 //////////////////////////////////////////////////////////////////////////////// 488 // HTMLTextFieldAccessible: Widgets 489 490 bool HTMLTextFieldAccessible::IsWidget() const { return true; } 491 492 LocalAccessible* HTMLTextFieldAccessible::ContainerWidget() const { 493 return nullptr; 494 } 495 496 //////////////////////////////////////////////////////////////////////////////// 497 // HTMLFileInputAccessible 498 //////////////////////////////////////////////////////////////////////////////// 499 500 HTMLFileInputAccessible::HTMLFileInputAccessible(nsIContent* aContent, 501 DocAccessible* aDoc) 502 : HyperTextAccessible(aContent, aDoc) { 503 mType = eHTMLFileInputType; 504 mGenericTypes |= eButton; 505 } 506 507 role HTMLFileInputAccessible::NativeRole() const { return roles::PUSHBUTTON; } 508 509 bool HTMLFileInputAccessible::IsAcceptableChild(nsIContent* aEl) const { 510 // File inputs are rendered using native anonymous children. However, we 511 // want to expose this as a button Accessible so that clients can pick up the 512 // name and description from the button they activate, rather than a 513 // container. We still expose the text leaf descendants so we can get the 514 // name of the Browse button and the file name. 515 return aEl->IsText(); 516 } 517 518 ENameValueFlag HTMLFileInputAccessible::DirectName(nsString& aName) const { 519 ENameValueFlag flag = HyperTextAccessible::DirectName(aName); 520 if (flag == eNameFromSubtree) { 521 // The author didn't provide a name. We'll compute the name from our subtree 522 // below. 523 aName.Truncate(); 524 } else { 525 // The author provided a name. We do use that, but we also append our 526 // subtree text so the user knows this is a file chooser button and what 527 // file has been chosen. 528 if (aName.IsEmpty()) { 529 // Name computation is recursing, perhaps due to a wrapping <label>. Don't 530 // append the subtree text. Return " " to prevent 531 // nsTextEquivUtils::AppendFromAccessible walking the subtree itself. 532 aName += ' '; 533 return flag; 534 } 535 } 536 // Unfortunately, GetNameFromSubtree doesn't separate the button text from the 537 // file name text. Compute the text ourselves. 538 uint32_t count = ChildCount(); 539 for (uint32_t c = 0; c < count; ++c) { 540 TextLeafAccessible* leaf = LocalChildAt(c)->AsTextLeaf(); 541 MOZ_ASSERT(leaf); 542 if (!aName.IsEmpty()) { 543 aName += ' '; 544 } 545 aName += leaf->Text(); 546 } 547 548 // XXX: Return eNameOK even if we got the name from a label or subtree. This 549 // is to force us to cache the name, since the calculation of this type is out 550 // of spec and pretty nuanced. 551 return eNameOK; 552 } 553 554 bool HTMLFileInputAccessible::HasPrimaryAction() const { return true; } 555 556 void HTMLFileInputAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 557 if (aIndex == 0) { 558 aName.AssignLiteral("press"); 559 } 560 } 561 562 bool HTMLFileInputAccessible::IsWidget() const { return true; } 563 564 //////////////////////////////////////////////////////////////////////////////// 565 // HTMLSpinnerAccessible 566 //////////////////////////////////////////////////////////////////////////////// 567 568 role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; } 569 570 void HTMLSpinnerAccessible::Value(nsString& aValue) const { 571 HTMLTextFieldAccessible::Value(aValue); 572 if (!aValue.IsEmpty()) return; 573 574 // Pass NonSystem as the caller type, to be safe. We don't expect to have a 575 // file input here. 576 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem); 577 } 578 579 double HTMLSpinnerAccessible::MaxValue() const { 580 double value = HTMLTextFieldAccessible::MaxValue(); 581 if (!std::isnan(value)) return value; 582 583 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble(); 584 } 585 586 double HTMLSpinnerAccessible::MinValue() const { 587 double value = HTMLTextFieldAccessible::MinValue(); 588 if (!std::isnan(value)) return value; 589 590 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble(); 591 } 592 593 double HTMLSpinnerAccessible::Step() const { 594 double value = HTMLTextFieldAccessible::Step(); 595 if (!std::isnan(value)) return value; 596 597 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble(); 598 } 599 600 double HTMLSpinnerAccessible::CurValue() const { 601 double value = HTMLTextFieldAccessible::CurValue(); 602 if (!std::isnan(value)) return value; 603 604 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble(); 605 } 606 607 bool HTMLSpinnerAccessible::SetCurValue(double aValue) { 608 ErrorResult er; 609 HTMLInputElement::FromNode(mContent)->SetValueAsNumber(aValue, er); 610 return !er.Failed(); 611 } 612 613 //////////////////////////////////////////////////////////////////////////////// 614 // HTMLRangeAccessible 615 //////////////////////////////////////////////////////////////////////////////// 616 617 role HTMLRangeAccessible::NativeRole() const { return roles::SLIDER; } 618 619 bool HTMLRangeAccessible::IsWidget() const { return true; } 620 621 void HTMLRangeAccessible::Value(nsString& aValue) const { 622 LeafAccessible::Value(aValue); 623 if (!aValue.IsEmpty()) return; 624 625 // Pass NonSystem as the caller type, to be safe. We don't expect to have a 626 // file input here. 627 HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem); 628 } 629 630 double HTMLRangeAccessible::MaxValue() const { 631 double value = LeafAccessible::MaxValue(); 632 if (!std::isnan(value)) return value; 633 634 return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble(); 635 } 636 637 double HTMLRangeAccessible::MinValue() const { 638 double value = LeafAccessible::MinValue(); 639 if (!std::isnan(value)) return value; 640 641 return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble(); 642 } 643 644 double HTMLRangeAccessible::Step() const { 645 double value = LeafAccessible::Step(); 646 if (!std::isnan(value)) return value; 647 648 return HTMLInputElement::FromNode(mContent)->GetStep().toDouble(); 649 } 650 651 double HTMLRangeAccessible::CurValue() const { 652 double value = LeafAccessible::CurValue(); 653 if (!std::isnan(value)) return value; 654 655 return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble(); 656 } 657 658 bool HTMLRangeAccessible::SetCurValue(double aValue) { 659 nsAutoString strValue; 660 strValue.AppendFloat(aValue); 661 HTMLInputElement::FromNode(mContent)->SetUserInput( 662 strValue, *nsContentUtils::GetSystemPrincipal()); 663 return true; 664 } 665 666 //////////////////////////////////////////////////////////////////////////////// 667 // HTMLGroupboxAccessible 668 //////////////////////////////////////////////////////////////////////////////// 669 670 HTMLGroupboxAccessible::HTMLGroupboxAccessible(nsIContent* aContent, 671 DocAccessible* aDoc) 672 : HyperTextAccessible(aContent, aDoc) {} 673 674 role HTMLGroupboxAccessible::NativeRole() const { return roles::GROUPING; } 675 676 nsIContent* HTMLGroupboxAccessible::GetLegend() const { 677 for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent; 678 legendContent = legendContent->GetNextSibling()) { 679 if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend, 680 mContent->GetNameSpaceID())) { 681 // Either XHTML namespace or no namespace 682 return legendContent; 683 } 684 } 685 686 return nullptr; 687 } 688 689 ENameValueFlag HTMLGroupboxAccessible::NativeName(nsString& aName) const { 690 ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); 691 if (!aName.IsEmpty()) return nameFlag; 692 693 nsIContent* legendContent = GetLegend(); 694 if (legendContent) { 695 bool usedHiddenContent = nsTextEquivUtils::AppendTextEquivFromContent( 696 this, legendContent, &aName); 697 aName.CompressWhitespace(); 698 if (!usedHiddenContent && !aName.IsEmpty()) { 699 return eNameFromRelations; 700 } 701 } 702 703 return eNameOK; 704 } 705 706 Relation HTMLGroupboxAccessible::RelationByType(RelationType aType) const { 707 Relation rel = HyperTextAccessible::RelationByType(aType); 708 // No override for label, so use <legend> for this <fieldset> 709 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, GetLegend()); 710 711 return rel; 712 } 713 714 //////////////////////////////////////////////////////////////////////////////// 715 // HTMLLegendAccessible 716 //////////////////////////////////////////////////////////////////////////////// 717 718 HTMLLegendAccessible::HTMLLegendAccessible(nsIContent* aContent, 719 DocAccessible* aDoc) 720 : HyperTextAccessible(aContent, aDoc) {} 721 722 Relation HTMLLegendAccessible::RelationByType(RelationType aType) const { 723 Relation rel = HyperTextAccessible::RelationByType(aType); 724 if (aType != RelationType::LABEL_FOR) return rel; 725 726 LocalAccessible* groupbox = LocalParent(); 727 if (groupbox && groupbox->Role() == roles::GROUPING) { 728 rel.AppendTarget(groupbox); 729 } 730 731 return rel; 732 } 733 734 //////////////////////////////////////////////////////////////////////////////// 735 // HTMLFigureAccessible 736 //////////////////////////////////////////////////////////////////////////////// 737 738 HTMLFigureAccessible::HTMLFigureAccessible(nsIContent* aContent, 739 DocAccessible* aDoc) 740 : HyperTextAccessible(aContent, aDoc) {} 741 742 ENameValueFlag HTMLFigureAccessible::NativeName(nsString& aName) const { 743 ENameValueFlag nameFlag = HyperTextAccessible::NativeName(aName); 744 if (!aName.IsEmpty()) return nameFlag; 745 746 nsIContent* captionContent = Caption(); 747 if (captionContent) { 748 bool usedHiddenContent = nsTextEquivUtils::AppendTextEquivFromContent( 749 this, captionContent, &aName); 750 aName.CompressWhitespace(); 751 if (!usedHiddenContent && !aName.IsEmpty()) { 752 return eNameFromRelations; 753 } 754 } 755 756 return eNameOK; 757 } 758 759 Relation HTMLFigureAccessible::RelationByType(RelationType aType) const { 760 Relation rel = HyperTextAccessible::RelationByType(aType); 761 if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, Caption()); 762 763 return rel; 764 } 765 766 nsIContent* HTMLFigureAccessible::Caption() const { 767 for (nsIContent* childContent = mContent->GetFirstChild(); childContent; 768 childContent = childContent->GetNextSibling()) { 769 if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption, 770 mContent->GetNameSpaceID())) { 771 return childContent; 772 } 773 } 774 775 return nullptr; 776 } 777 778 //////////////////////////////////////////////////////////////////////////////// 779 // HTMLFigcaptionAccessible 780 //////////////////////////////////////////////////////////////////////////////// 781 782 HTMLFigcaptionAccessible::HTMLFigcaptionAccessible(nsIContent* aContent, 783 DocAccessible* aDoc) 784 : HyperTextAccessible(aContent, aDoc) {} 785 786 Relation HTMLFigcaptionAccessible::RelationByType(RelationType aType) const { 787 Relation rel = HyperTextAccessible::RelationByType(aType); 788 if (aType != RelationType::LABEL_FOR) return rel; 789 790 LocalAccessible* figure = LocalParent(); 791 if (figure && figure->GetContent()->NodeInfo()->Equals( 792 nsGkAtoms::figure, mContent->GetNameSpaceID())) { 793 rel.AppendTarget(figure); 794 } 795 796 return rel; 797 } 798 799 //////////////////////////////////////////////////////////////////////////////// 800 // HTMLProgressAccessible 801 //////////////////////////////////////////////////////////////////////////////// 802 803 role HTMLProgressAccessible::NativeRole() const { return roles::PROGRESSBAR; } 804 805 uint64_t HTMLProgressAccessible::NativeState() const { 806 uint64_t state = LeafAccessible::NativeState(); 807 // Progress bars are always readonly. 808 state |= states::READONLY; 809 810 // An undetermined progressbar (i.e. without a value) has a mixed state. 811 nsAutoString attrValue; 812 mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue); 813 if (attrValue.IsEmpty()) { 814 state |= states::MIXED; 815 } 816 817 return state; 818 } 819 820 bool HTMLProgressAccessible::IsWidget() const { return true; } 821 822 void HTMLProgressAccessible::Value(nsString& aValue) const { 823 LeafAccessible::Value(aValue); 824 if (!aValue.IsEmpty()) { 825 return; 826 } 827 828 double maxValue = MaxValue(); 829 if (std::isnan(maxValue) || maxValue == 0) { 830 return; 831 } 832 833 double curValue = CurValue(); 834 if (std::isnan(curValue)) { 835 return; 836 } 837 838 // Treat the current value bigger than maximum as 100%. 839 double percentValue = 840 (curValue < maxValue) ? (curValue / maxValue) * 100 : 100; 841 842 aValue.AppendFloat(percentValue); 843 aValue.Append('%'); 844 } 845 846 double HTMLProgressAccessible::MaxValue() const { 847 double value = LeafAccessible::MaxValue(); 848 if (!std::isnan(value)) { 849 return value; 850 } 851 852 nsAutoString strValue; 853 if (mContent->AsElement()->GetAttr(nsGkAtoms::max, strValue)) { 854 nsresult result = NS_OK; 855 value = strValue.ToDouble(&result); 856 if (NS_SUCCEEDED(result)) { 857 return value; 858 } 859 } 860 861 return 1; 862 } 863 864 double HTMLProgressAccessible::MinValue() const { 865 double value = LeafAccessible::MinValue(); 866 return std::isnan(value) ? 0 : value; 867 } 868 869 double HTMLProgressAccessible::Step() const { 870 double value = LeafAccessible::Step(); 871 return std::isnan(value) ? 0 : value; 872 } 873 874 double HTMLProgressAccessible::CurValue() const { 875 double value = LeafAccessible::CurValue(); 876 if (!std::isnan(value)) { 877 return value; 878 } 879 880 nsAutoString attrValue; 881 if (!mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue)) { 882 return UnspecifiedNaN<double>(); 883 } 884 885 nsresult error = NS_OK; 886 value = attrValue.ToDouble(&error); 887 return NS_FAILED(error) ? UnspecifiedNaN<double>() : value; 888 } 889 890 bool HTMLProgressAccessible::SetCurValue(double aValue) { 891 return false; // progress meters are readonly. 892 } 893 894 void HTMLProgressAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 895 nsAtom* aAttribute, 896 AttrModType aModType, 897 const nsAttrValue* aOldValue, 898 uint64_t aOldState) { 899 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 900 aOldValue, aOldState); 901 902 if (aAttribute == nsGkAtoms::value) { 903 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this); 904 905 uint64_t currState = NativeState(); 906 if ((aOldState ^ currState) & states::MIXED) { 907 RefPtr<AccEvent> stateChangeEvent = new AccStateChangeEvent( 908 this, states::MIXED, (currState & states::MIXED)); 909 mDoc->FireDelayedEvent(stateChangeEvent); 910 } 911 } 912 } 913 914 //////////////////////////////////////////////////////////////////////////////// 915 // HTMLMeterAccessible 916 //////////////////////////////////////////////////////////////////////////////// 917 918 role HTMLMeterAccessible::NativeRole() const { return roles::METER; } 919 920 bool HTMLMeterAccessible::IsWidget() const { return true; } 921 922 void HTMLMeterAccessible::Value(nsString& aValue) const { 923 LeafAccessible::Value(aValue); 924 if (!aValue.IsEmpty()) { 925 return; 926 } 927 928 // If we did not get a value from the above LeafAccessible call, 929 // we should check to see if the meter has inner text. 930 // If it does, we'll use that as our value. 931 nsTextEquivUtils::AppendFromDOMChildren(mContent, &aValue); 932 aValue.CompressWhitespace(); 933 if (!aValue.IsEmpty()) { 934 return; 935 } 936 937 // If no inner text is found, use curValue 938 double curValue = CurValue(); 939 if (std::isnan(curValue)) { 940 return; 941 } 942 943 aValue.AppendFloat(curValue); 944 } 945 946 double HTMLMeterAccessible::MaxValue() const { 947 double max = LeafAccessible::MaxValue(); 948 double min = MinValue(); 949 950 if (!std::isnan(max)) { 951 return max > min ? max : min; 952 } 953 954 // If we didn't find a max value, check for the max attribute 955 nsAutoString strValue; 956 if (mContent->AsElement()->GetAttr(nsGkAtoms::max, strValue)) { 957 nsresult result = NS_OK; 958 max = strValue.ToDouble(&result); 959 if (NS_SUCCEEDED(result)) { 960 return max > min ? max : min; 961 } 962 } 963 964 return 1 > min ? 1 : min; 965 } 966 967 double HTMLMeterAccessible::MinValue() const { 968 double min = LeafAccessible::MinValue(); 969 if (!std::isnan(min)) { 970 return min; 971 } 972 973 nsAutoString strValue; 974 if (mContent->AsElement()->GetAttr(nsGkAtoms::min, strValue)) { 975 nsresult result = NS_OK; 976 min = strValue.ToDouble(&result); 977 if (NS_SUCCEEDED(result)) { 978 return min; 979 } 980 } 981 982 return 0; 983 } 984 985 double HTMLMeterAccessible::CurValue() const { 986 double value = LeafAccessible::CurValue(); 987 double minValue = MinValue(); 988 989 if (std::isnan(value)) { 990 /* If we didn't find a value from the LeafAccessible call above, check 991 * for a value attribute */ 992 nsAutoString attrValue; 993 if (!mContent->AsElement()->GetAttr(nsGkAtoms::value, attrValue)) { 994 return minValue; 995 } 996 997 // If we find a value attribute, attempt to convert it to a double 998 nsresult error = NS_OK; 999 value = attrValue.ToDouble(&error); 1000 if (NS_FAILED(error)) { 1001 return minValue; 1002 } 1003 } 1004 1005 /* If we end up with a defined value, verify it falls between 1006 * our established min/max. Otherwise, snap it to the nearest boundary. */ 1007 double maxValue = MaxValue(); 1008 if (value > maxValue) { 1009 value = maxValue; 1010 } else if (value < minValue) { 1011 value = minValue; 1012 } 1013 1014 return value; 1015 } 1016 1017 bool HTMLMeterAccessible::SetCurValue(double aValue) { 1018 return false; // meters are readonly. 1019 } 1020 1021 int32_t HTMLMeterAccessible::ValueRegion() const { 1022 dom::HTMLMeterElement* elm = dom::HTMLMeterElement::FromNode(mContent); 1023 if (!elm) { 1024 return -1; 1025 } 1026 double high = elm->High(); 1027 double low = elm->Low(); 1028 double optimum = elm->Optimum(); 1029 double value = elm->Value(); 1030 // For more information on how these regions are defined, see 1031 // "UA requirements for regions of the gauge" 1032 // https://html.spec.whatwg.org/multipage/form-elements.html#the-meter-element 1033 if (optimum > high) { 1034 if (value > high) { 1035 return 1; 1036 } 1037 return value > low ? 0 : -1; 1038 } 1039 if (optimum < low) { 1040 if (value < low) { 1041 return 1; 1042 } 1043 return value < high ? 0 : -1; 1044 } 1045 // optimum is between low and high, inclusive 1046 if (value >= low && value <= high) { 1047 return 1; 1048 } 1049 // Both upper and lower regions are considered equally 1050 // non-optimal. 1051 return 0; 1052 } 1053 1054 void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID, 1055 nsAtom* aAttribute, 1056 AttrModType aModType, 1057 const nsAttrValue* aOldValue, 1058 uint64_t aOldState) { 1059 LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, 1060 aOldValue, aOldState); 1061 1062 if (aAttribute == nsGkAtoms::value) { 1063 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this); 1064 } 1065 1066 if (aAttribute == nsGkAtoms::high || aAttribute == nsGkAtoms::low || 1067 aAttribute == nsGkAtoms::optimum) { 1068 // Our meter's value region may have changed, queue an update for 1069 // the value domain. 1070 mDoc->QueueCacheUpdate(this, CacheDomain::Value); 1071 return; 1072 } 1073 }