/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Author: Eric Vaughan (evaughan@netscape.com) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsHTMLFormControlAccessible.h" #include "nsAccUtils.h" #include "nsTextEquivUtils.h" #include "Relation.h" #include "Role.h" #include "States.h" #include "nsIAccessibleRelation.h" #include "nsIDOMDocument.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMNSEditableElement.h" #include "nsIDOMHTMLFormElement.h" #include "nsIDOMHTMLLegendElement.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsIDOMNodeList.h" #include "nsIEditor.h" #include "nsIFrame.h" #include "nsINameSpaceManager.h" #include "nsISelectionController.h" #include "jsapi.h" #include "nsIJSContextStack.h" #include "nsIServiceManager.h" #include "nsITextControlFrame.h" using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // nsHTMLCheckboxAccessible //////////////////////////////////////////////////////////////////////////////// nsHTMLCheckboxAccessible:: nsHTMLCheckboxAccessible(nsIContent* aContent, nsDocAccessible* aDoc) : nsFormControlAccessible(aContent, aDoc) { } role nsHTMLCheckboxAccessible::NativeRole() { return roles::CHECKBUTTON; } PRUint8 nsHTMLCheckboxAccessible::ActionCount() { return 1; } NS_IMETHODIMP nsHTMLCheckboxAccessible::GetActionName(PRUint8 aIndex, nsAString& aName) { if (aIndex == eAction_Click) { // 0 is the magic value for default action // cycle, check or uncheck PRUint64 state = NativeState(); if (state & states::CHECKED) aName.AssignLiteral("uncheck"); else if (state & states::MIXED) aName.AssignLiteral("cycle"); else aName.AssignLiteral("check"); return NS_OK; } return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsHTMLCheckboxAccessible::DoAction(PRUint8 aIndex) { if (aIndex != 0) return NS_ERROR_INVALID_ARG; DoCommand(); return NS_OK; } PRUint64 nsHTMLCheckboxAccessible::NativeState() { PRUint64 state = nsFormControlAccessible::NativeState(); state |= states::CHECKABLE; bool checkState = false; // Radio buttons and check boxes can be checked or mixed nsCOMPtr htmlCheckboxElement = do_QueryInterface(mContent); if (htmlCheckboxElement) { htmlCheckboxElement->GetIndeterminate(&checkState); if (checkState) { state |= states::MIXED; } else { // indeterminate can't be checked at the same time. htmlCheckboxElement->GetChecked(&checkState); if (checkState) state |= states::CHECKED; } } return state; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLCheckboxAccessible: Widgets bool nsHTMLCheckboxAccessible::IsWidget() const { return true; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLRadioButtonAccessible //////////////////////////////////////////////////////////////////////////////// nsHTMLRadioButtonAccessible:: nsHTMLRadioButtonAccessible(nsIContent* aContent, nsDocAccessible* aDoc) : nsRadioButtonAccessible(aContent, aDoc) { } PRUint64 nsHTMLRadioButtonAccessible::NativeState() { PRUint64 state = nsAccessibleWrap::NativeState(); state |= states::CHECKABLE; bool checked = false; // Radio buttons and check boxes can be checked nsCOMPtr htmlRadioElement = do_QueryInterface(mContent); if (htmlRadioElement) htmlRadioElement->GetChecked(&checked); if (checked) state |= states::CHECKED; return state; } void nsHTMLRadioButtonAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet, PRInt32 *aSetSize) { nsAutoString nsURI; mContent->NodeInfo()->GetNamespaceURI(nsURI); nsAutoString tagName; mContent->NodeInfo()->GetName(tagName); nsAutoString type; mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); nsAutoString name; mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); nsCOMPtr inputs; nsCOMPtr radio(do_QueryInterface(mContent)); nsCOMPtr form; radio->GetForm(getter_AddRefs(form)); if (form) { form->GetElementsByTagNameNS(nsURI, tagName, getter_AddRefs(inputs)); } else { nsIDocument* doc = mContent->OwnerDoc(); nsCOMPtr document(do_QueryInterface(doc)); if (document) document->GetElementsByTagNameNS(nsURI, tagName, getter_AddRefs(inputs)); } NS_ENSURE_TRUE(inputs, ); PRUint32 inputsCount = 0; inputs->GetLength(&inputsCount); // Compute posinset and setsize. PRInt32 indexOf = 0; PRInt32 count = 0; for (PRUint32 index = 0; index < inputsCount; index++) { nsCOMPtr itemNode; inputs->Item(index, getter_AddRefs(itemNode)); nsCOMPtr item(do_QueryInterface(itemNode)); if (item && item->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, type, eCaseMatters) && item->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, name, eCaseMatters)) { count++; if (item == mContent) indexOf = count; } } *aPosInSet = indexOf; *aSetSize = count; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLButtonAccessible //////////////////////////////////////////////////////////////////////////////// nsHTMLButtonAccessible:: nsHTMLButtonAccessible(nsIContent* aContent, nsDocAccessible* aDoc) : nsHyperTextAccessibleWrap(aContent, aDoc) { } PRUint8 nsHTMLButtonAccessible::ActionCount() { return 1; } NS_IMETHODIMP nsHTMLButtonAccessible::GetActionName(PRUint8 aIndex, nsAString& aName) { if (aIndex == eAction_Click) { aName.AssignLiteral("press"); return NS_OK; } return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsHTMLButtonAccessible::DoAction(PRUint8 aIndex) { if (aIndex != eAction_Click) return NS_ERROR_INVALID_ARG; DoCommand(); return NS_OK; } PRUint64 nsHTMLButtonAccessible::State() { PRUint64 state = nsHyperTextAccessibleWrap::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()) { PRUint64 parentState = mParent->State(); state |= parentState & (states::BUSY | states::REQUIRED | states::HASPOPUP | states::INVALID); } return state; } PRUint64 nsHTMLButtonAccessible::NativeState() { PRUint64 state = nsHyperTextAccessibleWrap::NativeState(); nsEventStates elmState = mContent->AsElement()->State(); if (elmState.HasState(NS_EVENT_STATE_DEFAULT)) state |= states::DEFAULT; return state; } role nsHTMLButtonAccessible::NativeRole() { return roles::PUSHBUTTON; } nsresult nsHTMLButtonAccessible::GetNameInternal(nsAString& aName) { nsAccessible::GetNameInternal(aName); if (!aName.IsEmpty() || mContent->Tag() != nsGkAtoms::input) return NS_OK; // No name from HTML or ARIA nsAutoString name; if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, name) && !mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, name)) { // Use the button's (default) label if nothing else works nsIFrame* frame = GetFrame(); if (frame) { nsIFormControlFrame* fcFrame = do_QueryFrame(frame); if (fcFrame) fcFrame->GetFormProperty(nsGkAtoms::defaultLabel, name); } } if (name.IsEmpty() && !mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, name)) { mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, name); } name.CompressWhitespace(); aName = name; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLButtonAccessible: Widgets bool nsHTMLButtonAccessible::IsWidget() const { return true; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLTextFieldAccessible //////////////////////////////////////////////////////////////////////////////// nsHTMLTextFieldAccessible:: nsHTMLTextFieldAccessible(nsIContent* aContent, nsDocAccessible* aDoc) : nsHyperTextAccessibleWrap(aContent, aDoc) { } NS_IMPL_ISUPPORTS_INHERITED3(nsHTMLTextFieldAccessible, nsAccessible, nsHyperTextAccessible, nsIAccessibleText, nsIAccessibleEditableText) role nsHTMLTextFieldAccessible::NativeRole() { if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::password, eIgnoreCase)) { return roles::PASSWORD_TEXT; } return roles::ENTRY; } nsresult nsHTMLTextFieldAccessible::GetNameInternal(nsAString& aName) { nsresult rv = nsAccessible::GetNameInternal(aName); NS_ENSURE_SUCCESS(rv, rv); if (!aName.IsEmpty()) return NS_OK; if (mContent->GetBindingParent()) { // XXX: bug 459640 // There's a binding parent. // This means we're part of another control, so use parent accessible for name. // This ensures that a textbox inside of a XUL widget gets // an accessible name. nsAccessible* parent = Parent(); if (parent) parent->GetName(aName); } if (!aName.IsEmpty()) return NS_OK; // text inputs and textareas might have useful placeholder text mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName); return NS_OK; } NS_IMETHODIMP nsHTMLTextFieldAccessible::GetValue(nsAString& _retval) { if (IsDefunct()) return NS_ERROR_FAILURE; if (NativeState() & states::PROTECTED) // Don't return password text! return NS_ERROR_FAILURE; nsCOMPtr textArea(do_QueryInterface(mContent)); if (textArea) { return textArea->GetValue(_retval); } nsCOMPtr inputElement(do_QueryInterface(mContent)); if (inputElement) { return inputElement->GetValue(_retval); } return NS_ERROR_FAILURE; } void nsHTMLTextFieldAccessible::ApplyARIAState(PRUint64* aState) { nsHyperTextAccessibleWrap::ApplyARIAState(aState); nsStateMapEntry::MapToStates(mContent, aState, eARIAAutoComplete); } PRUint64 nsHTMLTextFieldAccessible::State() { PRUint64 state = nsHyperTextAccessibleWrap::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 // by other code paths. if (mParent && mParent->IsHTMLFileInput()) { PRUint64 parentState = mParent->State(); state |= parentState & (states::BUSY | states::REQUIRED | states::HASPOPUP | states::INVALID); } return state; } PRUint64 nsHTMLTextFieldAccessible::NativeState() { PRUint64 state = nsHyperTextAccessibleWrap::NativeState(); // 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