diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/html/HTMLCanvasAccessible.cpp | 16 | ||||
-rw-r--r-- | accessible/html/HTMLCanvasAccessible.h | 35 | ||||
-rw-r--r-- | accessible/html/HTMLElementAccessibles.cpp | 233 | ||||
-rw-r--r-- | accessible/html/HTMLElementAccessibles.h | 160 | ||||
-rw-r--r-- | accessible/html/HTMLFormControlAccessible.cpp | 981 | ||||
-rw-r--r-- | accessible/html/HTMLFormControlAccessible.h | 382 | ||||
-rw-r--r-- | accessible/html/HTMLImageMapAccessible.cpp | 211 | ||||
-rw-r--r-- | accessible/html/HTMLImageMapAccessible.h | 85 | ||||
-rw-r--r-- | accessible/html/HTMLLinkAccessible.cpp | 134 | ||||
-rw-r--r-- | accessible/html/HTMLLinkAccessible.h | 62 | ||||
-rw-r--r-- | accessible/html/HTMLListAccessible.cpp | 115 | ||||
-rw-r--r-- | accessible/html/HTMLListAccessible.h | 87 | ||||
-rw-r--r-- | accessible/html/HTMLSelectAccessible.cpp | 474 | ||||
-rw-r--r-- | accessible/html/HTMLSelectAccessible.h | 216 | ||||
-rw-r--r-- | accessible/html/HTMLTableAccessible.cpp | 896 | ||||
-rw-r--r-- | accessible/html/HTMLTableAccessible.h | 241 | ||||
-rw-r--r-- | accessible/html/moz.build | 52 |
17 files changed, 4380 insertions, 0 deletions
diff --git a/accessible/html/HTMLCanvasAccessible.cpp b/accessible/html/HTMLCanvasAccessible.cpp new file mode 100644 index 0000000000..91e3f1c57d --- /dev/null +++ b/accessible/html/HTMLCanvasAccessible.cpp @@ -0,0 +1,16 @@ +/* -*- 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 "HTMLCanvasAccessible.h" + +#include "Role.h" + +using namespace mozilla::a11y; + +HTMLCanvasAccessible::HTMLCanvasAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +role HTMLCanvasAccessible::NativeRole() const { return roles::CANVAS; } diff --git a/accessible/html/HTMLCanvasAccessible.h b/accessible/html/HTMLCanvasAccessible.h new file mode 100644 index 0000000000..fc38906652 --- /dev/null +++ b/accessible/html/HTMLCanvasAccessible.h @@ -0,0 +1,35 @@ +/* -*- 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_HTMLCanvasAccessible_h__ +#define mozilla_a11y_HTMLCanvasAccessible_h__ + +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +/** + * HTML canvas accessible (html:canvas). + */ +class HTMLCanvasAccessible : public HyperTextAccessibleWrap { + public: + HTMLCanvasAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLCanvasAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + protected: + virtual ~HTMLCanvasAccessible() {} +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLElementAccessibles.cpp b/accessible/html/HTMLElementAccessibles.cpp new file mode 100644 index 0000000000..518506fe75 --- /dev/null +++ b/accessible/html/HTMLElementAccessibles.cpp @@ -0,0 +1,233 @@ +/* -*- 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 "HTMLElementAccessibles.h" + +#include "CacheConstants.h" +#include "DocAccessible.h" +#include "nsAccUtils.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +#include "mozilla/dom/HTMLLabelElement.h" +#include "mozilla/dom/HTMLDetailsElement.h" +#include "mozilla/dom/HTMLSummaryElement.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLHRAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLHRAccessible::NativeRole() const { return roles::SEPARATOR; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLBRAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLBRAccessible::NativeRole() const { return roles::WHITESPACE; } + +uint64_t HTMLBRAccessible::NativeState() const { return states::READONLY; } + +ENameValueFlag HTMLBRAccessible::NativeName(nsString& aName) const { + aName = static_cast<char16_t>('\n'); // Newline char + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLabelAccessible +//////////////////////////////////////////////////////////////////////////////// + +ENameValueFlag HTMLLabelAccessible::NativeName(nsString& aName) const { + nsTextEquivUtils::GetNameFromSubtree(this, aName); + return aName.IsEmpty() ? eNameOK : eNameFromSubtree; +} + +Relation HTMLLabelAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABEL_FOR) { + dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromNode(mContent); + rel.AppendTarget(mDoc, label->GetControl()); + } + + return rel; +} + +void HTMLLabelAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::_for) { + mDoc->QueueCacheUpdate(this, CacheDomain::Relations); + SendCache(CacheDomain::Actions, CacheUpdateType::Update); + } +} + +bool HTMLLabelAccessible::HasPrimaryAction() const { + return nsCoreUtils::IsLabelWithControl(mContent); +} + +void HTMLLabelAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == 0) { + if (HasPrimaryAction()) { + aName.AssignLiteral("click"); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLOuputAccessible +//////////////////////////////////////////////////////////////////////////////// + +Relation HTMLOutputAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::CONTROLLED_BY) { + rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for)); + } + + return rel; +} + +void HTMLOutputAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::_for) { + mDoc->QueueCacheUpdate(this, CacheDomain::Relations); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLSummaryAccessible::HTMLSummaryAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mGenericTypes |= eButton; +} + +bool HTMLSummaryAccessible::HasPrimaryAction() const { return true; } + +void HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex != eAction_Click) { + return; + } + + dom::HTMLSummaryElement* summary = + dom::HTMLSummaryElement::FromNode(mContent); + if (!summary) { + return; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return; + } + + if (details->Open()) { + aName.AssignLiteral("collapse"); + } else { + aName.AssignLiteral("expand"); + } +} + +uint64_t HTMLSummaryAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + dom::HTMLSummaryElement* summary = + dom::HTMLSummaryElement::FromNode(mContent); + if (!summary) { + return state; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return state; + } + + if (details->Open()) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + + return state; +} + +HTMLSummaryAccessible* HTMLSummaryAccessible::FromDetails( + LocalAccessible* details) { + if (!dom::HTMLDetailsElement::FromNodeOrNull(details->GetContent())) { + return nullptr; + } + + HTMLSummaryAccessible* summaryAccessible = nullptr; + for (uint32_t i = 0; i < details->ChildCount(); i++) { + // Iterate through the children of our details accessible to locate main + // summary. This iteration includes the anonymous summary if the details + // element was not explicitly created with one. + LocalAccessible* child = details->LocalChildAt(i); + auto* summary = + mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child->GetContent()); + if (summary && summary->IsMainSummary()) { + summaryAccessible = static_cast<HTMLSummaryAccessible*>(child); + break; + } + } + + return summaryAccessible; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible: Widgets + +bool HTMLSummaryAccessible::IsWidget() const { return true; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLHeaderOrFooterAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLHeaderOrFooterAccessible::NativeRole() const { + // Only map header and footer if they are direct descendants of the body tag. + // If other sectioning or sectioning root elements, they become sections. + nsIContent* parent = mContent->GetParent(); + while (parent) { + if (parent->IsAnyOfHTMLElements( + nsGkAtoms::article, nsGkAtoms::aside, nsGkAtoms::nav, + nsGkAtoms::section, nsGkAtoms::main, nsGkAtoms::blockquote, + nsGkAtoms::details, nsGkAtoms::dialog, nsGkAtoms::fieldset, + nsGkAtoms::figure, nsGkAtoms::td)) { + break; + } + parent = parent->GetParent(); + } + + // No sectioning or sectioning root elements found. + if (!parent) { + return roles::LANDMARK; + } + + return roles::SECTION; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSectionAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLSectionAccessible::NativeRole() const { + nsAutoString name; + const_cast<HTMLSectionAccessible*>(this)->Name(name); + return name.IsEmpty() ? roles::SECTION : roles::REGION; +} diff --git a/accessible/html/HTMLElementAccessibles.h b/accessible/html/HTMLElementAccessibles.h new file mode 100644 index 0000000000..b45e945912 --- /dev/null +++ b/accessible/html/HTMLElementAccessibles.h @@ -0,0 +1,160 @@ +/* -*- 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_HTMLElementAccessibles_h__ +#define mozilla_a11y_HTMLElementAccessibles_h__ + +#include "BaseAccessibles.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for HTML hr element. + */ +class HTMLHRAccessible : public LeafAccessible { + public: + HTMLHRAccessible(nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; +}; + +/** + * Used for HTML br element. + */ +class HTMLBRAccessible : public LeafAccessible { + public: + HTMLBRAccessible(nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + mType = eHTMLBRType; + mGenericTypes |= eText; + } + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Used for HTML label element. + */ +class HTMLLabelAccessible : public HyperTextAccessibleWrap { + public: + HTMLLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLLabelAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual Relation RelationByType(RelationType aType) const override; + + // ActionAccessible + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool HasPrimaryAction() const override; + + protected: + virtual ~HTMLLabelAccessible() {} + virtual ENameValueFlag NativeName(nsString& aName) const override; + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; +}; + +/** + * Used for HTML output element. + */ +class HTMLOutputAccessible : public HyperTextAccessibleWrap { + public: + HTMLOutputAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLOutputAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual Relation RelationByType(RelationType aType) const override; + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + protected: + virtual ~HTMLOutputAccessible() {} +}; + +/** + * Accessible for the HTML summary element. + */ +class HTMLSummaryAccessible : public HyperTextAccessibleWrap { + public: + enum { eAction_Click = 0 }; + + HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Check that the given LocalAccessible belongs to a details frame. + // If so, find and return the accessible for the detail frame's + // main summary. + static HTMLSummaryAccessible* FromDetails(LocalAccessible* aDetails); + + // LocalAccessible + virtual uint64_t NativeState() const override; + + // ActionAccessible + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool HasPrimaryAction() const override; + + // Widgets + virtual bool IsWidget() const override; +}; + +/** + * Used for HTML header and footer elements. + */ +class HTMLHeaderOrFooterAccessible : public HyperTextAccessibleWrap { + public: + HTMLHeaderOrFooterAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLHeaderOrFooterAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + protected: + virtual ~HTMLHeaderOrFooterAccessible() {} +}; + +/** + * Used for HTML section element. + */ +class HTMLSectionAccessible : public HyperTextAccessibleWrap { + public: + HTMLSectionAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLSectionAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + protected: + virtual ~HTMLSectionAccessible() = default; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLFormControlAccessible.cpp b/accessible/html/HTMLFormControlAccessible.cpp new file mode 100644 index 0000000000..a632a31513 --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.cpp @@ -0,0 +1,981 @@ +/* -*- 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 "HTMLFormControlAccessible.h" + +#include "CacheConstants.h" +#include "DocAccessible-inl.h" +#include "LocalAccessible-inl.h" +#include "nsAccUtils.h" +#include "nsEventShell.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +#include "nsContentList.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLTextAreaElement.h" +#include "nsIFormControl.h" +#include "nsITextControlFrame.h" +#include "nsNameSpaceManager.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/EditorBase.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEditor.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFormAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLFormAccessible::NativeRole() const { + nsAutoString name; + const_cast<HTMLFormAccessible*>(this)->Name(name); + return name.IsEmpty() ? roles::FORM : roles::FORM_LANDMARK; +} + +void HTMLFormAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + if (aAttribute == nsGkAtoms::autocomplete) { + dom::HTMLFormElement* formEl = dom::HTMLFormElement::FromNode(mContent); + + nsIHTMLCollection* controls = formEl->Elements(); + uint32_t length = controls->Length(); + for (uint32_t i = 0; i < length; i++) { + if (LocalAccessible* acc = mDoc->GetAccessible(controls->Item(i))) { + if (acc->IsTextField() && !acc->IsPassword()) { + if (!acc->Elm()->HasAttr(nsGkAtoms::list_) && + !acc->Elm()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::autocomplete, nsGkAtoms::OFF, + eIgnoreCase)) { + RefPtr<AccEvent> stateChangeEvent = + new AccStateChangeEvent(acc, states::SUPPORTS_AUTOCOMPLETION); + mDoc->FireDelayedEvent(stateChangeEvent); + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLRadioButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +uint64_t HTMLRadioButtonAccessible::NativeState() const { + uint64_t state = AccessibleWrap::NativeState(); + + state |= states::CHECKABLE; + + HTMLInputElement* input = HTMLInputElement::FromNode(mContent); + if (input && input->Checked()) state |= states::CHECKED; + + return state; +} + +void HTMLRadioButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) { + Unused << ComputeGroupAttributes(aPosInSet, aSetSize); +} + +void HTMLRadioButtonAccessible::DOMAttributeChanged( + int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue, uint64_t aOldState) { + if (aAttribute == nsGkAtoms::name) { + // If our name changed, it's possible our MEMBER_OF relation + // also changed. Push a cache update for Relations. + mDoc->QueueCacheUpdate(this, CacheDomain::Relations); + } else { + // Otherwise, handle this attribute change the way our parent + // class wants us to handle it. + RadioButtonAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, aOldValue, aOldState); + } +} + +Relation HTMLRadioButtonAccessible::ComputeGroupAttributes( + int32_t* aPosInSet, int32_t* aSetSize) const { + Relation rel = Relation(); + int32_t namespaceId = mContent->NodeInfo()->NamespaceID(); + nsAutoString tagName; + mContent->NodeInfo()->GetName(tagName); + + nsAutoString type; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); + nsAutoString name; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); + + RefPtr<nsContentList> inputElms; + + nsCOMPtr<nsIFormControl> formControlNode(do_QueryInterface(mContent)); + if (dom::Element* formElm = formControlNode->GetForm()) { + inputElms = NS_GetContentList(formElm, namespaceId, tagName); + } else { + inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); + } + NS_ENSURE_TRUE(inputElms, rel); + + uint32_t inputCount = inputElms->Length(false); + + // Compute posinset and setsize. + int32_t indexOf = 0; + int32_t count = 0; + + for (uint32_t index = 0; index < inputCount; index++) { + nsIContent* inputElm = inputElms->Item(index, false); + if (inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + type, eCaseMatters) && + inputElm->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, + name, eCaseMatters) && + mDoc->HasAccessible(inputElm)) { + count++; + rel.AppendTarget(mDoc->GetAccessible(inputElm)); + if (inputElm == mContent) indexOf = count; + } + } + + *aPosInSet = indexOf; + *aSetSize = count; + return rel; +} + +Relation HTMLRadioButtonAccessible::RelationByType(RelationType aType) const { + if (aType == RelationType::MEMBER_OF) { + int32_t unusedPos, unusedSetSize; + return ComputeGroupAttributes(&unusedPos, &unusedSetSize); + } + + return LocalAccessible::RelationByType(aType); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLButtonAccessible::HTMLButtonAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mGenericTypes |= eButton; +} + +bool HTMLButtonAccessible::HasPrimaryAction() const { return true; } + +void HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("press"); +} + +uint64_t HTMLButtonAccessible::State() { + uint64_t state = HyperTextAccessibleWrap::State(); + if (state == states::DEFUNCT) return state; + + // Inherit states from input@type="file" suitable for the button. Note, + // no special processing for unavailable state since inheritance is supplied + // other code paths. + if (mParent && mParent->IsHTMLFileInput()) { + uint64_t parentState = mParent->State(); + state |= parentState & (states::BUSY | states::REQUIRED | states::HASPOPUP | + states::INVALID); + } + + return state; +} + +uint64_t HTMLButtonAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + ElementState elmState = mContent->AsElement()->State(); + if (elmState.HasState(ElementState::DEFAULT)) state |= states::DEFAULT; + + return state; +} + +role HTMLButtonAccessible::NativeRole() const { return roles::PUSHBUTTON; } + +ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const { + // No need to check @value attribute for buttons since this attribute results + // in native anonymous text node and the name is calculated from subtree. + // The same magic works for @alt and @value attributes in case of type="image" + // element that has no valid @src (note if input@type="image" has an image + // then neither @alt nor @value attributes are used to generate a visual label + // and thus we need to obtain the accessible name directly from attribute + // value). Also the same algorithm works in case of default labels for + // type="submit"/"reset"/"image" elements. + + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) || + !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::image, eCaseMatters)) { + return nameFlag; + } + + if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, + aName)) { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); + } + + aName.CompressWhitespace(); + return eNameOK; +} + +void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::value) { + dom::Element* elm = Elm(); + if (elm->IsHTMLElement(nsGkAtoms::input) || + (elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image, + eCaseMatters) && + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt))) { + if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) && + !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible: Widgets + +bool HTMLButtonAccessible::IsWidget() const { return true; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTextFieldAccessible::HTMLTextFieldAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase) + ? eHTMLTextPasswordFieldType + : eHTMLTextFieldType; +} + +role HTMLTextFieldAccessible::NativeRole() const { + if (mType == eHTMLTextPasswordFieldType) { + return roles::PASSWORD_TEXT; + } + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::list_)) { + return roles::EDITCOMBOBOX; + } + return roles::ENTRY; +} + +already_AddRefed<AccAttributes> HTMLTextFieldAccessible::NativeAttributes() { + RefPtr<AccAttributes> attributes = + HyperTextAccessibleWrap::NativeAttributes(); + + // Expose type for text input elements as it gives some useful context, + // especially for mobile. + if (const nsAttrValue* attr = + mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) { + RefPtr<nsAtom> inputType = attr->GetAsAtom(); + if (inputType) { + if (!ARIARoleMap() && inputType == nsGkAtoms::search) { + attributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::searchbox); + } + attributes->SetAttribute(nsGkAtoms::textInputType, inputType); + } + } + // If this element has the placeholder attribute set, + // and if that is not identical to the name, expose it as an object attribute. + nsString placeholderText; + if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, + placeholderText)) { + nsAutoString name; + const_cast<HTMLTextFieldAccessible*>(this)->Name(name); + if (!name.Equals(placeholderText)) { + attributes->SetAttribute(nsGkAtoms::placeholder, + std::move(placeholderText)); + } + } + + return attributes.forget(); +} + +ENameValueFlag HTMLTextFieldAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty()) return nameFlag; + + if (!aName.IsEmpty()) return eNameOK; + + // text inputs and textareas might have useful placeholder text + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, + aName); + return eNameOK; +} + +void HTMLTextFieldAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + if (NativeState() & states::PROTECTED) { // Don't return password text! + return; + } + + HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent); + if (textArea) { + textArea->GetValue(aValue); + return; + } + + HTMLInputElement* input = HTMLInputElement::FromNode(mContent); + if (input) { + // Pass NonSystem as the caller type, to be safe. We don't expect to have a + // file input here. + input->GetValue(aValue, CallerType::NonSystem); + } +} + +bool HTMLTextFieldAccessible::AttributeChangesState(nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::list_ || + aAttribute == nsGkAtoms::autocomplete) { + return true; + } + + return LocalAccessible::AttributeChangesState(aAttribute); +} + +void HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const { + HyperTextAccessibleWrap::ApplyARIAState(aState); + aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); +} + +uint64_t HTMLTextFieldAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + // Text fields are always editable, even if they are also read only or + // disabled. + state |= states::EDITABLE; + + // can be focusable, focused, protected. readonly, unavailable, selected + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase)) { + state |= states::PROTECTED; + } + + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) { + state |= states::READONLY; + } + + // Is it an <input> or a <textarea> ? + HTMLInputElement* input = HTMLInputElement::FromNode(mContent); + state |= input && input->IsSingleLineTextControl() ? states::SINGLE_LINE + : states::MULTI_LINE; + + if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY | + states::UNAVAILABLE)) { + return state; + } + + // Expose autocomplete state if it has associated autocomplete list. + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::list_)) { + return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP; + } + + if (Preferences::GetBool("browser.formfill.enable")) { + // Check to see if autocompletion is allowed on this input. We don't expose + // it for password fields even though the entire password can be remembered + // for a page if the user asks it to be. However, the kind of autocomplete + // we're talking here is based on what the user types, where a popup of + // possible choices comes up. + nsAutoString autocomplete; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, + autocomplete); + + if (!autocomplete.LowerCaseEqualsLiteral("off")) { + Element* formElement = input->GetForm(); + if (formElement) { + formElement->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, + autocomplete); + } + + if (!formElement || !autocomplete.LowerCaseEqualsLiteral("off")) { + state |= states::SUPPORTS_AUTOCOMPLETION; + } + } + } + + return state; +} + +bool HTMLTextFieldAccessible::HasPrimaryAction() const { return true; } + +void HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("activate"); +} + +bool HTMLTextFieldAccessible::DoAction(uint8_t aIndex) const { + if (aIndex != 0) return false; + + if (FocusMgr()->IsFocused(this)) { + // This already has focus, so TakeFocus()will do nothing. However, the user + // might be activating this element because they dismissed a touch keyboard + // and want to bring it back. + DoCommand(); + } else { + TakeFocus(); + } + return true; +} + +already_AddRefed<EditorBase> HTMLTextFieldAccessible::GetEditor() const { + RefPtr<TextControlElement> textControlElement = + TextControlElement::FromNodeOrNull(mContent); + if (!textControlElement) { + return nullptr; + } + RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor(); + return textEditor.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible: Widgets + +bool HTMLTextFieldAccessible::IsWidget() const { return true; } + +LocalAccessible* HTMLTextFieldAccessible::ContainerWidget() const { + if (!mParent || mParent->Role() != roles::AUTOCOMPLETE) { + return nullptr; + } + return mParent; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFileInputAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFileInputAccessible::HTMLFileInputAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLFileInputType; +} + +role HTMLFileInputAccessible::NativeRole() const { + // No specific role in AT APIs. We use GROUPING so that the label will be + // reported by screen readers when focus enters this control . + return roles::GROUPING; +} + +nsresult HTMLFileInputAccessible::HandleAccEvent(AccEvent* aEvent) { + nsresult rv = HyperTextAccessibleWrap::HandleAccEvent(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + // Redirect state change events for inherited states to child controls. Note, + // unavailable state is not redirected. That's a standard for unavailable + // state handling. + AccStateChangeEvent* event = downcast_accEvent(aEvent); + if (event && (event->GetState() == states::BUSY || + event->GetState() == states::REQUIRED || + event->GetState() == states::HASPOPUP || + event->GetState() == states::INVALID)) { + LocalAccessible* button = LocalChildAt(0); + if (button && button->Role() == roles::PUSHBUTTON) { + RefPtr<AccStateChangeEvent> childEvent = new AccStateChangeEvent( + button, event->GetState(), event->IsStateEnabled(), + event->FromUserInput()); + nsEventShell::FireEvent(childEvent); + } + } + + return NS_OK; +} + +LocalAccessible* HTMLFileInputAccessible::CurrentItem() const { + // Allow aria-activedescendant to override. + if (LocalAccessible* item = HyperTextAccessibleWrap::CurrentItem()) { + return item; + } + + // The HTML file input itself gets DOM focus, not the button inside it. + // For a11y, we want the button to get focus. + LocalAccessible* button = LocalFirstChild(); + if (!button) { + MOZ_ASSERT_UNREACHABLE("File input doesn't contain a button"); + return nullptr; + } + MOZ_ASSERT(button->IsButton()); + return button; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSpinnerAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; } + +void HTMLSpinnerAccessible::Value(nsString& aValue) const { + HTMLTextFieldAccessible::Value(aValue); + if (!aValue.IsEmpty()) return; + + // Pass NonSystem as the caller type, to be safe. We don't expect to have a + // file input here. + HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem); +} + +double HTMLSpinnerAccessible::MaxValue() const { + double value = HTMLTextFieldAccessible::MaxValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble(); +} + +double HTMLSpinnerAccessible::MinValue() const { + double value = HTMLTextFieldAccessible::MinValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble(); +} + +double HTMLSpinnerAccessible::Step() const { + double value = HTMLTextFieldAccessible::Step(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetStep().toDouble(); +} + +double HTMLSpinnerAccessible::CurValue() const { + double value = HTMLTextFieldAccessible::CurValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble(); +} + +bool HTMLSpinnerAccessible::SetCurValue(double aValue) { + ErrorResult er; + HTMLInputElement::FromNode(mContent)->SetValueAsNumber(aValue, er); + return !er.Failed(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLRangeAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLRangeAccessible::NativeRole() const { return roles::SLIDER; } + +bool HTMLRangeAccessible::IsWidget() const { return true; } + +void HTMLRangeAccessible::Value(nsString& aValue) const { + LeafAccessible::Value(aValue); + if (!aValue.IsEmpty()) return; + + // Pass NonSystem as the caller type, to be safe. We don't expect to have a + // file input here. + HTMLInputElement::FromNode(mContent)->GetValue(aValue, CallerType::NonSystem); +} + +double HTMLRangeAccessible::MaxValue() const { + double value = LeafAccessible::MaxValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble(); +} + +double HTMLRangeAccessible::MinValue() const { + double value = LeafAccessible::MinValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble(); +} + +double HTMLRangeAccessible::Step() const { + double value = LeafAccessible::Step(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetStep().toDouble(); +} + +double HTMLRangeAccessible::CurValue() const { + double value = LeafAccessible::CurValue(); + if (!IsNaN(value)) return value; + + return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble(); +} + +bool HTMLRangeAccessible::SetCurValue(double aValue) { + nsAutoString strValue; + strValue.AppendFloat(aValue); + HTMLInputElement::FromNode(mContent)->SetUserInput( + strValue, *nsContentUtils::GetSystemPrincipal()); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLGroupboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLGroupboxAccessible::HTMLGroupboxAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +role HTMLGroupboxAccessible::NativeRole() const { return roles::GROUPING; } + +nsIContent* HTMLGroupboxAccessible::GetLegend() const { + for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent; + legendContent = legendContent->GetNextSibling()) { + if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend, + mContent->GetNameSpaceID())) { + // Either XHTML namespace or no namespace + return legendContent; + } + } + + return nullptr; +} + +ENameValueFlag HTMLGroupboxAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty()) return nameFlag; + + nsIContent* legendContent = GetLegend(); + if (legendContent) { + nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent, &aName); + } + + aName.CompressWhitespace(); + return eNameOK; +} + +Relation HTMLGroupboxAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + // No override for label, so use <legend> for this <fieldset> + if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, GetLegend()); + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLegendAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLegendAccessible::HTMLLegendAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +Relation HTMLLegendAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABEL_FOR) return rel; + + LocalAccessible* groupbox = LocalParent(); + if (groupbox && groupbox->Role() == roles::GROUPING) { + rel.AppendTarget(groupbox); + } + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFigureAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFigureAccessible::HTMLFigureAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +ENameValueFlag HTMLFigureAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = HyperTextAccessibleWrap::NativeName(aName); + if (!aName.IsEmpty()) return nameFlag; + + nsIContent* captionContent = Caption(); + if (captionContent) { + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName); + } + + aName.CompressWhitespace(); + return eNameOK; +} + +Relation HTMLFigureAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABELLED_BY) rel.AppendTarget(mDoc, Caption()); + + return rel; +} + +nsIContent* HTMLFigureAccessible::Caption() const { + for (nsIContent* childContent = mContent->GetFirstChild(); childContent; + childContent = childContent->GetNextSibling()) { + if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption, + mContent->GetNameSpaceID())) { + return childContent; + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFigcaptionAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFigcaptionAccessible::HTMLFigcaptionAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +Relation HTMLFigcaptionAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABEL_FOR) return rel; + + LocalAccessible* figure = LocalParent(); + if (figure && figure->GetContent()->NodeInfo()->Equals( + nsGkAtoms::figure, mContent->GetNameSpaceID())) { + rel.AppendTarget(figure); + } + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLProgressAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLProgressAccessible::NativeRole() const { return roles::PROGRESSBAR; } + +uint64_t HTMLProgressAccessible::NativeState() const { + uint64_t state = LeafAccessible::NativeState(); + + // An undetermined progressbar (i.e. without a value) has a mixed state. + nsAutoString attrValue; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, + attrValue); + if (attrValue.IsEmpty()) { + state |= states::MIXED; + } + + return state; +} + +bool HTMLProgressAccessible::IsWidget() const { return true; } + +void HTMLProgressAccessible::Value(nsString& aValue) const { + LeafAccessible::Value(aValue); + if (!aValue.IsEmpty()) { + return; + } + + double maxValue = MaxValue(); + if (IsNaN(maxValue) || maxValue == 0) { + return; + } + + double curValue = CurValue(); + if (IsNaN(curValue)) { + return; + } + + // Treat the current value bigger than maximum as 100%. + double percentValue = + (curValue < maxValue) ? (curValue / maxValue) * 100 : 100; + + aValue.AppendFloat(percentValue); + aValue.Append('%'); +} + +double HTMLProgressAccessible::MaxValue() const { + double value = LeafAccessible::MaxValue(); + if (!IsNaN(value)) { + return value; + } + + nsAutoString strValue; + if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::max, + strValue)) { + nsresult result = NS_OK; + value = strValue.ToDouble(&result); + if (NS_SUCCEEDED(result)) { + return value; + } + } + + return 1; +} + +double HTMLProgressAccessible::MinValue() const { + double value = LeafAccessible::MinValue(); + return IsNaN(value) ? 0 : value; +} + +double HTMLProgressAccessible::Step() const { + double value = LeafAccessible::Step(); + return IsNaN(value) ? 0 : value; +} + +double HTMLProgressAccessible::CurValue() const { + double value = LeafAccessible::CurValue(); + if (!IsNaN(value)) { + return value; + } + + nsAutoString attrValue; + if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, + attrValue)) { + return UnspecifiedNaN<double>(); + } + + nsresult error = NS_OK; + value = attrValue.ToDouble(&error); + return NS_FAILED(error) ? UnspecifiedNaN<double>() : value; +} + +bool HTMLProgressAccessible::SetCurValue(double aValue) { + return false; // progress meters are readonly. +} + +void HTMLProgressAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, + aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::value) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this); + + uint64_t currState = NativeState(); + if ((aOldState ^ currState) & states::MIXED) { + RefPtr<AccEvent> stateChangeEvent = new AccStateChangeEvent( + this, states::MIXED, (currState & states::MIXED)); + mDoc->FireDelayedEvent(stateChangeEvent); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLMeterAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLMeterAccessible::NativeRole() const { return roles::METER; } + +bool HTMLMeterAccessible::IsWidget() const { return true; } + +void HTMLMeterAccessible::Value(nsString& aValue) const { + LeafAccessible::Value(aValue); + if (!aValue.IsEmpty()) { + return; + } + + // If we did not get a value from the above LeafAccessible call, + // we should check to see if the meter has inner text. + // If it does, we'll use that as our value. + nsTextEquivUtils::AppendFromDOMChildren(mContent, &aValue); + aValue.CompressWhitespace(); + if (!aValue.IsEmpty()) { + return; + } + + // If no inner text is found, use curValue + double curValue = CurValue(); + if (IsNaN(curValue)) { + return; + } + + aValue.AppendFloat(curValue); +} + +double HTMLMeterAccessible::MaxValue() const { + double max = LeafAccessible::MaxValue(); + double min = MinValue(); + + if (!IsNaN(max)) { + return max > min ? max : min; + } + + // If we didn't find a max value, check for the max attribute + nsAutoString strValue; + if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::max, + strValue)) { + nsresult result = NS_OK; + max = strValue.ToDouble(&result); + if (NS_SUCCEEDED(result)) { + return max > min ? max : min; + } + } + + return 1 > min ? 1 : min; +} + +double HTMLMeterAccessible::MinValue() const { + double min = LeafAccessible::MinValue(); + if (!IsNaN(min)) { + return min; + } + + nsAutoString strValue; + if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::min, + strValue)) { + nsresult result = NS_OK; + min = strValue.ToDouble(&result); + if (NS_SUCCEEDED(result)) { + return min; + } + } + + return 0; +} + +double HTMLMeterAccessible::CurValue() const { + double value = LeafAccessible::CurValue(); + double minValue = MinValue(); + + if (IsNaN(value)) { + /* If we didn't find a value from the LeafAccessible call above, check + * for a value attribute */ + nsAutoString attrValue; + if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, + attrValue)) { + return minValue; + } + + // If we find a value attribute, attempt to convert it to a double + nsresult error = NS_OK; + value = attrValue.ToDouble(&error); + if (NS_FAILED(error)) { + return minValue; + } + } + + /* If we end up with a defined value, verify it falls between + * our established min/max. Otherwise, snap it to the nearest boundary. */ + double maxValue = MaxValue(); + if (value > maxValue) { + value = maxValue; + } else if (value < minValue) { + value = minValue; + } + + return value; +} + +bool HTMLMeterAccessible::SetCurValue(double aValue) { + return false; // meters are readonly. +} + +void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + LeafAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, + aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::value) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this); + } +} diff --git a/accessible/html/HTMLFormControlAccessible.h b/accessible/html/HTMLFormControlAccessible.h new file mode 100644 index 0000000000..52b1d81d9b --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.h @@ -0,0 +1,382 @@ +/* -*- 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_HTMLFormControlAccessible_H_ +#define MOZILLA_A11Y_HTMLFormControlAccessible_H_ + +#include "FormControlAccessible.h" +#include "HyperTextAccessibleWrap.h" +#include "mozilla/a11y/AccTypes.h" +#include "mozilla/dom/Element.h" +#include "AccAttributes.h" +#include "nsAccUtils.h" +#include "Relation.h" + +namespace mozilla { +class EditorBase; +namespace a11y { + +/** + * Accessible for HTML input@type="radio" element. + */ +class HTMLRadioButtonAccessible : public RadioButtonAccessible { + public: + HTMLRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) + : RadioButtonAccessible(aContent, aDoc) { + // Ignore "RadioStateChange" DOM event in lieu of document observer + // state change notification. + mStateFlags |= eIgnoreDOMUIEvent; + mType = eHTMLRadioButtonType; + } + + // LocalAccessible + virtual uint64_t NativeState() const override; + virtual Relation RelationByType(RelationType aType) const override; + + protected: + virtual void GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) override; + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + private: + Relation ComputeGroupAttributes(int32_t* aPosInSet, int32_t* aSetSize) const; +}; + +/** + * Accessible for HTML input@type="button", @type="submit", @type="image" + * and HTML button elements. + */ +class HTMLButtonAccessible : public HyperTextAccessibleWrap { + public: + enum { eAction_Click = 0 }; + + HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t State() override; + virtual uint64_t NativeState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // Widgets + virtual bool IsWidget() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; +}; + +/** + * Accessible for HTML input@type="text", input@type="password", textarea + * and other HTML text controls. + */ +class HTMLTextFieldAccessible : public HyperTextAccessibleWrap { + public: + enum { eAction_Click = 0 }; + + HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTextFieldAccessible, + HyperTextAccessibleWrap) + + // HyperTextAccessible + MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual already_AddRefed<EditorBase> GetEditor() + const override; + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual void ApplyARIAState(uint64_t* aState) const override; + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual already_AddRefed<AccAttributes> NativeAttributes() override; + virtual bool AttributeChangesState(nsAtom* aAttribute) override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) const override; + + // Widgets + virtual bool IsWidget() const override; + virtual LocalAccessible* ContainerWidget() const override; + + protected: + virtual ~HTMLTextFieldAccessible() {} + + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * Accessible for input@type="file" element. + */ +class HTMLFileInputAccessible : public HyperTextAccessibleWrap { + public: + HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual nsresult HandleAccEvent(AccEvent* aAccEvent) override; + virtual LocalAccessible* CurrentItem() const override; +}; + +/** + * Used for HTML input@type="number". + */ +class HTMLSpinnerAccessible final : public HTMLTextFieldAccessible { + public: + HTMLSpinnerAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HTMLTextFieldAccessible(aContent, aDoc) { + mGenericTypes |= eNumericValue; + } + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override; + virtual void Value(nsString& aValue) const override; + + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual double Step() const override; + virtual bool SetCurValue(double aValue) override; +}; + +/** + * Used for input@type="range" element. + */ +class HTMLRangeAccessible : public LeafAccessible { + public: + HTMLRangeAccessible(nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + mGenericTypes |= eNumericValue; + } + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual mozilla::a11y::role NativeRole() const override; + + // Value + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual double Step() const override; + virtual bool SetCurValue(double aValue) override; + + // Widgets + virtual bool IsWidget() const override; +}; + +/** + * Accessible for HTML fieldset element. + */ +class HTMLGroupboxAccessible : public HyperTextAccessibleWrap { + public: + HTMLGroupboxAccessible(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; + + // HTMLGroupboxAccessible + nsIContent* GetLegend() const; +}; + +/** + * Accessible for HTML legend element. + */ +class HTMLLegendAccessible : public HyperTextAccessibleWrap { + public: + HTMLLegendAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual Relation RelationByType(RelationType aType) const override; +}; + +/** + * Accessible for HTML5 figure element. + */ +class HTMLFigureAccessible : public HyperTextAccessibleWrap { + public: + HTMLFigureAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual Relation RelationByType(RelationType aType) const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + + // HTMLLegendAccessible + nsIContent* Caption() const; +}; + +/** + * Accessible for HTML5 figcaption element. + */ +class HTMLFigcaptionAccessible : public HyperTextAccessibleWrap { + public: + HTMLFigcaptionAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual Relation RelationByType(RelationType aType) const override; +}; + +/** + * Used for HTML form element. + */ +class HTMLFormAccessible : public HyperTextAccessibleWrap { + public: + HTMLFormAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLFormAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + protected: + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + virtual ~HTMLFormAccessible() = default; +}; + +/** + * Accessible for HTML progress element. + */ + +class HTMLProgressAccessible : public LeafAccessible { + public: + HTMLProgressAccessible(nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + // Ignore 'ValueChange' DOM event in lieu of @value attribute change + // notifications. + mStateFlags |= eIgnoreDOMUIEvent; + mGenericTypes |= eNumericValue; + mType = eProgressType; + } + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // Value + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual double Step() const override; + virtual bool SetCurValue(double aValue) override; + + // Widgets + virtual bool IsWidget() const override; + + protected: + virtual ~HTMLProgressAccessible() {} + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; +}; + +/** + * Accessible for HTML meter element. + */ + +class HTMLMeterAccessible : public LeafAccessible { + public: + HTMLMeterAccessible(nsIContent* aContent, DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + // Ignore 'ValueChange' DOM event in lieu of @value attribute change + // notifications. + mStateFlags |= eIgnoreDOMUIEvent; + mGenericTypes |= eNumericValue; + mType = eProgressType; + } + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual mozilla::a11y::role NativeRole() const override; + + // Value + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual bool SetCurValue(double aValue) override; + + // Widgets + virtual bool IsWidget() const override; + + protected: + virtual ~HTMLMeterAccessible() {} + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; +}; + +/** + * Accessible for HTML date/time inputs. + */ +template <a11y::role R> +class HTMLDateTimeAccessible : public HyperTextAccessibleWrap { + public: + HTMLDateTimeAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLDateTimeFieldType; + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLDateTimeAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual mozilla::a11y::role NativeRole() const override { return R; } + virtual already_AddRefed<AccAttributes> NativeAttributes() override { + RefPtr<AccAttributes> attributes = + HyperTextAccessibleWrap::NativeAttributes(); + // Unfortunately, an nsStaticAtom can't be passed as a + // template argument, so fetch the type from the DOM. + if (const nsAttrValue* attr = + mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) { + RefPtr<nsAtom> inputType = attr->GetAsAtom(); + if (inputType) { + attributes->SetAttribute(nsGkAtoms::textInputType, inputType); + } + } + return attributes.forget(); + } + + // Widgets + virtual bool IsWidget() const override { return true; } + + protected: + virtual ~HTMLDateTimeAccessible() {} +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp new file mode 100644 index 0000000000..792524449f --- /dev/null +++ b/accessible/html/HTMLImageMapAccessible.cpp @@ -0,0 +1,211 @@ +/* -*- 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 "HTMLImageMapAccessible.h" + +#include "ARIAMap.h" +#include "nsAccUtils.h" +#include "DocAccessible-inl.h" +#include "EventTree.h" +#include "Role.h" + +#include "nsIFrame.h" +#include "nsImageFrame.h" +#include "nsImageMap.h" +#include "nsIURI.h" +#include "nsLayoutUtils.h" +#include "mozilla/dom/HTMLAreaElement.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLImageMapAccessible::HTMLImageMapAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : ImageAccessible(aContent, aDoc) { + mType = eImageMapType; + + UpdateChildAreas(false); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: LocalAccessible public + +role HTMLImageMapAccessible::NativeRole() const { return roles::IMAGE_MAP; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: HyperLinkAccessible + +uint32_t HTMLImageMapAccessible::AnchorCount() { return ChildCount(); } + +LocalAccessible* HTMLImageMapAccessible::AnchorAt(uint32_t aAnchorIndex) { + return LocalChildAt(aAnchorIndex); +} + +already_AddRefed<nsIURI> HTMLImageMapAccessible::AnchorURIAt( + uint32_t aAnchorIndex) const { + LocalAccessible* area = LocalChildAt(aAnchorIndex); + if (!area) return nullptr; + + nsIContent* linkContent = area->GetContent(); + return linkContent && linkContent->IsElement() + ? linkContent->AsElement()->GetHrefURI() + : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: public + +void HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) { + nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + + // If image map is not initialized yet then we trigger one time more later. + nsImageMap* imageMapObj = imageFrame->GetExistingImageMap(); + if (!imageMapObj) return; + + TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents); + + // Remove areas that are not a valid part of the image map anymore. + for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) { + LocalAccessible* area = mChildren.ElementAt(childIdx); + if (area->GetContent()->GetPrimaryFrame()) continue; + + mt.BeforeRemoval(area); + RemoveChild(area); + } + + // Insert new areas into the tree. + uint32_t areaElmCount = imageMapObj->AreaCount(); + for (uint32_t idx = 0; idx < areaElmCount; idx++) { + nsIContent* areaContent = imageMapObj->GetAreaAt(idx); + LocalAccessible* area = mChildren.SafeElementAt(idx); + if (!area || area->GetContent() != areaContent) { + RefPtr<LocalAccessible> area = new HTMLAreaAccessible(areaContent, mDoc); + mDoc->BindToDocument(area, aria::GetRoleMap(areaContent->AsElement())); + + if (!InsertChildAt(idx, area)) { + mDoc->UnbindFromDocument(area); + break; + } + + mt.AfterInsertion(area); + } + } + + mt.Done(); +} + +LocalAccessible* HTMLImageMapAccessible::GetChildAccessibleFor( + const nsINode* aNode) const { + uint32_t length = mChildren.Length(); + for (uint32_t i = 0; i < length; i++) { + LocalAccessible* area = mChildren[i]; + if (area->GetContent() == aNode) return area; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLAreaAccessible::HTMLAreaAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HTMLLinkAccessible(aContent, aDoc) { + // Make HTML area DOM element not accessible. HTML image map accessible + // manages its tree itself. + mStateFlags |= eNotNodeMapEntry; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible: LocalAccessible + +ENameValueFlag HTMLAreaAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty()) return nameFlag; + + if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, + aName)) { + Value(aName); + } + + return eNameOK; +} + +void HTMLAreaAccessible::Description(nsString& aDescription) const { + aDescription.Truncate(); + + // Still to do - follow IE's standard here + RefPtr<dom::HTMLAreaElement> area = + dom::HTMLAreaElement::FromNodeOrNull(mContent); + if (area) area->GetShape(aDescription); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible: LocalAccessible public + +LocalAccessible* HTMLAreaAccessible::LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) { + // Don't walk into area accessibles. + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: HyperLinkAccessible + +uint32_t HTMLAreaAccessible::StartOffset() { + // Image map accessible is not hypertext accessible therefore + // StartOffset/EndOffset implementations of LocalAccessible doesn't work here. + // We return index in parent because image map contains area links only which + // are embedded objects. + // XXX: image map should be a hypertext accessible. + return IndexInParent(); +} + +uint32_t HTMLAreaAccessible::EndOffset() { return IndexInParent() + 1; } + +nsRect HTMLAreaAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const { + nsIFrame* frame = GetFrame(); + if (!frame) return nsRect(); + + nsImageFrame* imageFrame = do_QueryFrame(frame); + nsImageMap* map = imageFrame->GetImageMap(); + + nsRect bounds; + nsresult rv = map->GetBoundsForAreaContent(mContent, bounds); + + if (NS_FAILED(rv)) return nsRect(); + + // XXX Areas are screwy; they return their rects as a pair of points, one pair + // stored into the width and height. + *aBoundingFrame = frame; + bounds.SizeTo(bounds.Width() - bounds.X(), bounds.Height() - bounds.Y()); + return bounds; +} + +nsRect HTMLAreaAccessible::ParentRelativeBounds() { + nsIFrame* boundingFrame = nullptr; + nsRect relativeBoundsRect = RelativeBounds(&boundingFrame); + + nsIFrame* parentBoundingFrame = nullptr; + if (mParent) { + parentBoundingFrame = mParent->GetFrame(); + } + + if (!parentBoundingFrame) { + // if we can't get the bounding frame, use the pres shell root for the + // bounding frame RelativeBounds returned + parentBoundingFrame = + nsLayoutUtils::GetContainingBlockForClientRect(boundingFrame); + } + + nsLayoutUtils::TransformRect(boundingFrame, parentBoundingFrame, + relativeBoundsRect); + + return relativeBoundsRect; +} diff --git a/accessible/html/HTMLImageMapAccessible.h b/accessible/html/HTMLImageMapAccessible.h new file mode 100644 index 0000000000..61b1d6fcb8 --- /dev/null +++ b/accessible/html/HTMLImageMapAccessible.h @@ -0,0 +1,85 @@ +/* -*- 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_HTMLImageMapAccessible_h__ +#define mozilla_a11y_HTMLImageMapAccessible_h__ + +#include "HTMLLinkAccessible.h" +#include "ImageAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for HTML image maps. + */ +class HTMLImageMapAccessible final : public ImageAccessible { + public: + HTMLImageMapAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports and cycle collector + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLImageMapAccessible, ImageAccessible) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + // HyperLinkAccessible + virtual uint32_t AnchorCount() override; + virtual LocalAccessible* AnchorAt(uint32_t aAnchorIndex) override; + virtual already_AddRefed<nsIURI> AnchorURIAt( + uint32_t aAnchorIndex) const override; + + /** + * Update area children of the image map. + */ + void UpdateChildAreas(bool aDoFireEvents = true); + + /** + * Return accessible of child node. + */ + LocalAccessible* GetChildAccessibleFor(const nsINode* aNode) const; + + protected: + virtual ~HTMLImageMapAccessible() {} +}; + +/** + * Accessible for image map areas - must be child of image. + */ +class HTMLAreaAccessible final : public HTMLLinkAccessible { + public: + HTMLAreaAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual void Description(nsString& aDescription) const override; + virtual LocalAccessible* LocalChildAtPoint( + int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) override; + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + virtual nsRect ParentRelativeBounds() override; + + // HyperLinkAccessible + virtual uint32_t StartOffset() override; + virtual uint32_t EndOffset() override; + + virtual bool IsAcceptableChild(nsIContent* aEl) const override { + return false; + } + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +//////////////////////////////////////////////////////////////////////////////// +// LocalAccessible downcasting method + +inline HTMLImageMapAccessible* LocalAccessible::AsImageMap() { + return IsImageMap() ? static_cast<HTMLImageMapAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLLinkAccessible.cpp b/accessible/html/HTMLLinkAccessible.cpp new file mode 100644 index 0000000000..bc8b6d854f --- /dev/null +++ b/accessible/html/HTMLLinkAccessible.cpp @@ -0,0 +1,134 @@ +/* -*- 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 "HTMLLinkAccessible.h" + +#include "CacheConstants.h" +#include "nsCoreUtils.h" +#include "DocAccessible.h" +#include "Role.h" +#include "States.h" + +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/MutationEventBinding.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLinkAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLinkAccessible::HTMLLinkAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLLinkType; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIAccessible + +role HTMLLinkAccessible::NativeRole() const { return roles::LINK; } + +uint64_t HTMLLinkAccessible::NativeState() const { + return HyperTextAccessibleWrap::NativeState() & ~states::READONLY; +} + +uint64_t HTMLLinkAccessible::NativeLinkState() const { + dom::ElementState state = mContent->AsElement()->State(); + if (state.HasState(dom::ElementState::UNVISITED)) { + return states::LINKED; + } + + if (state.HasState(dom::ElementState::VISITED)) { + return states::LINKED | states::TRAVERSED; + } + + // This is a either named anchor (a link with also a name attribute) or + // it doesn't have any attributes. Check if 'click' event handler is + // registered, otherwise bail out. + return nsCoreUtils::HasClickListener(mContent) ? states::LINKED : 0; +} + +uint64_t HTMLLinkAccessible::NativeInteractiveState() const { + uint64_t state = HyperTextAccessibleWrap::NativeInteractiveState(); + + // This is how we indicate it is a named anchor. In other words, this anchor + // can be selected as a location :) There is no other better state to use to + // indicate this. + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::name)) { + state |= states::SELECTABLE; + } + + return state; +} + +void HTMLLinkAccessible::Value(nsString& aValue) const { + aValue.Truncate(); + + HyperTextAccessible::Value(aValue); + if (aValue.IsEmpty()) { + nsContentUtils::GetLinkLocation(mContent->AsElement(), aValue); + } +} + +bool HTMLLinkAccessible::HasPrimaryAction() const { + return IsLinked() || HyperTextAccessible::HasPrimaryAction(); + ; +} + +void HTMLLinkAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + aName.Truncate(); + + if (!IsLinked()) { + HyperTextAccessible::ActionNameAt(aIndex, aName); + return; + } + + // Action 0 (default action): Jump to link + if (aIndex == eAction_Jump) aName.AssignLiteral("jump"); +} + +bool HTMLLinkAccessible::AttributeChangesState(nsAtom* aAttribute) { + return aAttribute == nsGkAtoms::href || + HyperTextAccessibleWrap::AttributeChangesState(aAttribute); +} + +void HTMLLinkAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::href && + (aModType == dom::MutationEvent_Binding::ADDITION || + aModType == dom::MutationEvent_Binding::REMOVAL)) { + SendCache(CacheDomain::Actions, CacheUpdateType::Update); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HyperLinkAccessible + +bool HTMLLinkAccessible::IsLink() const { + // Expose HyperLinkAccessible unconditionally. + return true; +} + +already_AddRefed<nsIURI> HTMLLinkAccessible::AnchorURIAt( + uint32_t aAnchorIndex) const { + return aAnchorIndex == 0 ? mContent->AsElement()->GetHrefURI() : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLinkAccessible + +bool HTMLLinkAccessible::IsLinked() const { + dom::ElementState state = mContent->AsElement()->State(); + return state.HasAtLeastOneOfStates(dom::ElementState::VISITED_OR_UNVISITED); +} diff --git a/accessible/html/HTMLLinkAccessible.h b/accessible/html/HTMLLinkAccessible.h new file mode 100644 index 0000000000..7a29bc16bc --- /dev/null +++ b/accessible/html/HTMLLinkAccessible.h @@ -0,0 +1,62 @@ +/* -*- 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_HTMLLinkAccessible_h__ +#define mozilla_a11y_HTMLLinkAccessible_h__ + +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class HTMLLinkAccessible : public HyperTextAccessibleWrap { + public: + HTMLLinkAccessible(nsIContent* aContent, DocAccessible* aDoc); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLLinkAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual void Value(nsString& aValue) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeLinkState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // HyperLinkAccessible + virtual bool IsLink() const override; + virtual already_AddRefed<nsIURI> AnchorURIAt( + uint32_t aAnchorIndex) const override; + + /** + * Returns true if the link has href attribute. + */ + bool IsLinked() const; + + protected: + virtual ~HTMLLinkAccessible() {} + + virtual bool AttributeChangesState(nsAtom* aAttribute) override; + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + enum { eAction_Jump = 0 }; +}; + +inline HTMLLinkAccessible* LocalAccessible::AsHTMLLink() { + return IsHTMLLink() ? static_cast<HTMLLinkAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp new file mode 100644 index 0000000000..39fc6149b9 --- /dev/null +++ b/accessible/html/HTMLListAccessible.cpp @@ -0,0 +1,115 @@ +/* -*- 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 "HTMLListAccessible.h" + +#include "AccAttributes.h" +#include "DocAccessible.h" +#include "EventTree.h" +#include "nsAccUtils.h" +#include "nsPersistentProperties.h" +#include "Role.h" +#include "States.h" + +#include "nsLayoutUtils.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLListAccessible::NativeRole() const { + a11y::role r = GetAccService()->MarkupRole(mContent); + return r != roles::NOTHING ? r : roles::LIST; +} + +uint64_t HTMLListAccessible::NativeState() const { + return HyperTextAccessibleWrap::NativeState() | states::READONLY; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLIAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLIAccessible::HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLLiType; +} + +role HTMLLIAccessible::NativeRole() const { + a11y::role r = GetAccService()->MarkupRole(mContent); + return r != roles::NOTHING ? r : roles::LISTITEM; +} + +uint64_t HTMLLIAccessible::NativeState() const { + return HyperTextAccessibleWrap::NativeState() | states::READONLY; +} + +nsRect HTMLLIAccessible::BoundsInAppUnits() const { + nsRect rect = AccessibleWrap::BoundsInAppUnits(); + + LocalAccessible* bullet = Bullet(); + nsIFrame* frame = GetFrame(); + MOZ_ASSERT(!(bullet && !frame), "Cannot have a bullet if there is no frame"); + + if (bullet && frame && + frame->StyleList()->mListStylePosition != + StyleListStylePosition::Inside) { + nsRect bulletRect = bullet->BoundsInAppUnits(); + return rect.Union(bulletRect); + } + + return rect; +} + +LocalAccessible* HTMLLIAccessible::Bullet() const { + LocalAccessible* firstChild = LocalFirstChild(); + if (firstChild && firstChild->NativeRole() == roles::LISTITEM_MARKER) { + return firstChild; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListBulletAccessible +//////////////////////////////////////////////////////////////////////////////// +HTMLListBulletAccessible::HTMLListBulletAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : LeafAccessible(aContent, aDoc) { + mGenericTypes |= eText; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListBulletAccessible: LocalAccessible + +ENameValueFlag HTMLListBulletAccessible::Name(nsString& aName) const { + nsLayoutUtils::GetMarkerSpokenText(mContent, aName); + return eNameOK; +} + +role HTMLListBulletAccessible::NativeRole() const { + return roles::LISTITEM_MARKER; +} + +uint64_t HTMLListBulletAccessible::NativeState() const { + return LeafAccessible::NativeState() | states::READONLY; +} + +already_AddRefed<AccAttributes> HTMLListBulletAccessible::NativeAttributes() { + RefPtr<AccAttributes> attributes = new AccAttributes(); + return attributes.forget(); +} + +void HTMLListBulletAccessible::AppendTextTo(nsAString& aText, + uint32_t aStartOffset, + uint32_t aLength) { + nsAutoString bulletText; + Name(bulletText); + aText.Append(Substring(bulletText, aStartOffset, aLength)); +} diff --git a/accessible/html/HTMLListAccessible.h b/accessible/html/HTMLListAccessible.h new file mode 100644 index 0000000000..73c8857d92 --- /dev/null +++ b/accessible/html/HTMLListAccessible.h @@ -0,0 +1,87 @@ +/* -*- 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_HTMLListAccessible_h__ +#define mozilla_a11y_HTMLListAccessible_h__ + +#include "BaseAccessibles.h" +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class HTMLListBulletAccessible; + +/** + * Used for HTML list (like HTML ul). + */ +class HTMLListAccessible : public HyperTextAccessibleWrap { + public: + HTMLListAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mGenericTypes |= eList; + } + + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLListAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + protected: + virtual ~HTMLListAccessible() {} +}; + +/** + * Used for HTML list item (e.g. HTML li). + */ +class HTMLLIAccessible : public HyperTextAccessibleWrap { + public: + HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLLIAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual nsRect BoundsInAppUnits() const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + + // HTMLLIAccessible + LocalAccessible* Bullet() const; + + protected: + virtual ~HTMLLIAccessible() {} +}; + +/** + * Used for bullet of HTML list item element (for example, HTML li). + */ +class HTMLListBulletAccessible : public LeafAccessible { + public: + HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLListBulletAccessible() {} + + // LocalAccessible + virtual ENameValueFlag Name(nsString& aName) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual already_AddRefed<AccAttributes> NativeAttributes() override; + virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0, + uint32_t aLength = UINT32_MAX) override; +}; + +inline HTMLLIAccessible* LocalAccessible::AsHTMLListItem() { + return IsHTMLListItem() ? static_cast<HTMLLIAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLSelectAccessible.cpp b/accessible/html/HTMLSelectAccessible.cpp new file mode 100644 index 0000000000..0366ce19ed --- /dev/null +++ b/accessible/html/HTMLSelectAccessible.cpp @@ -0,0 +1,474 @@ +/* -*- 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 "HTMLSelectAccessible.h" + +#include "LocalAccessible-inl.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "nsEventShell.h" +#include "nsTextEquivUtils.h" +#include "Role.h" +#include "States.h" + +#include "nsCOMPtr.h" +#include "mozilla/dom/HTMLOptionElement.h" +#include "mozilla/dom/HTMLOptGroupElement.h" +#include "mozilla/dom/HTMLSelectElement.h" +#include "nsComboboxControlFrame.h" +#include "nsContainerFrame.h" +#include "nsListControlFrame.h" + +using namespace mozilla::a11y; +using namespace mozilla::dom; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectListAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLSelectListAccessible::HTMLSelectListAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mGenericTypes |= eListControl | eSelect; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectListAccessible: LocalAccessible public + +uint64_t HTMLSelectListAccessible::NativeState() const { + uint64_t state = AccessibleWrap::NativeState(); + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { + state |= states::MULTISELECTABLE | states::EXTSELECTABLE; + } + + return state; +} + +role HTMLSelectListAccessible::NativeRole() const { return roles::LISTBOX; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectListAccessible: SelectAccessible + +bool HTMLSelectListAccessible::SelectAll() { + return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) + ? AccessibleWrap::SelectAll() + : false; +} + +bool HTMLSelectListAccessible::UnselectAll() { + return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) + ? AccessibleWrap::UnselectAll() + : false; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectListAccessible: Widgets + +bool HTMLSelectListAccessible::IsWidget() const { return true; } + +bool HTMLSelectListAccessible::IsActiveWidget() const { + return FocusMgr()->HasDOMFocus(mContent); +} + +bool HTMLSelectListAccessible::AreItemsOperable() const { return true; } + +LocalAccessible* HTMLSelectListAccessible::CurrentItem() const { + nsListControlFrame* listControlFrame = do_QueryFrame(GetFrame()); + if (listControlFrame) { + nsCOMPtr<nsIContent> activeOptionNode = + listControlFrame->GetCurrentOption(); + if (activeOptionNode) { + DocAccessible* document = Document(); + if (document) return document->GetAccessible(activeOptionNode); + } + } + return nullptr; +} + +void HTMLSelectListAccessible::SetCurrentItem(const LocalAccessible* aItem) { + if (!aItem->GetContent()->IsElement()) return; + + aItem->GetContent()->AsElement()->SetAttr( + kNameSpaceID_None, nsGkAtoms::selected, u"true"_ns, true); +} + +bool HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const { + return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup); +} + +bool HTMLSelectListAccessible::AttributeChangesState(nsAtom* aAttribute) { + return aAttribute == nsGkAtoms::multiple || + LocalAccessible::AttributeChangesState(aAttribute); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectOptionAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLSelectOptionAccessible::HTMLSelectOptionAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) {} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectOptionAccessible: LocalAccessible public + +role HTMLSelectOptionAccessible::NativeRole() const { + if (GetCombobox()) return roles::COMBOBOX_OPTION; + + return roles::OPTION; +} + +ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const { + if (auto* option = dom::HTMLOptionElement::FromNode(mContent)) { + option->GetAttr(nsGkAtoms::label, aName); + if (!aName.IsEmpty()) { + return eNameOK; + } + option->GetText(aName); + return eNameFromSubtree; + } + if (auto* group = dom::HTMLOptGroupElement::FromNode(mContent)) { + group->GetLabel(aName); + return aName.IsEmpty() ? eNameOK : eNameFromSubtree; + } + MOZ_ASSERT_UNREACHABLE("What content do we have?"); + return eNameFromSubtree; +} + +void HTMLSelectOptionAccessible::DOMAttributeChanged( + int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue, uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::label) { + dom::Element* elm = Elm(); + if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) && + !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + } + } +} + +uint64_t HTMLSelectOptionAccessible::NativeState() const { + // As a HTMLSelectOptionAccessible we can have the following states: + // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN + // Upcall to LocalAccessible, but skip HyperTextAccessible impl + // because we don't want EDITABLE or SELECTABLE_TEXT + uint64_t state = LocalAccessible::NativeState(); + + LocalAccessible* select = GetSelect(); + if (!select) return state; + + uint64_t selectState = select->State(); + if (selectState & states::INVISIBLE) return state; + + // Are we selected? + HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent); + bool selected = option && option->Selected(); + if (selected) state |= states::SELECTED; + + if (selectState & states::OFFSCREEN) { + state |= states::OFFSCREEN; + } else if (selectState & states::COLLAPSED) { + // <select> is COLLAPSED: add OFFSCREEN, if not the currently + // visible option + if (!selected) { + state |= states::OFFSCREEN; + // Ensure the invisible state is removed. Otherwise, group info will skip + // this option. Furthermore, this gets cached and this doesn't get + // invalidated even once the select is expanded. + state &= ~states::INVISIBLE; + } else { + // Clear offscreen and invisible for currently showing option + state &= ~(states::OFFSCREEN | states::INVISIBLE); + state |= selectState & states::OPAQUE1; + } + } else { + // XXX list frames are weird, don't rely on LocalAccessible's general + // visibility implementation unless they get reimplemented in layout + state &= ~states::OFFSCREEN; + // <select> is not collapsed: compare bounds to calculate OFFSCREEN + LocalAccessible* listAcc = LocalParent(); + if (listAcc) { + LayoutDeviceIntRect optionRect = Bounds(); + LayoutDeviceIntRect listRect = listAcc->Bounds(); + if (optionRect.Y() < listRect.Y() || + optionRect.YMost() > listRect.YMost()) { + state |= states::OFFSCREEN; + } + } + } + + return state; +} + +uint64_t HTMLSelectOptionAccessible::NativeInteractiveState() const { + return NativelyUnavailable() ? states::UNAVAILABLE + : states::FOCUSABLE | states::SELECTABLE; +} + +nsRect HTMLSelectOptionAccessible::RelativeBounds( + nsIFrame** aBoundingFrame) const { + LocalAccessible* combobox = GetCombobox(); + if (combobox && (combobox->State() & states::COLLAPSED)) { + return combobox->RelativeBounds(aBoundingFrame); + } + + return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame); +} + +void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex, + nsAString& aName) { + if (aIndex == eAction_Select) aName.AssignLiteral("select"); +} + +bool HTMLSelectOptionAccessible::HasPrimaryAction() const { return true; } + +void HTMLSelectOptionAccessible::SetSelected(bool aSelect) { + HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent); + if (option) option->SetSelected(aSelect); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectOptionAccessible: Widgets + +LocalAccessible* HTMLSelectOptionAccessible::ContainerWidget() const { + LocalAccessible* parent = LocalParent(); + if (parent && parent->IsHTMLOptGroup()) { + parent = parent->LocalParent(); + } + + return parent && parent->IsListControl() ? parent : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSelectOptGroupAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLSelectOptGroupAccessible::NativeRole() const { + return roles::GROUPING; +} + +uint64_t HTMLSelectOptGroupAccessible::NativeInteractiveState() const { + return NativelyUnavailable() ? states::UNAVAILABLE : 0; +} + +bool HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent* aEl) const { + return aEl->IsCharacterData() || aEl->IsHTMLElement(nsGkAtoms::option); +} + +bool HTMLSelectOptGroupAccessible::HasPrimaryAction() const { return false; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) { + mType = eHTMLComboboxType; + mGenericTypes |= eCombobox; + mStateFlags |= eNoKidsFromDOM; + + if ((nsComboboxControlFrame*)do_QueryFrame(GetFrame())) { + mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc); + Document()->BindToDocument(mListAccessible, nullptr); + AppendChild(mListAccessible); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxAccessible: LocalAccessible + +role HTMLComboboxAccessible::NativeRole() const { return roles::COMBOBOX; } + +bool HTMLComboboxAccessible::RemoveChild(LocalAccessible* aChild) { + MOZ_ASSERT(aChild == mListAccessible); + if (AccessibleWrap::RemoveChild(aChild)) { + mListAccessible = nullptr; + return true; + } + return false; +} + +void HTMLComboboxAccessible::Shutdown() { + MOZ_ASSERT(!mDoc || mDoc->IsDefunct() || !mListAccessible); + if (mListAccessible) { + mListAccessible->Shutdown(); + mListAccessible = nullptr; + } + + AccessibleWrap::Shutdown(); +} + +uint64_t HTMLComboboxAccessible::NativeState() const { + // As a HTMLComboboxAccessible we can have the following states: + // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED + // Get focus status from base class + uint64_t state = LocalAccessible::NativeState(); + + nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame()); + if (comboFrame && comboFrame->IsDroppedDown()) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + + state |= states::HASPOPUP; + return state; +} + +void HTMLComboboxAccessible::Description(nsString& aDescription) const { + aDescription.Truncate(); + // First check to see if combo box itself has a description, perhaps through + // tooltip (title attribute) or via aria-describedby + LocalAccessible::Description(aDescription); + if (!aDescription.IsEmpty()) return; + + // Otherwise use description of selected option. + LocalAccessible* option = SelectedOption(); + if (option) option->Description(aDescription); +} + +void HTMLComboboxAccessible::Value(nsString& aValue) const { + // Use accessible name of selected option. + LocalAccessible* option = SelectedOption(); + if (option) option->Name(aValue); +} + +bool HTMLComboboxAccessible::HasPrimaryAction() const { return true; } + +void HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex != HTMLComboboxAccessible::eAction_Click) return; + + nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame()); + if (!comboFrame) return; + + if (comboFrame->IsDroppedDown()) { + aName.AssignLiteral("close"); + } else { + aName.AssignLiteral("open"); + } +} + +bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const { + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxAccessible: Widgets + +bool HTMLComboboxAccessible::IsWidget() const { return true; } + +bool HTMLComboboxAccessible::IsActiveWidget() const { + return FocusMgr()->HasDOMFocus(mContent); +} + +bool HTMLComboboxAccessible::AreItemsOperable() const { + nsComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame()); + return comboboxFrame && comboboxFrame->IsDroppedDown(); +} + +LocalAccessible* HTMLComboboxAccessible::CurrentItem() const { + return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr; +} + +void HTMLComboboxAccessible::SetCurrentItem(const LocalAccessible* aItem) { + if (AreItemsOperable()) mListAccessible->SetCurrentItem(aItem); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxAccessible: protected + +LocalAccessible* HTMLComboboxAccessible::SelectedOption() const { + HTMLSelectElement* select = HTMLSelectElement::FromNode(mContent); + int32_t selectedIndex = select->SelectedIndex(); + + if (selectedIndex >= 0) { + HTMLOptionElement* option = select->Item(selectedIndex); + if (option) { + DocAccessible* document = Document(); + if (document) return document->GetAccessible(option); + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxListAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent, + nsIContent* aContent, + DocAccessible* aDoc) + : HTMLSelectListAccessible(aContent, aDoc) { + mStateFlags |= eSharedNode; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxAccessible: LocalAccessible + +role HTMLComboboxListAccessible::NativeRole() const { + return roles::COMBOBOX_LIST; +} + +uint64_t HTMLComboboxListAccessible::NativeState() const { + // As a HTMLComboboxListAccessible we can have the following states: + // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE + // Get focus status from base class + uint64_t state = LocalAccessible::NativeState(); + + nsComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame()); + if (comboFrame && comboFrame->IsDroppedDown()) { + state |= states::FLOATING; + } else { + state |= states::INVISIBLE; + } + + return state; +} + +nsRect HTMLComboboxListAccessible::RelativeBounds( + nsIFrame** aBoundingFrame) const { + *aBoundingFrame = nullptr; + + LocalAccessible* comboAcc = LocalParent(); + if (!comboAcc) return nsRect(); + + if (0 == (comboAcc->State() & states::COLLAPSED)) { + return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame); + } + + // Get the first option. + nsIContent* content = mContent->GetFirstChild(); + if (!content) return nsRect(); + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + *aBoundingFrame = nullptr; + return nsRect(); + } + + *aBoundingFrame = frame->GetParent(); + return (*aBoundingFrame)->GetRect(); +} + +bool HTMLComboboxListAccessible::IsAcceptableChild(nsIContent* aEl) const { + return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLComboboxListAccessible: Widgets + +bool HTMLComboboxListAccessible::IsActiveWidget() const { + return mParent && mParent->IsActiveWidget(); +} + +bool HTMLComboboxListAccessible::AreItemsOperable() const { + return mParent && mParent->AreItemsOperable(); +} diff --git a/accessible/html/HTMLSelectAccessible.h b/accessible/html/HTMLSelectAccessible.h new file mode 100644 index 0000000000..e2498a52f8 --- /dev/null +++ b/accessible/html/HTMLSelectAccessible.h @@ -0,0 +1,216 @@ +/* -*- 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_HTMLSelectAccessible_h__ +#define mozilla_a11y_HTMLSelectAccessible_h__ + +#include "HTMLFormControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Selects, Listboxes and Comboboxes, are made up of a number of different + * widgets, some of which are shared between the two. This file contains + * all of the widgets for both of the Selects, for HTML only. + * + * Listbox: + * - HTMLSelectListAccessible + * - HTMLSelectOptionAccessible + * + * Comboboxes: + * - HTMLComboboxAccessible + * - HTMLComboboxListAccessible [ inserted in accessible tree ] + * - HTMLSelectOptionAccessible(s) + */ + +/* + * The list that contains all the options in the select. + */ +class HTMLSelectListAccessible : public AccessibleWrap { + public: + HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLSelectListAccessible() {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual bool IsAcceptableChild(nsIContent* aEl) const override; + virtual bool AttributeChangesState(nsAtom* aAttribute) override; + + // SelectAccessible + 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; +}; + +/* + * Options inside the select, contained within the list + */ +class HTMLSelectOptionAccessible : public HyperTextAccessibleWrap { + public: + enum { eAction_Select = 0 }; + + HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLSelectOptionAccessible() {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + virtual void SetSelected(bool aSelect) override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // Widgets + virtual LocalAccessible* ContainerWidget() const override; + + protected: + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + private: + /** + * Return a select accessible the option belongs to if any. + */ + LocalAccessible* GetSelect() const { + LocalAccessible* parent = mParent; + if (parent && parent->IsHTMLOptGroup()) { + parent = parent->LocalParent(); + } + + if (parent && parent->IsListControl()) { + LocalAccessible* combobox = parent->LocalParent(); + return combobox && combobox->IsCombobox() ? combobox : mParent; + } + + return nullptr; + } + + /** + * Return a combobox accessible the option belongs to if any. + */ + LocalAccessible* GetCombobox() const { + LocalAccessible* parent = mParent; + if (parent && parent->IsHTMLOptGroup()) { + parent = parent->LocalParent(); + } + + if (parent && parent->IsListControl()) { + LocalAccessible* combobox = parent->LocalParent(); + return combobox && combobox->IsCombobox() ? combobox : nullptr; + } + + return nullptr; + } +}; + +/* + * Opt Groups inside the select, contained within the list + */ +class HTMLSelectOptGroupAccessible : public HTMLSelectOptionAccessible { + public: + HTMLSelectOptGroupAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HTMLSelectOptionAccessible(aContent, aDoc) { + mType = eHTMLOptGroupType; + } + virtual ~HTMLSelectOptGroupAccessible() {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeInteractiveState() const override; + virtual bool IsAcceptableChild(nsIContent* aEl) const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; +}; + +/** ------------------------------------------------------ */ +/** Finally, the Combobox widgets */ +/** ------------------------------------------------------ */ + +class HTMLComboboxListAccessible; + +/* + * A class the represents the HTML Combobox widget. + */ +class HTMLComboboxAccessible final : public AccessibleWrap { + public: + enum { eAction_Click = 0 }; + + HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLComboboxAccessible() {} + + // LocalAccessible + virtual void Shutdown() override; + virtual void Description(nsString& aDescription) const override; + virtual void Value(nsString& aValue) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual bool RemoveChild(LocalAccessible* aChild) override; + virtual bool IsAcceptableChild(nsIContent* aEl) const 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 LocalAccessible* CurrentItem() const override; + virtual void SetCurrentItem(const LocalAccessible* aItem) override; + + HTMLComboboxListAccessible* List() const { return mListAccessible; } + + /** + * Return selected option. + */ + LocalAccessible* SelectedOption() const; + + private: + RefPtr<HTMLComboboxListAccessible> mListAccessible; +}; + +/* + * A class that represents the window that lives to the right + * of the drop down button inside the Select. This is the window + * that is made visible when the button is pressed. + */ +class HTMLComboboxListAccessible : public HTMLSelectListAccessible { + public: + HTMLComboboxListAccessible(LocalAccessible* aParent, nsIContent* aContent, + DocAccessible* aDoc); + virtual ~HTMLComboboxListAccessible() {} + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + virtual bool IsAcceptableChild(nsIContent* aEl) const override; + + // Widgets + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp new file mode 100644 index 0000000000..0fccd6a6bd --- /dev/null +++ b/accessible/html/HTMLTableAccessible.cpp @@ -0,0 +1,896 @@ +/* -*- 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 "HTMLTableAccessible.h" + +#include "mozilla/DebugOnly.h" + +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "AccAttributes.h" +#include "CacheConstants.h" +#include "DocAccessible.h" +#include "LocalAccessible-inl.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" +#include "TreeWalker.h" + +#include "mozilla/PresShell.h" +#include "mozilla/dom/HTMLTableElement.h" +#include "nsIHTMLCollection.h" +#include "mozilla/dom/Document.h" +#include "nsITableCellLayout.h" +#include "nsFrameSelection.h" +#include "nsError.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsNameSpaceManager.h" +#include "nsTableCellFrame.h" +#include "nsTableWrapperFrame.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTableCellAccessible::HTMLTableCellAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLTableCellType; + mGenericTypes |= eTableCell; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: LocalAccessible implementation + +role HTMLTableCellAccessible::NativeRole() const { + if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) { + return roles::MATHML_CELL; + } + return roles::CELL; +} + +uint64_t HTMLTableCellAccessible::NativeState() const { + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + nsIFrame* frame = mContent->GetPrimaryFrame(); + NS_ASSERTION(frame, "No frame for valid cell accessible!"); + + if (frame && frame->IsSelected()) { + state |= states::SELECTED; + } + + return state; +} + +uint64_t HTMLTableCellAccessible::NativeInteractiveState() const { + return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE; +} + +already_AddRefed<AccAttributes> HTMLTableCellAccessible::NativeAttributes() { + RefPtr<AccAttributes> attributes = + HyperTextAccessibleWrap::NativeAttributes(); + + // table-cell-index attribute + TableAccessible* table = Table(); + if (!table) { + return attributes.forget(); + } + + int32_t rowIdx = -1, colIdx = -1; + nsresult rv = GetCellIndexes(rowIdx, colIdx); + if (NS_FAILED(rv)) { + return attributes.forget(); + } + + attributes->SetAttribute(nsGkAtoms::tableCellIndex, + table->CellIndexAt(rowIdx, colIdx)); + + // abbr attribute + + // Pick up object attribute from abbr DOM element (a child of the cell) or + // from abbr DOM attribute. + nsString abbrText; + if (ChildCount() == 1) { + LocalAccessible* abbr = LocalFirstChild(); + if (abbr->IsAbbreviation()) { + nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild(); + if (firstChildNode) { + nsTextEquivUtils::AppendTextEquivFromTextContent(firstChildNode, + &abbrText); + } + } + } + if (abbrText.IsEmpty()) { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, + abbrText); + } + + if (!abbrText.IsEmpty()) { + attributes->SetAttribute(nsGkAtoms::abbr, std::move(abbrText)); + } + + // axis attribute + nsString axisText; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText); + if (!axisText.IsEmpty()) { + attributes->SetAttribute(nsGkAtoms::axis, std::move(axisText)); + } + +#ifdef DEBUG + RefPtr<nsAtom> cppClass = NS_Atomize(u"cppclass"_ns); + attributes->SetAttributeStringCopy(cppClass, u"HTMLTableCellAccessible"_ns); +#endif + + return attributes.forget(); +} + +void HTMLTableCellAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::headers || aAttribute == nsGkAtoms::abbr || + aAttribute == nsGkAtoms::scope) { + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, + this); + if (TableAccessible* table = Table()) { + // Modifying these attributes can also modify our table's classification + // as either a layout or data table. Queue an update on the table itself + // to re-compute our "layout guess" + mDoc->QueueCacheUpdate(table->AsAccessible(), CacheDomain::Table); + } + mDoc->QueueCacheUpdate(this, CacheDomain::Table); + } else if (aAttribute == nsGkAtoms::rowspan || + aAttribute == nsGkAtoms::colspan) { + if (TableAccessible* table = Table()) { + // Modifying these attributes can also modify our table's classification + // as either a layout or data table. Queue an update on the table itself + // to re-compute our "layout guess" + mDoc->QueueCacheUpdate(table->AsAccessible(), CacheDomain::Table); + } + mDoc->QueueCacheUpdate(this, CacheDomain::Table); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: TableCellAccessible implementation + +TableAccessible* HTMLTableCellAccessible::Table() const { + LocalAccessible* parent = const_cast<HTMLTableCellAccessible*>(this); + while ((parent = parent->LocalParent())) { + if (parent->IsTable()) { + return parent->AsTable(); + } + } + + return nullptr; +} + +uint32_t HTMLTableCellAccessible::ColIdx() const { + nsTableCellFrame* cellFrame = GetCellFrame(); + NS_ENSURE_TRUE(cellFrame, 0); + return cellFrame->ColIndex(); +} + +uint32_t HTMLTableCellAccessible::RowIdx() const { + nsTableCellFrame* cellFrame = GetCellFrame(); + NS_ENSURE_TRUE(cellFrame, 0); + return cellFrame->RowIndex(); +} + +uint32_t HTMLTableCellAccessible::ColExtent() const { + int32_t rowIdx = -1, colIdx = -1; + if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { + return 0; + } + + TableAccessible* table = Table(); + NS_ASSERTION(table, "cell not in a table!"); + if (!table) { + return 0; + } + + return table->ColExtentAt(rowIdx, colIdx); +} + +uint32_t HTMLTableCellAccessible::RowExtent() const { + int32_t rowIdx = -1, colIdx = -1; + if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { + return 0; + } + + TableAccessible* table = Table(); + NS_ASSERTION(table, "cell not in atable!"); + if (!table) { + return 0; + } + + return table->RowExtentAt(rowIdx, colIdx); +} + +void HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) { + IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); + while (LocalAccessible* cell = itr.Next()) { + a11y::role cellRole = cell->Role(); + if (cellRole == roles::COLUMNHEADER) { + aCells->AppendElement(cell); + } else if (cellRole != roles::ROWHEADER) { + // If referred table cell is at the same column then treat it as a column + // header. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (tableCell && tableCell->ColIdx() == ColIdx()) { + aCells->AppendElement(cell); + } + } + } + + if (aCells->IsEmpty()) { + TableCellAccessible::ColHeaderCells(aCells); + } +} + +void HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells) { + IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); + while (LocalAccessible* cell = itr.Next()) { + a11y::role cellRole = cell->Role(); + if (cellRole == roles::ROWHEADER) { + aCells->AppendElement(cell); + } else if (cellRole != roles::COLUMNHEADER) { + // If referred table cell is at the same row then treat it as a column + // header. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (tableCell && tableCell->RowIdx() == RowIdx()) { + aCells->AppendElement(cell); + } + } + } + + if (aCells->IsEmpty()) { + TableCellAccessible::RowHeaderCells(aCells); + } +} + +bool HTMLTableCellAccessible::Selected() { + int32_t rowIdx = -1, colIdx = -1; + if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { + return false; + } + + TableAccessible* table = Table(); + NS_ENSURE_TRUE(table, false); + + return table->IsCellSelected(rowIdx, colIdx); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: protected implementation + +nsITableCellLayout* HTMLTableCellAccessible::GetCellLayout() const { + return do_QueryFrame(mContent->GetPrimaryFrame()); +} + +nsTableCellFrame* HTMLTableCellAccessible::GetCellFrame() const { + return do_QueryFrame(mContent->GetPrimaryFrame()); +} + +nsresult HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, + int32_t& aColIdx) const { + nsITableCellLayout* cellLayout = GetCellLayout(); + NS_ENSURE_STATE(cellLayout); + + return cellLayout->GetCellIndexes(aRowIdx, aColIdx); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableHeaderCellAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTableHeaderCellAccessible::HTMLTableHeaderCellAccessible( + nsIContent* aContent, DocAccessible* aDoc) + : HTMLTableCellAccessible(aContent, aDoc) {} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableHeaderCellAccessible: LocalAccessible implementation + +role HTMLTableHeaderCellAccessible::NativeRole() const { + return GetHeaderCellRole(this); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableRowAccessible +//////////////////////////////////////////////////////////////////////////////// + +role HTMLTableRowAccessible::NativeRole() const { + if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) { + return roles::MATHML_TABLE_ROW; + } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) { + return roles::MATHML_LABELED_ROW; + } + return roles::ROW; +} + +// LocalAccessible protected +ENameValueFlag HTMLTableRowAccessible::NativeName(nsString& aName) const { + // For table row accessibles, we only want to calculate the name from the + // sub tree if an ARIA role is present. + if (HasStrongARIARole()) { + return AccessibleWrap::NativeName(aName); + } + + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: LocalAccessible + +bool HTMLTableAccessible::InsertChildAt(uint32_t aIndex, + LocalAccessible* aChild) { + // Move caption accessible so that it's the first child. Check for the first + // caption only, because nsAccessibilityService ensures we don't create + // accessibles for the other captions, since only the first is actually + // visible. + return HyperTextAccessible::InsertChildAt( + aChild->IsHTMLCaption() ? 0 : aIndex, aChild); +} + +role HTMLTableAccessible::NativeRole() const { + if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) { + return roles::MATHML_TABLE; + } + return roles::TABLE; +} + +uint64_t HTMLTableAccessible::NativeState() const { + return LocalAccessible::NativeState() | states::READONLY; +} + +ENameValueFlag HTMLTableAccessible::NativeName(nsString& aName) const { + ENameValueFlag nameFlag = LocalAccessible::NativeName(aName); + if (!aName.IsEmpty()) { + return nameFlag; + } + + // Use table caption as a name. + LocalAccessible* caption = Caption(); + if (caption) { + nsIContent* captionContent = caption->GetContent(); + if (captionContent) { + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, + &aName); + if (!aName.IsEmpty()) { + return eNameOK; + } + } + } + + // If no caption then use summary as a name. + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName); + return eNameOK; +} + +void HTMLTableAccessible::DOMAttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) { + HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute, + aModType, aOldValue, aOldState); + + if (aAttribute == nsGkAtoms::summary) { + nsAutoString name; + ARIAName(name); + if (name.IsEmpty()) { + if (!Caption()) { + // XXX: Should really be checking if caption provides a name. + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); + } + } + + mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, + this); + mDoc->QueueCacheUpdate(this, CacheDomain::Table); + } +} + +already_AddRefed<AccAttributes> HTMLTableAccessible::NativeAttributes() { + RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes(); + + if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) { + GetAccService()->MarkupAttributes(this, attributes); + } + + if (IsProbablyLayoutTable()) { + attributes->SetAttribute(nsGkAtoms::layout_guess, true); + } + + return attributes.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: LocalAccessible + +Relation HTMLTableAccessible::RelationByType(RelationType aType) const { + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABELLED_BY) { + rel.AppendTarget(Caption()); + } + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: Table + +LocalAccessible* HTMLTableAccessible::Caption() const { + LocalAccessible* child = mChildren.SafeElementAt(0, nullptr); + // Since this is an HTML table the caption needs to be a caption + // element with no ARIA role (except for a reduntant role='caption'). + // If we did a full Role() calculation here we risk getting into an infinite + // loop where the parent role would depend on its name which would need to be + // calculated by retrieving the caption (bug 1420773.) + return child && child->NativeRole() == roles::CAPTION && + (!child->HasStrongARIARole() || + child->IsARIARole(nsGkAtoms::caption)) + ? child + : nullptr; +} + +void HTMLTableAccessible::Summary(nsString& aSummary) { + dom::HTMLTableElement* table = dom::HTMLTableElement::FromNode(mContent); + + if (table) { + table->GetSummary(aSummary); + } +} + +uint32_t HTMLTableAccessible::ColCount() const { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + return tableFrame ? tableFrame->GetColCount() : 0; +} + +uint32_t HTMLTableAccessible::RowCount() { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + return tableFrame ? tableFrame->GetRowCount() : 0; +} + +uint32_t HTMLTableAccessible::SelectedCellCount() { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return 0; + } + + uint32_t count = 0, rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) { + continue; + } + + uint32_t startRow = cellFrame->RowIndex(); + uint32_t startCol = cellFrame->ColIndex(); + if (startRow == rowIdx && startCol == colIdx) { + count++; + } + } + } + + return count; +} + +uint32_t HTMLTableAccessible::SelectedColCount() { + uint32_t count = 0, colCount = ColCount(); + + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + if (IsColSelected(colIdx)) { + count++; + } + } + + return count; +} + +uint32_t HTMLTableAccessible::SelectedRowCount() { + uint32_t count = 0, rowCount = RowCount(); + + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (IsRowSelected(rowIdx)) { + count++; + } + } + + return count; +} + +void HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return; + } + + uint32_t rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) { + continue; + } + + uint32_t startRow = cellFrame->RowIndex(); + uint32_t startCol = cellFrame->ColIndex(); + if (startRow != rowIdx || startCol != colIdx) { + continue; + } + + LocalAccessible* cell = mDoc->GetAccessible(cellFrame->GetContent()); + aCells->AppendElement(cell); + } + } +} + +void HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return; + } + + uint32_t rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) { + continue; + } + + uint32_t startCol = cellFrame->ColIndex(); + uint32_t startRow = cellFrame->RowIndex(); + if (startRow == rowIdx && startCol == colIdx) { + aCells->AppendElement(CellIndexAt(rowIdx, colIdx)); + } + } + } +} + +void HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) { + uint32_t colCount = ColCount(); + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + if (IsColSelected(colIdx)) { + aCols->AppendElement(colIdx); + } + } +} + +void HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) { + uint32_t rowCount = RowCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (IsRowSelected(rowIdx)) { + aRows->AppendElement(rowIdx); + } + } +} + +LocalAccessible* HTMLTableAccessible::CellAt(uint32_t aRowIdx, + uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return nullptr; + } + + nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); + LocalAccessible* cell = mDoc->GetAccessible(cellContent); + + // Sometimes, the accessible returned here is a row accessible instead of + // a cell accessible, for example when a cell has CSS display:block; set. + // In such cases, iterate through the cells in this row differently to find + // it. + if (cell && cell->IsTableRow()) { + return CellInRowAt(cell, aColIdx); + } + + // XXX bug 576838: bizarre tables (like table6 in tables/test_table2.html) may + // return itself as a cell what makes Orca hang. + return cell == this ? nullptr : cell; +} + +int32_t HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return -1; + } + + int32_t cellIndex = tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx); + if (cellIndex == -1) { + // Sometimes, the accessible returned here is a row accessible instead of + // a cell accessible, for example when a cell has CSS display:block; set. + // In such cases, iterate through the cells in this row differently to find + // it. + nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); + LocalAccessible* cell = mDoc->GetAccessible(cellContent); + if (cell && cell->IsTableRow()) { + return TableAccessible::CellIndexAt(aRowIdx, aColIdx); + } + } + + return cellIndex; +} + +int32_t HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return -1; + } + + int32_t rowIdx = -1, colIdx = -1; + tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); + + if (colIdx == -1) { + // Sometimes, the index returned indicates that this is not a regular + // cell, for example when a cell has CSS display:block; set. + // In such cases, try the super class method to find it. + return TableAccessible::ColIndexAt(aCellIdx); + } + + return colIdx; +} + +int32_t HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return -1; + } + + int32_t rowIdx = -1, colIdx = -1; + tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); + + if (rowIdx == -1) { + // Sometimes, the index returned indicates that this is not a regular + // cell, for example when a cell has CSS display:block; set. + // In such cases, try the super class method to find it. + return TableAccessible::RowIndexAt(aCellIdx); + } + + return rowIdx; +} + +void HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, + int32_t* aRowIdx, + int32_t* aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (tableFrame) { + tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx); + if (*aRowIdx == -1 || *aColIdx == -1) { + // Sometimes, the index returned indicates that this is not a regular + // cell, for example when a cell has CSS display:block; set. + // In such cases, try the super class method to find it. + TableAccessible::RowAndColIndicesAt(aCellIdx, aRowIdx, aColIdx); + } + } +} + +uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return 0; + } + + uint32_t colExtent = tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx); + if (colExtent == 0) { + nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); + LocalAccessible* cell = mDoc->GetAccessible(cellContent); + if (cell && cell->IsTableRow()) { + return TableAccessible::ColExtentAt(aRowIdx, aColIdx); + } + } + + return colExtent; +} + +uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return 0; + } + + return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx); +} + +bool HTMLTableAccessible::IsColSelected(uint32_t aColIdx) { + bool isSelected = false; + + uint32_t rowCount = RowCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + isSelected = IsCellSelected(rowIdx, aColIdx); + if (!isSelected) { + return false; + } + } + + return isSelected; +} + +bool HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx) { + bool isSelected = false; + + uint32_t colCount = ColCount(); + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + isSelected = IsCellSelected(aRowIdx, colIdx); + if (!isSelected) { + return false; + } + } + + return isSelected; +} + +bool HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return false; + } + + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx); + return cellFrame ? cellFrame->IsSelected() : false; +} + +void HTMLTableAccessible::SelectRow(uint32_t aRowIdx) { + DebugOnly<nsresult> rv = + RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); + + AddRowOrColumnToSelection(aRowIdx, TableSelectionMode::Row); +} + +void HTMLTableAccessible::SelectCol(uint32_t aColIdx) { + DebugOnly<nsresult> rv = RemoveRowsOrColumnsFromSelection( + aColIdx, TableSelectionMode::Column, true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); + + AddRowOrColumnToSelection(aColIdx, TableSelectionMode::Column); +} + +void HTMLTableAccessible::UnselectRow(uint32_t aRowIdx) { + RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, false); +} + +void HTMLTableAccessible::UnselectCol(uint32_t aColIdx) { + RemoveRowsOrColumnsFromSelection(aColIdx, TableSelectionMode::Column, false); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: protected implementation + +nsresult HTMLTableAccessible::AddRowOrColumnToSelection( + int32_t aIndex, TableSelectionMode aTarget) { + bool doSelectRow = (aTarget == TableSelectionMode::Row); + + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return NS_OK; + } + + uint32_t count = 0; + if (doSelectRow) { + count = ColCount(); + } else { + count = RowCount(); + } + + PresShell* presShell = mDoc->PresShellPtr(); + RefPtr<nsFrameSelection> tableSelection = + const_cast<nsFrameSelection*>(presShell->ConstFrameSelection()); + + for (uint32_t idx = 0; idx < count; idx++) { + int32_t rowIdx = doSelectRow ? aIndex : idx; + int32_t colIdx = doSelectRow ? idx : aIndex; + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (cellFrame && !cellFrame->IsSelected()) { + nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult HTMLTableAccessible::RemoveRowsOrColumnsFromSelection( + int32_t aIndex, TableSelectionMode aTarget, bool aIsOuter) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return NS_OK; + } + + PresShell* presShell = mDoc->PresShellPtr(); + RefPtr<nsFrameSelection> tableSelection = + const_cast<nsFrameSelection*>(presShell->ConstFrameSelection()); + + bool doUnselectRow = (aTarget == TableSelectionMode::Row); + uint32_t count = doUnselectRow ? ColCount() : RowCount(); + + int32_t startRowIdx = doUnselectRow ? aIndex : 0; + int32_t endRowIdx = doUnselectRow ? aIndex : count - 1; + int32_t startColIdx = doUnselectRow ? 0 : aIndex; + int32_t endColIdx = doUnselectRow ? count - 1 : aIndex; + + if (aIsOuter) { + return tableSelection->RestrictCellsToSelection( + mContent, startRowIdx, startColIdx, endRowIdx, endColIdx); + } + + return tableSelection->RemoveCellsFromSelection( + mContent, startRowIdx, startColIdx, endRowIdx, endColIdx); +} + +void HTMLTableAccessible::Description(nsString& aDescription) const { + // Helpful for debugging layout vs. data tables + aDescription.Truncate(); + LocalAccessible::Description(aDescription); + if (!aDescription.IsEmpty()) { + return; + } + + // Use summary as description if it weren't used as a name. + // XXX: get rid code duplication with NameInternal(). + LocalAccessible* caption = Caption(); + if (caption) { + nsIContent* captionContent = caption->GetContent(); + if (captionContent) { + nsAutoString captionText; + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, + &captionText); + + if (!captionText.IsEmpty()) { // summary isn't used as a name. + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, + aDescription); + } + } + } + +#ifdef SHOW_LAYOUT_HEURISTIC + if (aDescription.IsEmpty()) { + bool isProbablyForLayout = IsProbablyLayoutTable(); + aDescription = mLayoutHeuristic; + } + printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get()); +#endif +} + +nsTableWrapperFrame* HTMLTableAccessible::GetTableWrapperFrame() const { + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (tableFrame && tableFrame->PrincipalChildList().FirstChild()) { + return tableFrame; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLCaptionAccessible +//////////////////////////////////////////////////////////////////////////////// + +Relation HTMLCaptionAccessible::RelationByType(RelationType aType) const { + Relation rel = HyperTextAccessible::RelationByType(aType); + if (aType == RelationType::LABEL_FOR) { + rel.AppendTarget(LocalParent()); + } + + return rel; +} + +role HTMLCaptionAccessible::NativeRole() const { return roles::CAPTION; } diff --git a/accessible/html/HTMLTableAccessible.h b/accessible/html/HTMLTableAccessible.h new file mode 100644 index 0000000000..a581d1d0c5 --- /dev/null +++ b/accessible/html/HTMLTableAccessible.h @@ -0,0 +1,241 @@ +/* -*- 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_HTMLTableAccessible_h__ +#define mozilla_a11y_HTMLTableAccessible_h__ + +#include "HyperTextAccessibleWrap.h" +#include "TableAccessible.h" +#include "TableCellAccessible.h" + +class nsITableCellLayout; +class nsTableCellFrame; +class nsTableWrapperFrame; + +namespace mozilla { + +enum class TableSelectionMode : uint32_t; + +namespace a11y { + +/** + * HTML table cell accessible (html:td). + */ +class HTMLTableCellAccessible : public HyperTextAccessibleWrap, + public TableCellAccessible { + public: + HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTableCellAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual TableCellAccessible* AsTableCell() override { return this; } + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + virtual already_AddRefed<AccAttributes> NativeAttributes() override; + + protected: + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + // TableCellAccessible + public: + virtual TableAccessible* Table() const override; + virtual uint32_t ColIdx() const override; + virtual uint32_t RowIdx() const override; + virtual uint32_t ColExtent() const override; + virtual uint32_t RowExtent() const override; + virtual void ColHeaderCells(nsTArray<Accessible*>* aCells) override; + virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) override; + virtual bool Selected() override; + + protected: + virtual ~HTMLTableCellAccessible() {} + + /** + * Return nsITableCellLayout of the table cell frame. + */ + nsITableCellLayout* GetCellLayout() const; + + /** + * Return the table cell frame. + */ + nsTableCellFrame* GetCellFrame() const; + + /** + * Return row and column indices of the cell. + */ + nsresult GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const; +}; + +/** + * HTML table row/column header accessible (html:th or html:td@scope). + */ +class HTMLTableHeaderCellAccessible : public HTMLTableCellAccessible { + public: + HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // LocalAccessible + virtual a11y::role NativeRole() const override; +}; + +/** + * HTML table row accessible (html:tr). + */ +class HTMLTableRowAccessible : public HyperTextAccessibleWrap { + public: + HTMLTableRowAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLTableRowType; + mGenericTypes |= eTableRow; + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTableRowAccessible, + HyperTextAccessibleWrap) + + // LocalAccessible + virtual a11y::role NativeRole() const override; + + protected: + virtual ~HTMLTableRowAccessible() {} + + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; +}; + +/** + * HTML table accessible (html:table). + */ + +// To turn on table debugging descriptions define SHOW_LAYOUT_HEURISTIC +// This allow release trunk builds to be used by testers to refine the +// data vs. layout heuristic +// #define SHOW_LAYOUT_HEURISTIC + +class HTMLTableAccessible : public HyperTextAccessibleWrap, + public TableAccessible { + public: + HTMLTableAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLTableType; + mGenericTypes |= eTable; + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTableAccessible, + HyperTextAccessibleWrap) + + // TableAccessible + virtual LocalAccessible* Caption() const override; + virtual void Summary(nsString& aSummary) override; + virtual uint32_t ColCount() const override; + virtual uint32_t RowCount() override; + virtual LocalAccessible* CellAt(uint32_t aRowIndex, + uint32_t aColumnIndex) override; + virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override; + 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; + virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) 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 void SelectCol(uint32_t aColIdx) override; + virtual void SelectRow(uint32_t aRowIdx) override; + virtual void UnselectCol(uint32_t aColIdx) override; + virtual void UnselectRow(uint32_t aRowIdx) override; + virtual LocalAccessible* AsAccessible() override { return this; } + + // LocalAccessible + virtual TableAccessible* AsTable() override { return this; } + virtual void Description(nsString& aDescription) const override; + virtual a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual already_AddRefed<AccAttributes> NativeAttributes() override; + virtual Relation RelationByType(RelationType aRelationType) const override; + + virtual bool InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) override; + + protected: + virtual ~HTMLTableAccessible() {} + + // LocalAccessible + virtual ENameValueFlag NativeName(nsString& aName) const override; + + virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue, + uint64_t aOldState) override; + + // HTMLTableAccessible + + /** + * Add row or column to selection. + * + * @param aIndex [in] index of row or column to be selected + * @param aTarget [in] indicates what should be selected, either row or + * column (see nsFrameSelection) + */ + nsresult AddRowOrColumnToSelection(int32_t aIndex, + TableSelectionMode aTarget); + + /** + * Removes rows or columns at the given index or outside it from selection. + * + * @param aIndex [in] row or column index + * @param aTarget [in] indicates whether row or column should unselected + * @param aIsOuter [in] indicates whether all rows or column excepting + * the given one should be unselected or the given one + * should be unselected only + */ + nsresult RemoveRowsOrColumnsFromSelection(int32_t aIndex, + TableSelectionMode aTarget, + bool aIsOuter); + +#ifdef SHOW_LAYOUT_HEURISTIC + nsString mLayoutHeuristic; +#endif + + private: + /** + * Get table wrapper frame, or return null if there is no inner table. + */ + nsTableWrapperFrame* GetTableWrapperFrame() const; +}; + +/** + * HTML caption accessible (html:caption). + */ +class HTMLCaptionAccessible : public HyperTextAccessibleWrap { + public: + HTMLCaptionAccessible(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessibleWrap(aContent, aDoc) { + mType = eHTMLCaptionType; + } + + // LocalAccessible + virtual a11y::role NativeRole() const override; + virtual Relation RelationByType(RelationType aRelationType) const override; + + protected: + virtual ~HTMLCaptionAccessible() {} +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/moz.build b/accessible/html/moz.build new file mode 100644 index 0000000000..3a246373da --- /dev/null +++ b/accessible/html/moz.build @@ -0,0 +1,52 @@ +# -*- 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 += [ + "HTMLCanvasAccessible.cpp", + "HTMLElementAccessibles.cpp", + "HTMLFormControlAccessible.cpp", + "HTMLImageMapAccessible.cpp", + "HTMLLinkAccessible.cpp", + "HTMLListAccessible.cpp", + "HTMLSelectAccessible.cpp", + "HTMLTableAccessible.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/xpcom", + "/layout/forms", + "/layout/generic", + "/layout/tables", + "/layout/xul", +] + +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" |