From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- accessible/html/HTMLFormControlAccessible.cpp | 797 ++++++++++++++++++++++++++ 1 file changed, 797 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..ed0da75622 --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.cpp @@ -0,0 +1,797 @@ +/* -*- 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 "Accessible-inl.h" +#include "nsAccUtils.h" +#include "nsEventShell.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +#include "nsContentList.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLTextAreaElement.h" +#include "nsIFormControl.h" +#include "nsIPersistentProperties2.h" +#include "nsITextControlFrame.h" +#include "nsNameSpaceManager.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/EventStates.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; +} + +nsAtom* HTMLFormAccessible::LandmarkRole() const { + if (!HasOwnContent()) { + return nullptr; + } + + // Only return xml-roles "form" if the form has an accessible name. + nsAutoString name; + const_cast(this)->Name(name); + return name.IsEmpty() ? HyperTextAccessibleWrap::LandmarkRole() + : nsGkAtoms::form; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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::GetPositionAndSizeInternal(int32_t* aPosInSet, + int32_t* aSetSize) { + Unused << ComputeGroupAttributes(aPosInSet, aSetSize); +} + +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(kNameSpaceID_None, nsGkAtoms::type, type); + nsAutoString name; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); + + RefPtr inputElms; + + nsCOMPtr formControlNode(do_QueryInterface(mContent)); + if (dom::Element* formElm = formControlNode->GetFormElement()) { + 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 Accessible::RelationByType(aType); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mGenericTypes |= eButton; +} + +uint8_t HTMLButtonAccessible::ActionCount() const { return 1; } + +void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("press"); +} + +bool HTMLButtonAccessible::DoAction(uint8_t aIndex) const { + if (aIndex != eAction_Click) return false; + + DoCommand(); + return true; +} + +uint64_t HTMLButtonAccessible::State() { + uint64_t state = HyperTextAccessibleWrap::State(); + if (state == states::DEFUNCT) return state; + + // Inherit states from input@type="file" suitable for the button. Note, + // no special processing for unavailable state since inheritance is supplied + // other code paths. + if (mParent && mParent->IsHTMLFileInput()) { + uint64_t parentState = mParent->State(); + state |= parentState & (states::BUSY | states::REQUIRED | states::HASPOPUP | + states::INVALID); + } + + return state; +} + +uint64_t HTMLButtonAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + EventStates elmState = mContent->AsElement()->State(); + if (elmState.HasState(NS_EVENT_STATE_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 = Accessible::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(kNameSpaceID_None, nsGkAtoms::alt, aName)) + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); + + aName.CompressWhitespace(); + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible: Widgets + +bool HTMLButtonAccessible::IsWidget() const { return true; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(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(kNameSpaceID_None, nsGkAtoms::list_)) { + return roles::EDITCOMBOBOX; + } + return roles::ENTRY; +} + +already_AddRefed +HTMLTextFieldAccessible::NativeAttributes() { + nsCOMPtr attributes = + HyperTextAccessibleWrap::NativeAttributes(); + + // Expose type for text input elements as it gives some useful context, + // especially for mobile. + nsAutoString type; + // In the case of this element being part of a binding, the binding's + // parent's type should have precedence. For example an input[type=number] + // has an embedded anonymous input[type=text] (along with spinner buttons). + // In that case, we would want to take the input type from the parent + // and not the anonymous content. + nsIContent* widgetElm = BindingOrWidgetParent(); + if ((widgetElm && widgetElm->AsElement()->GetAttr(kNameSpaceID_None, + nsGkAtoms::type, type)) || + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, + type)) { + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type); + if (!ARIARoleMap() && type.EqualsLiteral("search")) { + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, u"searchbox"_ns); + } + } + + // If this element has the placeholder attribute set, + // and if that is not identical to the name, expose it as an object attribute. + nsAutoString placeholderText; + if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, + placeholderText)) { + nsAutoString name; + const_cast(this)->Name(name); + if (!name.Equals(placeholderText)) { + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::placeholder, + placeholderText); + } + } + + return attributes.forget(); +} + +ENameValueFlag HTMLTextFieldAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty()) return nameFlag; + + // If part of compound of XUL widget then grab a name from XUL widget element. + nsIContent* widgetElm = BindingOrWidgetParent(); + if (widgetElm) XULElmName(mDoc, widgetElm, aName); + + if (!aName.IsEmpty()) return eNameOK; + + // text inputs and textareas might have useful placeholder text + mContent->AsElement()->GetAttr(kNameSpaceID_None, 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); + } +} + +void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const { + HyperTextAccessibleWrap::ApplyARIAState(aState); + aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); + + // If part of compound of XUL widget then pick up ARIA stuff from XUL widget + // element. + nsIContent* widgetElm = BindingOrWidgetParent(); + if (widgetElm) + aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState); +} + +uint64_t HTMLTextFieldAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::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(kNameSpaceID_None, nsGkAtoms::readonly)) { + state |= states::READONLY; + } + + // Is it an or a