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 "XULFormControlAccessible.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "HTMLFormControlAccessible.h" michael@0: #include "nsAccUtils.h" michael@0: #include "nsCoreUtils.h" michael@0: #include "DocAccessible.h" michael@0: #include "nsIAccessibleRelation.h" michael@0: #include "Relation.h" michael@0: #include "Role.h" michael@0: #include "States.h" michael@0: #include "TreeWalker.h" michael@0: #include "XULMenuAccessible.h" michael@0: michael@0: #include "nsIDOMNSEditableElement.h" michael@0: #include "nsIDOMXULButtonElement.h" michael@0: #include "nsIDOMXULCheckboxElement.h" michael@0: #include "nsIDOMXULMenuListElement.h" michael@0: #include "nsIDOMXULSelectCntrlItemEl.h" michael@0: #include "nsIDOMXULTextboxElement.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsITextControlFrame.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: using namespace mozilla::a11y; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: XULButtonAccessible:: michael@0: XULButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: AccessibleWrap(aContent, aDoc) michael@0: { michael@0: if (ContainsMenu()) { michael@0: mGenericTypes |= eMenuButton; michael@0: } else { michael@0: mGenericTypes |= eButton; michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible: nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(XULButtonAccessible, Accessible) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible: nsIAccessible michael@0: michael@0: uint8_t michael@0: XULButtonAccessible::ActionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XULButtonAccessible::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: XULButtonAccessible::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: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible: Accessible michael@0: michael@0: role michael@0: XULButtonAccessible::NativeRole() michael@0: { michael@0: return roles::PUSHBUTTON; michael@0: } michael@0: michael@0: uint64_t michael@0: XULButtonAccessible::NativeState() michael@0: { michael@0: // Possible states: focused, focusable, unavailable(disabled). michael@0: michael@0: // get focus and disable status from base class michael@0: uint64_t state = Accessible::NativeState(); michael@0: michael@0: // Buttons can be checked -- they simply appear pressed in rather than checked michael@0: nsCOMPtr xulButtonElement(do_QueryInterface(mContent)); michael@0: if (xulButtonElement) { michael@0: nsAutoString type; michael@0: xulButtonElement->GetType(type); michael@0: if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) { michael@0: state |= states::CHECKABLE; michael@0: bool checked = false; michael@0: int32_t checkState = 0; michael@0: xulButtonElement->GetChecked(&checked); michael@0: if (checked) { michael@0: state |= states::PRESSED; michael@0: xulButtonElement->GetCheckState(&checkState); michael@0: if (checkState == nsIDOMXULButtonElement::CHECKSTATE_MIXED) { michael@0: state |= states::MIXED; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (ContainsMenu()) michael@0: state |= states::HASPOPUP; michael@0: michael@0: if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::_default)) michael@0: state |= states::DEFAULT; michael@0: michael@0: return state; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible: Widgets michael@0: michael@0: bool michael@0: XULButtonAccessible::IsWidget() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: XULButtonAccessible::IsActiveWidget() const michael@0: { michael@0: return FocusMgr()->HasDOMFocus(mContent); michael@0: } michael@0: michael@0: bool michael@0: XULButtonAccessible::AreItemsOperable() const michael@0: { michael@0: if (IsMenuButton()) { michael@0: Accessible* menuPopup = mChildren.SafeElementAt(0, nullptr); michael@0: if (menuPopup) { michael@0: nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame()); michael@0: return menuPopupFrame->IsOpen(); michael@0: } michael@0: } michael@0: return false; // no items michael@0: } michael@0: michael@0: Accessible* michael@0: XULButtonAccessible::ContainerWidget() const michael@0: { michael@0: if (IsMenuButton() && mParent && mParent->IsAutoComplete()) michael@0: return mParent; michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: XULButtonAccessible::IsAcceptableChild(Accessible* aPossibleChild) const michael@0: { michael@0: // In general XUL button has not accessible children. Nevertheless menu michael@0: // buttons can have button (@type="menu-button") and popup accessibles michael@0: // (@type="menu-button", @type="menu" or columnpicker. michael@0: michael@0: // XXX: no children until the button is menu button. Probably it's not michael@0: // totally correct but in general AT wants to have leaf buttons. michael@0: roles::Role role = aPossibleChild->Role(); michael@0: michael@0: // Get an accessible for menupopup or panel elements. michael@0: if (role == roles::MENUPOPUP) michael@0: return true; michael@0: michael@0: // Button type="menu-button" contains a real button. Get an accessible michael@0: // for it. Ignore dropmarker button which is placed as a last child. michael@0: if (role != roles::PUSHBUTTON || michael@0: aPossibleChild->GetContent()->Tag() == nsGkAtoms::dropMarker) michael@0: return false; michael@0: michael@0: return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::menuButton, eCaseMatters); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULButtonAccessible protected michael@0: michael@0: bool michael@0: XULButtonAccessible::ContainsMenu() michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::menu, &nsGkAtoms::menuButton, nullptr}; michael@0: michael@0: return mContent->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::type, michael@0: strings, eCaseMatters) >= 0; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULDropmarkerAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: XULDropmarkerAccessible:: michael@0: XULDropmarkerAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: LeafAccessible(aContent, aDoc) michael@0: { michael@0: } michael@0: michael@0: uint8_t michael@0: XULDropmarkerAccessible::ActionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: bool michael@0: XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen) michael@0: { michael@0: bool isOpen = false; michael@0: michael@0: nsCOMPtr parentButtonElement = michael@0: do_QueryInterface(mContent->GetFlattenedTreeParent()); michael@0: michael@0: if (parentButtonElement) { michael@0: parentButtonElement->GetOpen(&isOpen); michael@0: if (aToggleOpen) michael@0: parentButtonElement->SetOpen(!isOpen); michael@0: } michael@0: else { michael@0: nsCOMPtr parentMenuListElement = michael@0: do_QueryInterface(parentButtonElement); michael@0: if (parentMenuListElement) { michael@0: parentMenuListElement->GetOpen(&isOpen); michael@0: if (aToggleOpen) michael@0: parentMenuListElement->SetOpen(!isOpen); michael@0: } michael@0: } michael@0: michael@0: return isOpen; michael@0: } michael@0: michael@0: /** michael@0: * Return the name of our only action michael@0: */ michael@0: NS_IMETHODIMP michael@0: XULDropmarkerAccessible::GetActionName(uint8_t aIndex, nsAString& aName) michael@0: { michael@0: if (aIndex == eAction_Click) { michael@0: if (DropmarkerOpen(false)) michael@0: aName.AssignLiteral("close"); michael@0: else michael@0: aName.AssignLiteral("open"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: /** michael@0: * Tell the Dropmarker to do its action michael@0: */ michael@0: NS_IMETHODIMP michael@0: XULDropmarkerAccessible::DoAction(uint8_t index) michael@0: { michael@0: if (index == eAction_Click) { michael@0: DropmarkerOpen(true); // Reverse the open attribute michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: role michael@0: XULDropmarkerAccessible::NativeRole() michael@0: { michael@0: return roles::PUSHBUTTON; michael@0: } michael@0: michael@0: uint64_t michael@0: XULDropmarkerAccessible::NativeState() michael@0: { michael@0: return DropmarkerOpen(false) ? states::PRESSED : 0; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULCheckboxAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: XULCheckboxAccessible:: michael@0: XULCheckboxAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: LeafAccessible(aContent, aDoc) michael@0: { michael@0: } michael@0: michael@0: role michael@0: XULCheckboxAccessible::NativeRole() michael@0: { michael@0: return roles::CHECKBUTTON; michael@0: } michael@0: michael@0: uint8_t michael@0: XULCheckboxAccessible::ActionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: /** michael@0: * Return the name of our only action michael@0: */ michael@0: NS_IMETHODIMP michael@0: XULCheckboxAccessible::GetActionName(uint8_t aIndex, nsAString& aName) michael@0: { michael@0: if (aIndex == eAction_Click) { michael@0: // check or uncheck michael@0: michael@0: if (NativeState() & states::CHECKED) michael@0: aName.AssignLiteral("uncheck"); 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: /** michael@0: * Tell the checkbox to do its only action -- check( or uncheck) itself michael@0: */ michael@0: NS_IMETHODIMP michael@0: XULCheckboxAccessible::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: XULCheckboxAccessible::NativeState() michael@0: { michael@0: // Possible states: focused, focusable, unavailable(disabled), checked michael@0: // Get focus and disable status from base class michael@0: uint64_t state = LeafAccessible::NativeState(); michael@0: michael@0: state |= states::CHECKABLE; michael@0: michael@0: // Determine Checked state michael@0: nsCOMPtr xulCheckboxElement = michael@0: do_QueryInterface(mContent); michael@0: if (xulCheckboxElement) { michael@0: bool checked = false; michael@0: xulCheckboxElement->GetChecked(&checked); michael@0: if (checked) { michael@0: state |= states::CHECKED; michael@0: int32_t checkState = 0; michael@0: xulCheckboxElement->GetCheckState(&checkState); michael@0: if (checkState == nsIDOMXULCheckboxElement::CHECKSTATE_MIXED) michael@0: state |= states::MIXED; michael@0: } michael@0: } michael@0: michael@0: return state; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // XULGroupboxAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: XULGroupboxAccessible:: michael@0: XULGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc) : michael@0: AccessibleWrap(aContent, aDoc) michael@0: { michael@0: } michael@0: michael@0: role michael@0: XULGroupboxAccessible::NativeRole() michael@0: { michael@0: return roles::GROUPING; michael@0: } michael@0: michael@0: ENameValueFlag michael@0: XULGroupboxAccessible::NativeName(nsString& aName) michael@0: { michael@0: // XXX: we use the first related accessible only. michael@0: Accessible* label = michael@0: RelationByType(RelationType::LABELLED_BY).Next(); michael@0: if (label) michael@0: return label->Name(aName); michael@0: michael@0: return eNameOK; michael@0: } michael@0: michael@0: Relation michael@0: XULGroupboxAccessible::RelationByType(RelationType aType) michael@0: { michael@0: Relation rel = AccessibleWrap::RelationByType(aType); michael@0: if (aType != RelationType::LABELLED_BY) michael@0: return rel; michael@0: michael@0: // The label for xul:groupbox is generated from xul:label that is michael@0: // inside the anonymous content of the xul:caption. michael@0: // The xul:label has an accessible object but the xul:caption does not michael@0: uint32_t childCount = ChildCount(); michael@0: for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { michael@0: Accessible* childAcc = GetChildAt(childIdx); michael@0: if (childAcc->Role() == roles::LABEL) { michael@0: // Ensure that it's our label michael@0: Relation reverseRel = childAcc->RelationByType(RelationType::LABEL_FOR); michael@0: Accessible* testGroupbox = nullptr; michael@0: while ((testGroupbox = reverseRel.Next())) michael@0: if (testGroupbox == this) { michael@0: // The