From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- accessible/html/HTMLFormControlAccessible.cpp | 979 ++++++++++++++++++++++++++ 1 file changed, 979 insertions(+) create mode 100644 accessible/html/HTMLFormControlAccessible.cpp (limited to 'accessible/html/HTMLFormControlAccessible.cpp') diff --git a/accessible/html/HTMLFormControlAccessible.cpp b/accessible/html/HTMLFormControlAccessible.cpp new file mode 100644 index 0000000000..95d6fed7b3 --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.cpp @@ -0,0 +1,979 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HTMLFormControlAccessible.h" + +#include "CacheConstants.h" +#include "DocAccessible-inl.h" +#include "LocalAccessible-inl.h" +#include "nsAccUtils.h" +#include "nsEventShell.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "mozilla/a11y/Role.h" +#include "States.h" +#include "TextLeafAccessible.h" + +#include "nsContentList.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLTextAreaElement.h" +#include "mozilla/dom/HTMLFormControlsCollection.h" +#include "nsIFormControl.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEditor.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFormAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLFormAccessible::NativeRole() const { + nsAutoString name; + const_cast(this)->Name(name); + return name.IsEmpty() ? roles::FORM : roles::FORM_LANDMARK; +} + +void HTMLFormAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, + aOldValue, aOldState); + if (aAttribute == nsGkAtoms::autocomplete) { + dom::HTMLFormElement* formEl = dom::HTMLFormElement::FromNode(mContent); + + HTMLFormControlsCollection* controls = formEl->Elements(); + uint32_t length = controls->Length(); + for (uint32_t i = 0; i < length; i++) { + if (LocalAccessible* acc = mDoc->GetAccessible(controls->Item(i))) { + if (acc->IsTextField() && !acc->IsPassword()) { + if (!acc->Elm()->HasAttr(nsGkAtoms::list_) && + !acc->Elm()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::autocomplete, nsGkAtoms::OFF, + eIgnoreCase)) { + RefPtr stateChangeEvent = + new AccStateChangeEvent(acc, states::SUPPORTS_AUTOCOMPLETION); + mDoc->FireDelayedEvent(stateChangeEvent); + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLRadioButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +uint64_t HTMLRadioButtonAccessible::NativeState() const { + uint64_t state = AccessibleWrap::NativeState(); + + state |= states::CHECKABLE; + + HTMLInputElement* input = HTMLInputElement::FromNode(mContent); + if (input && input->Checked()) state |= states::CHECKED; + + return state; +} + +void HTMLRadioButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) { + Unused << ComputeGroupAttributes(aPosInSet, aSetSize); +} + +void HTMLRadioButtonAccessible::DOMAttributeChanged( + int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue, uint64_t aOldState) { + if (aAttribute == nsGkAtoms::name) { + // If our name changed, it's possible our MEMBER_OF relation + // also changed. Push a cache update for Relations. + mDoc->QueueCacheUpdate(this, CacheDomain::Relations); + } else { + // Otherwise, handle this attribute change the way our parent + // class wants us to handle it. + RadioButtonAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + } +} + +Relation HTMLRadioButtonAccessible::ComputeGroupAttributes( + int32_t* aPosInSet, int32_t* aSetSize) const { + Relation rel = Relation(); + int32_t namespaceId = mContent->NodeInfo()->NamespaceID(); + nsAutoString tagName; + mContent->NodeInfo()->GetName(tagName); + + nsAutoString type; + mContent->AsElement()->GetAttr(nsGkAtoms::type, type); + nsAutoString name; + mContent->AsElement()->GetAttr(nsGkAtoms::name, name); + + RefPtr inputElms; + + nsCOMPtr formControlNode(do_QueryInterface(mContent)); + if (dom::Element* formElm = formControlNode->GetForm()) { + inputElms = NS_GetContentList(formElm, namespaceId, tagName); + } else { + inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); + } + NS_ENSURE_TRUE(inputElms, rel); + + uint32_t inputCount = inputElms->Length(false); + + // Compute posinset and setsize. + int32_t indexOf = 0; + int32_t count = 0; + + for (uint32_t index = 0; index < inputCount; index++) { + nsIContent* inputElm = inputElms->Item(index, false); + if (inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + type, eCaseMatters) && + inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, + name, eCaseMatters) && + mDoc->HasAccessible(inputElm)) { + count++; + rel.AppendTarget(mDoc->GetAccessible(inputElm)); + if (inputElm == mContent) indexOf = count; + } + } + + *aPosInSet = indexOf; + *aSetSize = count; + return rel; +} + +Relation HTMLRadioButtonAccessible::RelationByType(RelationType aType) const { + if (aType == RelationType::MEMBER_OF) { + int32_t unusedPos, unusedSetSize; + return ComputeGroupAttributes(&unusedPos, &unusedSetSize); + } + + return LocalAccessible::RelationByType(aType); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessible(aContent, aDoc) { + mGenericTypes |= eButton; +} + +bool HTMLButtonAccessible::HasPrimaryAction() const { return true; } + +void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("press"); +} + +uint64_t HTMLButtonAccessible::NativeState() const { + uint64_t state = HyperTextAccessible::NativeState(); + + dom::Element* elm = Elm(); + if (auto* popover = elm->GetEffectivePopoverTargetElement()) { + LocalAccessible* popoverAcc = mDoc->GetAccessible(popover); + if (!popoverAcc || !popoverAcc->IsAncestorOf(this)) { + if (popover->IsPopoverOpen()) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + } + } + + ElementState elmState = mContent->AsElement()->State(); + if (elmState.HasState(ElementState::DEFAULT)) state |= states::DEFAULT; + + return state; +} + +role HTMLButtonAccessible::NativeRole() const { return roles::PUSHBUTTON; } + +ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const { + // No need to check @value attribute for buttons since this attribute results + // in native anonymous text node and the name is calculated from subtree. + // The same magic works for @alt and @value attributes in case of type="image" + // element that has no valid @src (note if input@type="image" has an image + // then neither @alt nor @value attributes are used to generate a visual label + // and thus we need to obtain the accessible name directly from attribute + // value). Also the same algorithm works in case of default labels for + // type="submit"/"reset"/"image" elements. + + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) || + !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::image, eCaseMatters)) { + return nameFlag; + } + + if (!mContent->AsElement()->GetAttr(nsGkAtoms::alt, aName)) { + mContent->AsElement()->GetAttr(nsGkAtoms::value, aName); + } + + aName.CompressWhitespace(); + return eNameOK; +} + +void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, + aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::value) { + dom::Element* elm = Elm(); + if (elm->IsHTMLElement(nsGkAtoms::input) || + (elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image, + eCaseMatters) && + !elm->HasAttr(nsGkAtoms::alt))) { + if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) && + !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible: Widgets + +bool HTMLButtonAccessible::IsWidget() const { return true; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessible(aContent, aDoc) { + mType = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase) + ? eHTMLTextPasswordFieldType + : eHTMLTextFieldType; +} + +role HTMLTextFieldAccessible::NativeRole() const { + if (mType == eHTMLTextPasswordFieldType) { + return roles::PASSWORD_TEXT; + } + if (mContent->AsElement()->HasAttr(nsGkAtoms::list_)) { + return roles::EDITCOMBOBOX; + } + return roles::ENTRY; +} + +already_AddRefed HTMLTextFieldAccessible::NativeAttributes() { + RefPtr attributes = HyperTextAccessible::NativeAttributes(); + + // Expose type for text input elements as it gives some useful context, + // especially for mobile. + if (const nsAttrValue* attr = + mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) { + RefPtr inputType = attr->GetAsAtom(); + if (inputType) { + if (!ARIARoleMap() && inputType == nsGkAtoms::search) { + attributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::searchbox); + } + attributes->SetAttribute(nsGkAtoms::textInputType, inputType); + } + } + // If this element has the placeholder attribute set, + // and if that is not identical to the name, expose it as an object attribute. + nsString placeholderText; + if (mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholderText)) { + nsAutoString name; + const_cast(this)->Name(name); + if (!name.Equals(placeholderText)) { + attributes->SetAttribute(nsGkAtoms::placeholder, + std::move(placeholderText)); + } + } + + return attributes.forget(); +} + +ENameValueFlag HTMLTextFieldAccessible::Name(nsString& aName) const { + ENameValueFlag nameFlag = LocalAccessible::Name(aName); + if (!aName.IsEmpty()) return nameFlag; + + // text inputs and textareas might have useful placeholder text + mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, aName); + return eNameOK; +} + +void HTMLTextFieldAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + if (NativeState() & states::PROTECTED) { // Don't return password text! + return; + } + + HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent); + if (textArea) { + textArea->GetValue(aValue); + return; + } + + HTMLInputElement* input = HTMLInputElement::FromNode(mContent); + if (input) { + // Pass NonSystem as the caller type, to be safe. We don't expect to have a + // file input here. + input->GetValue(aValue, CallerType::NonSystem); + } +} + +bool HTMLTextFieldAccessible::AttributeChangesState(nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::list_ || + aAttribute == nsGkAtoms::autocomplete) { + return true; + } + + return LocalAccessible::AttributeChangesState(aAttribute); +} + +void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const { + HyperTextAccessible::ApplyARIAState(aState); + aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); +} + +uint64_t HTMLTextFieldAccessible::NativeState() const { + uint64_t state = HyperTextAccessible::NativeState(); + + // Text fields are always editable, even if they are also read only or + // disabled. + state |= states::EDITABLE; + + // can be focusable, focused, protected. readonly, unavailable, selected + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase)) { + state |= states::PROTECTED; + } + + if (mContent->AsElement()->HasAttr(nsGkAtoms::readonly)) { + state |= states::READONLY; + } + + // Is it an or a