diff options
Diffstat (limited to 'accessible/xul/XULFormControlAccessible.cpp')
-rw-r--r-- | accessible/xul/XULFormControlAccessible.cpp | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/accessible/xul/XULFormControlAccessible.cpp b/accessible/xul/XULFormControlAccessible.cpp new file mode 100644 index 0000000000..038f93dd68 --- /dev/null +++ b/accessible/xul/XULFormControlAccessible.cpp @@ -0,0 +1,446 @@ +/* -*- 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 "XULFormControlAccessible.h" + +#include "LocalAccessible-inl.h" +#include "HTMLFormControlAccessible.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "Relation.h" +#include "mozilla/a11y/Role.h" +#include "States.h" +#include "TreeWalker.h" +#include "XULMenuAccessible.h" + +#include "nsIDOMXULButtonElement.h" +#include "nsIDOMXULMenuListElement.h" +#include "nsIDOMXULRadioGroupElement.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsNameSpaceManager.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULButtonAccessible::XULButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + if (ContainsMenu()) { + mGenericTypes |= eMenuButton; + } else { + mGenericTypes |= eButton; + } +} + +XULButtonAccessible::~XULButtonAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible: nsISupports + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible: nsIAccessible + +bool XULButtonAccessible::HasPrimaryAction() const { return true; } + +void XULButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("press"); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible: LocalAccessible + +role XULButtonAccessible::NativeRole() const { + // Buttons can be checked; they simply appear pressed in rather than checked. + // In this case, we must expose them as toggle buttons. + nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement = Elm()->AsXULButton(); + if (xulButtonElement) { + nsAutoString type; + xulButtonElement->GetType(type); + if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) { + return roles::TOGGLE_BUTTON; + } + } + return roles::PUSHBUTTON; +} + +uint64_t XULButtonAccessible::NativeState() const { + // Possible states: focused, focusable, unavailable(disabled). + + // get focus and disable status from base class + uint64_t state = LocalAccessible::NativeState(); + + nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement = Elm()->AsXULButton(); + if (xulButtonElement) { + // Some buttons can have their checked state set without being of type + // checkbox or radio. Expose the pressed state unconditionally. + bool checked = false; + xulButtonElement->GetChecked(&checked); + if (checked) { + state |= states::PRESSED; + } + } + + if (ContainsMenu()) state |= states::HASPOPUP; + + if (mContent->AsElement()->HasAttr(nsGkAtoms::_default)) { + state |= states::DEFAULT; + } + + return state; +} + +bool XULButtonAccessible::AttributeChangesState(nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::checked) { + return true; + } + return AccessibleWrap::AttributeChangesState(aAttribute); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible: Widgets + +bool XULButtonAccessible::IsWidget() const { return true; } + +bool XULButtonAccessible::IsActiveWidget() const { + return FocusMgr()->HasDOMFocus(mContent); +} + +bool XULButtonAccessible::AreItemsOperable() const { + if (IsMenuButton()) { + LocalAccessible* menuPopup = mChildren.SafeElementAt(0, nullptr); + if (menuPopup) { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame()); + return menuPopupFrame->IsOpen(); + } + } + return false; // no items +} + +bool XULButtonAccessible::IsAcceptableChild(nsIContent* aEl) const { + // In general XUL buttons should not have accessible children. However: + return + // menu buttons can have popup accessibles (@type="menu" or + // columnpicker). + aEl->IsXULElement(nsGkAtoms::menupopup) || + // A XUL button can be labelled by a direct child text node, so we need to + // allow that as a child so it will be picked up when computing name from + // subtree. + (aEl->IsText() && aEl->GetParent() == mContent); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULButtonAccessible protected + +bool XULButtonAccessible::ContainsMenu() const { + return mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::menu, eCaseMatters); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULDropmarkerAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULDropmarkerAccessible::XULDropmarkerAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) {} + +bool XULDropmarkerAccessible::HasPrimaryAction() const { return true; } + +bool XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen) const { + bool isOpen = false; + + nsIContent* parent = mContent->GetFlattenedTreeParent(); + + while (parent) { + nsCOMPtr<nsIDOMXULButtonElement> parentButtonElement = + parent->AsElement()->AsXULButton(); + if (parentButtonElement) { + parentButtonElement->GetOpen(&isOpen); + if (aToggleOpen) parentButtonElement->SetOpen(!isOpen); + return isOpen; + } + + nsCOMPtr<nsIDOMXULMenuListElement> parentMenuListElement = + parent->AsElement()->AsXULMenuList(); + if (parentMenuListElement) { + parentMenuListElement->GetOpen(&isOpen); + if (aToggleOpen) parentMenuListElement->SetOpen(!isOpen); + return isOpen; + } + parent = parent->GetFlattenedTreeParent(); + } + + return isOpen; +} + +void XULDropmarkerAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + aName.Truncate(); + if (aIndex == eAction_Click) { + if (DropmarkerOpen(false)) { + aName.AssignLiteral("close"); + } else { + aName.AssignLiteral("open"); + } + } +} + +bool XULDropmarkerAccessible::DoAction(uint8_t index) const { + if (index == eAction_Click) { + DropmarkerOpen(true); // Reverse the open attribute + return true; + } + return false; +} + +role XULDropmarkerAccessible::NativeRole() const { return roles::PUSHBUTTON; } + +uint64_t XULDropmarkerAccessible::NativeState() const { + return DropmarkerOpen(false) ? states::PRESSED : 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULGroupboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULGroupboxAccessible::XULGroupboxAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +role XULGroupboxAccessible::NativeRole() const { return roles::GROUPING; } + +ENameValueFlag XULGroupboxAccessible::NativeName(nsString& aName) const { + // XXX: we use the first related accessible only. + LocalAccessible* label = + RelationByType(RelationType::LABELLED_BY).LocalNext(); + if (label) return label->Name(aName); + + return eNameOK; +} + +Relation XULGroupboxAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + + // The label for xul:groupbox is generated from the first xul:label + if (aType == RelationType::LABELLED_BY && ChildCount() > 0) { + LocalAccessible* childAcc = LocalChildAt(0); + if (childAcc->Role() == roles::LABEL && + childAcc->GetContent()->IsXULElement(nsGkAtoms::label)) { + rel.AppendTarget(childAcc); + } + } + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULRadioButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULRadioButtonAccessible::XULRadioButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : RadioButtonAccessible(aContent, aDoc) {} + +uint64_t XULRadioButtonAccessible::NativeState() const { + uint64_t state = LeafAccessible::NativeState(); + state |= states::CHECKABLE; + + nsCOMPtr<nsIDOMXULSelectControlItemElement> radioButton = + Elm()->AsXULSelectControlItem(); + if (radioButton) { + bool selected = false; // Radio buttons can be selected + radioButton->GetSelected(&selected); + if (selected) { + state |= states::CHECKED; + } + } + + return state; +} + +uint64_t XULRadioButtonAccessible::NativeInteractiveState() const { + return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULRadioButtonAccessible: Widgets + +LocalAccessible* XULRadioButtonAccessible::ContainerWidget() const { + return mParent; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULRadioGroupAccessible +//////////////////////////////////////////////////////////////////////////////// + +/** + * XUL Radio Group + * The Radio Group proxies for the Radio Buttons themselves. The Group gets + * focus whereas the Buttons do not. So we only have an accessible object for + * this for the purpose of getting the proper RadioButton. Need this here to + * avoid circular reference problems when navigating the accessible tree and + * for getting to the radiobuttons. + */ + +XULRadioGroupAccessible::XULRadioGroupAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULSelectControlAccessible(aContent, aDoc) {} + +role XULRadioGroupAccessible::NativeRole() const { return roles::RADIO_GROUP; } + +uint64_t XULRadioGroupAccessible::NativeInteractiveState() const { + // The radio group is not focusable. Sometimes the focus controller will + // report that it is focused. That means that the actual selected radio button + // should be considered focused. + return NativelyUnavailable() ? states::UNAVAILABLE : 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULRadioGroupAccessible: Widgets + +bool XULRadioGroupAccessible::IsWidget() const { return true; } + +bool XULRadioGroupAccessible::IsActiveWidget() const { + return FocusMgr()->HasDOMFocus(mContent); +} + +bool XULRadioGroupAccessible::AreItemsOperable() const { return true; } + +LocalAccessible* XULRadioGroupAccessible::CurrentItem() const { + if (!mSelectControl) { + return nullptr; + } + + RefPtr<dom::Element> currentItemElm; + nsCOMPtr<nsIDOMXULRadioGroupElement> group = + mSelectControl->AsXULRadioGroup(); + if (group) { + group->GetFocusedItem(getter_AddRefs(currentItemElm)); + } + + if (currentItemElm) { + DocAccessible* document = Document(); + if (document) { + return document->GetAccessible(currentItemElm); + } + } + + return nullptr; +} + +void XULRadioGroupAccessible::SetCurrentItem(const LocalAccessible* aItem) { + if (!mSelectControl) { + return; + } + + nsCOMPtr<dom::Element> itemElm = aItem->Elm(); + nsCOMPtr<nsIDOMXULRadioGroupElement> group = + mSelectControl->AsXULRadioGroup(); + if (group) { + group->SetFocusedItem(itemElm); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULStatusBarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULStatusBarAccessible::XULStatusBarAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +role XULStatusBarAccessible::NativeRole() const { return roles::STATUSBAR; } + +//////////////////////////////////////////////////////////////////////////////// +// XULToolbarButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULToolbarButtonAccessible::XULToolbarButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULButtonAccessible(aContent, aDoc) {} + +void XULToolbarButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) { + int32_t setSize = 0; + int32_t posInSet = 0; + + LocalAccessible* parent = LocalParent(); + if (!parent) return; + + uint32_t childCount = parent->ChildCount(); + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { + LocalAccessible* child = parent->LocalChildAt(childIdx); + if (IsSeparator(child)) { // end of a group of buttons + if (posInSet) break; // we've found our group, so we're done + + setSize = 0; // not our group, so start a new group + + } else { + setSize++; // another button in the group + + if (child == this) posInSet = setSize; // we've found our button + } + } + + *aPosInSet = posInSet; + *aSetSize = setSize; +} + +bool XULToolbarButtonAccessible::IsSeparator(LocalAccessible* aAccessible) { + nsIContent* content = aAccessible->GetContent(); + return content && content->IsAnyOfXULElements(nsGkAtoms::toolbarseparator, + nsGkAtoms::toolbarspacer, + nsGkAtoms::toolbarspring); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULToolbarButtonAccessible: Widgets + +bool XULToolbarButtonAccessible::IsAcceptableChild(nsIContent* aEl) const { + return XULButtonAccessible::IsAcceptableChild(aEl) || + // In addition to the children allowed by buttons, toolbarbuttons can + // have labels as children, but only if the label attribute is not + // present. + (aEl->IsXULElement(nsGkAtoms::label) && + !mContent->AsElement()->HasAttr(nsGkAtoms::label)); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULToolbarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULToolbarAccessible::XULToolbarAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +role XULToolbarAccessible::NativeRole() const { return roles::TOOLBAR; } + +ENameValueFlag XULToolbarAccessible::NativeName(nsString& aName) const { + if (mContent->AsElement()->GetAttr(nsGkAtoms::toolbarname, aName)) { + aName.CompressWhitespace(); + } + + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULToolbarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULToolbarSeparatorAccessible::XULToolbarSeparatorAccessible( + nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) {} + +role XULToolbarSeparatorAccessible::NativeRole() const { + return roles::SEPARATOR; +} + +uint64_t XULToolbarSeparatorAccessible::NativeState() const { return 0; } |