/* -*- 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 "nsIAccessibleRelation.h" #include "nsIDOMNSEditableElement.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsIEditor.h" #include "nsIFormControl.h" #include "nsIPersistentProperties2.h" #include "nsISelectionController.h" #include "nsIServiceManager.h" #include "nsITextControlFrame.h" #include "nsNameSpaceManager.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/EventStates.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Preferences.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // HTMLCheckboxAccessible //////////////////////////////////////////////////////////////////////////////// role HTMLCheckboxAccessible::NativeRole() { return roles::CHECKBUTTON; } uint8_t HTMLCheckboxAccessible::ActionCount() { return 1; } void HTMLCheckboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { if (aIndex == eAction_Click) { // 0 is the magic value for default action uint64_t state = NativeState(); if (state & states::CHECKED) aName.AssignLiteral("uncheck"); else if (state & states::MIXED) aName.AssignLiteral("cycle"); else aName.AssignLiteral("check"); } } bool HTMLCheckboxAccessible::DoAction(uint8_t aIndex) { if (aIndex != 0) return false; DoCommand(); return true; } uint64_t HTMLCheckboxAccessible::NativeState() { uint64_t state = LeafAccessible::NativeState(); state |= states::CHECKABLE; HTMLInputElement* input = HTMLInputElement::FromContent(mContent); if (!input) return state; if (input->Indeterminate()) return state | states::MIXED; if (input->Checked()) return state | states::CHECKED; return state; } //////////////////////////////////////////////////////////////////////////////// // HTMLCheckboxAccessible: Widgets bool HTMLCheckboxAccessible::IsWidget() const { return true; } //////////////////////////////////////////////////////////////////////////////// // HTMLRadioButtonAccessible //////////////////////////////////////////////////////////////////////////////// uint64_t HTMLRadioButtonAccessible::NativeState() { uint64_t state = AccessibleWrap::NativeState(); state |= states::CHECKABLE; HTMLInputElement* input = HTMLInputElement::FromContent(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->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); nsAutoString name; mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); nsRefPtr 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->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, type, eCaseMatters) && inputElm->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() { return 1; } void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { if (aIndex == eAction_Click) aName.AssignLiteral("press"); } bool HTMLButtonAccessible::DoAction(uint8_t aIndex) { 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() { 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() { return roles::PUSHBUTTON; } ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) { // 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->Tag() != nsGkAtoms::input || !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image, eCaseMatters)) return nameFlag; if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName)) mContent->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 = eHTMLTextFieldType; } NS_IMPL_ISUPPORTS_INHERITED(HTMLTextFieldAccessible, Accessible, nsIAccessibleText, nsIAccessibleEditableText) role HTMLTextFieldAccessible::NativeRole() { if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::password, eIgnoreCase)) { 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; if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type); return attributes.forget(); } ENameValueFlag HTMLTextFieldAccessible::NativeName(nsString& aName) { 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 = XULWidgetElm(); if (widgetElm) XULElmName(mDoc, widgetElm, aName); if (!aName.IsEmpty()) return eNameOK; // text inputs and textareas might have useful placeholder text mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName); return eNameOK; } void HTMLTextFieldAccessible::Value(nsString& aValue) { aValue.Truncate(); if (NativeState() & states::PROTECTED) // Don't return password text! return; nsCOMPtr textArea(do_QueryInterface(mContent)); if (textArea) { textArea->GetValue(aValue); return; } HTMLInputElement* input = HTMLInputElement::FromContent(mContent); if (input) input->GetValue(aValue); } 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 = XULWidgetElm(); if (widgetElm) aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState); } uint64_t HTMLTextFieldAccessible::NativeState() { 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->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::password, eIgnoreCase)) { state |= states::PROTECTED; } if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) { state |= states::READONLY; } // Is it an or a