michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "HTMLFormControlAccessible.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "nsAccUtils.h" michael@0: #include "nsEventShell.h" michael@0: #include "nsTextEquivUtils.h" michael@0: #include "Relation.h" michael@0: #include "Role.h" michael@0: #include "States.h" michael@0: michael@0: #include "nsContentList.h" michael@0: #include "mozilla/dom/HTMLInputElement.h" michael@0: #include "nsIAccessibleRelation.h" michael@0: #include "nsIDOMNSEditableElement.h" michael@0: #include "nsIDOMHTMLTextAreaElement.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsIPersistentProperties2.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsITextControlFrame.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "mozilla/dom/ScriptSettings.h" michael@0: michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::a11y; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLCheckboxAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: role michael@0: HTMLCheckboxAccessible::NativeRole() michael@0: { michael@0: return roles::CHECKBUTTON; michael@0: } michael@0: michael@0: uint8_t michael@0: HTMLCheckboxAccessible::ActionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: HTMLCheckboxAccessible::GetActionName(uint8_t aIndex, nsAString& aName) michael@0: { michael@0: if (aIndex == eAction_Click) { // 0 is the magic value for default action michael@0: // cycle, check or uncheck michael@0: uint64_t state = NativeState(); michael@0: michael@0: if (state & states::CHECKED) michael@0: aName.AssignLiteral("uncheck"); michael@0: else if (state & states::MIXED) michael@0: aName.AssignLiteral("cycle"); michael@0: else michael@0: aName.AssignLiteral("check"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: HTMLCheckboxAccessible::DoAction(uint8_t aIndex) michael@0: { michael@0: if (aIndex != 0) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: DoCommand(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t michael@0: HTMLCheckboxAccessible::NativeState() michael@0: { michael@0: uint64_t state = LeafAccessible::NativeState(); michael@0: michael@0: state |= states::CHECKABLE; michael@0: HTMLInputElement* input = HTMLInputElement::FromContent(mContent); michael@0: if (!input) michael@0: return state; michael@0: michael@0: if (input->Indeterminate()) michael@0: return state | states::MIXED; michael@0: michael@0: if (input->Checked()) michael@0: return state | states::CHECKED; michael@0: michael@0: return state; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLCheckboxAccessible: Widgets michael@0: michael@0: bool michael@0: HTMLCheckboxAccessible::IsWidget() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLRadioButtonAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: uint64_t michael@0: HTMLRadioButtonAccessible::NativeState() michael@0: { michael@0: uint64_t state = AccessibleWrap::NativeState(); michael@0: michael@0: state |= states::CHECKABLE; michael@0: michael@0: HTMLInputElement* input = HTMLInputElement::FromContent(mContent); michael@0: if (input && input->Checked()) michael@0: state |= states::CHECKED; michael@0: michael@0: return state; michael@0: } michael@0: michael@0: void michael@0: HTMLRadioButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet, michael@0: int32_t* aSetSize) michael@0: { michael@0: int32_t namespaceId = mContent->NodeInfo()->NamespaceID(); michael@0: nsAutoString tagName; michael@0: mContent->NodeInfo()->GetName(tagName); michael@0: michael@0: nsAutoString type; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); michael@0: nsAutoString name; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); michael@0: michael@0: nsRefPtr inputElms; michael@0: michael@0: nsCOMPtr formControlNode(do_QueryInterface(mContent)); michael@0: dom::Element* formElm = formControlNode->GetFormElement(); michael@0: if (formElm) michael@0: inputElms = NS_GetContentList(formElm, namespaceId, tagName); michael@0: else michael@0: inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); michael@0: NS_ENSURE_TRUE_VOID(inputElms); michael@0: michael@0: uint32_t inputCount = inputElms->Length(false); michael@0: michael@0: // Compute posinset and setsize. michael@0: int32_t indexOf = 0; michael@0: int32_t count = 0; michael@0: michael@0: for (uint32_t index = 0; index < inputCount; index++) { michael@0: nsIContent* inputElm = inputElms->Item(index, false); michael@0: if (inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: type, eCaseMatters) && michael@0: inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, michael@0: name, eCaseMatters) && mDoc->HasAccessible(inputElm)) { michael@0: count++; michael@0: if (inputElm == mContent) michael@0: indexOf = count; michael@0: } michael@0: } michael@0: michael@0: *aPosInSet = indexOf; michael@0: *aSetSize = count; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLButtonAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: HTMLButtonAccessible:: michael@0: HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: HyperTextAccessibleWrap(aContent, aDoc) michael@0: { michael@0: mGenericTypes |= eButton; michael@0: } michael@0: michael@0: uint8_t michael@0: HTMLButtonAccessible::ActionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: HTMLButtonAccessible::GetActionName(uint8_t aIndex, nsAString& aName) michael@0: { michael@0: if (aIndex == eAction_Click) { michael@0: aName.AssignLiteral("press"); michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: HTMLButtonAccessible::DoAction(uint8_t aIndex) michael@0: { michael@0: if (aIndex != eAction_Click) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: DoCommand(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t michael@0: HTMLButtonAccessible::State() michael@0: { michael@0: uint64_t state = HyperTextAccessibleWrap::State(); michael@0: if (state == states::DEFUNCT) michael@0: return state; michael@0: michael@0: // Inherit states from input@type="file" suitable for the button. Note, michael@0: // no special processing for unavailable state since inheritance is supplied michael@0: // other code paths. michael@0: if (mParent && mParent->IsHTMLFileInput()) { michael@0: uint64_t parentState = mParent->State(); michael@0: state |= parentState & (states::BUSY | states::REQUIRED | michael@0: states::HASPOPUP | states::INVALID); michael@0: } michael@0: michael@0: return state; michael@0: } michael@0: michael@0: uint64_t michael@0: HTMLButtonAccessible::NativeState() michael@0: { michael@0: uint64_t state = HyperTextAccessibleWrap::NativeState(); michael@0: michael@0: EventStates elmState = mContent->AsElement()->State(); michael@0: if (elmState.HasState(NS_EVENT_STATE_DEFAULT)) michael@0: state |= states::DEFAULT; michael@0: michael@0: return state; michael@0: } michael@0: michael@0: role michael@0: HTMLButtonAccessible::NativeRole() michael@0: { michael@0: return roles::PUSHBUTTON; michael@0: } michael@0: michael@0: ENameValueFlag michael@0: HTMLButtonAccessible::NativeName(nsString& aName) michael@0: { michael@0: // No need to check @value attribute for buttons since this attribute results michael@0: // in native anonymous text node and the name is calculated from subtree. michael@0: // The same magic works for @alt and @value attributes in case of type="image" michael@0: // element that has no valid @src (note if input@type="image" has an image michael@0: // then neither @alt nor @value attributes are used to generate a visual label michael@0: // and thus we need to obtain the accessible name directly from attribute michael@0: // value). Also the same algorithm works in case of default labels for michael@0: // type="submit"/"reset"/"image" elements. michael@0: michael@0: ENameValueFlag nameFlag = Accessible::NativeName(aName); michael@0: if (!aName.IsEmpty() || mContent->Tag() != nsGkAtoms::input || michael@0: !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::image, eCaseMatters)) michael@0: return nameFlag; michael@0: michael@0: if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName)) michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); michael@0: michael@0: aName.CompressWhitespace(); michael@0: return eNameOK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLButtonAccessible: Widgets michael@0: michael@0: bool michael@0: HTMLButtonAccessible::IsWidget() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HTMLTextFieldAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: HTMLTextFieldAccessible:: michael@0: HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: HyperTextAccessibleWrap(aContent, aDoc) michael@0: { michael@0: mType = eHTMLTextFieldType; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(HTMLTextFieldAccessible, michael@0: Accessible, michael@0: nsIAccessibleText, michael@0: nsIAccessibleEditableText) michael@0: michael@0: role michael@0: HTMLTextFieldAccessible::NativeRole() michael@0: { michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::password, eIgnoreCase)) { michael@0: return roles::PASSWORD_TEXT; michael@0: } michael@0: michael@0: return roles::ENTRY; michael@0: } michael@0: michael@0: already_AddRefed michael@0: HTMLTextFieldAccessible::NativeAttributes() michael@0: { michael@0: nsCOMPtr attributes = michael@0: HyperTextAccessibleWrap::NativeAttributes(); michael@0: michael@0: // Expose type for text input elements as it gives some useful context, michael@0: // especially for mobile. michael@0: nsAutoString type; michael@0: if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type); michael@0: michael@0: return attributes.forget(); michael@0: } michael@0: michael@0: ENameValueFlag michael@0: HTMLTextFieldAccessible::NativeName(nsString& aName) michael@0: { michael@0: ENameValueFlag nameFlag = Accessible::NativeName(aName); michael@0: if (!aName.IsEmpty()) michael@0: return nameFlag; michael@0: michael@0: // If part of compound of XUL widget then grab a name from XUL widget element. michael@0: nsIContent* widgetElm = XULWidgetElm(); michael@0: if (widgetElm) michael@0: XULElmName(mDoc, widgetElm, aName); michael@0: michael@0: if (!aName.IsEmpty()) michael@0: return eNameOK; michael@0: michael@0: // text inputs and textareas might have useful placeholder text michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName); michael@0: return eNameOK; michael@0: } michael@0: michael@0: void michael@0: HTMLTextFieldAccessible::Value(nsString& aValue) michael@0: { michael@0: aValue.Truncate(); michael@0: if (NativeState() & states::PROTECTED) // Don't return password text! michael@0: return; michael@0: michael@0: nsCOMPtr textArea(do_QueryInterface(mContent)); michael@0: if (textArea) { michael@0: textArea->GetValue(aValue); michael@0: return; michael@0: } michael@0: michael@0: HTMLInputElement* input = HTMLInputElement::FromContent(mContent); michael@0: if (input) michael@0: input->GetValue(aValue); michael@0: } michael@0: michael@0: void michael@0: HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const michael@0: { michael@0: HyperTextAccessibleWrap::ApplyARIAState(aState); michael@0: aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); michael@0: michael@0: // If part of compound of XUL widget then pick up ARIA stuff from XUL widget michael@0: // element. michael@0: nsIContent* widgetElm = XULWidgetElm(); michael@0: if (widgetElm) michael@0: aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState); michael@0: } michael@0: michael@0: uint64_t michael@0: HTMLTextFieldAccessible::NativeState() michael@0: { michael@0: uint64_t state = HyperTextAccessibleWrap::NativeState(); michael@0: michael@0: // Text fields are always editable, even if they are also read only or michael@0: // disabled. michael@0: state |= states::EDITABLE; michael@0: michael@0: // can be focusable, focused, protected. readonly, unavailable, selected michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::password, eIgnoreCase)) { michael@0: state |= states::PROTECTED; michael@0: } michael@0: michael@0: if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) { michael@0: state |= states::READONLY; michael@0: } michael@0: michael@0: // Is it an or a