diff options
Diffstat (limited to 'accessible/xul')
21 files changed, 5198 insertions, 0 deletions
diff --git a/accessible/xul/XULAlertAccessible.cpp b/accessible/xul/XULAlertAccessible.cpp new file mode 100644 index 0000000000..73d0c69752 --- /dev/null +++ b/accessible/xul/XULAlertAccessible.cpp @@ -0,0 +1,44 @@ +/* -*- 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 "XULAlertAccessible.h" + +#include "LocalAccessible-inl.h" +#include "Role.h" +#include "States.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULAlertAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULAlertAccessible::XULAlertAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mGenericTypes |= eAlert; +} + +XULAlertAccessible::~XULAlertAccessible() {} + +role XULAlertAccessible::NativeRole() const { return roles::ALERT; } + +uint64_t XULAlertAccessible::NativeState() const { + return LocalAccessible::NativeState() | states::ALERT; +} + +ENameValueFlag XULAlertAccessible::Name(nsString& aName) const { + // Screen readers need to read contents of alert, not the accessible name. + // If we have both some screen readers will read the alert twice. + aName.Truncate(); + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// Widgets + +bool XULAlertAccessible::IsWidget() const { return true; } + +LocalAccessible* XULAlertAccessible::ContainerWidget() const { return nullptr; } diff --git a/accessible/xul/XULAlertAccessible.h b/accessible/xul/XULAlertAccessible.h new file mode 100644 index 0000000000..12594e987d --- /dev/null +++ b/accessible/xul/XULAlertAccessible.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULAlertAccessible_h__ +#define mozilla_a11y_XULAlertAccessible_h__ + +#include "AccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +/** + * LocalAccessible for supporting XUL alerts. + */ + +class XULAlertAccessible : public AccessibleWrap { + public: + XULAlertAccessible(nsIContent* aContent, DocAccessible* aDoc); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(XULAlertAccessible, AccessibleWrap) + + // LocalAccessible + virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // Widgets + virtual bool IsWidget() const override; + virtual LocalAccessible* ContainerWidget() const override; + + protected: + ~XULAlertAccessible(); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULComboboxAccessible.cpp b/accessible/xul/XULComboboxAccessible.cpp new file mode 100644 index 0000000000..a020e6f3cb --- /dev/null +++ b/accessible/xul/XULComboboxAccessible.cpp @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; 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 "XULComboboxAccessible.h" + +#include "LocalAccessible-inl.h" +#include "nsAccessibilityService.h" +#include "DocAccessible.h" +#include "nsCoreUtils.h" +#include "Role.h" +#include "States.h" + +#include "mozilla/dom/Element.h" +#include "nsIDOMXULMenuListElement.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULComboboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULComboboxAccessible::XULComboboxAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mGenericTypes |= eCombobox; +} + +role XULComboboxAccessible::NativeRole() const { return roles::COMBOBOX; } + +uint64_t XULComboboxAccessible::NativeState() const { + // As a nsComboboxAccessible we can have the following states: + // STATE_FOCUSED + // STATE_FOCUSABLE + // STATE_HASPOPUP + // STATE_EXPANDED + // STATE_COLLAPSED + + // Get focus status from base class + uint64_t state = LocalAccessible::NativeState(); + + nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); + if (menuList) { + bool isOpen = false; + menuList->GetOpen(&isOpen); + if (isOpen) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + } + + return state | states::HASPOPUP; +} + +bool XULComboboxAccessible::IsAcceptableChild(nsIContent* aContent) const { + return AccessibleWrap::IsAcceptableChild(aContent) && !aContent->IsText(); +} + +void XULComboboxAccessible::Description(nsString& aDescription) const { + aDescription.Truncate(); + // Use description of currently focused option + nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = Elm()->AsXULMenuList(); + if (!menuListElm) return; + + nsCOMPtr<dom::Element> focusedOptionItem; + menuListElm->GetSelectedItem(getter_AddRefs(focusedOptionItem)); + if (focusedOptionItem && mDoc) { + LocalAccessible* focusedOptionAcc = mDoc->GetAccessible(focusedOptionItem); + if (focusedOptionAcc) focusedOptionAcc->Description(aDescription); + } +} + +void XULComboboxAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + + // The value is the option or text shown entered in the combobox. + nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); + if (menuList) menuList->GetLabel(aValue); +} + +bool XULComboboxAccessible::HasPrimaryAction() const { return true; } + +bool XULComboboxAccessible::DoAction(uint8_t aIndex) const { + if (aIndex != XULComboboxAccessible::eAction_Click) return false; + + // Programmaticaly toggle the combo box. + nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); + if (!menuList) return false; + + bool isDroppedDown = false; + menuList->GetOpen(&isDroppedDown); + menuList->SetOpen(!isDroppedDown); + return true; +} + +void XULComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + aName.Truncate(); + if (aIndex != XULComboboxAccessible::eAction_Click) return; + + nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); + if (!menuList) return; + + bool isDroppedDown = false; + menuList->GetOpen(&isDroppedDown); + if (isDroppedDown) { + aName.AssignLiteral("close"); + } else { + aName.AssignLiteral("open"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Widgets + +bool XULComboboxAccessible::IsActiveWidget() const { + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, + nsGkAtoms::_true, eIgnoreCase)) { + int32_t childCount = mChildren.Length(); + for (int32_t idx = 0; idx < childCount; idx++) { + LocalAccessible* child = mChildren[idx]; + if (child->Role() == roles::ENTRY) { + return FocusMgr()->HasDOMFocus(child->GetContent()); + } + } + return false; + } + + return FocusMgr()->HasDOMFocus(mContent); +} + +bool XULComboboxAccessible::AreItemsOperable() const { + nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = Elm()->AsXULMenuList(); + if (menuListElm) { + bool isOpen = false; + menuListElm->GetOpen(&isOpen); + return isOpen; + } + + return false; +} diff --git a/accessible/xul/XULComboboxAccessible.h b/accessible/xul/XULComboboxAccessible.h new file mode 100644 index 0000000000..92f909690e --- /dev/null +++ b/accessible/xul/XULComboboxAccessible.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef mozilla_a11y_XULComboboxAccessible_h__ +#define mozilla_a11y_XULComboboxAccessible_h__ + +#include "XULMenuAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for XUL comboboxes like xul:menulist and autocomplete textbox. + */ +class XULComboboxAccessible : public AccessibleWrap { + public: + enum { eAction_Click = 0 }; + + XULComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + void Description(nsString& aDescription) const override; + void Value(nsString& aValue) const override; + a11y::role NativeRole() const override; + uint64_t NativeState() const override; + bool IsAcceptableChild(nsIContent*) const override; + + // ActionAccessible + bool HasPrimaryAction() const override; + void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + bool DoAction(uint8_t aIndex) const override; + + // Widgets + bool IsActiveWidget() const override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY bool AreItemsOperable() const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULElementAccessibles.cpp b/accessible/xul/XULElementAccessibles.cpp new file mode 100644 index 0000000000..8c257ea20d --- /dev/null +++ b/accessible/xul/XULElementAccessibles.cpp @@ -0,0 +1,205 @@ +/* -*- 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 "XULElementAccessibles.h" + +#include "LocalAccessible-inl.h" +#include "BaseAccessibles.h" +#include "DocAccessible-inl.h" +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" +#include "TextUpdater.h" + +#ifdef A11Y_LOG +# include "Logging.h" +#endif + +#include "nsNameSpaceManager.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsXULElement.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULLabelAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULLabelAccessible::XULLabelAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eXULLabelType; +} + +void XULLabelAccessible::Shutdown() { + mValueTextLeaf = nullptr; + HyperTextAccessibleWrap::Shutdown(); +} + +void XULLabelAccessible::DispatchClickEvent(nsIContent* aContent, + uint32_t aActionIndex) const { + // Bug 1578140: For labels inside buttons, The base implementation of + // DispatchClickEvent doesn't fire a command event on the button. + RefPtr<nsXULElement> el = nsXULElement::FromNodeOrNull(aContent); + if (el) { + el->Click(mozilla::dom::CallerType::System); + } +} + +ENameValueFlag XULLabelAccessible::NativeName(nsString& aName) const { + // if the value attr doesn't exist, the screen reader must get the accessible + // text from the accessible text interface or from the children + if (mValueTextLeaf) return mValueTextLeaf->Name(aName); + + return LocalAccessible::NativeName(aName); +} + +role XULLabelAccessible::NativeRole() const { return roles::LABEL; } + +uint64_t XULLabelAccessible::NativeState() const { + // Labels and description have read only state + // They are not focusable or selectable + return HyperTextAccessibleWrap::NativeState() | states::READONLY; +} + +Relation XULLabelAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + + // The label for xul:groupbox is generated from the first xul:label + if (aType == RelationType::LABEL_FOR) { + LocalAccessible* parent = LocalParent(); + if (parent && parent->Role() == roles::GROUPING && + parent->LocalChildAt(0) == this) { + nsIContent* parentContent = parent->GetContent(); + if (parentContent && parentContent->IsXULElement(nsGkAtoms::groupbox)) { + rel.AppendTarget(parent); + } + } + } + + return rel; +} + +void XULLabelAccessible::UpdateLabelValue(const nsString& aValue) { +#ifdef A11Y_LOG + if (logging::IsEnabled(logging::eText)) { + logging::MsgBegin("TEXT", "text may be changed (xul:label @value update)"); + logging::Node("container", mContent); + logging::MsgEntry("old text '%s'", + NS_ConvertUTF16toUTF8(mValueTextLeaf->Text()).get()); + logging::MsgEntry("new text: '%s'", NS_ConvertUTF16toUTF8(aValue).get()); + logging::MsgEnd(); + } +#endif + + TextUpdater::Run(mDoc, mValueTextLeaf, aValue); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULLabelTextLeafAccessible +//////////////////////////////////////////////////////////////////////////////// + +role XULLabelTextLeafAccessible::NativeRole() const { return roles::TEXT_LEAF; } + +uint64_t XULLabelTextLeafAccessible::NativeState() const { + return TextLeafAccessible::NativeState() | states::READONLY; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTooltipAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTooltipAccessible::XULTooltipAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + mType = eXULTooltipType; +} + +uint64_t XULTooltipAccessible::NativeState() const { + return LeafAccessible::NativeState() | states::READONLY; +} + +role XULTooltipAccessible::NativeRole() const { return roles::TOOLTIP; } + +//////////////////////////////////////////////////////////////////////////////// +// XULLinkAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULLinkAccessible::XULLinkAccessible(nsIContent* aContent, DocAccessible* aDoc) + : XULLabelAccessible(aContent, aDoc) {} + +XULLinkAccessible::~XULLinkAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULLinkAccessible: LocalAccessible + +void XULLinkAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::href, aValue); +} + +ENameValueFlag XULLinkAccessible::NativeName(nsString& aName) const { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); + if (!aName.IsEmpty()) return eNameOK; + + nsTextEquivUtils::GetNameFromSubtree(this, aName); + return aName.IsEmpty() ? eNameOK : eNameFromSubtree; +} + +role XULLinkAccessible::NativeRole() const { return roles::LINK; } + +uint64_t XULLinkAccessible::NativeLinkState() const { return states::LINKED; } + +bool XULLinkAccessible::HasPrimaryAction() const { return true; } + +void XULLinkAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + aName.Truncate(); + + if (aIndex == eAction_Jump) aName.AssignLiteral("jump"); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULLinkAccessible: HyperLinkAccessible + +bool XULLinkAccessible::IsLink() const { + // Expose HyperLinkAccessible unconditionally. + return true; +} + +uint32_t XULLinkAccessible::StartOffset() { + // If XUL link accessible is not contained by hypertext accessible then + // start offset matches index in parent because the parent doesn't contains + // a text. + // XXX: accessible parent of XUL link accessible should be a hypertext + // accessible. + if (LocalAccessible::IsLink()) return LocalAccessible::StartOffset(); + return IndexInParent(); +} + +uint32_t XULLinkAccessible::EndOffset() { + if (LocalAccessible::IsLink()) return LocalAccessible::EndOffset(); + return IndexInParent() + 1; +} + +already_AddRefed<nsIURI> XULLinkAccessible::AnchorURIAt( + uint32_t aAnchorIndex) const { + if (aAnchorIndex != 0) return nullptr; + + nsAutoString href; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); + + dom::Document* document = mContent->OwnerDoc(); + + nsCOMPtr<nsIURI> anchorURI; + NS_NewURI(getter_AddRefs(anchorURI), href, + document->GetDocumentCharacterSet(), mContent->GetBaseURI()); + + return anchorURI.forget(); +} diff --git a/accessible/xul/XULElementAccessibles.h b/accessible/xul/XULElementAccessibles.h new file mode 100644 index 0000000000..c45f7c4bf3 --- /dev/null +++ b/accessible/xul/XULElementAccessibles.h @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULElementAccessibles_h__ +#define mozilla_a11y_XULElementAccessibles_h__ + +#include "HyperTextAccessibleWrap.h" +#include "TextLeafAccessible.h" + +namespace mozilla { +namespace a11y { + +class XULLabelTextLeafAccessible; + +/** + * Used for XUL description and label elements. + */ +class XULLabelAccessible : public HyperTextAccessibleWrap { + public: + XULLabelAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Shutdown() override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual Relation RelationByType(RelationType aType) const override; + + void UpdateLabelValue(const nsString& aValue); + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + virtual void DispatchClickEvent(nsIContent* aContent, + uint32_t aActionIndex) const override; + + private: + RefPtr<XULLabelTextLeafAccessible> mValueTextLeaf; +}; + +inline XULLabelAccessible* LocalAccessible::AsXULLabel() { + return IsXULLabel() ? static_cast<XULLabelAccessible*>(this) : nullptr; +} + +/** + * Used to implement text interface on XUL label accessible in case when text + * is provided by @value attribute (no underlying text frame). + */ +class XULLabelTextLeafAccessible final : public TextLeafAccessible { + public: + XULLabelTextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc) + : TextLeafAccessible(aContent, aDoc) { + mStateFlags |= eSharedNode; + } + + virtual ~XULLabelTextLeafAccessible() {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; +}; + +/** + * Used for XUL tooltip element. + */ +class XULTooltipAccessible : public LeafAccessible { + public: + XULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; +}; + +class XULLinkAccessible : public XULLabelAccessible { + public: + XULLinkAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeLinkState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // HyperLinkAccessible + virtual bool IsLink() const override; + virtual uint32_t StartOffset() override; + virtual uint32_t EndOffset() override; + virtual already_AddRefed<nsIURI> AnchorURIAt( + uint32_t aAnchorIndex) const override; + + protected: + virtual ~XULLinkAccessible(); + + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + + enum { eAction_Jump = 0 }; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULFormControlAccessible.cpp b/accessible/xul/XULFormControlAccessible.cpp new file mode 100644 index 0000000000..4f414e6b0c --- /dev/null +++ b/accessible/xul/XULFormControlAccessible.cpp @@ -0,0 +1,450 @@ +/* -*- 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 "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(kNameSpaceID_None, 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) || + aEl->IsXULElement(nsGkAtoms::popup) || + // 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 { + // In general XUL button has not accessible children. Nevertheless menu + // buttons can have popup accessibles (@type="menu" or columnpicker). + // Also: Toolbar buttons can have labels as children. + // But only if the label attribute is not present. + return aEl->IsXULElement(nsGkAtoms::menupopup) || + aEl->IsXULElement(nsGkAtoms::popup) || + (aEl->IsXULElement(nsGkAtoms::label) && + !mContent->AsElement()->HasAttr(kNameSpaceID_None, 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(kNameSpaceID_None, 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; } diff --git a/accessible/xul/XULFormControlAccessible.h b/accessible/xul/XULFormControlAccessible.h new file mode 100644 index 0000000000..e48b0249a2 --- /dev/null +++ b/accessible/xul/XULFormControlAccessible.h @@ -0,0 +1,186 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_A11Y_XULFormControlAccessible_H_ +#define MOZILLA_A11Y_XULFormControlAccessible_H_ + +// NOTE: alphabetically ordered +#include "AccessibleWrap.h" +#include "FormControlAccessible.h" +#include "HyperTextAccessibleWrap.h" +#include "XULSelectControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for XUL button. + * + * @note Don't inherit from LeafAccessible - it doesn't allow children + * and a button can have a dropmarker child. + */ +class XULButtonAccessible : public AccessibleWrap { + public: + enum { eAction_Click = 0 }; + XULButtonAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(XULButtonAccessible, AccessibleWrap) + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual bool AttributeChangesState(nsAtom* aAttribute) override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + + virtual bool IsAcceptableChild(nsIContent* aEl) const override; + + protected: + virtual ~XULButtonAccessible(); + + // XULButtonAccessible + bool ContainsMenu() const; +}; + +/** + * Used for XUL dropmarker element. + */ +class XULDropmarkerAccessible : public LeafAccessible { + public: + enum { eAction_Click = 0 }; + XULDropmarkerAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) const override; + + private: + bool DropmarkerOpen(bool aToggleOpen) const; +}; + +/** + * Used for XUL groupbox element. + */ +class XULGroupboxAccessible final : public AccessibleWrap { + public: + XULGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual Relation RelationByType(RelationType aType) const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Used for XUL radio element (radio button). + */ +class XULRadioButtonAccessible : public RadioButtonAccessible { + public: + XULRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // Widgets + virtual LocalAccessible* ContainerWidget() const override; +}; + +/** + * Used for XUL radiogroup element. + */ +class XULRadioGroupAccessible : public XULSelectControlAccessible { + public: + XULRadioGroupAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeInteractiveState() const override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual LocalAccessible* CurrentItem() const override; + virtual void SetCurrentItem(const LocalAccessible* aItem) override; +}; + +/** + * Used for XUL statusbar element. + */ +class XULStatusBarAccessible : public AccessibleWrap { + public: + XULStatusBarAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; +}; + +/** + * Used for XUL toolbarbutton element. + */ +class XULToolbarButtonAccessible : public XULButtonAccessible { + public: + XULToolbarButtonAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsXULToolbarButtonAccessible + static bool IsSeparator(LocalAccessible* aAccessible); + + // Widgets + virtual bool IsAcceptableChild(nsIContent* aEl) const override; + + protected: + // LocalAccessible + virtual void GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) override; +}; + +/** + * Used for XUL toolbar element. + */ +class XULToolbarAccessible : public AccessibleWrap { + public: + XULToolbarAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Used for XUL toolbarseparator element. + */ +class XULToolbarSeparatorAccessible : public LeafAccessible { + public: + XULToolbarSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULListboxAccessible.cpp b/accessible/xul/XULListboxAccessible.cpp new file mode 100644 index 0000000000..c0c5b864aa --- /dev/null +++ b/accessible/xul/XULListboxAccessible.cpp @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 4; 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 "XULListboxAccessible.h" + +#include "LocalAccessible-inl.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "Role.h" +#include "States.h" + +#include "nsComponentManagerUtils.h" +#include "nsIAutoCompletePopup.h" +#include "nsIDOMXULMenuListElement.h" +#include "nsIDOMXULMultSelectCntrlEl.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsINodeList.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULColumAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULColumAccessible::XULColumAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +role XULColumAccessible::NativeRole() const { return roles::LIST; } + +uint64_t XULColumAccessible::NativeState() const { return states::READONLY; } + +//////////////////////////////////////////////////////////////////////////////// +// XULColumnItemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULColumnItemAccessible::XULColumnItemAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) {} + +role XULColumnItemAccessible::NativeRole() const { return roles::COLUMNHEADER; } + +uint64_t XULColumnItemAccessible::NativeState() const { + return states::READONLY; +} + +bool XULColumnItemAccessible::HasPrimaryAction() const { return true; } + +void XULColumnItemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("click"); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULListboxAccessible::XULListboxAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULSelectControlAccessible(aContent, aDoc) { + dom::Element* parentEl = mContent->GetParentElement(); + if (parentEl) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + parentEl->AsAutoCompletePopup(); + if (autoCompletePopupElm) mGenericTypes |= eAutoCompletePopup; + } + + if (IsMulticolumn()) mGenericTypes |= eTable; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListboxAccessible: LocalAccessible + +uint64_t XULListboxAccessible::NativeState() const { + // As a XULListboxAccessible we can have the following states: + // FOCUSED, READONLY, FOCUSABLE + + // Get focus status from base class + uint64_t states = LocalAccessible::NativeState(); + + // see if we are multiple select if so set ourselves as such + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::seltype, + nsGkAtoms::multiple, eCaseMatters)) { + states |= states::MULTISELECTABLE | states::EXTSELECTABLE; + } + + return states; +} + +role XULListboxAccessible::NativeRole() const { + // A richlistbox is used with the new autocomplete URL bar, and has a parent + // popup <panel>. + if (mContent->GetParent() && + mContent->GetParent()->IsXULElement(nsGkAtoms::panel)) { + return roles::COMBOBOX_LIST; + } + + return IsMulticolumn() ? roles::TABLE : roles::LISTBOX; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListboxAccessible: Table + +uint32_t XULListboxAccessible::ColCount() const { return 0; } + +uint32_t XULListboxAccessible::RowCount() { + nsCOMPtr<nsIDOMXULSelectControlElement> element = Elm()->AsXULSelectControl(); + + uint32_t itemCount = 0; + if (element) element->GetItemCount(&itemCount); + + return itemCount; +} + +LocalAccessible* XULListboxAccessible::CellAt(uint32_t aRowIndex, + uint32_t aColumnIndex) { + nsCOMPtr<nsIDOMXULSelectControlElement> control = Elm()->AsXULSelectControl(); + NS_ENSURE_TRUE(control, nullptr); + + RefPtr<dom::Element> element; + control->GetItemAtIndex(aRowIndex, getter_AddRefs(element)); + if (!element) return nullptr; + + LocalAccessible* row = mDoc->GetAccessible(element); + NS_ENSURE_TRUE(row, nullptr); + + return row->LocalChildAt(aColumnIndex); +} + +bool XULListboxAccessible::IsColSelected(uint32_t aColIdx) { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + int32_t selectedrowCount = 0; + nsresult rv = control->GetSelectedCount(&selectedrowCount); + NS_ENSURE_SUCCESS(rv, false); + + return selectedrowCount == static_cast<int32_t>(RowCount()); +} + +bool XULListboxAccessible::IsRowSelected(uint32_t aRowIdx) { + nsCOMPtr<nsIDOMXULSelectControlElement> control = Elm()->AsXULSelectControl(); + NS_ASSERTION(control, "Doesn't implement nsIDOMXULSelectControlElement."); + + RefPtr<dom::Element> element; + nsresult rv = control->GetItemAtIndex(aRowIdx, getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, false); + if (!element) { + return false; + } + + nsCOMPtr<nsIDOMXULSelectControlItemElement> item = + element->AsXULSelectControlItem(); + + bool isSelected = false; + item->GetSelected(&isSelected); + return isSelected; +} + +bool XULListboxAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { + return IsRowSelected(aRowIdx); +} + +uint32_t XULListboxAccessible::SelectedCellCount() { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + nsCOMPtr<nsINodeList> selectedItems; + control->GetSelectedItems(getter_AddRefs(selectedItems)); + if (!selectedItems) return 0; + + uint32_t selectedItemsCount = selectedItems->Length(); + + return selectedItemsCount * ColCount(); +} + +uint32_t XULListboxAccessible::SelectedColCount() { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + int32_t selectedRowCount = 0; + nsresult rv = control->GetSelectedCount(&selectedRowCount); + NS_ENSURE_SUCCESS(rv, 0); + + return selectedRowCount > 0 && + selectedRowCount == static_cast<int32_t>(RowCount()) + ? ColCount() + : 0; +} + +uint32_t XULListboxAccessible::SelectedRowCount() { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + int32_t selectedRowCount = 0; + nsresult rv = control->GetSelectedCount(&selectedRowCount); + NS_ENSURE_SUCCESS(rv, 0); + + return selectedRowCount >= 0 ? selectedRowCount : 0; +} + +void XULListboxAccessible::SelectedCells(nsTArray<Accessible*>* aCells) { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + nsCOMPtr<nsINodeList> selectedItems; + control->GetSelectedItems(getter_AddRefs(selectedItems)); + if (!selectedItems) return; + + uint32_t selectedItemsCount = selectedItems->Length(); + + for (uint32_t index = 0; index < selectedItemsCount; index++) { + nsIContent* itemContent = selectedItems->Item(index); + LocalAccessible* item = mDoc->GetAccessible(itemContent); + + if (item) { + uint32_t cellCount = item->ChildCount(); + for (uint32_t cellIdx = 0; cellIdx < cellCount; cellIdx++) { + LocalAccessible* cell = mChildren[cellIdx]; + if (cell->Role() == roles::CELL) aCells->AppendElement(cell); + } + } + } +} + +void XULListboxAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + nsCOMPtr<nsINodeList> selectedItems; + control->GetSelectedItems(getter_AddRefs(selectedItems)); + if (!selectedItems) return; + + uint32_t selectedItemsCount = selectedItems->Length(); + + uint32_t colCount = ColCount(); + aCells->SetCapacity(selectedItemsCount * colCount); + aCells->AppendElements(selectedItemsCount * colCount); + + for (uint32_t selItemsIdx = 0, cellsIdx = 0; selItemsIdx < selectedItemsCount; + selItemsIdx++) { + nsIContent* itemContent = selectedItems->Item(selItemsIdx); + + nsCOMPtr<nsIDOMXULSelectControlItemElement> item = + itemContent->AsElement()->AsXULSelectControlItem(); + if (item) { + int32_t itemIdx = -1; + control->GetIndexOfItem(item, &itemIdx); + if (itemIdx >= 0) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++, cellsIdx++) { + aCells->ElementAt(cellsIdx) = itemIdx * colCount + colIdx; + } + } + } + } +} + +void XULListboxAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) { + uint32_t selColCount = SelectedColCount(); + aCols->SetCapacity(selColCount); + + for (uint32_t colIdx = 0; colIdx < selColCount; colIdx++) { + aCols->AppendElement(colIdx); + } +} + +void XULListboxAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = + Elm()->AsXULMultiSelectControl(); + NS_ASSERTION(control, + "Doesn't implement nsIDOMXULMultiSelectControlElement."); + + nsCOMPtr<nsINodeList> selectedItems; + control->GetSelectedItems(getter_AddRefs(selectedItems)); + if (!selectedItems) return; + + uint32_t rowCount = selectedItems->Length(); + + if (!rowCount) return; + + aRows->SetCapacity(rowCount); + aRows->AppendElements(rowCount); + + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + nsIContent* itemContent = selectedItems->Item(rowIdx); + nsCOMPtr<nsIDOMXULSelectControlItemElement> item = + itemContent->AsElement()->AsXULSelectControlItem(); + + if (item) { + int32_t itemIdx = -1; + control->GetIndexOfItem(item, &itemIdx); + if (itemIdx >= 0) aRows->ElementAt(rowIdx) = itemIdx; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListboxAccessible: Widgets + +bool XULListboxAccessible::IsWidget() const { return true; } + +bool XULListboxAccessible::IsActiveWidget() const { + if (IsAutoCompletePopup()) { + nsIContent* parentContent = mContent->GetParent(); + if (parentContent) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + parentContent->AsElement()->AsAutoCompletePopup(); + if (autoCompletePopupElm) { + bool isOpen = false; + autoCompletePopupElm->GetPopupOpen(&isOpen); + return isOpen; + } + } + } + return FocusMgr()->HasDOMFocus(mContent); +} + +bool XULListboxAccessible::AreItemsOperable() const { + if (IsAutoCompletePopup()) { + nsIContent* parentContent = mContent->GetParent(); + if (parentContent) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + parentContent->AsElement()->AsAutoCompletePopup(); + if (autoCompletePopupElm) { + bool isOpen = false; + autoCompletePopupElm->GetPopupOpen(&isOpen); + return isOpen; + } + } + } + return true; +} + +LocalAccessible* XULListboxAccessible::ContainerWidget() const { + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListitemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULListitemAccessible::XULListitemAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULMenuitemAccessible(aContent, aDoc) { + mIsCheckbox = mContent->AsElement()->AttrValueIs( + kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::checkbox, eCaseMatters); + mType = eXULListItemType; +} + +XULListitemAccessible::~XULListitemAccessible() {} + +LocalAccessible* XULListitemAccessible::GetListAccessible() const { + if (IsDefunct()) return nullptr; + + nsCOMPtr<nsIDOMXULSelectControlItemElement> listItem = + Elm()->AsXULSelectControlItem(); + if (!listItem) return nullptr; + + RefPtr<dom::Element> listElement; + listItem->GetControl(getter_AddRefs(listElement)); + if (!listElement) return nullptr; + + return mDoc->GetAccessible(listElement); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListitemAccessible LocalAccessible + +void XULListitemAccessible::Description(nsString& aDesc) const { + AccessibleWrap::Description(aDesc); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListitemAccessible: LocalAccessible + +/** + * Get the name from GetXULName. + */ +ENameValueFlag XULListitemAccessible::NativeName(nsString& aName) const { + return LocalAccessible::NativeName(aName); +} + +role XULListitemAccessible::NativeRole() const { + LocalAccessible* list = GetListAccessible(); + if (!list) { + NS_ERROR("No list accessible for listitem accessible!"); + return roles::NOTHING; + } + + if (list->Role() == roles::TABLE) return roles::ROW; + + if (mIsCheckbox) return roles::CHECK_RICH_OPTION; + + if (mParent && mParent->Role() == roles::COMBOBOX_LIST) { + return roles::COMBOBOX_OPTION; + } + + return roles::RICH_OPTION; +} + +uint64_t XULListitemAccessible::NativeState() const { + if (mIsCheckbox) return XULMenuitemAccessible::NativeState(); + + uint64_t states = NativeInteractiveState(); + + nsCOMPtr<nsIDOMXULSelectControlItemElement> listItem = + Elm()->AsXULSelectControlItem(); + if (listItem) { + bool isSelected; + listItem->GetSelected(&isSelected); + if (isSelected) states |= states::SELECTED; + + if (FocusMgr()->IsFocused(this)) states |= states::FOCUSED; + } + + return states; +} + +uint64_t XULListitemAccessible::NativeInteractiveState() const { + return NativelyUnavailable() || (mParent && mParent->NativelyUnavailable()) + ? states::UNAVAILABLE + : states::FOCUSABLE | states::SELECTABLE; +} + +void XULListitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click && mIsCheckbox) { + uint64_t states = NativeState(); + if (states & states::CHECKED) { + aName.AssignLiteral("uncheck"); + } else { + aName.AssignLiteral("check"); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULListitemAccessible: Widgets + +LocalAccessible* XULListitemAccessible::ContainerWidget() const { + return LocalParent(); +} diff --git a/accessible/xul/XULListboxAccessible.h b/accessible/xul/XULListboxAccessible.h new file mode 100644 index 0000000000..2745b323d0 --- /dev/null +++ b/accessible/xul/XULListboxAccessible.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef mozilla_a11y_XULListboxAccessible_h__ +#define mozilla_a11y_XULListboxAccessible_h__ + +#include "BaseAccessibles.h" +#include "mozilla/a11y/TableAccessible.h" +#include "XULMenuAccessible.h" +#include "XULSelectControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * XULColumAccessible are accessible for list and tree columns elements + * (xul:treecols and xul:listheader). + */ +class XULColumAccessible : public AccessibleWrap { + public: + XULColumAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; +}; + +/** + * XULColumnItemAccessible are accessibles for list and tree column elements + * (xul:treecol). + */ +class XULColumnItemAccessible : public LeafAccessible { + public: + XULColumnItemAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + enum { eAction_Click = 0 }; +}; + +/* + * A class the represents the XUL Listbox widget. + */ +class XULListboxAccessible : public XULSelectControlAccessible, + public TableAccessible { + public: + XULListboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // TableAccessible + virtual uint32_t ColCount() const override; + virtual uint32_t RowCount() override; + virtual LocalAccessible* CellAt(uint32_t aRowIndex, + uint32_t aColumnIndex) override; + virtual bool IsColSelected(uint32_t aColIdx) override; + virtual bool IsRowSelected(uint32_t aRowIdx) override; + virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual uint32_t SelectedCellCount() override; + virtual uint32_t SelectedColCount() override; + virtual uint32_t SelectedRowCount() override; + virtual void SelectedCells(nsTArray<Accessible*>* aCells) override; + virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override; + virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override; + virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override; + virtual LocalAccessible* AsAccessible() override { return this; } + + // LocalAccessible + virtual TableAccessible* AsTable() override { return this; } + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + + virtual LocalAccessible* ContainerWidget() const override; + + protected: + virtual ~XULListboxAccessible() {} + + bool IsMulticolumn() const { return ColCount() > 1; } +}; + +/** + * Listitems -- used in listboxes + */ +class XULListitemAccessible : public XULMenuitemAccessible { + public: + enum { eAction_Click = 0 }; + + NS_INLINE_DECL_REFCOUNTING_INHERITED(XULListitemAccessible, + XULMenuitemAccessible) + + XULListitemAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Description(nsString& aDesc) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // Actions + virtual void ActionNameAt(uint8_t index, nsAString& aName) override; + + // Widgets + virtual LocalAccessible* ContainerWidget() const override; + + protected: + virtual ~XULListitemAccessible(); + + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + + // XULListitemAccessible + + /** + * Return listbox accessible for the listitem. + */ + LocalAccessible* GetListAccessible() const; + + private: + bool mIsCheckbox; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULMenuAccessible.cpp b/accessible/xul/XULMenuAccessible.cpp new file mode 100644 index 0000000000..dc39a6ee94 --- /dev/null +++ b/accessible/xul/XULMenuAccessible.cpp @@ -0,0 +1,484 @@ +/* -*- 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 "XULMenuAccessible.h" + +#include "LocalAccessible-inl.h" +#include "XULMenuBarElement.h" +#include "XULMenuParentElement.h" +#include "XULPopupElement.h" +#include "mozilla/Assertions.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "Role.h" +#include "States.h" +#include "XULFormControlAccessible.h" + +#include "nsIContentInlines.h" +#include "nsIDOMXULContainerElement.h" +#include "nsIDOMXULSelectCntrlEl.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIContent.h" +#include "nsMenuPopupFrame.h" + +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/XULButtonElement.h" +#include "mozilla/dom/KeyboardEventBinding.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenuitemAccessible::XULMenuitemAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +uint64_t XULMenuitemAccessible::NativeState() const { + uint64_t state = LocalAccessible::NativeState(); + + // Has Popup? + if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) { + state |= states::HASPOPUP; + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::open)) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + } + + // Checkable/checked? + static dom::Element::AttrValuesArray strings[] = { + nsGkAtoms::radio, nsGkAtoms::checkbox, nullptr}; + + if (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + strings, eCaseMatters) >= 0) { + // Checkable? + state |= states::CHECKABLE; + + // Checked? + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::checked, nsGkAtoms::_true, + eCaseMatters)) { + state |= states::CHECKED; + } + } + + // Combo box listitem + bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION); + if (isComboboxOption) { + // Is selected? + bool isSelected = false; + nsCOMPtr<nsIDOMXULSelectControlItemElement> item = + Elm()->AsXULSelectControlItem(); + NS_ENSURE_TRUE(item, state); + item->GetSelected(&isSelected); + + // Is collapsed? + bool isCollapsed = false; + LocalAccessible* parent = LocalParent(); + if (parent && parent->State() & states::INVISIBLE) isCollapsed = true; + + if (isSelected) { + state |= states::SELECTED; + + // Selected and collapsed? + if (isCollapsed) { + // Set selected option offscreen/invisible according to combobox state + LocalAccessible* grandParent = parent->LocalParent(); + if (!grandParent) return state; + NS_ASSERTION(grandParent->IsCombobox(), + "grandparent of combobox listitem is not combobox"); + uint64_t grandParentState = grandParent->State(); + state &= ~(states::OFFSCREEN | states::INVISIBLE); + state |= (grandParentState & states::OFFSCREEN) | + (grandParentState & states::INVISIBLE) | + (grandParentState & states::OPAQUE1); + } // isCollapsed + } // isSelected + } // ROLE_COMBOBOX_OPTION + + return state; +} + +uint64_t XULMenuitemAccessible::NativeInteractiveState() const { + if (NativelyUnavailable()) { + // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic. + auto* button = dom::XULButtonElement::FromNode(GetContent()); + bool skipNavigatingDisabledMenuItem = true; + if (!button || !button->IsOnMenuBar()) { + skipNavigatingDisabledMenuItem = LookAndFeel::GetInt( + LookAndFeel::IntID::SkipNavigatingDisabledMenuItem); + } + + if (skipNavigatingDisabledMenuItem) return states::UNAVAILABLE; + + return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE; + } + + return states::FOCUSABLE | states::SELECTABLE; +} + +ENameValueFlag XULMenuitemAccessible::NativeName(nsString& aName) const { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + return eNameOK; +} + +void XULMenuitemAccessible::Description(nsString& aDescription) const { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::description, + aDescription); +} + +KeyBinding XULMenuitemAccessible::AccessKey() const { + // Return menu accesskey: N or Alt+F. + static int32_t gMenuAccesskeyModifier = + -1; // magic value of -1 indicates unitialized state + + // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for + // menu are't registered by EventStateManager. + nsAutoString accesskey; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, + accesskey); + if (accesskey.IsEmpty()) return KeyBinding(); + + uint32_t modifierKey = 0; + + LocalAccessible* parentAcc = LocalParent(); + if (parentAcc) { + if (parentAcc->NativeRole() == roles::MENUBAR) { + // If top level menu item, add Alt+ or whatever modifier text to string + // No need to cache pref service, this happens rarely + if (gMenuAccesskeyModifier == -1) { + // Need to initialize cached global accesskey pref + gMenuAccesskeyModifier = Preferences::GetInt("ui.key.menuAccessKey", 0); + } + + switch (gMenuAccesskeyModifier) { + case dom::KeyboardEvent_Binding::DOM_VK_CONTROL: + modifierKey = KeyBinding::kControl; + break; + case dom::KeyboardEvent_Binding::DOM_VK_ALT: + modifierKey = KeyBinding::kAlt; + break; + case dom::KeyboardEvent_Binding::DOM_VK_META: + modifierKey = KeyBinding::kMeta; + break; + case dom::KeyboardEvent_Binding::DOM_VK_WIN: + modifierKey = KeyBinding::kOS; + break; + } + } + } + + return KeyBinding(accesskey[0], modifierKey); +} + +KeyBinding XULMenuitemAccessible::KeyboardShortcut() const { + nsAutoString keyElmId; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyElmId); + if (keyElmId.IsEmpty()) return KeyBinding(); + + dom::Element* keyElm = mContent->OwnerDoc()->GetElementById(keyElmId); + if (!keyElm) return KeyBinding(); + + uint32_t key = 0; + + nsAutoString keyStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr); + if (keyStr.IsEmpty()) { + nsAutoString keyCodeStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeStr); + nsresult errorCode; + key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10); + if (NS_FAILED(errorCode)) { + key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16); + } + } else { + key = keyStr[0]; + } + + nsAutoString modifiersStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr); + + uint32_t modifierMask = 0; + if (modifiersStr.Find(u"shift") != -1) modifierMask |= KeyBinding::kShift; + if (modifiersStr.Find(u"alt") != -1) modifierMask |= KeyBinding::kAlt; + if (modifiersStr.Find(u"meta") != -1) modifierMask |= KeyBinding::kMeta; + if (modifiersStr.Find(u"os") != -1) modifierMask |= KeyBinding::kOS; + if (modifiersStr.Find(u"control") != -1) modifierMask |= KeyBinding::kControl; + if (modifiersStr.Find(u"accel") != -1) { + modifierMask |= KeyBinding::AccelModifier(); + } + + return KeyBinding(key, modifierMask); +} + +role XULMenuitemAccessible::NativeRole() const { + nsCOMPtr<nsIDOMXULContainerElement> xulContainer = Elm()->AsXULContainer(); + if (xulContainer) return roles::PARENT_MENUITEM; + + LocalAccessible* widget = ContainerWidget(); + if (widget && widget->Role() == roles::COMBOBOX_LIST) { + return roles::COMBOBOX_OPTION; + } + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::radio, eCaseMatters)) { + return roles::RADIO_MENU_ITEM; + } + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::checkbox, eCaseMatters)) { + return roles::CHECK_MENU_ITEM; + } + + return roles::MENUITEM; +} + +int32_t XULMenuitemAccessible::GetLevel(bool aFast) const { + return nsAccUtils::GetLevelForXULContainerItem(mContent); +} + +void XULMenuitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("click"); +} + +bool XULMenuitemAccessible::HasPrimaryAction() const { return true; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible: Widgets + +bool XULMenuitemAccessible::IsActiveWidget() const { + // Parent menu item is a widget, it's active when its popup is open. + // Typically the <menupopup> is included in the document markup, and + // <menu> prepends content in front of it. + nsIContent* menuPopupContent = mContent->GetLastChild(); + if (menuPopupContent) { + nsMenuPopupFrame* menuPopupFrame = + do_QueryFrame(menuPopupContent->GetPrimaryFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); + } + return false; +} + +bool XULMenuitemAccessible::AreItemsOperable() const { + // Parent menu item is a widget, its items are operable when its popup is + // open. + nsIContent* menuPopupContent = mContent->GetLastChild(); + if (menuPopupContent) { + nsMenuPopupFrame* menuPopupFrame = + do_QueryFrame(menuPopupContent->GetPrimaryFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); + } + return false; +} + +LocalAccessible* XULMenuitemAccessible::ContainerWidget() const { + if (auto* button = dom::XULButtonElement::FromNode(GetContent())) { + if (auto* popup = button->GetMenuParent()) { + // We use GetAccessibleOrContainer instead of just GetAccessible because + // we strip menupopups from the tree for ATK. + return mDoc->GetAccessibleOrContainer(popup); + } + } + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuSeparatorAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULMenuitemAccessible(aContent, aDoc) {} + +uint64_t XULMenuSeparatorAccessible::NativeState() const { + // Isn't focusable, but can be offscreen/invisible -- only copy those states + return XULMenuitemAccessible::NativeState() & + (states::OFFSCREEN | states::INVISIBLE); +} + +ENameValueFlag XULMenuSeparatorAccessible::NativeName(nsString& aName) const { + return eNameOK; +} + +role XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR; } + +bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenupopupAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenupopupAccessible::XULMenupopupAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULSelectControlAccessible(aContent, aDoc) { + if (nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame())) { + if (menuPopupFrame->GetPopupType() == widget::PopupType::Menu) { + mType = eMenuPopupType; + } + } + + // May be the anonymous <menupopup> inside <menulist> (a combobox) + auto* parent = mContent->GetParentElement(); + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + parent ? parent->AsXULSelectControl() : nullptr; + if (selectControl) { + mSelectControl = parent; + } else { + mSelectControl = nullptr; + mGenericTypes &= ~eSelect; + } +} + +uint64_t XULMenupopupAccessible::NativeState() const { + uint64_t state = LocalAccessible::NativeState(); + +#ifdef DEBUG + // We are onscreen if our parent is active + bool isActive = + mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive); + if (!isActive) { + LocalAccessible* parent = LocalParent(); + if (parent) { + nsIContent* parentContent = parent->GetContent(); + if (parentContent && parentContent->IsElement()) + isActive = parentContent->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::open); + } + } + + NS_ASSERTION(isActive || (state & states::INVISIBLE), + "XULMenupopup doesn't have INVISIBLE when it's inactive"); +#endif + + if (state & states::INVISIBLE) state |= states::OFFSCREEN | states::COLLAPSED; + + return state; +} + +ENameValueFlag XULMenupopupAccessible::NativeName(nsString& aName) const { + nsIContent* content = mContent; + while (content && aName.IsEmpty()) { + if (content->IsElement()) { + content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + } + content = content->GetFlattenedTreeParent(); + } + + return eNameOK; +} + +role XULMenupopupAccessible::NativeRole() const { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + if (menuPopupFrame && menuPopupFrame->IsContextMenu()) { + return roles::MENUPOPUP; + } + + if (mParent) { + if (mParent->IsCombobox()) { + return roles::COMBOBOX_LIST; + } + } + + // If accessible is not bound to the tree (this happens while children are + // cached) return general role. + return roles::MENUPOPUP; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenupopupAccessible: Widgets + +bool XULMenupopupAccessible::IsWidget() const { return true; } + +bool XULMenupopupAccessible::IsActiveWidget() const { + // If menupopup is a widget (the case of context menus) then active when open. + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); +} + +bool XULMenupopupAccessible::AreItemsOperable() const { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); +} + +LocalAccessible* XULMenupopupAccessible::ContainerWidget() const { + DocAccessible* document = Document(); + + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + MOZ_ASSERT(menuPopupFrame); + if (!menuPopupFrame) { + return nullptr; + } + + auto* cur = dom::XULPopupElement::FromNode(GetContent()); + while (cur) { + auto* menu = cur->GetContainingMenu(); + if (!menu) { + // <panel> / <tooltip> / etc. + return nullptr; + } + dom::XULMenuParentElement* parent = menu->GetMenuParent(); + if (!parent) { + LocalAccessible* menuPopup = document->GetAccessible(cur); + MOZ_ASSERT(menuPopup); + return menuPopup ? menuPopup->LocalParent() : nullptr; + } + + if (parent->IsMenuBar()) { + return document->GetAccessible(parent); + } + + cur = dom::XULPopupElement::FromNode(parent); + MOZ_ASSERT(cur, "Should be a popup"); + } + + MOZ_ASSERT_UNREACHABLE("How did we get out of the loop without returning?"); + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenubarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenubarAccessible::XULMenubarAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +ENameValueFlag XULMenubarAccessible::NativeName(nsString& aName) const { + aName.AssignLiteral("Application"); + return eNameOK; +} + +role XULMenubarAccessible::NativeRole() const { return roles::MENUBAR; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenubarAccessible: Widgets + +bool XULMenubarAccessible::IsActiveWidget() const { + auto* menuBar = dom::XULMenuBarElement::FromNode(GetContent()); + return menuBar && menuBar->IsActive(); +} + +bool XULMenubarAccessible::AreItemsOperable() const { return true; } + +LocalAccessible* XULMenubarAccessible::CurrentItem() const { + auto* content = dom::XULMenuParentElement::FromNode(GetContent()); + MOZ_ASSERT(content); + if (!content || !content->GetActiveMenuChild()) { + return nullptr; + } + return mDoc->GetAccessible(content->GetActiveMenuChild()); +} + +void XULMenubarAccessible::SetCurrentItem(const LocalAccessible* aItem) { + NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented"); +} diff --git a/accessible/xul/XULMenuAccessible.h b/accessible/xul/XULMenuAccessible.h new file mode 100644 index 0000000000..43d165d798 --- /dev/null +++ b/accessible/xul/XULMenuAccessible.h @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULMenuAccessible_h__ +#define mozilla_a11y_XULMenuAccessible_h__ + +#include "AccessibleWrap.h" +#include "XULSelectControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for XUL menu, menuitem elements. + */ +class XULMenuitemAccessible : public AccessibleWrap { + public: + enum { eAction_Click = 0 }; + + XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Description(nsString& aDescription) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual KeyBinding AccessKey() const override; + virtual KeyBinding KeyboardShortcut() const override; + + // Widgets + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual LocalAccessible* ContainerWidget() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + virtual int32_t GetLevel(bool aFast) const override; +}; + +/** + * Used for XUL menuseparator element. + */ +class XULMenuSeparatorAccessible : public XULMenuitemAccessible { + public: + XULMenuSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Used for XUL menupopup and panel. + */ +class XULMenupopupAccessible : public XULSelectControlAccessible { + public: + XULMenupopupAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + + virtual LocalAccessible* ContainerWidget() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Used for XUL menubar element. + */ +class XULMenubarAccessible : public AccessibleWrap { + public: + XULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + // Widget + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual LocalAccessible* CurrentItem() const override; + virtual void SetCurrentItem(const LocalAccessible* aItem) override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULSelectControlAccessible.cpp b/accessible/xul/XULSelectControlAccessible.cpp new file mode 100644 index 0000000000..006d1daf89 --- /dev/null +++ b/accessible/xul/XULSelectControlAccessible.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "XULSelectControlAccessible.h" + +#include "nsAccessibilityService.h" +#include "DocAccessible.h" + +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIDOMXULMultSelectCntrlEl.h" + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/KeyboardEventBinding.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULSelectControlAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULSelectControlAccessible::XULSelectControlAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mGenericTypes |= eSelect; + mSelectControl = aContent->AsElement(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULSelectControlAccessible: LocalAccessible + +void XULSelectControlAccessible::Shutdown() { + mSelectControl = nullptr; + AccessibleWrap::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULSelectControlAccessible: SelectAccessible + +void XULSelectControlAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { + // For XUL multi-select control + nsCOMPtr<nsIDOMXULMultiSelectControlElement> xulMultiSelect = + mSelectControl->AsXULMultiSelectControl(); + if (xulMultiSelect) { + int32_t length = 0; + xulMultiSelect->GetSelectedCount(&length); + for (int32_t index = 0; index < length; index++) { + RefPtr<dom::Element> element; + xulMultiSelect->MultiGetSelectedItem(index, getter_AddRefs(element)); + LocalAccessible* item = mDoc->GetAccessible(element); + if (item) aItems->AppendElement(item); + } + } else { // Single select? + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + RefPtr<dom::Element> element; + selectControl->GetSelectedItem(getter_AddRefs(element)); + if (element) { + LocalAccessible* item = mDoc->GetAccessible(element); + if (item) aItems->AppendElement(item); + } + } +} + +Accessible* XULSelectControlAccessible::GetSelectedItem(uint32_t aIndex) { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + + RefPtr<dom::Element> element; + if (multiSelectControl) { + multiSelectControl->MultiGetSelectedItem(aIndex, getter_AddRefs(element)); + } else if (aIndex == 0) { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->GetSelectedItem(getter_AddRefs(element)); + } + } + + return element && mDoc ? mDoc->GetAccessible(element) : nullptr; +} + +uint32_t XULSelectControlAccessible::SelectedItemCount() { + // For XUL multi-select control + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + if (multiSelectControl) { + int32_t count = 0; + multiSelectControl->GetSelectedCount(&count); + return count; + } + + // For XUL single-select control/menulist + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + int32_t index = -1; + selectControl->GetSelectedIndex(&index); + return (index >= 0) ? 1 : 0; + } + + return 0; +} + +bool XULSelectControlAccessible::AddItemToSelection(uint32_t aIndex) { + LocalAccessible* item = LocalChildAt(aIndex); + if (!item || !item->GetContent()) return false; + + nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm = + item->GetContent()->AsElement()->AsXULSelectControlItem(); + if (!itemElm) return false; + + bool isItemSelected = false; + itemElm->GetSelected(&isItemSelected); + if (isItemSelected) return true; + + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + + if (multiSelectControl) { + multiSelectControl->AddItemToSelection(itemElm); + } else { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->SetSelectedItem(item->Elm()); + } + } + + return true; +} + +bool XULSelectControlAccessible::RemoveItemFromSelection(uint32_t aIndex) { + LocalAccessible* item = LocalChildAt(aIndex); + if (!item || !item->GetContent()) return false; + + nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm = + item->GetContent()->AsElement()->AsXULSelectControlItem(); + if (!itemElm) return false; + + bool isItemSelected = false; + itemElm->GetSelected(&isItemSelected); + if (!isItemSelected) return true; + + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + + if (multiSelectControl) { + multiSelectControl->RemoveItemFromSelection(itemElm); + } else { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->SetSelectedItem(nullptr); + } + } + + return true; +} + +bool XULSelectControlAccessible::IsItemSelected(uint32_t aIndex) { + LocalAccessible* item = LocalChildAt(aIndex); + if (!item || !item->GetContent()) return false; + + nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm = + item->GetContent()->AsElement()->AsXULSelectControlItem(); + if (!itemElm) return false; + + bool isItemSelected = false; + itemElm->GetSelected(&isItemSelected); + return isItemSelected; +} + +bool XULSelectControlAccessible::UnselectAll() { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + if (multiSelectControl) { + multiSelectControl->ClearSelection(); + } else { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->SetSelectedIndex(-1); + } + } + + return true; +} + +bool XULSelectControlAccessible::SelectAll() { + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + if (multiSelectControl) { + multiSelectControl->SelectAll(); + return true; + } + + // otherwise, don't support this method + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULSelectControlAccessible: Widgets + +LocalAccessible* XULSelectControlAccessible::CurrentItem() const { + // aria-activedescendant should override. + LocalAccessible* current = AccessibleWrap::CurrentItem(); + if (current) { + return current; + } + + if (!mSelectControl) return nullptr; + + RefPtr<dom::Element> currentItemElm; + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + mSelectControl->AsXULMultiSelectControl(); + if (multiSelectControl) { + multiSelectControl->GetCurrentItem(getter_AddRefs(currentItemElm)); + } else { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->GetSelectedItem(getter_AddRefs(currentItemElm)); + } + } + + if (currentItemElm) { + DocAccessible* document = Document(); + if (document) return document->GetAccessible(currentItemElm); + } + + return nullptr; +} + +void XULSelectControlAccessible::SetCurrentItem(const LocalAccessible* aItem) { + if (!mSelectControl) return; + + nsCOMPtr<dom::Element> itemElm = aItem->Elm(); + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl = + itemElm->AsXULMultiSelectControl(); + if (multiSelectControl) { + multiSelectControl->SetCurrentItem(itemElm); + } else { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + mSelectControl->AsXULSelectControl(); + if (selectControl) { + selectControl->SetSelectedItem(itemElm); + } + } +} diff --git a/accessible/xul/XULSelectControlAccessible.h b/accessible/xul/XULSelectControlAccessible.h new file mode 100644 index 0000000000..ae201c74b0 --- /dev/null +++ b/accessible/xul/XULSelectControlAccessible.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_a11y_XULSelectControlAccessible_h__ +#define mozilla_a11y_XULSelectControlAccessible_h__ + +#include "AccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +/** + * The basic implementation of accessible selection for XUL select controls. + */ +class XULSelectControlAccessible : public AccessibleWrap { + public: + XULSelectControlAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~XULSelectControlAccessible() {} + + // LocalAccessible + virtual void Shutdown() override; + + // SelectAccessible + virtual void SelectedItems(nsTArray<Accessible*>* aItems) override; + virtual uint32_t SelectedItemCount() override; + virtual Accessible* GetSelectedItem(uint32_t aIndex) override; + virtual bool IsItemSelected(uint32_t aIndex) override; + virtual bool AddItemToSelection(uint32_t aIndex) override; + virtual bool RemoveItemFromSelection(uint32_t aIndex) override; + virtual bool SelectAll() override; + virtual bool UnselectAll() override; + + // Widgets + virtual LocalAccessible* CurrentItem() const override; + virtual void SetCurrentItem(const LocalAccessible* aItem) override; + + protected: + RefPtr<dom::Element> mSelectControl; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULTabAccessible.cpp b/accessible/xul/XULTabAccessible.cpp new file mode 100644 index 0000000000..36358b3960 --- /dev/null +++ b/accessible/xul/XULTabAccessible.cpp @@ -0,0 +1,218 @@ +/* -*- 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 "XULTabAccessible.h" + +#include "ARIAMap.h" +#include "nsAccUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +// NOTE: alphabetically ordered +#include "mozilla/dom/Document.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIDOMXULRelatedElement.h" +#include "nsXULElement.h" + +#include "mozilla/dom/BindingDeclarations.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULTabAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTabAccessible::XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTabAccessible: LocalAccessible + +bool XULTabAccessible::HasPrimaryAction() const { return true; } + +void XULTabAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Switch) aName.AssignLiteral("switch"); +} + +bool XULTabAccessible::DoAction(uint8_t index) const { + if (index == eAction_Switch) { + // XXXbz Could this just FromContent? + RefPtr<nsXULElement> tab = nsXULElement::FromNodeOrNull(mContent); + if (tab) { + tab->Click(mozilla::dom::CallerType::System); + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTabAccessible: LocalAccessible + +role XULTabAccessible::NativeRole() const { return roles::PAGETAB; } + +uint64_t XULTabAccessible::NativeState() const { + // Possible states: focused, focusable, unavailable(disabled), offscreen. + + // get focus and disable status from base class + uint64_t state = AccessibleWrap::NativeState(); + + // Check whether the tab is selected and/or pinned + nsCOMPtr<nsIDOMXULSelectControlItemElement> tab = + Elm()->AsXULSelectControlItem(); + if (tab) { + bool selected = false; + if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected) { + state |= states::SELECTED; + } + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::pinned, + nsGkAtoms::_true, eCaseMatters)) { + state |= states::PINNED; + } + } + + return state; +} + +uint64_t XULTabAccessible::NativeInteractiveState() const { + uint64_t state = LocalAccessible::NativeInteractiveState(); + return (state & states::UNAVAILABLE) ? state : state | states::SELECTABLE; +} + +Relation XULTabAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABEL_FOR) return rel; + + // Expose 'LABEL_FOR' relation on tab accessible for tabpanel accessible. + ErrorResult rv; + nsIContent* parent = mContent->AsElement()->Closest("tabs"_ns, rv); + if (!parent) return rel; + + nsCOMPtr<nsIDOMXULRelatedElement> tabsElm = + parent->AsElement()->AsXULRelated(); + if (!tabsElm) return rel; + + RefPtr<mozilla::dom::Element> tabpanelElement; + tabsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabpanelElement)); + if (!tabpanelElement) return rel; + + rel.AppendTarget(mDoc, tabpanelElement); + return rel; +} + +void XULTabAccessible::ApplyARIAState(uint64_t* aState) const { + HyperTextAccessibleWrap::ApplyARIAState(aState); + // XUL tab has an implicit ARIA role of tab, so support aria-selected. + // Don't use aria::MapToState because that will set the SELECTABLE state + // even if the tab is disabled. + if (nsAccUtils::IsARIASelected(this)) { + *aState |= states::SELECTED; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTabsAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTabsAccessible::XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc) + : XULSelectControlAccessible(aContent, aDoc) {} + +role XULTabsAccessible::NativeRole() const { return roles::PAGETABLIST; } + +bool XULTabsAccessible::HasPrimaryAction() const { return false; } + +void XULTabsAccessible::Value(nsString& aValue) const { aValue.Truncate(); } + +ENameValueFlag XULTabsAccessible::NativeName(nsString& aName) const { + // no name + return eNameOK; +} + +void XULTabsAccessible::ApplyARIAState(uint64_t* aState) const { + XULSelectControlAccessible::ApplyARIAState(aState); + // XUL tabs has an implicit ARIA role of tablist, so support + // aria-multiselectable. + MOZ_ASSERT(Elm()); + aria::MapToState(aria::eARIAMultiSelectable, Elm(), aState); +} + +// XUL tabs is a single selection control and doesn't allow ARIA selection. +// However, if aria-multiselectable is used, it becomes a multiselectable +// control, where both native and ARIA markup are used to set selection. +// Therefore, if aria-multiselectable is set, use the base implementation of +// the selection retrieval methods in order to support ARIA selection. +// We don't bother overriding the selection setting methods because +// current front-end code using XUL tabs doesn't support setting of +// aria-selected by the a11y engine and we still want to be able to set the +// primary selected item according to XUL. + +void XULTabsAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { + if (nsAccUtils::IsARIAMultiSelectable(this)) { + AccessibleWrap::SelectedItems(aItems); + } else { + XULSelectControlAccessible::SelectedItems(aItems); + } +} + +Accessible* XULTabsAccessible::GetSelectedItem(uint32_t aIndex) { + if (nsAccUtils::IsARIAMultiSelectable(this)) { + return AccessibleWrap::GetSelectedItem(aIndex); + } + + return XULSelectControlAccessible::GetSelectedItem(aIndex); +} + +uint32_t XULTabsAccessible::SelectedItemCount() { + if (nsAccUtils::IsARIAMultiSelectable(this)) { + return AccessibleWrap::SelectedItemCount(); + } + + return XULSelectControlAccessible::SelectedItemCount(); +} + +bool XULTabsAccessible::IsItemSelected(uint32_t aIndex) { + if (nsAccUtils::IsARIAMultiSelectable(this)) { + return AccessibleWrap::IsItemSelected(aIndex); + } + + return XULSelectControlAccessible::IsItemSelected(aIndex); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTabpanelsAccessible +//////////////////////////////////////////////////////////////////////////////// + +role XULTabpanelsAccessible::NativeRole() const { return roles::PANE; } + +//////////////////////////////////////////////////////////////////////////////// +// XULTabpanelAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTabpanelAccessible::XULTabpanelAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +role XULTabpanelAccessible::NativeRole() const { return roles::PROPERTYPAGE; } + +Relation XULTabpanelAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABELLED_BY) return rel; + + // Expose 'LABELLED_BY' relation on tabpanel accessible for tab accessible. + if (!mContent->GetParent()) return rel; + + nsCOMPtr<nsIDOMXULRelatedElement> tabpanelsElm = + mContent->GetParent()->AsElement()->AsXULRelated(); + if (!tabpanelsElm) return rel; + + RefPtr<mozilla::dom::Element> tabElement; + tabpanelsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabElement)); + if (!tabElement) return rel; + + rel.AppendTarget(mDoc, tabElement); + return rel; +} diff --git a/accessible/xul/XULTabAccessible.h b/accessible/xul/XULTabAccessible.h new file mode 100644 index 0000000000..5ab4138788 --- /dev/null +++ b/accessible/xul/XULTabAccessible.h @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULTabAccessible_h__ +#define mozilla_a11y_XULTabAccessible_h__ + +// NOTE: alphabetically ordered +#include "HyperTextAccessibleWrap.h" +#include "XULMenuAccessible.h" +#include "XULSelectControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * An individual tab, xul:tab element. + */ +class XULTabAccessible : public HyperTextAccessibleWrap { + public: + enum { eAction_Switch = 0 }; + + XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + virtual Relation RelationByType(RelationType aType) const override; + virtual void ApplyARIAState(uint64_t* aState) const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) const override; +}; + +/** + * A container of tab objects, xul:tabs element. + */ +class XULTabsAccessible : public XULSelectControlAccessible { + public: + XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual a11y::role NativeRole() const override; + virtual void ApplyARIAState(uint64_t* aState) const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + + // SelectAccessible + virtual void SelectedItems(nsTArray<Accessible*>* aItems) override; + virtual uint32_t SelectedItemCount() override; + virtual Accessible* GetSelectedItem(uint32_t aIndex) override; + virtual bool IsItemSelected(uint32_t aIndex) override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * A container of tab panels, xul:tabpanels element. + */ +class XULTabpanelsAccessible : public AccessibleWrap { + public: + XULTabpanelsAccessible(nsIContent* aContent, DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mType = eXULTabpanelsType; + } + + // LocalAccessible + virtual a11y::role NativeRole() const override; +}; + +/** + * A tabpanel object, child elements of xul:tabpanels element. + * + * XXX: we need to move the class logic into generic class since + * for example we do not create instance of this class for XUL textbox used as + * a tabpanel. + */ +class XULTabpanelAccessible : public AccessibleWrap { + public: + XULTabpanelAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual Relation RelationByType(RelationType aType) const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULTreeAccessible.cpp b/accessible/xul/XULTreeAccessible.cpp new file mode 100644 index 0000000000..376b0423ca --- /dev/null +++ b/accessible/xul/XULTreeAccessible.cpp @@ -0,0 +1,995 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "XULTreeAccessible.h" + +#include "LocalAccessible-inl.h" +#include "DocAccessible-inl.h" +#include "nsAccCache.h" +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "nsEventShell.h" +#include "DocAccessible.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" +#include "XULTreeGridAccessible.h" +#include "nsQueryObject.h" + +#include "nsComponentManagerUtils.h" +#include "nsIAutoCompletePopup.h" +#include "nsIDOMXULMenuListElement.h" +#include "nsITreeSelection.h" +#include "nsTreeBodyFrame.h" +#include "nsTreeColumns.h" +#include "nsTreeUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/XULTreeElementBinding.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTreeAccessible::XULTreeAccessible(nsIContent* aContent, DocAccessible* aDoc, + nsTreeBodyFrame* aTreeFrame) + : AccessibleWrap(aContent, aDoc), + mAccessibleCache(kDefaultTreeCacheLength) { + mType = eXULTreeType; + mGenericTypes |= eSelect; + + nsCOMPtr<nsITreeView> view = aTreeFrame->GetExistingView(); + mTreeView = view; + + mTree = nsCoreUtils::GetTree(aContent); + NS_ASSERTION(mTree, "Can't get mTree!\n"); + + nsIContent* parentContent = mContent->GetParent(); + if (parentContent) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + do_QueryInterface(parentContent); + if (autoCompletePopupElm) mGenericTypes |= eAutoCompletePopup; + } +} + +XULTreeAccessible::~XULTreeAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: nsISupports and cycle collection implementation + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeAccessible, LocalAccessible, mTree, + mAccessibleCache) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeAccessible) +NS_INTERFACE_MAP_END_INHERITING(LocalAccessible) + +NS_IMPL_ADDREF_INHERITED(XULTreeAccessible, LocalAccessible) +NS_IMPL_RELEASE_INHERITED(XULTreeAccessible, LocalAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: LocalAccessible implementation + +uint64_t XULTreeAccessible::NativeState() const { + // Get focus status from base class. + uint64_t state = LocalAccessible::NativeState(); + + // readonly state + state |= states::READONLY; + + // multiselectable state. + if (!mTreeView) return state; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_TRUE(selection, state); + + bool isSingle = false; + nsresult rv = selection->GetSingle(&isSingle); + NS_ENSURE_SUCCESS(rv, state); + + if (!isSingle) state |= states::MULTISELECTABLE; + + return state; +} + +void XULTreeAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + if (!mTreeView) return; + + // Return the value is the first selected child. + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (!selection) return; + + int32_t currentIndex; + selection->GetCurrentIndex(¤tIndex); + if (currentIndex >= 0) { + RefPtr<nsTreeColumn> keyCol; + + RefPtr<nsTreeColumns> cols = mTree->GetColumns(); + if (cols) keyCol = cols->GetKeyColumn(); + + mTreeView->GetCellText(currentIndex, keyCol, aValue); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: LocalAccessible implementation + +void XULTreeAccessible::Shutdown() { + if (mDoc && !mDoc->IsDefunct()) { + UnbindCacheEntriesFromDocument(mAccessibleCache); + } + + mTree = nullptr; + mTreeView = nullptr; + + AccessibleWrap::Shutdown(); +} + +role XULTreeAccessible::NativeRole() const { + // No primary column means we're in a list. In fact, history and mail turn off + // the primary flag when switching to a flat view. + + nsIContent* child = + nsTreeUtils::GetDescendantChild(mContent, nsGkAtoms::treechildren); + NS_ASSERTION(child, "tree without treechildren!"); + nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame()); + NS_ASSERTION(treeFrame, "xul tree accessible for tree without a frame!"); + if (!treeFrame) return roles::LIST; + + RefPtr<nsTreeColumns> cols = treeFrame->Columns(); + nsTreeColumn* primaryCol = cols->GetPrimaryColumn(); + + return primaryCol ? roles::OUTLINE : roles::LIST; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: LocalAccessible implementation (DON'T put methods here) + +LocalAccessible* XULTreeAccessible::LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) { + nsIFrame* frame = GetFrame(); + if (!frame) return nullptr; + + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + + nsIFrame* rootFrame = presShell->GetRootFrame(); + NS_ENSURE_TRUE(rootFrame, nullptr); + + CSSIntRect rootRect = rootFrame->GetScreenRect(); + + int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.X(); + int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.Y(); + + ErrorResult rv; + dom::TreeCellInfo cellInfo; + mTree->GetCellAt(clientX, clientY, cellInfo, rv); + + // If we failed to find tree cell for the given point then it might be + // tree columns. + if (cellInfo.mRow == -1 || !cellInfo.mCol) { + return AccessibleWrap::LocalChildAtPoint(aX, aY, aWhichChild); + } + + XULTreeItemAccessibleBase* child = GetTreeItemAccessible(cellInfo.mRow); + if (aWhichChild == EWhichChildAtPoint::DeepestChild && child) { + LocalAccessible* cell = child->GetCellAccessible(cellInfo.mCol); + if (cell) { + return cell; + } + } + + return child; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: SelectAccessible + +LocalAccessible* XULTreeAccessible::CurrentItem() const { + if (!mTreeView) return nullptr; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + int32_t currentIndex = -1; + selection->GetCurrentIndex(¤tIndex); + if (currentIndex >= 0) return GetTreeItemAccessible(currentIndex); + } + + return nullptr; +} + +void XULTreeAccessible::SetCurrentItem(const LocalAccessible* aItem) { + NS_ERROR("XULTreeAccessible::SetCurrentItem not implemented"); +} + +void XULTreeAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { + if (!mTreeView) return; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (!selection) return; + + int32_t rangeCount = 0; + selection->GetRangeCount(&rangeCount); + for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) { + int32_t firstIdx = 0, lastIdx = -1; + selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx); + for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) { + LocalAccessible* item = GetTreeItemAccessible(rowIdx); + if (item) aItems->AppendElement(item); + } + } +} + +uint32_t XULTreeAccessible::SelectedItemCount() { + if (!mTreeView) return 0; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + int32_t count = 0; + selection->GetCount(&count); + return count; + } + + return 0; +} + +bool XULTreeAccessible::AddItemToSelection(uint32_t aIndex) { + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected = false; + selection->IsSelected(aIndex, &isSelected); + if (!isSelected) selection->ToggleSelect(aIndex); + + return true; + } + return false; +} + +bool XULTreeAccessible::RemoveItemFromSelection(uint32_t aIndex) { + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected = false; + selection->IsSelected(aIndex, &isSelected); + if (isSelected) selection->ToggleSelect(aIndex); + + return true; + } + return false; +} + +bool XULTreeAccessible::IsItemSelected(uint32_t aIndex) { + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected = false; + selection->IsSelected(aIndex, &isSelected); + return isSelected; + } + return false; +} + +bool XULTreeAccessible::UnselectAll() { + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (!selection) return false; + + selection->ClearSelection(); + return true; +} + +Accessible* XULTreeAccessible::GetSelectedItem(uint32_t aIndex) { + if (!mTreeView) return nullptr; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (!selection) return nullptr; + + uint32_t selCount = 0; + int32_t rangeCount = 0; + selection->GetRangeCount(&rangeCount); + for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) { + int32_t firstIdx = 0, lastIdx = -1; + selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx); + for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) { + if (selCount == aIndex) return GetTreeItemAccessible(rowIdx); + + selCount++; + } + } + + return nullptr; +} + +bool XULTreeAccessible::SelectAll() { + // see if we are multiple select if so set ourselves as such + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool single = false; + selection->GetSingle(&single); + if (!single) { + selection->SelectAll(); + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: LocalAccessible implementation + +LocalAccessible* XULTreeAccessible::LocalChildAt(uint32_t aIndex) const { + uint32_t childCount = LocalAccessible::ChildCount(); + if (aIndex < childCount) { + return LocalAccessible::LocalChildAt(aIndex); + } + + return GetTreeItemAccessible(aIndex - childCount); +} + +uint32_t XULTreeAccessible::ChildCount() const { + // Tree's children count is row count + treecols count. + uint32_t childCount = LocalAccessible::ChildCount(); + if (!mTreeView) return childCount; + + int32_t rowCount = 0; + mTreeView->GetRowCount(&rowCount); + childCount += rowCount; + + return childCount; +} + +Relation XULTreeAccessible::RelationByType(RelationType aType) const { + if (aType == RelationType::NODE_PARENT_OF) { + if (mTreeView) { + return Relation(new XULTreeItemIterator(this, mTreeView, -1)); + } + + return Relation(); + } + + return LocalAccessible::RelationByType(aType); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: Widgets + +bool XULTreeAccessible::IsWidget() const { return true; } + +bool XULTreeAccessible::IsActiveWidget() const { + if (IsAutoCompletePopup()) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + do_QueryInterface(mContent->GetParent()); + + if (autoCompletePopupElm) { + bool isOpen = false; + autoCompletePopupElm->GetPopupOpen(&isOpen); + return isOpen; + } + } + return FocusMgr()->HasDOMFocus(mContent); +} + +bool XULTreeAccessible::AreItemsOperable() const { + if (IsAutoCompletePopup()) { + nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm = + do_QueryInterface(mContent->GetParent()); + + if (autoCompletePopupElm) { + bool isOpen = false; + autoCompletePopupElm->GetPopupOpen(&isOpen); + return isOpen; + } + } + return true; +} + +LocalAccessible* XULTreeAccessible::ContainerWidget() const { return nullptr; } + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: public implementation + +XULTreeItemAccessibleBase* XULTreeAccessible::GetTreeItemAccessible( + int32_t aRow) const { + if (aRow < 0 || IsDefunct() || !mTreeView) return nullptr; + + int32_t rowCount = 0; + nsresult rv = mTreeView->GetRowCount(&rowCount); + if (NS_FAILED(rv) || aRow >= rowCount) return nullptr; + + void* key = reinterpret_cast<void*>(intptr_t(aRow)); + return mAccessibleCache.WithEntryHandle( + key, [&](auto&& entry) -> XULTreeItemAccessibleBase* { + if (entry) { + return entry->get(); + } + + RefPtr<XULTreeItemAccessibleBase> treeItem = + CreateTreeItemAccessible(aRow); + if (treeItem) { + entry.Insert(RefPtr{treeItem}); + Document()->BindToDocument(treeItem, nullptr); + return treeItem.get(); + } + + return nullptr; + }); +} + +void XULTreeAccessible::InvalidateCache(int32_t aRow, int32_t aCount) { + if (IsDefunct()) return; + + if (!mTreeView) { + UnbindCacheEntriesFromDocument(mAccessibleCache); + return; + } + + // Do not invalidate the cache if rows have been inserted. + if (aCount > 0) return; + + DocAccessible* document = Document(); + + // Fire destroy event for removed tree items and delete them from caches. + for (int32_t rowIdx = aRow; rowIdx < aRow - aCount; rowIdx++) { + void* key = reinterpret_cast<void*>(intptr_t(rowIdx)); + XULTreeItemAccessibleBase* treeItem = mAccessibleCache.GetWeak(key); + + if (treeItem) { + RefPtr<AccEvent> event = + new AccEvent(nsIAccessibleEvent::EVENT_HIDE, treeItem); + nsEventShell::FireEvent(event); + + // Unbind from document, shutdown and remove from tree cache. + document->UnbindFromDocument(treeItem); + mAccessibleCache.Remove(key); + } + } + + // We dealt with removed tree items already however we may keep tree items + // having row indexes greater than row count. We should remove these dead tree + // items silently from caches. + int32_t newRowCount = 0; + nsresult rv = mTreeView->GetRowCount(&newRowCount); + if (NS_FAILED(rv)) return; + + int32_t oldRowCount = newRowCount - aCount; + + for (int32_t rowIdx = newRowCount; rowIdx < oldRowCount; ++rowIdx) { + void* key = reinterpret_cast<void*>(intptr_t(rowIdx)); + XULTreeItemAccessibleBase* treeItem = mAccessibleCache.GetWeak(key); + + if (treeItem) { + // Unbind from document, shutdown and remove from tree cache. + document->UnbindFromDocument(treeItem); + mAccessibleCache.Remove(key); + } + } +} + +void XULTreeAccessible::TreeViewInvalidated(int32_t aStartRow, int32_t aEndRow, + int32_t aStartCol, + int32_t aEndCol) { + if (IsDefunct()) return; + + if (!mTreeView) { + UnbindCacheEntriesFromDocument(mAccessibleCache); + return; + } + + int32_t endRow = aEndRow; + + nsresult rv; + if (endRow == -1) { + int32_t rowCount = 0; + rv = mTreeView->GetRowCount(&rowCount); + if (NS_FAILED(rv)) return; + + endRow = rowCount - 1; + } + + RefPtr<nsTreeColumns> treeColumns = mTree->GetColumns(); + if (!treeColumns) return; + + int32_t endCol = aEndCol; + + if (endCol == -1) { + // We need to make sure to cast to int32_t before we do the subtraction, in + // case the column count is 0. + endCol = static_cast<int32_t>(treeColumns->Count()) - 1; + } + + for (int32_t rowIdx = aStartRow; rowIdx <= endRow; ++rowIdx) { + void* key = reinterpret_cast<void*>(intptr_t(rowIdx)); + XULTreeItemAccessibleBase* treeitemAcc = mAccessibleCache.GetWeak(key); + + if (treeitemAcc) { + treeitemAcc->RowInvalidated(aStartCol, endCol); + } + } +} + +void XULTreeAccessible::TreeViewChanged(nsITreeView* aView) { + if (IsDefunct()) return; + + // Fire reorder event on tree accessible on accessible tree (do not fire + // show/hide events on tree items because it can be expensive to fire them for + // each tree item. + RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this); + Document()->FireDelayedEvent(reorderEvent); + + // Clear cache. + UnbindCacheEntriesFromDocument(mAccessibleCache); + + mTreeView = aView; + LocalAccessible* item = CurrentItem(); + if (item) { + FocusMgr()->ActiveItemChanged(item, true); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeAccessible: protected implementation + +already_AddRefed<XULTreeItemAccessibleBase> +XULTreeAccessible::CreateTreeItemAccessible(int32_t aRow) const { + RefPtr<XULTreeItemAccessibleBase> accessible = new XULTreeItemAccessible( + mContent, mDoc, const_cast<XULTreeAccessible*>(this), mTree, mTreeView, + aRow); + + return accessible.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase +//////////////////////////////////////////////////////////////////////////////// + +XULTreeItemAccessibleBase::XULTreeItemAccessibleBase( + nsIContent* aContent, DocAccessible* aDoc, LocalAccessible* aParent, + dom::XULTreeElement* aTree, nsITreeView* aTreeView, int32_t aRow) + : AccessibleWrap(aContent, aDoc), + mTree(aTree), + mTreeView(aTreeView), + mRow(aRow) { + mParent = aParent; + mStateFlags |= eSharedNode; +} + +XULTreeItemAccessibleBase::~XULTreeItemAccessibleBase() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: nsISupports implementation + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase, LocalAccessible, + mTree) + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XULTreeItemAccessibleBase, + LocalAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: LocalAccessible + +Accessible* XULTreeItemAccessibleBase::FocusedChild() { + return FocusMgr()->FocusedLocalAccessible() == this ? this : nullptr; +} + +nsIntRect XULTreeItemAccessibleBase::BoundsInCSSPixels() const { + // Get x coordinate and width from treechildren element, get y coordinate and + // height from tree cell. + + RefPtr<nsTreeColumn> column = nsCoreUtils::GetFirstSensibleColumn(mTree); + + nsresult rv; + nsIntRect rect = mTree->GetCoordsForCellItem(mRow, column, u"cell"_ns, rv); + if (NS_FAILED(rv)) { + return nsIntRect(); + } + + RefPtr<dom::Element> bodyElement = mTree->GetTreeBody(); + if (!bodyElement || !bodyElement->IsXULElement()) { + return nsIntRect(); + } + + rect.width = bodyElement->ClientWidth(); + + nsIFrame* bodyFrame = bodyElement->GetPrimaryFrame(); + if (!bodyFrame) { + return nsIntRect(); + } + + CSSIntRect screenRect = bodyFrame->GetScreenRect(); + rect.x = screenRect.x; + rect.y += screenRect.y; + return rect; +} + +nsRect XULTreeItemAccessibleBase::BoundsInAppUnits() const { + nsIntRect bounds = BoundsInCSSPixels(); + nsPresContext* presContext = mDoc->PresContext(); + return nsRect(presContext->CSSPixelsToAppUnits(bounds.X()), + presContext->CSSPixelsToAppUnits(bounds.Y()), + presContext->CSSPixelsToAppUnits(bounds.Width()), + presContext->CSSPixelsToAppUnits(bounds.Height())); +} + +void XULTreeItemAccessibleBase::SetSelected(bool aSelect) { + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected = false; + selection->IsSelected(mRow, &isSelected); + if (isSelected != aSelect) selection->ToggleSelect(mRow); + } +} + +void XULTreeItemAccessibleBase::TakeFocus() const { + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) selection->SetCurrentIndex(mRow); + + // focus event will be fired here + LocalAccessible::TakeFocus(); +} + +Relation XULTreeItemAccessibleBase::RelationByType(RelationType aType) const { + switch (aType) { + case RelationType::NODE_CHILD_OF: { + int32_t parentIndex = -1; + if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex))) { + return Relation(); + } + + if (parentIndex == -1) return Relation(mParent); + + XULTreeAccessible* treeAcc = mParent->AsXULTree(); + return Relation(treeAcc->GetTreeItemAccessible(parentIndex)); + } + + case RelationType::NODE_PARENT_OF: { + bool isTrue = false; + if (NS_FAILED(mTreeView->IsContainerEmpty(mRow, &isTrue)) || isTrue) { + return Relation(); + } + + if (NS_FAILED(mTreeView->IsContainerOpen(mRow, &isTrue)) || !isTrue) { + return Relation(); + } + + XULTreeAccessible* tree = mParent->AsXULTree(); + return Relation(new XULTreeItemIterator(tree, mTreeView, mRow)); + } + + default: + return Relation(); + } +} + +bool XULTreeItemAccessibleBase::HasPrimaryAction() const { return true; } + +uint8_t XULTreeItemAccessibleBase::ActionCount() const { + // "activate" action is available for all treeitems, "expand/collapse" action + // is avaible for treeitem which is container. + return IsExpandable() ? 2 : 1; +} + +void XULTreeItemAccessibleBase::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) { + aName.AssignLiteral("activate"); + return; + } + + if (aIndex == eAction_Expand && IsExpandable()) { + bool isContainerOpen = false; + mTreeView->IsContainerOpen(mRow, &isContainerOpen); + if (isContainerOpen) { + aName.AssignLiteral("collapse"); + } else { + aName.AssignLiteral("expand"); + } + } +} + +bool XULTreeItemAccessibleBase::DoAction(uint8_t aIndex) const { + if (aIndex != eAction_Click && + (aIndex != eAction_Expand || !IsExpandable())) { + return false; + } + + DoCommand(nullptr, aIndex); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: LocalAccessible implementation + +void XULTreeItemAccessibleBase::Shutdown() { + mTree = nullptr; + mTreeView = nullptr; + mRow = -1; + mParent = nullptr; // null-out to prevent base class's shutdown ops + + AccessibleWrap::Shutdown(); +} + +GroupPos XULTreeItemAccessibleBase::GroupPosition() { + GroupPos groupPos; + + int32_t level; + nsresult rv = mTreeView->GetLevel(mRow, &level); + NS_ENSURE_SUCCESS(rv, groupPos); + + int32_t topCount = 1; + for (int32_t index = mRow - 1; index >= 0; index--) { + int32_t lvl = -1; + if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) { + if (lvl < level) break; + + if (lvl == level) topCount++; + } + } + + int32_t rowCount = 0; + rv = mTreeView->GetRowCount(&rowCount); + NS_ENSURE_SUCCESS(rv, groupPos); + + int32_t bottomCount = 0; + for (int32_t index = mRow + 1; index < rowCount; index++) { + int32_t lvl = -1; + if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) { + if (lvl < level) break; + + if (lvl == level) bottomCount++; + } + } + + groupPos.level = level + 1; + groupPos.setSize = topCount + bottomCount; + groupPos.posInSet = topCount; + + return groupPos; +} + +uint64_t XULTreeItemAccessibleBase::NativeState() const { + // focusable and selectable states + uint64_t state = NativeInteractiveState(); + + // expanded/collapsed state + if (IsExpandable()) { + bool isContainerOpen; + mTreeView->IsContainerOpen(mRow, &isContainerOpen); + state |= isContainerOpen ? states::EXPANDED : states::COLLAPSED; + } + + // selected state + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected; + selection->IsSelected(mRow, &isSelected); + if (isSelected) state |= states::SELECTED; + } + + // focused state + if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED; + + // invisible state + int32_t firstVisibleRow = mTree->GetFirstVisibleRow(); + int32_t lastVisibleRow = mTree->GetLastVisibleRow(); + if (mRow < firstVisibleRow || mRow > lastVisibleRow) { + state |= states::INVISIBLE; + } + + return state; +} + +uint64_t XULTreeItemAccessibleBase::NativeInteractiveState() const { + return states::FOCUSABLE | states::SELECTABLE; +} + +int32_t XULTreeItemAccessibleBase::IndexInParent() const { + return mParent ? mParent->ContentChildCount() + mRow : -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: Widgets + +LocalAccessible* XULTreeItemAccessibleBase::ContainerWidget() const { + return mParent; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: LocalAccessible protected methods + +void XULTreeItemAccessibleBase::DispatchClickEvent( + nsIContent* aContent, uint32_t aActionIndex) const { + if (IsDefunct()) return; + + RefPtr<nsTreeColumns> columns = mTree->GetColumns(); + if (!columns) return; + + // Get column and pseudo element. + RefPtr<nsTreeColumn> column; + nsAutoString pseudoElm; + + if (aActionIndex == eAction_Click) { + // Key column is visible and clickable. + column = columns->GetKeyColumn(); + } else { + // Primary column contains a twisty we should click on. + column = columns->GetPrimaryColumn(); + pseudoElm = u"twisty"_ns; + } + + if (column) { + RefPtr<dom::XULTreeElement> tree = mTree; + nsCoreUtils::DispatchClickEvent(tree, mRow, column, pseudoElm); + } +} + +LocalAccessible* XULTreeItemAccessibleBase::GetSiblingAtOffset( + int32_t aOffset, nsresult* aError) const { + if (aError) *aError = NS_OK; // fail peacefully + + return mParent->LocalChildAt(IndexInParent() + aOffset); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessibleBase: protected implementation + +bool XULTreeItemAccessibleBase::IsExpandable() const { + bool isContainer = false; + mTreeView->IsContainer(mRow, &isContainer); + if (isContainer) { + bool isEmpty = false; + mTreeView->IsContainerEmpty(mRow, &isEmpty); + if (!isEmpty) { + RefPtr<nsTreeColumns> columns = mTree->GetColumns(); + if (columns) { + nsTreeColumn* primaryColumn = columns->GetPrimaryColumn(); + if (primaryColumn && !nsCoreUtils::IsColumnHidden(primaryColumn)) { + return true; + } + } + } + } + + return false; +} + +void XULTreeItemAccessibleBase::GetCellName(nsTreeColumn* aColumn, + nsAString& aName) const { + mTreeView->GetCellText(mRow, aColumn, aName); + + // If there is still no name try the cell value: + // This is for graphical cells. We need tree/table view implementors to + // implement FooView::GetCellValue to return a meaningful string for cases + // where there is something shown in the cell (non-text) such as a star icon; + // in which case GetCellValue for that cell would return "starred" or + // "flagged" for example. + if (aName.IsEmpty()) mTreeView->GetCellValue(mRow, aColumn, aName); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTreeItemAccessible::XULTreeItemAccessible( + nsIContent* aContent, DocAccessible* aDoc, LocalAccessible* aParent, + dom::XULTreeElement* aTree, nsITreeView* aTreeView, int32_t aRow) + : XULTreeItemAccessibleBase(aContent, aDoc, aParent, aTree, aTreeView, + aRow) { + mStateFlags |= eNoKidsFromDOM; + mColumn = nsCoreUtils::GetFirstSensibleColumn(mTree, FlushType::None); + GetCellName(mColumn, mCachedName); +} + +XULTreeItemAccessible::~XULTreeItemAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessible: nsISupports implementation + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessible, + XULTreeItemAccessibleBase, mColumn) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeItemAccessible) +NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase) +NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) +NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessible: nsIAccessible implementation + +ENameValueFlag XULTreeItemAccessible::Name(nsString& aName) const { + aName.Truncate(); + + GetCellName(mColumn, aName); + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessible: LocalAccessible implementation + +void XULTreeItemAccessible::Shutdown() { + mColumn = nullptr; + XULTreeItemAccessibleBase::Shutdown(); +} + +role XULTreeItemAccessible::NativeRole() const { + RefPtr<nsTreeColumns> columns = mTree->GetColumns(); + if (!columns) { + NS_ERROR("No tree columns object in the tree!"); + return roles::NOTHING; + } + + nsTreeColumn* primaryColumn = columns->GetPrimaryColumn(); + + return primaryColumn ? roles::OUTLINEITEM : roles::LISTITEM; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeItemAccessible: XULTreeItemAccessibleBase implementation + +void XULTreeItemAccessible::RowInvalidated(int32_t aStartColIdx, + int32_t aEndColIdx) { + nsAutoString name; + Name(name); + + if (name != mCachedName) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + mCachedName = name; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeColumAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTreeColumAccessible::XULTreeColumAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULColumAccessible(aContent, aDoc) {} + +LocalAccessible* XULTreeColumAccessible::GetSiblingAtOffset( + int32_t aOffset, nsresult* aError) const { + if (aOffset < 0) { + return XULColumAccessible::GetSiblingAtOffset(aOffset, aError); + } + + if (aError) *aError = NS_OK; // fail peacefully + + RefPtr<dom::XULTreeElement> tree = nsCoreUtils::GetTree(mContent); + if (tree) { + nsCOMPtr<nsITreeView> treeView = tree->GetView(FlushType::None); + if (treeView) { + int32_t rowCount = 0; + treeView->GetRowCount(&rowCount); + if (rowCount > 0 && aOffset <= rowCount) { + XULTreeAccessible* treeAcc = LocalParent()->AsXULTree(); + + if (treeAcc) return treeAcc->GetTreeItemAccessible(aOffset - 1); + } + } + } + + return nullptr; +} diff --git a/accessible/xul/XULTreeAccessible.h b/accessible/xul/XULTreeAccessible.h new file mode 100644 index 0000000000..7abc53df42 --- /dev/null +++ b/accessible/xul/XULTreeAccessible.h @@ -0,0 +1,265 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULTreeAccessible_h__ +#define mozilla_a11y_XULTreeAccessible_h__ + +#include "nsITreeView.h" +#include "XULListboxAccessible.h" +#include "mozilla/dom/XULTreeElement.h" + +class nsTreeBodyFrame; +class nsTreeColumn; + +namespace mozilla { +namespace a11y { + +class XULTreeGridCellAccessible; +class XULTreeItemAccessibleBase; + +/* + * A class the represents the XUL Tree widget. + */ +const uint32_t kMaxTreeColumns = 100; +const uint32_t kDefaultTreeCacheLength = 128; + +/** + * LocalAccessible class for XUL tree element. + */ + +class XULTreeAccessible : public AccessibleWrap { + public: + XULTreeAccessible(nsIContent* aContent, DocAccessible* aDoc, + nsTreeBodyFrame* aTreeframe); + + // nsISupports and cycle collection + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeAccessible, LocalAccessible) + + // LocalAccessible + virtual void Shutdown() override; + virtual void Value(nsString& aValue) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual LocalAccessible* LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) override; + + virtual LocalAccessible* LocalChildAt(uint32_t aIndex) const override; + virtual uint32_t ChildCount() const override; + virtual Relation RelationByType(RelationType aType) const override; + + // SelectAccessible + virtual void SelectedItems(nsTArray<Accessible*>* aItems) override; + virtual uint32_t SelectedItemCount() override; + virtual Accessible* GetSelectedItem(uint32_t aIndex) override; + virtual bool IsItemSelected(uint32_t aIndex) override; + virtual bool AddItemToSelection(uint32_t aIndex) override; + virtual bool RemoveItemFromSelection(uint32_t aIndex) override; + virtual bool SelectAll() override; + virtual bool UnselectAll() override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual LocalAccessible* CurrentItem() const override; + virtual void SetCurrentItem(const LocalAccessible* aItem) override; + + virtual LocalAccessible* ContainerWidget() const override; + + // XULTreeAccessible + + /** + * Return tree item accessible at the givem row. If accessible doesn't exist + * in the cache then create and cache it. + * + * @param aRow [in] the given row index + */ + XULTreeItemAccessibleBase* GetTreeItemAccessible(int32_t aRow) const; + + /** + * Invalidates the number of cached treeitem accessibles. + * + * @param aRow [in] row index the invalidation starts from + * @param aCount [in] the number of treeitem accessibles to invalidate, + * the number sign specifies whether rows have been + * inserted (plus) or removed (minus) + */ + void InvalidateCache(int32_t aRow, int32_t aCount); + + /** + * Fires name change events for invalidated area of tree. + * + * @param aStartRow [in] row index invalidation starts from + * @param aEndRow [in] row index invalidation ends, -1 means last row index + * @param aStartCol [in] column index invalidation starts from + * @param aEndCol [in] column index invalidation ends, -1 mens last column + * index + */ + void TreeViewInvalidated(int32_t aStartRow, int32_t aEndRow, + int32_t aStartCol, int32_t aEndCol); + + /** + * Invalidates children created for previous tree view. + */ + void TreeViewChanged(nsITreeView* aView); + + protected: + virtual ~XULTreeAccessible(); + + /** + * Creates tree item accessible for the given row index. + */ + virtual already_AddRefed<XULTreeItemAccessibleBase> CreateTreeItemAccessible( + int32_t aRow) const; + + RefPtr<dom::XULTreeElement> mTree; + nsITreeView* mTreeView; + mutable nsRefPtrHashtable<nsPtrHashKey<const void>, XULTreeItemAccessibleBase> + mAccessibleCache; +}; + +/** + * Base class for tree item accessibles. + */ + +class XULTreeItemAccessibleBase : public AccessibleWrap { + public: + XULTreeItemAccessibleBase(nsIContent* aContent, DocAccessible* aDoc, + LocalAccessible* aParent, + dom::XULTreeElement* aTree, nsITreeView* aTreeView, + int32_t aRow); + + // nsISupports and cycle collection + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeItemAccessibleBase, + AccessibleWrap) + + // LocalAccessible + virtual void Shutdown() override; + virtual nsRect BoundsInAppUnits() const override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual nsIntRect BoundsInCSSPixels() const override; + virtual GroupPos GroupPosition() override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + virtual int32_t IndexInParent() const override; + virtual Relation RelationByType(RelationType aType) const override; + virtual Accessible* FocusedChild() override; + virtual void SetSelected(bool aSelect) override; + virtual void TakeFocus() const override; + + // ActionAccessible + virtual uint8_t ActionCount() const override; + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) const override; + + // Widgets + virtual LocalAccessible* ContainerWidget() const override; + + /** + * Return row index associated with the accessible. + */ + int32_t GetRowIndex() const { return mRow; } + + /** + * Return cell accessible for the given column. If XUL tree accessible is not + * accessible table then return null. + */ + virtual XULTreeGridCellAccessible* GetCellAccessible( + nsTreeColumn* aColumn) const { + return nullptr; + } + + /** + * Proccess row invalidation. Used to fires name change events. + */ + virtual void RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx) = 0; + + protected: + virtual ~XULTreeItemAccessibleBase(); + + enum { eAction_Click = 0, eAction_Expand = 1 }; + + // LocalAccessible + MOZ_CAN_RUN_SCRIPT + virtual void DispatchClickEvent(nsIContent* aContent, + uint32_t aActionIndex) const override; + virtual LocalAccessible* GetSiblingAtOffset( + int32_t aOffset, nsresult* aError = nullptr) const override; + + // XULTreeItemAccessibleBase + + /** + * Return true if the tree item accessible is expandable (contains subrows). + */ + bool IsExpandable() const; + + /** + * Return name for cell at the given column. + */ + void GetCellName(nsTreeColumn* aColumn, nsAString& aName) const; + + RefPtr<dom::XULTreeElement> mTree; + nsITreeView* mTreeView; + int32_t mRow; +}; + +/** + * LocalAccessible class for items for XUL tree. + */ +class XULTreeItemAccessible : public XULTreeItemAccessibleBase { + public: + XULTreeItemAccessible(nsIContent* aContent, DocAccessible* aDoc, + LocalAccessible* aParent, dom::XULTreeElement* aTree, + nsITreeView* aTreeView, int32_t aRow); + + // nsISupports and cycle collection + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeItemAccessible, + XULTreeItemAccessibleBase) + + // LocalAccessible + virtual void Shutdown() override; + virtual ENameValueFlag Name(nsString& aName) const override; + virtual a11y::role NativeRole() const override; + + // XULTreeItemAccessibleBase + virtual void RowInvalidated(int32_t aStartColIdx, + int32_t aEndColIdx) override; + + protected: + virtual ~XULTreeItemAccessible(); + + // XULTreeItemAccessible + RefPtr<nsTreeColumn> mColumn; + nsString mCachedName; +}; + +/** + * LocalAccessible class for columns element of XUL tree. + */ +class XULTreeColumAccessible : public XULColumAccessible { + public: + XULTreeColumAccessible(nsIContent* aContent, DocAccessible* aDoc); + + protected: + // LocalAccessible + virtual LocalAccessible* GetSiblingAtOffset( + int32_t aOffset, nsresult* aError = nullptr) const override; +}; + +//////////////////////////////////////////////////////////////////////////////// +// LocalAccessible downcasting method + +inline XULTreeAccessible* LocalAccessible::AsXULTree() { + return IsXULTree() ? static_cast<XULTreeAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/XULTreeGridAccessible.cpp b/accessible/xul/XULTreeGridAccessible.cpp new file mode 100644 index 0000000000..ad49394701 --- /dev/null +++ b/accessible/xul/XULTreeGridAccessible.cpp @@ -0,0 +1,666 @@ +/* -*- 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 "XULTreeGridAccessible.h" + +#include <stdint.h> +#include "AccAttributes.h" +#include "LocalAccessible-inl.h" +#include "nsAccCache.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "nsEventShell.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" +#include "nsQueryObject.h" +#include "nsTreeColumns.h" + +#include "nsITreeSelection.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TreeColumnBinding.h" +#include "mozilla/dom/XULTreeElementBinding.h" + +using namespace mozilla::a11y; +using namespace mozilla; + +XULTreeGridAccessible::~XULTreeGridAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridAccessible: Table + +uint32_t XULTreeGridAccessible::ColCount() const { + return nsCoreUtils::GetSensibleColumnCount(mTree); +} + +uint32_t XULTreeGridAccessible::RowCount() { + if (!mTreeView) return 0; + + int32_t rowCount = 0; + mTreeView->GetRowCount(&rowCount); + return rowCount >= 0 ? rowCount : 0; +} + +uint32_t XULTreeGridAccessible::SelectedCellCount() { + return SelectedRowCount() * ColCount(); +} + +uint32_t XULTreeGridAccessible::SelectedColCount() { + // If all the row has been selected, then all the columns are selected, + // because we can't select a column alone. + + uint32_t selectedRowCount = SelectedItemCount(); + return selectedRowCount > 0 && selectedRowCount == RowCount() ? ColCount() + : 0; +} + +uint32_t XULTreeGridAccessible::SelectedRowCount() { + return SelectedItemCount(); +} + +void XULTreeGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells) { + uint32_t colCount = ColCount(), rowCount = RowCount(); + + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (IsRowSelected(rowIdx)) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + LocalAccessible* cell = CellAt(rowIdx, colIdx); + aCells->AppendElement(cell); + } + } + } +} + +void XULTreeGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) { + uint32_t colCount = ColCount(), rowCount = RowCount(); + + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (IsRowSelected(rowIdx)) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + aCells->AppendElement(rowIdx * colCount + colIdx); + } + } + } +} + +void XULTreeGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) { + if (RowCount() != SelectedRowCount()) return; + + uint32_t colCount = ColCount(); + aCols->SetCapacity(colCount); + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + aCols->AppendElement(colIdx); + } +} + +void XULTreeGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) { + uint32_t rowCount = RowCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (IsRowSelected(rowIdx)) aRows->AppendElement(rowIdx); + } +} + +LocalAccessible* XULTreeGridAccessible::CellAt(uint32_t aRowIndex, + uint32_t aColumnIndex) { + XULTreeItemAccessibleBase* rowAcc = GetTreeItemAccessible(aRowIndex); + if (!rowAcc) return nullptr; + + RefPtr<nsTreeColumn> column = + nsCoreUtils::GetSensibleColumnAt(mTree, aColumnIndex); + if (!column) return nullptr; + + return rowAcc->GetCellAccessible(column); +} + +void XULTreeGridAccessible::ColDescription(uint32_t aColIdx, + nsString& aDescription) { + aDescription.Truncate(); + + LocalAccessible* treeColumns = LocalAccessible::LocalChildAt(0); + if (treeColumns) { + LocalAccessible* treeColumnItem = treeColumns->LocalChildAt(aColIdx); + if (treeColumnItem) treeColumnItem->Name(aDescription); + } +} + +bool XULTreeGridAccessible::IsColSelected(uint32_t aColIdx) { + // If all the row has been selected, then all the columns are selected. + // Because we can't select a column alone. + return SelectedItemCount() == RowCount(); +} + +bool XULTreeGridAccessible::IsRowSelected(uint32_t aRowIdx) { + if (!mTreeView) return false; + + nsCOMPtr<nsITreeSelection> selection; + nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, false); + + bool isSelected = false; + selection->IsSelected(aRowIdx, &isSelected); + return isSelected; +} + +bool XULTreeGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { + return IsRowSelected(aRowIdx); +} + +int32_t XULTreeGridAccessible::ColIndexAt(uint32_t aCellIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + return -1; // Error: column count is 0 or index out of bounds. + } + + return static_cast<int32_t>(aCellIdx % colCount); +} + +int32_t XULTreeGridAccessible::RowIndexAt(uint32_t aCellIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + return -1; // Error: column count is 0 or index out of bounds. + } + + return static_cast<int32_t>(aCellIdx / colCount); +} + +void XULTreeGridAccessible::RowAndColIndicesAt(uint32_t aCellIdx, + int32_t* aRowIdx, + int32_t* aColIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + *aRowIdx = -1; + *aColIdx = -1; + return; // Error: column count is 0 or index out of bounds. + } + + *aRowIdx = static_cast<int32_t>(aCellIdx / colCount); + *aColIdx = static_cast<int32_t>(aCellIdx % colCount); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridAccessible: LocalAccessible implementation + +role XULTreeGridAccessible::NativeRole() const { + RefPtr<nsTreeColumns> treeColumns = mTree->GetColumns(FlushType::None); + if (!treeColumns) { + NS_ERROR("No treecolumns object for tree!"); + return roles::NOTHING; + } + + nsTreeColumn* primaryColumn = treeColumns->GetPrimaryColumn(); + + return primaryColumn ? roles::TREE_TABLE : roles::TABLE; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridAccessible: XULTreeAccessible implementation + +already_AddRefed<XULTreeItemAccessibleBase> +XULTreeGridAccessible::CreateTreeItemAccessible(int32_t aRow) const { + RefPtr<XULTreeItemAccessibleBase> accessible = new XULTreeGridRowAccessible( + mContent, mDoc, const_cast<XULTreeGridAccessible*>(this), mTree, + mTreeView, aRow); + + return accessible.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridRowAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTreeGridRowAccessible::XULTreeGridRowAccessible( + nsIContent* aContent, DocAccessible* aDoc, LocalAccessible* aTreeAcc, + dom::XULTreeElement* aTree, nsITreeView* aTreeView, int32_t aRow) + : XULTreeItemAccessibleBase(aContent, aDoc, aTreeAcc, aTree, aTreeView, + aRow), + mAccessibleCache(kDefaultTreeCacheLength) { + mGenericTypes |= eTableRow; + mStateFlags |= eNoKidsFromDOM; +} + +XULTreeGridRowAccessible::~XULTreeGridRowAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridRowAccessible: nsISupports and cycle collection implementation + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridRowAccessible, + XULTreeItemAccessibleBase, mAccessibleCache) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeGridRowAccessible) +NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase) + +NS_IMPL_ADDREF_INHERITED(XULTreeGridRowAccessible, XULTreeItemAccessibleBase) +NS_IMPL_RELEASE_INHERITED(XULTreeGridRowAccessible, XULTreeItemAccessibleBase) + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridRowAccessible: LocalAccessible implementation + +void XULTreeGridRowAccessible::Shutdown() { + if (mDoc && !mDoc->IsDefunct()) { + UnbindCacheEntriesFromDocument(mAccessibleCache); + } + + XULTreeItemAccessibleBase::Shutdown(); +} + +role XULTreeGridRowAccessible::NativeRole() const { return roles::ROW; } + +ENameValueFlag XULTreeGridRowAccessible::Name(nsString& aName) const { + aName.Truncate(); + + // XXX: the row name sholdn't be a concatenation of cell names (bug 664384). + RefPtr<nsTreeColumn> column = nsCoreUtils::GetFirstSensibleColumn(mTree); + while (column) { + if (!aName.IsEmpty()) aName.Append(' '); + + nsAutoString cellName; + GetCellName(column, cellName); + aName.Append(cellName); + + column = nsCoreUtils::GetNextSensibleColumn(column); + } + + return eNameOK; +} + +LocalAccessible* XULTreeGridRowAccessible::LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) { + nsIFrame* frame = GetFrame(); + if (!frame) return nullptr; + + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + + nsIFrame* rootFrame = presShell->GetRootFrame(); + NS_ENSURE_TRUE(rootFrame, nullptr); + + CSSIntRect rootRect = rootFrame->GetScreenRect(); + + int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.X(); + int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.Y(); + + ErrorResult rv; + dom::TreeCellInfo cellInfo; + mTree->GetCellAt(clientX, clientY, cellInfo, rv); + + // Return if we failed to find tree cell in the row for the given point. + if (cellInfo.mRow != mRow || !cellInfo.mCol) return nullptr; + + return GetCellAccessible(cellInfo.mCol); +} + +LocalAccessible* XULTreeGridRowAccessible::LocalChildAt(uint32_t aIndex) const { + if (IsDefunct()) return nullptr; + + RefPtr<nsTreeColumn> column = nsCoreUtils::GetSensibleColumnAt(mTree, aIndex); + if (!column) return nullptr; + + return GetCellAccessible(column); +} + +uint32_t XULTreeGridRowAccessible::ChildCount() const { + return nsCoreUtils::GetSensibleColumnCount(mTree); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridRowAccessible: XULTreeItemAccessibleBase implementation + +XULTreeGridCellAccessible* XULTreeGridRowAccessible::GetCellAccessible( + nsTreeColumn* aColumn) const { + MOZ_ASSERT(aColumn, "No tree column!"); + + void* key = static_cast<void*>(aColumn); + XULTreeGridCellAccessible* cachedCell = mAccessibleCache.GetWeak(key); + if (cachedCell) return cachedCell; + + RefPtr<XULTreeGridCellAccessible> cell = new XULTreeGridCellAccessible( + mContent, mDoc, const_cast<XULTreeGridRowAccessible*>(this), mTree, + mTreeView, mRow, aColumn); + mAccessibleCache.InsertOrUpdate(key, RefPtr{cell}); + Document()->BindToDocument(cell, nullptr); + return cell; +} + +void XULTreeGridRowAccessible::RowInvalidated(int32_t aStartColIdx, + int32_t aEndColIdx) { + RefPtr<nsTreeColumns> treeColumns = mTree->GetColumns(FlushType::None); + if (!treeColumns) return; + + bool nameChanged = false; + for (int32_t colIdx = aStartColIdx; colIdx <= aEndColIdx; ++colIdx) { + nsTreeColumn* column = treeColumns->GetColumnAt(colIdx); + if (column && !nsCoreUtils::IsColumnHidden(column)) { + XULTreeGridCellAccessible* cell = GetCellAccessible(column); + if (cell) nameChanged |= cell->CellInvalidated(); + } + } + + if (nameChanged) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULTreeGridCellAccessible::XULTreeGridCellAccessible( + nsIContent* aContent, DocAccessible* aDoc, + XULTreeGridRowAccessible* aRowAcc, dom::XULTreeElement* aTree, + nsITreeView* aTreeView, int32_t aRow, nsTreeColumn* aColumn) + : LeafAccessible(aContent, aDoc), + mTree(aTree), + mTreeView(aTreeView), + mRow(aRow), + mColumn(aColumn) { + mParent = aRowAcc; + mStateFlags |= eSharedNode; + mGenericTypes |= eTableCell; + + NS_ASSERTION(mTreeView, "mTreeView is null"); + + if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { + mTreeView->GetCellValue(mRow, mColumn, mCachedTextEquiv); + } else { + mTreeView->GetCellText(mRow, mColumn, mCachedTextEquiv); + } +} + +XULTreeGridCellAccessible::~XULTreeGridCellAccessible() {} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: nsISupports implementation + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible, LeafAccessible, + mTree, mColumn) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULTreeGridCellAccessible) +NS_INTERFACE_MAP_END_INHERITING(LeafAccessible) +NS_IMPL_ADDREF_INHERITED(XULTreeGridCellAccessible, LeafAccessible) +NS_IMPL_RELEASE_INHERITED(XULTreeGridCellAccessible, LeafAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: LocalAccessible + +void XULTreeGridCellAccessible::Shutdown() { + mTree = nullptr; + mTreeView = nullptr; + mRow = -1; + mColumn = nullptr; + mParent = nullptr; // null-out to prevent base class's shutdown ops + + LeafAccessible::Shutdown(); +} + +Accessible* XULTreeGridCellAccessible::FocusedChild() { return nullptr; } + +ENameValueFlag XULTreeGridCellAccessible::Name(nsString& aName) const { + aName.Truncate(); + + if (!mTreeView) return eNameOK; + + mTreeView->GetCellText(mRow, mColumn, aName); + + // If there is still no name try the cell value: + // This is for graphical cells. We need tree/table view implementors to + // implement FooView::GetCellValue to return a meaningful string for cases + // where there is something shown in the cell (non-text) such as a star icon; + // in which case GetCellValue for that cell would return "starred" or + // "flagged" for example. + if (aName.IsEmpty()) mTreeView->GetCellValue(mRow, mColumn, aName); + + return eNameOK; +} + +nsIntRect XULTreeGridCellAccessible::BoundsInCSSPixels() const { + // Get bounds for tree cell and add x and y of treechildren element to + // x and y of the cell. + nsresult rv; + nsIntRect rect = mTree->GetCoordsForCellItem(mRow, mColumn, u"cell"_ns, rv); + if (NS_FAILED(rv)) { + return nsIntRect(); + } + + RefPtr<dom::Element> bodyElement = mTree->GetTreeBody(); + if (!bodyElement || !bodyElement->IsXULElement()) { + return nsIntRect(); + } + + nsIFrame* bodyFrame = bodyElement->GetPrimaryFrame(); + if (!bodyFrame) { + return nsIntRect(); + } + + CSSIntRect screenRect = bodyFrame->GetScreenRect(); + rect.x += screenRect.x; + rect.y += screenRect.y; + return rect; +} + +nsRect XULTreeGridCellAccessible::BoundsInAppUnits() const { + nsIntRect bounds = BoundsInCSSPixels(); + nsPresContext* presContext = mDoc->PresContext(); + return nsRect(presContext->CSSPixelsToAppUnits(bounds.X()), + presContext->CSSPixelsToAppUnits(bounds.Y()), + presContext->CSSPixelsToAppUnits(bounds.Width()), + presContext->CSSPixelsToAppUnits(bounds.Height())); +} + +bool XULTreeGridCellAccessible::HasPrimaryAction() const { + return mColumn->Cycler() || + (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && + IsEditable()); +} + +void XULTreeGridCellAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + aName.Truncate(); + + if (aIndex != eAction_Click || !mTreeView) return; + + if (mColumn->Cycler()) { + aName.AssignLiteral("cycle"); + return; + } + + if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && + IsEditable()) { + nsAutoString value; + mTreeView->GetCellValue(mRow, mColumn, value); + if (value.EqualsLiteral("true")) { + aName.AssignLiteral("uncheck"); + } else { + aName.AssignLiteral("check"); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: TableCell + +TableAccessible* XULTreeGridCellAccessible::Table() const { + LocalAccessible* grandParent = mParent->LocalParent(); + if (grandParent) return grandParent->AsTable(); + + return nullptr; +} + +uint32_t XULTreeGridCellAccessible::ColIdx() const { + uint32_t colIdx = 0; + RefPtr<nsTreeColumn> column = mColumn; + while ((column = nsCoreUtils::GetPreviousSensibleColumn(column))) colIdx++; + + return colIdx; +} + +uint32_t XULTreeGridCellAccessible::RowIdx() const { return mRow; } + +void XULTreeGridCellAccessible::ColHeaderCells( + nsTArray<Accessible*>* aHeaderCells) { + dom::Element* columnElm = mColumn->Element(); + + LocalAccessible* headerCell = mDoc->GetAccessible(columnElm); + if (headerCell) aHeaderCells->AppendElement(headerCell); +} + +bool XULTreeGridCellAccessible::Selected() { + nsCOMPtr<nsITreeSelection> selection; + nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, false); + + bool selected = false; + selection->IsSelected(mRow, &selected); + return selected; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: LocalAccessible public implementation + +already_AddRefed<AccAttributes> XULTreeGridCellAccessible::NativeAttributes() { + RefPtr<AccAttributes> attributes = new AccAttributes(); + + // "table-cell-index" attribute + TableAccessible* table = Table(); + if (!table) return attributes.forget(); + + attributes->SetAttribute(nsGkAtoms::tableCellIndex, + table->CellIndexAt(mRow, ColIdx())); + + // "cycles" attribute + if (mColumn->Cycler()) { + attributes->SetAttribute(nsGkAtoms::cycles, true); + } + + return attributes.forget(); +} + +role XULTreeGridCellAccessible::NativeRole() const { return roles::GRID_CELL; } + +uint64_t XULTreeGridCellAccessible::NativeState() const { + if (!mTreeView) return states::DEFUNCT; + + // selectable/selected state + uint64_t states = + states::SELECTABLE; // keep in sync with NativeInteractiveState + + nsCOMPtr<nsITreeSelection> selection; + mTreeView->GetSelection(getter_AddRefs(selection)); + if (selection) { + bool isSelected = false; + selection->IsSelected(mRow, &isSelected); + if (isSelected) states |= states::SELECTED; + } + + // checked state + if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { + states |= states::CHECKABLE; + nsAutoString checked; + mTreeView->GetCellValue(mRow, mColumn, checked); + if (checked.EqualsIgnoreCase("true")) states |= states::CHECKED; + } + + return states; +} + +uint64_t XULTreeGridCellAccessible::NativeInteractiveState() const { + return states::SELECTABLE; +} + +int32_t XULTreeGridCellAccessible::IndexInParent() const { return ColIdx(); } + +Relation XULTreeGridCellAccessible::RelationByType(RelationType aType) const { + return Relation(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: public implementation + +bool XULTreeGridCellAccessible::CellInvalidated() { + nsAutoString textEquiv; + + if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX) { + mTreeView->GetCellValue(mRow, mColumn, textEquiv); + if (mCachedTextEquiv != textEquiv) { + bool isEnabled = textEquiv.EqualsLiteral("true"); + RefPtr<AccEvent> accEvent = + new AccStateChangeEvent(this, states::CHECKED, isEnabled); + nsEventShell::FireEvent(accEvent); + + mCachedTextEquiv = textEquiv; + return true; + } + + return false; + } + + mTreeView->GetCellText(mRow, mColumn, textEquiv); + if (mCachedTextEquiv != textEquiv) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + mCachedTextEquiv = textEquiv; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: LocalAccessible protected implementation + +LocalAccessible* XULTreeGridCellAccessible::GetSiblingAtOffset( + int32_t aOffset, nsresult* aError) const { + if (aError) *aError = NS_OK; // fail peacefully + + RefPtr<nsTreeColumn> columnAtOffset(mColumn), column; + if (aOffset < 0) { + for (int32_t index = aOffset; index < 0 && columnAtOffset; index++) { + column = nsCoreUtils::GetPreviousSensibleColumn(columnAtOffset); + column.swap(columnAtOffset); + } + } else { + for (int32_t index = aOffset; index > 0 && columnAtOffset; index--) { + column = nsCoreUtils::GetNextSensibleColumn(columnAtOffset); + column.swap(columnAtOffset); + } + } + + if (!columnAtOffset) return nullptr; + + XULTreeItemAccessibleBase* rowAcc = + static_cast<XULTreeItemAccessibleBase*>(LocalParent()); + return rowAcc->GetCellAccessible(columnAtOffset); +} + +void XULTreeGridCellAccessible::DispatchClickEvent( + nsIContent* aContent, uint32_t aActionIndex) const { + if (IsDefunct()) return; + + RefPtr<dom::XULTreeElement> tree = mTree; + RefPtr<nsTreeColumn> column = mColumn; + nsCoreUtils::DispatchClickEvent(tree, mRow, column); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessible: protected implementation + +bool XULTreeGridCellAccessible::IsEditable() const { + // XXX: logic corresponds to tree.xml, it's preferable to have interface + // method to check it. + bool isEditable = false; + nsresult rv = mTreeView->IsEditable(mRow, mColumn, &isEditable); + if (NS_FAILED(rv) || !isEditable) return false; + + dom::Element* columnElm = mColumn->Element(); + + if (!columnElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, + nsGkAtoms::_true, eCaseMatters)) { + return false; + } + + return mContent->AsElement()->AttrValueIs( + kNameSpaceID_None, nsGkAtoms::editable, nsGkAtoms::_true, eCaseMatters); +} diff --git a/accessible/xul/XULTreeGridAccessible.h b/accessible/xul/XULTreeGridAccessible.h new file mode 100644 index 0000000000..cfb314d30e --- /dev/null +++ b/accessible/xul/XULTreeGridAccessible.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- 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/. */ + +#ifndef mozilla_a11y_XULTreeGridAccessible_h__ +#define mozilla_a11y_XULTreeGridAccessible_h__ + +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "XULTreeAccessible.h" + +namespace mozilla { +namespace a11y { + +class XULTreeGridCellAccessible; + +/** + * Represents accessible for XUL tree in the case when it has multiple columns. + */ +class XULTreeGridAccessible : public XULTreeAccessible, public TableAccessible { + public: + XULTreeGridAccessible(nsIContent* aContent, DocAccessible* aDoc, + nsTreeBodyFrame* aTreeFrame) + : XULTreeAccessible(aContent, aDoc, aTreeFrame) { + mGenericTypes |= eTable; + } + + // TableAccessible + virtual uint32_t ColCount() const override; + virtual uint32_t RowCount() override; + virtual LocalAccessible* CellAt(uint32_t aRowIndex, + uint32_t aColumnIndex) override; + virtual void ColDescription(uint32_t aColIdx, + nsString& aDescription) override; + virtual bool IsColSelected(uint32_t aColIdx) override; + virtual bool IsRowSelected(uint32_t aRowIdx) override; + virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual uint32_t SelectedCellCount() override; + virtual uint32_t SelectedColCount() override; + virtual uint32_t SelectedRowCount() override; + virtual void SelectedCells(nsTArray<Accessible*>* aCells) override; + virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override; + virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override; + virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override; + virtual LocalAccessible* AsAccessible() override { return this; } + + virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override { + return static_cast<int32_t>(ColCount() * aRowIdx + aColIdx); + } + + virtual int32_t ColIndexAt(uint32_t aCellIdx) override; + virtual int32_t RowIndexAt(uint32_t aCellIdx) override; + virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) override; + + // LocalAccessible + virtual TableAccessible* AsTable() override { return this; } + virtual a11y::role NativeRole() const override; + + protected: + virtual ~XULTreeGridAccessible(); + + // XULTreeAccessible + virtual already_AddRefed<XULTreeItemAccessibleBase> CreateTreeItemAccessible( + int32_t aRow) const override; +}; + +/** + * Represents accessible for XUL tree item in the case when XUL tree has + * multiple columns. + */ +class XULTreeGridRowAccessible final : public XULTreeItemAccessibleBase { + public: + using LocalAccessible::LocalChildAt; + + XULTreeGridRowAccessible(nsIContent* aContent, DocAccessible* aDoc, + LocalAccessible* aParent, dom::XULTreeElement* aTree, + nsITreeView* aTreeView, int32_t aRow); + + // nsISupports and cycle collection + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeGridRowAccessible, + XULTreeItemAccessibleBase) + + // LocalAccessible + virtual void Shutdown() override; + virtual a11y::role NativeRole() const override; + virtual ENameValueFlag Name(nsString& aName) const override; + virtual LocalAccessible* LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) override; + + virtual LocalAccessible* LocalChildAt(uint32_t aIndex) const override; + virtual uint32_t ChildCount() const override; + + // XULTreeItemAccessibleBase + XULTreeGridCellAccessible* GetCellAccessible( + nsTreeColumn* aColumn) const final; + virtual void RowInvalidated(int32_t aStartColIdx, + int32_t aEndColIdx) override; + + protected: + virtual ~XULTreeGridRowAccessible(); + + // XULTreeItemAccessibleBase + mutable nsRefPtrHashtable<nsPtrHashKey<const void>, XULTreeGridCellAccessible> + mAccessibleCache; +}; + +/** + * Represents an accessible for XUL tree cell in the case when XUL tree has + * multiple columns. + */ + +class XULTreeGridCellAccessible : public LeafAccessible, + public TableCellAccessible { + public: + XULTreeGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc, + XULTreeGridRowAccessible* aRowAcc, + dom::XULTreeElement* aTree, nsITreeView* aTreeView, + int32_t aRow, nsTreeColumn* aColumn); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeGridCellAccessible, + LeafAccessible) + + // LocalAccessible + virtual void Shutdown() override; + virtual TableCellAccessible* AsTableCell() override { return this; } + virtual nsRect BoundsInAppUnits() const override; + virtual nsIntRect BoundsInCSSPixels() const override; + virtual ENameValueFlag Name(nsString& aName) const override; + virtual Accessible* FocusedChild() override; + virtual already_AddRefed<AccAttributes> NativeAttributes() override; + virtual int32_t IndexInParent() const override; + virtual Relation RelationByType(RelationType aType) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // TableCellAccessible + virtual TableAccessible* Table() const override; + virtual uint32_t ColIdx() const override; + virtual uint32_t RowIdx() const override; + virtual void ColHeaderCells(nsTArray<Accessible*>* aHeaderCells) override; + virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) override {} + virtual bool Selected() override; + + /** + * Fire name or state change event if the accessible text or value has been + * changed. + * @return true if name has changed + */ + bool CellInvalidated(); + + protected: + virtual ~XULTreeGridCellAccessible(); + + // LocalAccessible + virtual LocalAccessible* GetSiblingAtOffset( + int32_t aOffset, nsresult* aError = nullptr) const override; + MOZ_CAN_RUN_SCRIPT + virtual void DispatchClickEvent(nsIContent* aContent, + uint32_t aActionIndex) const override; + + // XULTreeGridCellAccessible + + /** + * Return true if value of cell can be modified. + */ + bool IsEditable() const; + + enum { eAction_Click = 0 }; + + RefPtr<dom::XULTreeElement> mTree; + nsITreeView* mTreeView; + + int32_t mRow; + RefPtr<nsTreeColumn> mColumn; + + nsString mCachedTextEquiv; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/xul/moz.build b/accessible/xul/moz.build new file mode 100644 index 0000000000..4fccfff6e0 --- /dev/null +++ b/accessible/xul/moz.build @@ -0,0 +1,56 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "XULAlertAccessible.cpp", + "XULComboboxAccessible.cpp", + "XULElementAccessibles.cpp", + "XULFormControlAccessible.cpp", + "XULListboxAccessible.cpp", + "XULMenuAccessible.cpp", + "XULSelectControlAccessible.cpp", + "XULTabAccessible.cpp", + "XULTreeAccessible.cpp", + "XULTreeGridAccessible.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/xpcom", + "/dom/base", + "/dom/xul", + "/layout/generic", + "/layout/xul", + "/layout/xul/tree", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + LOCAL_INCLUDES += [ + "/accessible/atk", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + LOCAL_INCLUDES += [ + "/accessible/windows/ia2", + "/accessible/windows/msaa", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/accessible/mac", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/accessible/android", + ] +else: + LOCAL_INCLUDES += [ + "/accessible/other", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |