summaryrefslogtreecommitdiffstats
path: root/accessible/html
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/html/HTMLCanvasAccessible.cpp16
-rw-r--r--accessible/html/HTMLCanvasAccessible.h35
-rw-r--r--accessible/html/HTMLElementAccessibles.cpp233
-rw-r--r--accessible/html/HTMLElementAccessibles.h160
-rw-r--r--accessible/html/HTMLFormControlAccessible.cpp981
-rw-r--r--accessible/html/HTMLFormControlAccessible.h382
-rw-r--r--accessible/html/HTMLImageMapAccessible.cpp211
-rw-r--r--accessible/html/HTMLImageMapAccessible.h85
-rw-r--r--accessible/html/HTMLLinkAccessible.cpp134
-rw-r--r--accessible/html/HTMLLinkAccessible.h62
-rw-r--r--accessible/html/HTMLListAccessible.cpp115
-rw-r--r--accessible/html/HTMLListAccessible.h87
-rw-r--r--accessible/html/HTMLSelectAccessible.cpp474
-rw-r--r--accessible/html/HTMLSelectAccessible.h216
-rw-r--r--accessible/html/HTMLTableAccessible.cpp896
-rw-r--r--accessible/html/HTMLTableAccessible.h241
-rw-r--r--accessible/html/moz.build52
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"