/* -*- 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 "nsIEditor.h" #include "nsIFormControl.h" #include "nsIPersistentProperties2.h" #include "nsISelectionController.h" #include "nsIServiceManager.h" #include "nsITextControlElement.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() ? nullptr : 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) { 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)); dom::Element* formElm = formControlNode->GetFormElement(); if (formElm) inputElms = NS_GetContentList(formElm, namespaceId, tagName); else inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); NS_ENSURE_TRUE_VOID(inputElms); 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++; if (inputElm == mContent) indexOf = count; } } *aPosInSet = indexOf; *aSetSize = count; } //////////////////////////////////////////////////////////////////////////////// // 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; } 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 = BindingParent(); 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, NS_LITERAL_STRING("searchbox")); } } // 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 = BindingParent(); 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 = BindingParent(); 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