summaryrefslogtreecommitdiffstats
path: root/accessible/basetypes
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/basetypes')
-rw-r--r--accessible/basetypes/Accessible.cpp723
-rw-r--r--accessible/basetypes/Accessible.h725
-rw-r--r--accessible/basetypes/HyperTextAccessibleBase.cpp843
-rw-r--r--accessible/basetypes/HyperTextAccessibleBase.h310
-rw-r--r--accessible/basetypes/TableAccessible.h172
-rw-r--r--accessible/basetypes/TableCellAccessible.h68
-rw-r--r--accessible/basetypes/moz.build25
7 files changed, 2866 insertions, 0 deletions
diff --git a/accessible/basetypes/Accessible.cpp b/accessible/basetypes/Accessible.cpp
new file mode 100644
index 0000000000..cc41cd3a46
--- /dev/null
+++ b/accessible/basetypes/Accessible.cpp
@@ -0,0 +1,723 @@
+/* -*- 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 "Accessible.h"
+#include "AccGroupInfo.h"
+#include "ARIAMap.h"
+#include "nsAccUtils.h"
+#include "nsIURI.h"
+#include "Relation.h"
+#include "States.h"
+#include "mozilla/a11y/FocusManager.h"
+#include "mozilla/a11y/HyperTextAccessibleBase.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Components.h"
+#include "nsIStringBundle.h"
+
+#ifdef A11Y_LOG
+# include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+Accessible::Accessible()
+ : mType(static_cast<uint32_t>(0)),
+ mGenericTypes(static_cast<uint32_t>(0)),
+ mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX) {}
+
+Accessible::Accessible(AccType aType, AccGenericType aGenericTypes,
+ uint8_t aRoleMapEntryIndex)
+ : mType(static_cast<uint32_t>(aType)),
+ mGenericTypes(static_cast<uint32_t>(aGenericTypes)),
+ mRoleMapEntryIndex(aRoleMapEntryIndex) {}
+
+void Accessible::StaticAsserts() const {
+ static_assert(eLastAccType <= (1 << kTypeBits) - 1,
+ "Accessible::mType was oversized by eLastAccType!");
+ static_assert(
+ eLastAccGenericType <= (1 << kGenericTypesBits) - 1,
+ "Accessible::mGenericType was oversized by eLastAccGenericType!");
+}
+
+bool Accessible::IsBefore(const Accessible* aAcc) const {
+ // Build the chain of parents.
+ const Accessible* thisP = this;
+ const Accessible* otherP = aAcc;
+ AutoTArray<const Accessible*, 30> thisParents, otherParents;
+ do {
+ thisParents.AppendElement(thisP);
+ thisP = thisP->Parent();
+ } while (thisP);
+ do {
+ otherParents.AppendElement(otherP);
+ otherP = otherP->Parent();
+ } while (otherP);
+
+ // Find where the parent chain differs.
+ uint32_t thisPos = thisParents.Length(), otherPos = otherParents.Length();
+ for (uint32_t len = std::min(thisPos, otherPos); len > 0; --len) {
+ const Accessible* thisChild = thisParents.ElementAt(--thisPos);
+ const Accessible* otherChild = otherParents.ElementAt(--otherPos);
+ if (thisChild != otherChild) {
+ return thisChild->IndexInParent() < otherChild->IndexInParent();
+ }
+ }
+
+ // If the ancestries are the same length (both thisPos and otherPos are 0),
+ // we should have returned by now.
+ MOZ_ASSERT(thisPos != 0 || otherPos != 0);
+ // At this point, one of the ancestries is a superset of the other, so one of
+ // thisPos or otherPos should be 0.
+ MOZ_ASSERT(thisPos != otherPos);
+ // If the other Accessible is deeper than this one (otherPos > 0), this
+ // Accessible comes before the other.
+ return otherPos > 0;
+}
+
+Accessible* Accessible::FocusedChild() {
+ Accessible* doc = nsAccUtils::DocumentFor(this);
+ Accessible* child = doc->FocusedChild();
+ if (child && (child == this || child->Parent() == this)) {
+ return child;
+ }
+
+ return nullptr;
+}
+
+const nsRoleMapEntry* Accessible::ARIARoleMap() const {
+ return aria::GetRoleMapFromIndex(mRoleMapEntryIndex);
+}
+
+bool Accessible::HasARIARole() const {
+ return mRoleMapEntryIndex != aria::NO_ROLE_MAP_ENTRY_INDEX;
+}
+
+bool Accessible::IsARIARole(nsAtom* aARIARole) const {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->Is(aARIARole);
+}
+
+bool Accessible::HasStrongARIARole() const {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->roleRule == kUseMapRole;
+}
+
+bool Accessible::HasGenericType(AccGenericType aType) const {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return (mGenericTypes & aType) ||
+ (roleMapEntry && roleMapEntry->IsOfType(aType));
+}
+
+nsIntRect Accessible::BoundsInCSSPixels() const {
+ return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel());
+}
+
+LayoutDeviceIntSize Accessible::Size() const { return Bounds().Size(); }
+
+LayoutDeviceIntPoint Accessible::Position(uint32_t aCoordType) {
+ LayoutDeviceIntPoint point = Bounds().TopLeft();
+ nsAccUtils::ConvertScreenCoordsTo(&point.x.value, &point.y.value, aCoordType,
+ this);
+ return point;
+}
+
+bool Accessible::IsTextRole() {
+ if (!IsHyperText()) {
+ return false;
+ }
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && (roleMapEntry->role == roles::GRAPHIC ||
+ roleMapEntry->role == roles::IMAGE_MAP ||
+ roleMapEntry->role == roles::SLIDER ||
+ roleMapEntry->role == roles::PROGRESSBAR ||
+ roleMapEntry->role == roles::SEPARATOR)) {
+ return false;
+ }
+
+ return true;
+}
+
+uint32_t Accessible::StartOffset() {
+ MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!");
+ Accessible* parent = Parent();
+ HyperTextAccessibleBase* hyperText =
+ parent ? parent->AsHyperTextBase() : nullptr;
+ return hyperText ? hyperText->GetChildOffset(this) : 0;
+}
+
+uint32_t Accessible::EndOffset() {
+ MOZ_ASSERT(IsLink(), "EndOffset is called on not hyper link!");
+ Accessible* parent = Parent();
+ HyperTextAccessibleBase* hyperText =
+ parent ? parent->AsHyperTextBase() : nullptr;
+ return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
+}
+
+GroupPos Accessible::GroupPosition() {
+ GroupPos groupPos;
+
+ // Try aria-row/colcount/index.
+ if (IsTableRow()) {
+ Accessible* table = nsAccUtils::TableFor(this);
+ if (table) {
+ if (auto count = table->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
+ if (*count >= 0) {
+ groupPos.setSize = *count;
+ }
+ }
+ }
+ if (auto index = GetIntARIAAttr(nsGkAtoms::aria_rowindex)) {
+ groupPos.posInSet = *index;
+ }
+ if (groupPos.setSize && groupPos.posInSet) {
+ return groupPos;
+ }
+ }
+ if (IsTableCell()) {
+ Accessible* table;
+ for (table = Parent(); table; table = table->Parent()) {
+ if (table->IsTable()) {
+ break;
+ }
+ }
+ if (table) {
+ if (auto count = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) {
+ if (*count >= 0) {
+ groupPos.setSize = *count;
+ }
+ }
+ }
+ if (auto index = GetIntARIAAttr(nsGkAtoms::aria_colindex)) {
+ groupPos.posInSet = *index;
+ }
+ if (groupPos.setSize && groupPos.posInSet) {
+ return groupPos;
+ }
+ }
+
+ // Get group position from ARIA attributes.
+ ARIAGroupPosition(&groupPos.level, &groupPos.setSize, &groupPos.posInSet);
+
+ // If ARIA is missed and the accessible is visible then calculate group
+ // position from hierarchy.
+ if (State() & states::INVISIBLE) return groupPos;
+
+ // Calculate group level if ARIA is missed.
+ if (groupPos.level == 0) {
+ groupPos.level = GetLevel(false);
+ }
+
+ // Calculate position in group and group size if ARIA is missed.
+ if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
+ int32_t posInSet = 0, setSize = 0;
+ GetPositionAndSetSize(&posInSet, &setSize);
+ if (posInSet != 0 && setSize != 0) {
+ if (groupPos.posInSet == 0) groupPos.posInSet = posInSet;
+
+ if (groupPos.setSize == 0) groupPos.setSize = setSize;
+ }
+ }
+
+ return groupPos;
+}
+
+int32_t Accessible::GetLevel(bool aFast) const {
+ int32_t level = 0;
+ if (!Parent()) return level;
+
+ roles::Role role = Role();
+ if (role == roles::OUTLINEITEM) {
+ // Always expose 'level' attribute for 'outlineitem' accessible. The number
+ // of nested 'grouping' accessibles containing 'outlineitem' accessible is
+ // its level.
+ level = 1;
+
+ if (!aFast) {
+ const Accessible* parent = this;
+ while ((parent = parent->Parent()) && !parent->IsDoc()) {
+ roles::Role parentRole = parent->Role();
+
+ if (parentRole == roles::OUTLINE) break;
+ if (parentRole == roles::GROUPING) ++level;
+ }
+ }
+ } else if (role == roles::LISTITEM && !aFast) {
+ // Expose 'level' attribute on nested lists. We support two hierarchies:
+ // a) list -> listitem -> list -> listitem (nested list is a last child
+ // of listitem of the parent list);
+ // b) list -> listitem -> group -> listitem (nested listitems are contained
+ // by group that is a last child of the parent listitem).
+
+ // Calculate 'level' attribute based on number of parent listitems.
+ level = 0;
+ const Accessible* parent = this;
+ while ((parent = parent->Parent()) && !parent->IsDoc()) {
+ roles::Role parentRole = parent->Role();
+
+ if (parentRole == roles::LISTITEM) {
+ ++level;
+ } else if (parentRole != roles::LIST && parentRole != roles::GROUPING) {
+ break;
+ }
+ }
+
+ if (level == 0) {
+ // If this listitem is on top of nested lists then expose 'level'
+ // attribute.
+ parent = Parent();
+ uint32_t siblingCount = parent->ChildCount();
+ for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
+ Accessible* sibling = parent->ChildAt(siblingIdx);
+
+ Accessible* siblingChild = sibling->LastChild();
+ if (siblingChild) {
+ roles::Role lastChildRole = siblingChild->Role();
+ if (lastChildRole == roles::LIST ||
+ lastChildRole == roles::GROUPING) {
+ return 1;
+ }
+ }
+ }
+ } else {
+ ++level; // level is 1-index based
+ }
+ } else if (role == roles::OPTION || role == roles::COMBOBOX_OPTION) {
+ if (const Accessible* parent = Parent()) {
+ if (parent->IsHTMLOptGroup()) {
+ return 2;
+ }
+
+ if (parent->IsListControl() && !parent->ARIARoleMap()) {
+ // This is for HTML selects only.
+ if (aFast) {
+ return 1;
+ }
+
+ for (uint32_t i = 0, count = parent->ChildCount(); i < count; ++i) {
+ if (parent->ChildAt(i)->IsHTMLOptGroup()) {
+ return 1;
+ }
+ }
+ }
+ }
+ } else if (role == roles::HEADING) {
+ nsAtom* tagName = TagName();
+ if (tagName == nsGkAtoms::h1) {
+ return 1;
+ }
+ if (tagName == nsGkAtoms::h2) {
+ return 2;
+ }
+ if (tagName == nsGkAtoms::h3) {
+ return 3;
+ }
+ if (tagName == nsGkAtoms::h4) {
+ return 4;
+ }
+ if (tagName == nsGkAtoms::h5) {
+ return 5;
+ }
+ if (tagName == nsGkAtoms::h6) {
+ return 6;
+ }
+
+ const nsRoleMapEntry* ariaRole = this->ARIARoleMap();
+ if (ariaRole && ariaRole->Is(nsGkAtoms::heading)) {
+ // An aria heading with no aria level has a default level of 2.
+ return 2;
+ }
+ } else if (role == roles::COMMENT) {
+ // For comments, count the ancestor elements with the same role to get the
+ // level.
+ level = 1;
+
+ if (!aFast) {
+ const Accessible* parent = this;
+ while ((parent = parent->Parent()) && !parent->IsDoc()) {
+ roles::Role parentRole = parent->Role();
+ if (parentRole == roles::COMMENT) {
+ ++level;
+ }
+ }
+ }
+ } else if (role == roles::ROW) {
+ // It is a row inside flatten treegrid. Group level is always 1 until it
+ // is overriden by aria-level attribute.
+ const Accessible* parent = Parent();
+ if (parent->Role() == roles::TREE_TABLE) {
+ return 1;
+ }
+ }
+
+ return level;
+}
+
+void Accessible::GetPositionAndSetSize(int32_t* aPosInSet, int32_t* aSetSize) {
+ auto groupInfo = GetOrCreateGroupInfo();
+ if (groupInfo) {
+ *aPosInSet = groupInfo->PosInSet();
+ *aSetSize = groupInfo->SetSize();
+ }
+}
+
+bool Accessible::IsLinkValid() {
+ MOZ_ASSERT(IsLink(), "IsLinkValid is called on not hyper link!");
+
+ // XXX In order to implement this we would need to follow every link
+ // Perhaps we can get information about invalid links from the cache
+ // In the mean time authors can use role="link" aria-invalid="true"
+ // to force it for links they internally know to be invalid
+ return (0 == (State() & mozilla::a11y::states::INVALID));
+}
+
+uint32_t Accessible::AnchorCount() {
+ if (IsImageMap()) {
+ return ChildCount();
+ }
+
+ MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!");
+ return 1;
+}
+
+Accessible* Accessible::AnchorAt(uint32_t aAnchorIndex) const {
+ if (IsImageMap()) {
+ return ChildAt(aAnchorIndex);
+ }
+
+ MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!");
+ return aAnchorIndex == 0 ? const_cast<Accessible*>(this) : nullptr;
+}
+
+already_AddRefed<nsIURI> Accessible::AnchorURIAt(uint32_t aAnchorIndex) const {
+ Accessible* anchor = nullptr;
+
+ if (IsTextLeaf() || IsImage()) {
+ for (Accessible* parent = Parent(); parent && !parent->IsOuterDoc();
+ parent = parent->Parent()) {
+ if (parent->IsLink()) {
+ anchor = parent->AnchorAt(aAnchorIndex);
+ }
+ }
+ } else {
+ anchor = AnchorAt(aAnchorIndex);
+ }
+
+ if (anchor) {
+ RefPtr<nsIURI> uri;
+ nsAutoString spec;
+ anchor->Value(spec);
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
+ if (NS_SUCCEEDED(rv)) {
+ return uri.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+bool Accessible::IsSearchbox() const {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::searchbox)) {
+ return true;
+ }
+
+ RefPtr<nsAtom> inputType = InputType();
+ return inputType == nsGkAtoms::search;
+}
+
+#ifdef A11Y_LOG
+void Accessible::DebugDescription(nsCString& aDesc) const {
+ aDesc.Truncate();
+ aDesc.AppendPrintf("%s", IsRemote() ? "Remote" : "Local");
+ aDesc.AppendPrintf("[%p] ", this);
+ nsAutoString role;
+ GetAccService()->GetStringRole(Role(), role);
+ aDesc.Append(NS_ConvertUTF16toUTF8(role));
+
+ if (nsAtom* tagAtom = TagName()) {
+ nsAutoCString tag;
+ tagAtom->ToUTF8String(tag);
+ aDesc.AppendPrintf(" %s", tag.get());
+
+ nsAutoString id;
+ DOMNodeID(id);
+ if (!id.IsEmpty()) {
+ aDesc.Append("#");
+ aDesc.Append(NS_ConvertUTF16toUTF8(id));
+ }
+ }
+ nsAutoString id;
+
+ nsAutoString name;
+ Name(name);
+ if (!name.IsEmpty()) {
+ aDesc.Append(" '");
+ aDesc.Append(NS_ConvertUTF16toUTF8(name));
+ aDesc.Append("'");
+ }
+}
+
+void Accessible::DebugPrint(const char* aPrefix,
+ const Accessible* aAccessible) {
+ nsAutoCString desc;
+ aAccessible->DebugDescription(desc);
+# if defined(ANDROID)
+ printf_stderr("%s %s\n", aPrefix, desc.get());
+# else
+ printf("%s %s\n", aPrefix, desc.get());
+# endif
+}
+
+#endif
+
+void Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ components::StringBundle::Service();
+ if (!stringBundleService) return;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/accessible.properties",
+ getter_AddRefs(stringBundle));
+ if (!stringBundle) return;
+
+ nsAutoString xsValue;
+ nsresult rv = stringBundle->GetStringFromName(
+ NS_ConvertUTF16toUTF8(aKey).get(), xsValue);
+ if (NS_SUCCEEDED(rv)) aStringOut.Assign(xsValue);
+}
+
+const Accessible* Accessible::ActionAncestor() const {
+ // We do want to consider a click handler on the document. However, we don't
+ // want to walk outside of this document, so we stop if we see an OuterDoc.
+ for (Accessible* parent = Parent(); parent && !parent->IsOuterDoc();
+ parent = parent->Parent()) {
+ if (parent->HasPrimaryAction()) {
+ return parent;
+ }
+ }
+
+ return nullptr;
+}
+
+nsStaticAtom* Accessible::LandmarkRole() const {
+ nsAtom* tagName = TagName();
+ if (!tagName) {
+ // Either no associated content, or no cache.
+ return nullptr;
+ }
+
+ if (tagName == nsGkAtoms::nav) {
+ return nsGkAtoms::navigation;
+ }
+
+ if (tagName == nsGkAtoms::aside) {
+ return nsGkAtoms::complementary;
+ }
+
+ if (tagName == nsGkAtoms::main) {
+ return nsGkAtoms::main;
+ }
+
+ if (tagName == nsGkAtoms::header) {
+ if (Role() == roles::LANDMARK) {
+ return nsGkAtoms::banner;
+ }
+ }
+
+ if (tagName == nsGkAtoms::footer) {
+ if (Role() == roles::LANDMARK) {
+ return nsGkAtoms::contentinfo;
+ }
+ }
+
+ if (tagName == nsGkAtoms::section) {
+ nsAutoString name;
+ Name(name);
+ if (!name.IsEmpty()) {
+ return nsGkAtoms::region;
+ }
+ }
+
+ if (tagName == nsGkAtoms::form) {
+ nsAutoString name;
+ Name(name);
+ if (!name.IsEmpty()) {
+ return nsGkAtoms::form;
+ }
+ }
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->IsOfType(eLandmark)
+ ? roleMapEntry->roleAtom
+ : nullptr;
+}
+
+nsStaticAtom* Accessible::ComputedARIARole() const {
+ const nsRoleMapEntry* roleMap = ARIARoleMap();
+ if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty &&
+ // region has its own Gecko role and it needs to be handled specially.
+ roleMap->roleAtom != nsGkAtoms::region &&
+ (roleMap->roleRule == kUseNativeRole || roleMap->IsOfType(eLandmark) ||
+ roleMap->roleAtom == nsGkAtoms::alertdialog ||
+ roleMap->roleAtom == nsGkAtoms::feed ||
+ roleMap->roleAtom == nsGkAtoms::rowgroup ||
+ roleMap->roleAtom == nsGkAtoms::searchbox)) {
+ // Explicit ARIA role (e.g. specified via the role attribute) which does not
+ // map to a unique Gecko role.
+ return roleMap->roleAtom;
+ }
+ role geckoRole = Role();
+ if (geckoRole == roles::LANDMARK) {
+ // Landmark role from native markup; e.g. <main>, <nav>.
+ return LandmarkRole();
+ }
+ if (geckoRole == roles::GROUPING) {
+ // Gecko doesn't differentiate between group and rowgroup. It uses
+ // roles::GROUPING for both.
+ nsAtom* tag = TagName();
+ if (tag == nsGkAtoms::tbody || tag == nsGkAtoms::tfoot ||
+ tag == nsGkAtoms::thead) {
+ return nsGkAtoms::rowgroup;
+ }
+ }
+ // Role from native markup or layout.
+#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
+ msaaRole, ia2Role, androidClass, nameRule) \
+ case roles::_geckoRole: \
+ return ariaRole;
+ switch (geckoRole) {
+#include "RoleMap.h"
+ }
+#undef ROLE
+ MOZ_ASSERT_UNREACHABLE("Unknown role");
+ return nullptr;
+}
+
+void Accessible::ApplyImplicitState(uint64_t& aState) const {
+ // nsAccessibilityService (and thus FocusManager) can be shut down before
+ // RemoteAccessibles.
+ if (const auto* focusMgr = FocusMgr()) {
+ if (focusMgr->IsFocused(this)) {
+ aState |= states::FOCUSED;
+ }
+ }
+
+ // If this is an ARIA item of the selectable widget and if it's focused and
+ // not marked unselected explicitly (i.e. aria-selected="false") then expose
+ // it as selected to make ARIA widget authors life easier.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && !(aState & states::SELECTED) &&
+ ARIASelected().valueOr(true)) {
+ // Special case for tabs: focused tab or focus inside related tab panel
+ // implies selected state.
+ if (roleMapEntry->role == roles::PAGETAB) {
+ if (aState & states::FOCUSED) {
+ aState |= states::SELECTED;
+ } else {
+ // If focus is in a child of the tab panel surely the tab is selected!
+ Relation rel = RelationByType(RelationType::LABEL_FOR);
+ Accessible* relTarget = nullptr;
+ while ((relTarget = rel.Next())) {
+ if (relTarget->Role() == roles::PROPERTYPAGE &&
+ FocusMgr()->IsFocusWithin(relTarget)) {
+ aState |= states::SELECTED;
+ }
+ }
+ }
+ } else if (aState & states::FOCUSED) {
+ Accessible* container = nsAccUtils::GetSelectableContainer(this, aState);
+ if (container && !(container->State() & states::MULTISELECTABLE)) {
+ aState |= states::SELECTED;
+ }
+ }
+ }
+
+ if (Opacity() == 1.0f && !(aState & states::INVISIBLE)) {
+ aState |= states::OPAQUE1;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyBinding class
+
+// static
+uint32_t KeyBinding::AccelModifier() {
+ switch (WidgetInputEvent::AccelModifier()) {
+ case MODIFIER_ALT:
+ return kAlt;
+ case MODIFIER_CONTROL:
+ return kControl;
+ case MODIFIER_META:
+ return kMeta;
+ case MODIFIER_OS:
+ return kOS;
+ default:
+ MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
+ return 0;
+ }
+}
+
+void KeyBinding::ToPlatformFormat(nsAString& aValue) const {
+ nsCOMPtr<nsIStringBundle> keyStringBundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ if (stringBundleService) {
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/platformKeys.properties",
+ getter_AddRefs(keyStringBundle));
+ }
+
+ if (!keyStringBundle) return;
+
+ nsAutoString separator;
+ keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator);
+
+ nsAutoString modifierName;
+ if (mModifierMask & kControl) {
+ keyStringBundle->GetStringFromName("VK_CONTROL", modifierName);
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kAlt) {
+ keyStringBundle->GetStringFromName("VK_ALT", modifierName);
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kShift) {
+ keyStringBundle->GetStringFromName("VK_SHIFT", modifierName);
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kMeta) {
+ keyStringBundle->GetStringFromName("VK_META", modifierName);
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ aValue.Append(mKey);
+}
+
+void KeyBinding::ToAtkFormat(nsAString& aValue) const {
+ nsAutoString modifierName;
+ if (mModifierMask & kControl) aValue.AppendLiteral("<Control>");
+
+ if (mModifierMask & kAlt) aValue.AppendLiteral("<Alt>");
+
+ if (mModifierMask & kShift) aValue.AppendLiteral("<Shift>");
+
+ if (mModifierMask & kMeta) aValue.AppendLiteral("<Meta>");
+
+ aValue.Append(mKey);
+}
diff --git a/accessible/basetypes/Accessible.h b/accessible/basetypes/Accessible.h
new file mode 100644
index 0000000000..d0c2c3c2dd
--- /dev/null
+++ b/accessible/basetypes/Accessible.h
@@ -0,0 +1,725 @@
+/* -*- 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 _Accessible_H_
+#define _Accessible_H_
+
+#include "mozilla/a11y/Role.h"
+#include "mozilla/a11y/AccTypes.h"
+#include "nsString.h"
+#include "nsRect.h"
+#include "Units.h"
+
+class nsAtom;
+class nsStaticAtom;
+
+struct nsRoleMapEntry;
+
+class nsIURI;
+
+namespace mozilla {
+namespace a11y {
+
+class AccAttributes;
+class AccGroupInfo;
+class HyperTextAccessibleBase;
+class LocalAccessible;
+class Relation;
+enum class RelationType;
+class RemoteAccessible;
+class TableAccessible;
+class TableCellAccessible;
+
+/**
+ * Name type flags.
+ */
+enum ENameValueFlag {
+ /**
+ * Name either
+ * a) present (not empty): !name.IsEmpty()
+ * b) no name (was missed): name.IsVoid()
+ */
+ eNameOK,
+
+ /**
+ * Name was computed from the subtree.
+ */
+ eNameFromSubtree,
+
+ /**
+ * Tooltip was used as a name.
+ */
+ eNameFromTooltip
+};
+
+/**
+ * Group position (level, position in set and set size).
+ */
+struct GroupPos {
+ GroupPos() : level(0), posInSet(0), setSize(0) {}
+ GroupPos(int32_t aLevel, int32_t aPosInSet, int32_t aSetSize)
+ : level(aLevel), posInSet(aPosInSet), setSize(aSetSize) {}
+
+ int32_t level;
+ int32_t posInSet;
+ int32_t setSize;
+};
+
+/**
+ * Represent key binding associated with accessible (such as access key and
+ * global keyboard shortcuts).
+ */
+class KeyBinding {
+ public:
+ /**
+ * Modifier mask values.
+ */
+ static const uint32_t kShift = 1;
+ static const uint32_t kControl = 2;
+ static const uint32_t kAlt = 4;
+ static const uint32_t kMeta = 8;
+ static const uint32_t kOS = 16;
+
+ static uint32_t AccelModifier();
+
+ KeyBinding() : mKey(0), mModifierMask(0) {}
+ KeyBinding(uint32_t aKey, uint32_t aModifierMask)
+ : mKey(aKey), mModifierMask(aModifierMask) {}
+ explicit KeyBinding(uint64_t aSerialized) : mSerialized(aSerialized) {}
+
+ inline bool IsEmpty() const { return !mKey; }
+ inline uint32_t Key() const { return mKey; }
+ inline uint32_t ModifierMask() const { return mModifierMask; }
+
+ /**
+ * Serialize this KeyBinding to a uint64_t for use in the parent process
+ * cache. This is simpler than custom IPDL serialization for this simple case.
+ */
+ uint64_t Serialize() { return mSerialized; }
+
+ enum Format { ePlatformFormat, eAtkFormat };
+
+ /**
+ * Return formatted string for this key binding depending on the given format.
+ */
+ inline void ToString(nsAString& aValue,
+ Format aFormat = ePlatformFormat) const {
+ aValue.Truncate();
+ AppendToString(aValue, aFormat);
+ }
+ inline void AppendToString(nsAString& aValue,
+ Format aFormat = ePlatformFormat) const {
+ if (mKey) {
+ if (aFormat == ePlatformFormat) {
+ ToPlatformFormat(aValue);
+ } else {
+ ToAtkFormat(aValue);
+ }
+ }
+ }
+
+ private:
+ void ToPlatformFormat(nsAString& aValue) const;
+ void ToAtkFormat(nsAString& aValue) const;
+
+ union {
+ struct {
+ uint32_t mKey;
+ uint32_t mModifierMask;
+ };
+ uint64_t mSerialized;
+ };
+};
+
+/**
+ * The base type for an accessibility tree node. Methods and attributes in this
+ * class are available in both the content process and the parent process.
+ * Overrides for these methods live primarily in LocalAccessible and
+ * RemoteAccessibleBase.
+ */
+class Accessible {
+ protected:
+ Accessible();
+
+ Accessible(AccType aType, AccGenericType aGenericTypes,
+ uint8_t aRoleMapEntryIndex);
+
+ public:
+ /**
+ * Return an id for this Accessible which is unique within the document.
+ * Use nsAccUtils::GetAccessibleByID to retrieve an Accessible given an id
+ * returned from this method.
+ */
+ virtual uint64_t ID() const = 0;
+
+ virtual Accessible* Parent() const = 0;
+
+ virtual role Role() const = 0;
+
+ /**
+ * Return child accessible at the given index.
+ */
+ virtual Accessible* ChildAt(uint32_t aIndex) const = 0;
+
+ virtual Accessible* NextSibling() const = 0;
+ virtual Accessible* PrevSibling() const = 0;
+
+ virtual uint32_t ChildCount() const = 0;
+
+ virtual int32_t IndexInParent() const = 0;
+
+ bool HasChildren() const { return !!FirstChild(); }
+
+ inline Accessible* FirstChild() const {
+ return ChildCount() ? ChildAt(0) : nullptr;
+ }
+
+ inline Accessible* LastChild() const {
+ uint32_t childCount = ChildCount();
+ return childCount ? ChildAt(childCount - 1) : nullptr;
+ }
+
+ /**
+ * Return true if this Accessible is before another Accessible in the tree.
+ */
+ bool IsBefore(const Accessible* aAcc) const;
+
+ bool IsAncestorOf(const Accessible* aAcc) const {
+ for (const Accessible* parent = aAcc->Parent(); parent;
+ parent = parent->Parent()) {
+ if (parent == this) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used by ChildAtPoint() method to get direct or deepest child at point.
+ */
+ enum class EWhichChildAtPoint { DirectChild, DeepestChild };
+
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) = 0;
+
+ /**
+ * Return the focused child if any.
+ */
+ virtual Accessible* FocusedChild();
+
+ /**
+ * Return ARIA role map if any.
+ */
+ const nsRoleMapEntry* ARIARoleMap() const;
+
+ /**
+ * Return true if ARIA role is specified on the element.
+ */
+ bool HasARIARole() const;
+ bool IsARIARole(nsAtom* aARIARole) const;
+ bool HasStrongARIARole() const;
+
+ /**
+ * Return true if the accessible belongs to the given accessible type.
+ */
+ bool HasGenericType(AccGenericType aType) const;
+
+ /**
+ * Return group position (level, position in set and set size).
+ */
+ virtual GroupPos GroupPosition();
+
+ /**
+ * Return embedded accessible children count.
+ */
+ virtual uint32_t EmbeddedChildCount() = 0;
+
+ /**
+ * Return embedded accessible child at the given index.
+ */
+ virtual Accessible* EmbeddedChildAt(uint32_t aIndex) = 0;
+
+ /**
+ * Return index of the given embedded accessible child.
+ */
+ virtual int32_t IndexOfEmbeddedChild(Accessible* aChild) = 0;
+
+ // Methods that potentially access a cache.
+
+ /*
+ * Get the name of this accessible.
+ */
+ virtual ENameValueFlag Name(nsString& aName) const = 0;
+
+ /*
+ * Get the description of this accessible.
+ */
+ virtual void Description(nsString& aDescription) const = 0;
+
+ /**
+ * Get the value of this accessible.
+ */
+ virtual void Value(nsString& aValue) const = 0;
+
+ virtual double CurValue() const = 0;
+ virtual double MinValue() const = 0;
+ virtual double MaxValue() const = 0;
+ virtual double Step() const = 0;
+ virtual bool SetCurValue(double aValue) = 0;
+
+ /**
+ * Return boundaries in screen coordinates in device pixels.
+ */
+ virtual LayoutDeviceIntRect Bounds() const = 0;
+
+ /**
+ * Return boundaries in screen coordinates in app units.
+ */
+ virtual nsRect BoundsInAppUnits() const = 0;
+
+ /**
+ * Return boundaries in screen coordinates in CSS pixels.
+ */
+ virtual nsIntRect BoundsInCSSPixels() const;
+
+ /**
+ * Returns text of accessible if accessible has text role otherwise empty
+ * string.
+ *
+ * @param aText [in] returned text of the accessible
+ * @param aStartOffset [in, optional] start offset inside of the accessible,
+ * if missed entire text is appended
+ * @param aLength [in, optional] required length of text, if missed
+ * then text from start offset till the end is appended
+ */
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX) = 0;
+
+ /**
+ * Return all states of accessible (including ARIA states).
+ */
+ virtual uint64_t State() = 0;
+
+ /**
+ * Return the start offset of the embedded object within the parent
+ * HyperTextAccessibleBase.
+ */
+ virtual uint32_t StartOffset();
+
+ /**
+ * Return the end offset of the link within the parent
+ * HyperTextAccessibleBase.
+ */
+ virtual uint32_t EndOffset();
+
+ /**
+ * Return object attributes for the accessible.
+ */
+ virtual already_AddRefed<AccAttributes> Attributes() = 0;
+
+ virtual already_AddRefed<nsAtom> DisplayStyle() const = 0;
+
+ virtual float Opacity() const = 0;
+
+ /**
+ * Get the live region attributes (if any) for this single Accessible. This
+ * does not propagate attributes from ancestors. If any argument is null, that
+ * attribute is not fetched.
+ */
+ virtual void LiveRegionAttributes(nsAString* aLive, nsAString* aRelevant,
+ Maybe<bool>* aAtomic,
+ nsAString* aBusy) const = 0;
+
+ /**
+ * Get the aria-selected state. aria-selected not being specified is not
+ * always the same as aria-selected="false". If not specified, Nothing() will
+ * be returned.
+ */
+ virtual Maybe<bool> ARIASelected() const = 0;
+
+ LayoutDeviceIntSize Size() const;
+
+ LayoutDeviceIntPoint Position(uint32_t aCoordType);
+
+ virtual Maybe<int32_t> GetIntARIAAttr(nsAtom* aAttrName) const = 0;
+
+ /**
+ * Get the relation of the given type.
+ */
+ virtual Relation RelationByType(RelationType aType) const = 0;
+
+ /**
+ * Get the language associated with the accessible.
+ */
+ virtual void Language(nsAString& aLocale) = 0;
+
+ /**
+ * Get the role of this Accessible as an ARIA role token. This might have been
+ * set explicitly (e.g. role="button") or it might be implicit in native
+ * markup (e.g. <button> returns "button").
+ */
+ nsStaticAtom* ComputedARIARole() const;
+
+ // Methods that interact with content.
+
+ virtual void TakeFocus() const = 0;
+
+ /**
+ * Scroll the accessible into view.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ virtual void ScrollTo(uint32_t aHow) const = 0;
+
+ /**
+ * Return tag name of associated DOM node.
+ */
+ virtual nsAtom* TagName() const = 0;
+
+ /**
+ * Return input `type` attribute
+ */
+ virtual already_AddRefed<nsAtom> InputType() const = 0;
+
+ /**
+ * Return a landmark role if applied.
+ */
+ nsStaticAtom* LandmarkRole() const;
+
+ /**
+ * Return the id of the dom node this accessible represents.
+ */
+ virtual void DOMNodeID(nsString& aID) const = 0;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ActionAccessible
+
+ /**
+ * Return the number of actions that can be performed on this accessible.
+ */
+ virtual uint8_t ActionCount() const = 0;
+
+ /**
+ * Return action name at given index.
+ */
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) = 0;
+
+ /**
+ * Default to localized action name.
+ */
+ void ActionDescriptionAt(uint8_t aIndex, nsAString& aDescription) {
+ nsAutoString name;
+ ActionNameAt(aIndex, name);
+ TranslateString(name, aDescription);
+ }
+
+ /**
+ * Invoke the accessible action.
+ */
+ virtual bool DoAction(uint8_t aIndex) const = 0;
+
+ /**
+ * Return access key, such as Alt+D.
+ */
+ virtual KeyBinding AccessKey() const = 0;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // SelectAccessible
+
+ /**
+ * Return an array of selected items.
+ */
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems) = 0;
+
+ /**
+ * Return the number of selected items.
+ */
+ virtual uint32_t SelectedItemCount() = 0;
+
+ /**
+ * Return selected item at the given index.
+ */
+ virtual Accessible* GetSelectedItem(uint32_t aIndex) = 0;
+
+ /**
+ * Determine if item at the given index is selected.
+ */
+ virtual bool IsItemSelected(uint32_t aIndex) = 0;
+
+ /**
+ * Add item at the given index the selection. Return true if success.
+ */
+ virtual bool AddItemToSelection(uint32_t aIndex) = 0;
+
+ /**
+ * Remove item at the given index from the selection. Return if success.
+ */
+ virtual bool RemoveItemFromSelection(uint32_t aIndex) = 0;
+
+ /**
+ * Select all items. Return true if success.
+ */
+ virtual bool SelectAll() = 0;
+
+ /**
+ * Unselect all items. Return true if success.
+ */
+ virtual bool UnselectAll() = 0;
+
+ virtual void TakeSelection() = 0;
+
+ virtual void SetSelected(bool aSelect) = 0;
+
+ // Type "is" methods
+
+ bool IsDoc() const { return HasGenericType(eDocument); }
+
+ bool IsTableRow() const { return HasGenericType(eTableRow); }
+
+ bool IsTableCell() const {
+ // The eTableCell type defined in the ARIA map is used in
+ // nsAccessibilityService::CreateAccessible to specify when
+ // ARIAGridCellAccessible should be used for object creation. However, an
+ // invalid table structure might cause this class not to be used after all.
+ // To make sure we're really dealing with a cell, only check the generic
+ // type defined by the class, not the type defined in the ARIA map.
+ return mGenericTypes & eTableCell;
+ }
+
+ bool IsTable() const { return HasGenericType(eTable); }
+
+ bool IsHyperText() const { return HasGenericType(eHyperText); }
+
+ bool IsSelect() const { return HasGenericType(eSelect); }
+
+ bool IsActionable() const { return HasGenericType(eActionable); }
+
+ bool IsText() const { return mGenericTypes & eText; }
+
+ bool IsImage() const { return mType == eImageType; }
+
+ bool IsApplication() const { return mType == eApplicationType; }
+
+ bool IsAlert() const { return HasGenericType(eAlert); }
+
+ bool IsButton() const { return HasGenericType(eButton); }
+
+ bool IsCombobox() const { return HasGenericType(eCombobox); }
+
+ virtual bool IsLink() const = 0;
+
+ /**
+ * Return true if the used ARIA role (if any) allows the hypertext accessible
+ * to expose text interfaces.
+ */
+ bool IsTextRole();
+
+ bool IsGenericHyperText() const { return mType == eHyperTextType; }
+
+ bool IsHTMLBr() const { return mType == eHTMLBRType; }
+ bool IsHTMLCaption() const { return mType == eHTMLCaptionType; }
+ bool IsHTMLCombobox() const { return mType == eHTMLComboboxType; }
+ bool IsHTMLFileInput() const { return mType == eHTMLFileInputType; }
+
+ bool IsHTMLListItem() const { return mType == eHTMLLiType; }
+
+ bool IsHTMLLink() const { return mType == eHTMLLinkType; }
+
+ bool IsHTMLOptGroup() const { return mType == eHTMLOptGroupType; }
+
+ bool IsHTMLRadioButton() const { return mType == eHTMLRadioButtonType; }
+
+ bool IsHTMLTable() const { return mType == eHTMLTableType; }
+ bool IsHTMLTableCell() const { return mType == eHTMLTableCellType; }
+ bool IsHTMLTableRow() const { return mType == eHTMLTableRowType; }
+
+ bool IsImageMap() const { return mType == eImageMapType; }
+
+ bool IsList() const { return HasGenericType(eList); }
+
+ bool IsListControl() const { return HasGenericType(eListControl); }
+
+ bool IsMenuButton() const { return HasGenericType(eMenuButton); }
+
+ bool IsMenuPopup() const { return mType == eMenuPopupType; }
+
+ bool IsOuterDoc() const { return mType == eOuterDocType; }
+
+ bool IsProgress() const { return mType == eProgressType; }
+
+ bool IsRoot() const { return mType == eRootType; }
+
+ bool IsPassword() const { return mType == eHTMLTextPasswordFieldType; }
+
+ bool IsTextLeaf() const { return mType == eTextLeafType; }
+
+ bool IsXULLabel() const { return mType == eXULLabelType; }
+
+ bool IsXULListItem() const { return mType == eXULListItemType; }
+
+ bool IsXULTabpanels() const { return mType == eXULTabpanelsType; }
+
+ bool IsXULTooltip() const { return mType == eXULTooltipType; }
+
+ bool IsXULTree() const { return mType == eXULTreeType; }
+
+ bool IsAutoCompletePopup() const {
+ return HasGenericType(eAutoCompletePopup);
+ }
+
+ bool IsTextField() const {
+ return mType == eHTMLTextFieldType || mType == eHTMLTextPasswordFieldType;
+ }
+
+ bool IsDateTimeField() const { return mType == eHTMLDateTimeFieldType; }
+
+ bool IsSearchbox() const;
+
+ virtual bool HasNumericValue() const = 0;
+
+ /**
+ * Returns true if this is a generic container element that has no meaning on
+ * its own.
+ */
+ bool IsGeneric() const {
+ role accRole = Role();
+ return accRole == roles::TEXT || accRole == roles::TEXT_CONTAINER ||
+ accRole == roles::SECTION;
+ }
+
+ /**
+ * Returns the nearest ancestor which is not a generic element.
+ */
+ Accessible* GetNonGenericParent() const {
+ for (Accessible* parent = Parent(); parent; parent = parent->Parent()) {
+ if (!parent->IsGeneric()) {
+ return parent;
+ }
+ }
+ return nullptr;
+ }
+
+ /**
+ * Return true if the link is valid (e. g. points to a valid URL).
+ */
+ bool IsLinkValid();
+
+ /**
+ * Return the number of anchors within the link.
+ */
+ uint32_t AnchorCount();
+
+ /**
+ * Returns an anchor URI at the given index.
+ */
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) const;
+
+ /**
+ * Returns an anchor accessible at the given index.
+ */
+ Accessible* AnchorAt(uint32_t aAnchorIndex) const;
+
+ // Remote/Local types
+
+ virtual bool IsRemote() const = 0;
+ RemoteAccessible* AsRemote();
+
+ bool IsLocal() const { return !IsRemote(); }
+ LocalAccessible* AsLocal();
+
+ virtual HyperTextAccessibleBase* AsHyperTextBase() { return nullptr; }
+
+ virtual TableAccessible* AsTable() { return nullptr; }
+ virtual TableCellAccessible* AsTableCell() { return nullptr; }
+
+#ifdef A11Y_LOG
+ /**
+ * Provide a human readable description of the accessible,
+ * including memory address, role, name, DOM tag and DOM ID.
+ */
+ void DebugDescription(nsCString& aDesc) const;
+
+ static void DebugPrint(const char* aPrefix, const Accessible* aAccessible);
+#endif
+
+ /**
+ * Return the localized string for the given key.
+ */
+ static void TranslateString(const nsString& aKey, nsAString& aStringOut);
+
+ protected:
+ // Some abstracted group utility methods.
+
+ /**
+ * Get ARIA group attributes.
+ */
+ virtual void ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
+ int32_t* aPosInSet) const = 0;
+
+ /**
+ * Return group info if there is an up-to-date version.
+ */
+ virtual AccGroupInfo* GetGroupInfo() const = 0;
+
+ /**
+ * Return group info or create and update.
+ */
+ virtual AccGroupInfo* GetOrCreateGroupInfo() = 0;
+
+ /*
+ * Return calculated group level based on accessible hierarchy.
+ *
+ * @param aFast [in] Don't climb up tree. Calculate level from aria and
+ * roles.
+ */
+ virtual int32_t GetLevel(bool aFast) const;
+
+ /**
+ * Calculate position in group and group size ('posinset' and 'setsize') based
+ * on accessible hierarchy.
+ *
+ * @param aPosInSet [out] accessible position in the group
+ * @param aSetSize [out] the group size
+ */
+ virtual void GetPositionAndSetSize(int32_t* aPosInSet, int32_t* aSetSize);
+
+ /**
+ * Return the nearest ancestor that has a primary action, or null.
+ */
+ const Accessible* ActionAncestor() const;
+
+ /**
+ * Return true if accessible has a primary action directly related to it, like
+ * "click", "activate", "press", "jump", "open", "close", etc. A non-primary
+ * action would be a complementary one like "showlongdesc".
+ * If an accessible has an action that is associated with an ancestor, it is
+ * not a primary action either.
+ */
+ virtual bool HasPrimaryAction() const = 0;
+
+ /**
+ * Apply states which are implied by other information common to both
+ * LocalAccessible and RemoteAccessible.
+ */
+ void ApplyImplicitState(uint64_t& aState) const;
+
+ private:
+ static const uint8_t kTypeBits = 6;
+ static const uint8_t kGenericTypesBits = 18;
+
+ void StaticAsserts() const;
+
+ protected:
+ uint32_t mType : kTypeBits;
+ uint32_t mGenericTypes : kGenericTypesBits;
+ uint8_t mRoleMapEntryIndex;
+
+ friend class DocAccessibleChildBase;
+ friend class AccGroupInfo;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/basetypes/HyperTextAccessibleBase.cpp b/accessible/basetypes/HyperTextAccessibleBase.cpp
new file mode 100644
index 0000000000..e36b6d7209
--- /dev/null
+++ b/accessible/basetypes/HyperTextAccessibleBase.cpp
@@ -0,0 +1,843 @@
+/* -*- 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 "HyperTextAccessibleBase.h"
+
+#include "AccAttributes.h"
+#include "mozilla/a11y/Accessible.h"
+#include "nsAccUtils.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "TextLeafRange.h"
+#include "TextRange.h"
+
+namespace mozilla::a11y {
+
+int32_t HyperTextAccessibleBase::GetChildIndexAtOffset(uint32_t aOffset) const {
+ auto& offsets =
+ const_cast<HyperTextAccessibleBase*>(this)->GetCachedHyperTextOffsets();
+ int32_t lastOffset = 0;
+ const uint32_t offsetCount = offsets.Length();
+
+ if (offsetCount > 0) {
+ lastOffset = offsets[offsetCount - 1];
+ if (static_cast<int32_t>(aOffset) < lastOffset) {
+ // We've cached up to aOffset.
+ size_t index;
+ if (BinarySearch(offsets, 0, offsetCount, static_cast<int32_t>(aOffset),
+ &index)) {
+ // aOffset is the exclusive end of a child, so return the child before
+ // it.
+ return static_cast<int32_t>((index < offsetCount - 1) ? index + 1
+ : index);
+ }
+ if (index == offsetCount) {
+ // aOffset is past the end of the text.
+ return -1;
+ }
+ // index points at the exclusive end after aOffset.
+ return static_cast<int32_t>(index);
+ }
+ }
+
+ // We haven't yet cached up to aOffset. Find it, caching as we go.
+ const Accessible* thisAcc = Acc();
+ uint32_t childCount = thisAcc->ChildCount();
+ // Even though we're only caching up to aOffset, it's likely that we'll
+ // eventually cache offsets for all children. Pre-allocate thus to minimize
+ // re-allocations.
+ offsets.SetCapacity(childCount);
+ while (offsets.Length() < childCount) {
+ Accessible* child = thisAcc->ChildAt(offsets.Length());
+ lastOffset += static_cast<int32_t>(nsAccUtils::TextLength(child));
+ offsets.AppendElement(lastOffset);
+ if (static_cast<int32_t>(aOffset) < lastOffset) {
+ return static_cast<int32_t>(offsets.Length() - 1);
+ }
+ }
+
+ if (static_cast<int32_t>(aOffset) == lastOffset) {
+ return static_cast<int32_t>(offsets.Length() - 1);
+ }
+
+ return -1;
+}
+
+Accessible* HyperTextAccessibleBase::GetChildAtOffset(uint32_t aOffset) const {
+ const Accessible* thisAcc = Acc();
+ return thisAcc->ChildAt(GetChildIndexAtOffset(aOffset));
+}
+
+int32_t HyperTextAccessibleBase::GetChildOffset(const Accessible* aChild,
+ bool aInvalidateAfter) const {
+ const Accessible* thisAcc = Acc();
+ if (aChild->Parent() != thisAcc) {
+ return -1;
+ }
+ int32_t index = aChild->IndexInParent();
+ if (index == -1) {
+ return -1;
+ }
+ return GetChildOffset(index, aInvalidateAfter);
+}
+
+int32_t HyperTextAccessibleBase::GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter) const {
+ auto& offsets =
+ const_cast<HyperTextAccessibleBase*>(this)->GetCachedHyperTextOffsets();
+ if (aChildIndex == 0) {
+ if (aInvalidateAfter) {
+ offsets.Clear();
+ }
+ return 0;
+ }
+
+ int32_t countCachedAfterChild = static_cast<int32_t>(offsets.Length()) -
+ static_cast<int32_t>(aChildIndex);
+ if (countCachedAfterChild > 0) {
+ // We've cached up to aChildIndex.
+ if (aInvalidateAfter) {
+ offsets.RemoveElementsAt(aChildIndex, countCachedAfterChild);
+ }
+ return offsets[aChildIndex - 1];
+ }
+
+ // We haven't yet cached up to aChildIndex. Find it, caching as we go.
+ const Accessible* thisAcc = Acc();
+ // Even though we're only caching up to aChildIndex, it's likely that we'll
+ // eventually cache offsets for all children. Pre-allocate thus to minimize
+ // re-allocations.
+ offsets.SetCapacity(thisAcc->ChildCount());
+ uint32_t lastOffset = offsets.IsEmpty() ? 0 : offsets[offsets.Length() - 1];
+ while (offsets.Length() < aChildIndex) {
+ Accessible* child = thisAcc->ChildAt(offsets.Length());
+ lastOffset += nsAccUtils::TextLength(child);
+ offsets.AppendElement(lastOffset);
+ }
+
+ return offsets[aChildIndex - 1];
+}
+
+uint32_t HyperTextAccessibleBase::CharacterCount() const {
+ return GetChildOffset(Acc()->ChildCount());
+}
+
+index_t HyperTextAccessibleBase::ConvertMagicOffset(int32_t aOffset) const {
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
+ return CharacterCount();
+ }
+
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+ return CaretOffset();
+ }
+
+ return aOffset;
+}
+
+void HyperTextAccessibleBase::TextSubstring(int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aText) const {
+ aText.Truncate();
+
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ if (!startOffset.IsValid() || !endOffset.IsValid() ||
+ startOffset > endOffset || endOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset");
+ return;
+ }
+
+ int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
+ if (startChildIdx == -1) {
+ return;
+ }
+
+ int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
+ if (endChildIdx == -1) {
+ return;
+ }
+
+ const Accessible* thisAcc = Acc();
+ if (startChildIdx == endChildIdx) {
+ int32_t childOffset = GetChildOffset(startChildIdx);
+ if (childOffset == -1) {
+ return;
+ }
+
+ Accessible* child = thisAcc->ChildAt(startChildIdx);
+ child->AppendTextTo(aText, startOffset - childOffset,
+ endOffset - startOffset);
+ return;
+ }
+
+ int32_t startChildOffset = GetChildOffset(startChildIdx);
+ if (startChildOffset == -1) {
+ return;
+ }
+
+ Accessible* startChild = thisAcc->ChildAt(startChildIdx);
+ startChild->AppendTextTo(aText, startOffset - startChildOffset);
+
+ for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx;
+ childIdx++) {
+ Accessible* child = thisAcc->ChildAt(childIdx);
+ child->AppendTextTo(aText);
+ }
+
+ int32_t endChildOffset = GetChildOffset(endChildIdx);
+ if (endChildOffset == -1) {
+ return;
+ }
+
+ Accessible* endChild = thisAcc->ChildAt(endChildIdx);
+ endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
+}
+
+bool HyperTextAccessibleBase::CharAt(int32_t aOffset, nsAString& aChar,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ MOZ_ASSERT(!aStartOffset == !aEndOffset,
+ "Offsets should be both defined or both undefined!");
+
+ int32_t childIdx = GetChildIndexAtOffset(aOffset);
+ if (childIdx == -1) {
+ return false;
+ }
+
+ Accessible* child = Acc()->ChildAt(childIdx);
+ child->AppendTextTo(aChar, aOffset - GetChildOffset(childIdx), 1);
+
+ if (aStartOffset && aEndOffset) {
+ *aStartOffset = aOffset;
+ *aEndOffset = aOffset + aChar.Length();
+ }
+ return true;
+}
+
+LayoutDeviceIntRect HyperTextAccessibleBase::CharBounds(int32_t aOffset,
+ uint32_t aCoordType) {
+ index_t offset = ConvertMagicOffset(aOffset);
+ if (!offset.IsValid() || offset > CharacterCount()) {
+ return LayoutDeviceIntRect();
+ }
+ TextLeafPoint point = ToTextLeafPoint(static_cast<int32_t>(offset), false);
+ if (!point.mAcc) {
+ return LayoutDeviceIntRect();
+ }
+
+ LayoutDeviceIntRect bounds = point.CharBounds();
+ if (!bounds.x && !bounds.y && bounds.IsZeroArea()) {
+ return bounds;
+ }
+ nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, Acc());
+ return bounds;
+}
+
+LayoutDeviceIntRect HyperTextAccessibleBase::TextBounds(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordType) {
+ LayoutDeviceIntRect result;
+ if (CharacterCount() == 0) {
+ result = Acc()->Bounds();
+ nsAccUtils::ConvertScreenCoordsTo(&result.x, &result.y, aCoordType, Acc());
+ return result;
+ }
+
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ if (!startOffset.IsValid() || startOffset >= endOffset) {
+ return LayoutDeviceIntRect();
+ }
+
+ // Here's where things get complicated. We can't simply query the first
+ // and last character, and union their bounds. They might reside on different
+ // lines, and a simple union may yield an incorrect width. We
+ // should use the length of the longest spanned line for our width.
+
+ TextLeafPoint startPoint =
+ ToTextLeafPoint(static_cast<int32_t>(startOffset), false);
+ TextLeafPoint endPoint =
+ ToTextLeafPoint(static_cast<int32_t>(endOffset), true);
+ if (!endPoint) {
+ // The caller provided an invalid offset.
+ return LayoutDeviceIntRect();
+ }
+
+ // Step backwards from the point returned by ToTextLeafPoint above.
+ // For our purposes, `endPoint` should be inclusive.
+ endPoint =
+ endPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
+ if (endPoint < startPoint) {
+ return result;
+ }
+
+ if (endPoint == startPoint) {
+ result = startPoint.CharBounds();
+ } else {
+ TextLeafRange range(startPoint, endPoint);
+ result = range.Bounds();
+ }
+
+ // Calls to TextLeafRange::Bounds() will construct screen coordinates.
+ // Perform any additional conversions here.
+ nsAccUtils::ConvertScreenCoordsTo(&result.x, &result.y, aCoordType, Acc());
+ return result;
+}
+
+int32_t HyperTextAccessibleBase::OffsetAtPoint(int32_t aX, int32_t aY,
+ uint32_t aCoordType) {
+ Accessible* thisAcc = Acc();
+ LayoutDeviceIntPoint coords =
+ nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc);
+ if (!thisAcc->Bounds().Contains(coords.x, coords.y)) {
+ // The requested point does not exist in this accessible.
+ // Check if we used fuzzy hittesting to get here and, if
+ // so, return 0 to indicate this text leaf is a valid match.
+ LayoutDeviceIntPoint p(aX, aY);
+ if (aCoordType != nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) {
+ p = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc);
+ }
+ if (Accessible* doc = nsAccUtils::DocumentFor(thisAcc)) {
+ Accessible* hittestMatch = doc->ChildAtPoint(
+ p.x, p.y, Accessible::EWhichChildAtPoint::DeepestChild);
+ if (hittestMatch && thisAcc == hittestMatch->Parent()) {
+ return 0;
+ }
+ }
+ return -1;
+ }
+
+ TextLeafPoint startPoint = ToTextLeafPoint(0, false);
+ // As with TextBounds, we walk to the very end of the text contained in this
+ // hypertext and then step backwards to make our endPoint inclusive.
+ TextLeafPoint endPoint =
+ ToTextLeafPoint(static_cast<int32_t>(CharacterCount()), true);
+ endPoint =
+ endPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
+ TextLeafPoint point = startPoint;
+ // XXX: We should create a TextLeafRange object for this hypertext and move
+ // this search inside the TextLeafRange class.
+ // If there are no characters in this container, we might have moved endPoint
+ // before startPoint. In that case, we shouldn't try to move further forward,
+ // as that might result in an infinite loop.
+ if (startPoint <= endPoint) {
+ for (; !point.ContainsPoint(coords.x, coords.y) && point != endPoint;
+ point =
+ point.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext)) {
+ }
+ }
+ if (!point.ContainsPoint(coords.x, coords.y)) {
+ LayoutDeviceIntRect startRect = startPoint.CharBounds();
+ if (coords.x < startRect.x || coords.y < startRect.y) {
+ // Bug 1816601: The point is within the container but above or to the left
+ // of the rectangle at offset 0. We should really return -1, but we've
+ // returned 0 for many years due to a bug. Some users have unfortunately
+ // come to rely on this, so perpetuate this here.
+ return 0;
+ }
+ return -1;
+ }
+ DebugOnly<bool> ok = false;
+ int32_t htOffset;
+ std::tie(ok, htOffset) =
+ TransformOffset(point.mAcc, point.mOffset, /* aIsEndOffset */ false);
+ MOZ_ASSERT(ok, "point should be a descendant of this");
+ return htOffset;
+}
+
+TextLeafPoint HyperTextAccessibleBase::ToTextLeafPoint(int32_t aOffset,
+ bool aDescendToEnd) {
+ Accessible* thisAcc = Acc();
+ if (!thisAcc->HasChildren()) {
+ return TextLeafPoint(thisAcc, 0);
+ }
+ Accessible* child = GetChildAtOffset(aOffset);
+ if (!child) {
+ return TextLeafPoint();
+ }
+ if (HyperTextAccessibleBase* childHt = child->AsHyperTextBase()) {
+ return childHt->ToTextLeafPoint(
+ aDescendToEnd ? static_cast<int32_t>(childHt->CharacterCount()) : 0,
+ aDescendToEnd);
+ }
+ int32_t offset = aOffset - GetChildOffset(child);
+ return TextLeafPoint(child, offset);
+}
+
+std::pair<bool, int32_t> HyperTextAccessibleBase::TransformOffset(
+ Accessible* aDescendant, int32_t aOffset, bool aIsEndOffset) const {
+ const Accessible* thisAcc = Acc();
+ // From the descendant, go up and get the immediate child of this hypertext.
+ int32_t offset = aOffset;
+ Accessible* descendant = aDescendant;
+ while (descendant) {
+ Accessible* parent = descendant->Parent();
+ if (parent == thisAcc) {
+ return {true, GetChildOffset(descendant) + offset};
+ }
+
+ // This offset no longer applies because the passed-in text object is not
+ // a child of the hypertext. This happens when there are nested hypertexts,
+ // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
+ // to make it relative the hypertext.
+ // If the end offset is not supposed to be inclusive and the original point
+ // is not at 0 offset then the returned offset should be after an embedded
+ // character the original point belongs to.
+ if (aIsEndOffset) {
+ offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
+ } else {
+ offset = 0;
+ }
+
+ descendant = parent;
+ }
+
+ // The given a11y point cannot be mapped to an offset relative to this
+ // hypertext accessible. Return the start or the end depending on whether this
+ // is a start ofset or an end offset, thus clipping to the relevant endpoint.
+ return {false, aIsEndOffset ? static_cast<int32_t>(CharacterCount()) : 0};
+}
+
+void HyperTextAccessibleBase::AdjustOriginIfEndBoundary(
+ TextLeafPoint& aOrigin, AccessibleTextBoundary aBoundaryType,
+ bool aAtOffset) const {
+ if (aBoundaryType != nsIAccessibleText::BOUNDARY_LINE_END &&
+ aBoundaryType != nsIAccessibleText::BOUNDARY_WORD_END) {
+ return;
+ }
+ TextLeafPoint actualOrig =
+ aOrigin.IsCaret() ? aOrigin.ActualizeCaret(/* aAdjustAtEndOfLine */ false)
+ : aOrigin;
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_LINE_END) {
+ if (!actualOrig.IsLineFeedChar()) {
+ return;
+ }
+ aOrigin =
+ actualOrig.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
+ } else { // BOUNDARY_WORD_END
+ if (aAtOffset) {
+ // For TextAtOffset with BOUNDARY_WORD_END, we follow WebKitGtk here and
+ // return the word which ends after the origin if the origin is a word end
+ // boundary. Also, if the caret is at the end of a line, our tests expect
+ // the word after the caret, not the word before. The reason for that
+ // is a mystery lost to history. We can do that by explicitly using the
+ // actualized caret without adjusting for end of line.
+ aOrigin = actualOrig;
+ return;
+ }
+ if (!actualOrig.IsSpace()) {
+ return;
+ }
+ TextLeafPoint prevChar =
+ actualOrig.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
+ if (prevChar != actualOrig && !prevChar.IsSpace()) {
+ // aOrigin is a word end boundary.
+ aOrigin = prevChar;
+ }
+ }
+}
+
+void HyperTextAccessibleBase::TextBeforeOffset(
+ int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset, nsAString& aText) {
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START ||
+ aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) {
+ return; // Not implemented.
+ }
+
+ uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
+ if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
+ NS_ERROR("Wrong given offset!");
+ return;
+ }
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) {
+ if (adjustedOffset > 0) {
+ CharAt(static_cast<int32_t>(adjustedOffset) - 1, aText, aStartOffset,
+ aEndOffset);
+ }
+ return;
+ }
+
+ TextLeafPoint orig;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+ orig = TextLeafPoint::GetCaret(Acc());
+ } else {
+ orig = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset));
+ }
+ if (!orig) {
+ // This can happen if aOffset is invalid.
+ return;
+ }
+ AdjustOriginIfEndBoundary(orig, aBoundaryType);
+ TextLeafPoint end =
+ orig.FindBoundary(aBoundaryType, eDirPrevious,
+ TextLeafPoint::BoundaryFlags::eIncludeOrigin);
+ bool ok;
+ std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset,
+ /* aIsEndOffset */ true);
+ if (!ok) {
+ // There is no previous boundary inside this HyperText.
+ *aStartOffset = *aEndOffset = 0;
+ return;
+ }
+ TextLeafPoint start = end.FindBoundary(aBoundaryType, eDirPrevious);
+ // If TransformOffset fails because start is outside this HyperText,
+ // *aStartOffset will be 0, which is what we want.
+ std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset,
+ /* aIsEndOffset */ false);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+}
+
+void HyperTextAccessibleBase::TextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset,
+ nsAString& aText) {
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START ||
+ aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) {
+ return; // Not implemented.
+ }
+
+ uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
+ if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
+ NS_ERROR("Wrong given offset!");
+ return;
+ }
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) {
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+ TextLeafPoint caret = TextLeafPoint::GetCaret(Acc());
+ if (caret.IsCaretAtEndOfLine()) {
+ // The caret is at the end of the line. Return no character.
+ *aStartOffset = *aEndOffset = static_cast<int32_t>(adjustedOffset);
+ return;
+ }
+ }
+ CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
+ return;
+ }
+
+ TextLeafPoint start, end;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+ start = TextLeafPoint::GetCaret(Acc());
+ AdjustOriginIfEndBoundary(start, aBoundaryType, /* aAtOffset */ true);
+ end = start;
+ } else {
+ start = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset));
+ Accessible* childAcc = GetChildAtOffset(adjustedOffset);
+ if (childAcc && childAcc->IsHyperText()) {
+ // We're searching for boundaries enclosing an embedded object.
+ // An embedded object might contain several boundaries itself.
+ // Thus, we must ensure we search for the end boundary from the last
+ // text in the subtree, not just the first.
+ // For example, if the embedded object is a link and it contains two
+ // words, but the second word expands beyond the link, we want to
+ // include the part of the second word which is outside of the link.
+ end = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset),
+ /* aDescendToEnd */ true);
+ } else {
+ AdjustOriginIfEndBoundary(start, aBoundaryType,
+ /* aAtOffset */ true);
+ end = start;
+ }
+ }
+ if (!start) {
+ // This can happen if aOffset is invalid.
+ return;
+ }
+ start = start.FindBoundary(aBoundaryType, eDirPrevious,
+ TextLeafPoint::BoundaryFlags::eIncludeOrigin);
+ bool ok;
+ std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset,
+ /* aIsEndOffset */ false);
+ end = end.FindBoundary(aBoundaryType, eDirNext);
+ std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset,
+ /* aIsEndOffset */ true);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+}
+
+void HyperTextAccessibleBase::TextAfterOffset(
+ int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset, nsAString& aText) {
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START ||
+ aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) {
+ return; // Not implemented.
+ }
+
+ uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
+ if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
+ NS_ERROR("Wrong given offset!");
+ return;
+ }
+
+ if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) {
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && adjustedOffset > 0 &&
+ TextLeafPoint::GetCaret(Acc()).IsCaretAtEndOfLine()) {
+ --adjustedOffset;
+ }
+ uint32_t count = CharacterCount();
+ if (adjustedOffset >= count) {
+ *aStartOffset = *aEndOffset = static_cast<int32_t>(count);
+ } else {
+ CharAt(static_cast<int32_t>(adjustedOffset) + 1, aText, aStartOffset,
+ aEndOffset);
+ }
+ return;
+ }
+
+ TextLeafPoint orig;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+ orig = TextLeafPoint::GetCaret(Acc());
+ } else {
+ orig = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset),
+ /* aDescendToEnd */ true);
+ }
+ if (!orig) {
+ // This can happen if aOffset is invalid.
+ return;
+ }
+ AdjustOriginIfEndBoundary(orig, aBoundaryType);
+ TextLeafPoint start = orig.FindBoundary(aBoundaryType, eDirNext);
+ bool ok;
+ std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset,
+ /* aIsEndOffset */ false);
+ if (!ok) {
+ // There is no next boundary inside this HyperText.
+ *aStartOffset = *aEndOffset = static_cast<int32_t>(CharacterCount());
+ return;
+ }
+ TextLeafPoint end = start.FindBoundary(aBoundaryType, eDirNext);
+ // If TransformOffset fails because end is outside this HyperText,
+ // *aEndOffset will be CharacterCount(), which is what we want.
+ std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset,
+ /* aIsEndOffset */ true);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+}
+
+int32_t HyperTextAccessibleBase::CaretOffset() const {
+ TextLeafPoint point = TextLeafPoint::GetCaret(const_cast<Accessible*>(Acc()))
+ .ActualizeCaret(/* aAdjustAtEndOfLine */ false);
+ if (point.mOffset == 0 && point.mAcc == Acc()) {
+ // If a text box is empty, there will be no children, so point.mAcc will be
+ // this HyperText.
+ return 0;
+ }
+ auto [ok, htOffset] =
+ TransformOffset(point.mAcc, point.mOffset, /* aIsEndOffset */ false);
+ if (!ok) {
+ // The caret is not within this HyperText.
+ return -1;
+ }
+ return htOffset;
+}
+
+int32_t HyperTextAccessibleBase::CaretLineNumber() {
+ TextLeafPoint point = TextLeafPoint::GetCaret(const_cast<Accessible*>(Acc()))
+ .ActualizeCaret(/* aAdjustAtEndOfLine */ false);
+ if (point.mOffset == 0 && point.mAcc == Acc()) {
+ MOZ_ASSERT(CharacterCount() == 0);
+ // If a text box is empty, there will be no children, so point.mAcc will be
+ // this HyperText.
+ return 1;
+ }
+
+ if (!point.mAcc ||
+ (point.mAcc != Acc() && !Acc()->IsAncestorOf(point.mAcc))) {
+ // The caret is not within this HyperText.
+ return -1;
+ }
+
+ TextLeafPoint firstPointInThis = TextLeafPoint(Acc(), 0);
+ int32_t lineNumber = 1;
+ for (TextLeafPoint line = point; line && firstPointInThis < line;
+ line = line.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START,
+ eDirPrevious)) {
+ lineNumber++;
+ }
+
+ return lineNumber;
+}
+
+bool HyperTextAccessibleBase::IsValidOffset(int32_t aOffset) {
+ index_t offset = ConvertMagicOffset(aOffset);
+ return offset.IsValid() && offset <= CharacterCount();
+}
+
+bool HyperTextAccessibleBase::IsValidRange(int32_t aStartOffset,
+ int32_t aEndOffset) {
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ return startOffset.IsValid() && endOffset.IsValid() &&
+ startOffset <= endOffset && endOffset <= CharacterCount();
+}
+
+uint32_t HyperTextAccessibleBase::LinkCount() {
+ return Acc()->EmbeddedChildCount();
+}
+
+Accessible* HyperTextAccessibleBase::LinkAt(uint32_t aIndex) {
+ return Acc()->EmbeddedChildAt(aIndex);
+}
+
+int32_t HyperTextAccessibleBase::LinkIndexOf(Accessible* aLink) {
+ return Acc()->IndexOfEmbeddedChild(aLink);
+}
+
+already_AddRefed<AccAttributes> HyperTextAccessibleBase::TextAttributes(
+ bool aIncludeDefAttrs, int32_t aOffset, int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ *aStartOffset = *aEndOffset = 0;
+ index_t offset = ConvertMagicOffset(aOffset);
+ if (!offset.IsValid() || offset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return RefPtr{new AccAttributes()}.forget();
+ }
+
+ Accessible* originAcc = GetChildAtOffset(offset);
+ if (!originAcc) {
+ // Offset 0 is correct offset when accessible has empty text. Include
+ // default attributes if they were requested, otherwise return empty set.
+ if (offset == 0) {
+ if (aIncludeDefAttrs) {
+ return DefaultTextAttributes();
+ }
+ }
+ return RefPtr{new AccAttributes()}.forget();
+ }
+
+ if (!originAcc->IsText()) {
+ // This is an embedded object. One or more consecutive embedded objects
+ // form a single attrs run with no attributes.
+ *aStartOffset = aOffset;
+ *aEndOffset = aOffset + 1;
+ Accessible* parent = originAcc->Parent();
+ if (!parent) {
+ return RefPtr{new AccAttributes()}.forget();
+ }
+ int32_t originIdx = originAcc->IndexInParent();
+ if (originIdx > 0) {
+ // Check for embedded objects before the origin.
+ for (uint32_t idx = originIdx - 1;; --idx) {
+ Accessible* sibling = parent->ChildAt(idx);
+ if (sibling->IsText()) {
+ break;
+ }
+ --*aStartOffset;
+ if (idx == 0) {
+ break;
+ }
+ }
+ }
+ // Check for embedded objects after the origin.
+ for (uint32_t idx = originIdx + 1;; ++idx) {
+ Accessible* sibling = parent->ChildAt(idx);
+ if (!sibling || sibling->IsText()) {
+ break;
+ }
+ ++*aEndOffset;
+ }
+ return RefPtr{new AccAttributes()}.forget();
+ }
+
+ TextLeafPoint origin = ToTextLeafPoint(static_cast<int32_t>(offset));
+ TextLeafPoint start =
+ origin.FindTextAttrsStart(eDirPrevious, /* aIncludeOrigin */ true);
+ bool ok;
+ std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset,
+ /* aIsEndOffset */ false);
+ TextLeafPoint end =
+ origin.FindTextAttrsStart(eDirNext, /* aIncludeOrigin */ false);
+ std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset,
+ /* aIsEndOffset */ true);
+ return origin.GetTextAttributes(aIncludeDefAttrs);
+}
+
+void HyperTextAccessibleBase::CroppedSelectionRanges(
+ nsTArray<TextRange>& aRanges) const {
+ SelectionRanges(&aRanges);
+ const Accessible* acc = Acc();
+ aRanges.RemoveElementsBy([acc](auto& range) {
+ if (range.StartPoint() == range.EndPoint()) {
+ return true; // Collapsed, so remove this range.
+ }
+ // If this is the document, it contains all ranges, so there's no need to
+ // crop.
+ if (!acc->IsDoc()) {
+ // If we fail to crop, the range is outside acc, so remove it.
+ return !range.Crop(const_cast<Accessible*>(acc));
+ }
+ return false;
+ });
+}
+
+int32_t HyperTextAccessibleBase::SelectionCount() {
+ nsTArray<TextRange> ranges;
+ CroppedSelectionRanges(ranges);
+ return static_cast<int32_t>(ranges.Length());
+}
+
+bool HyperTextAccessibleBase::SelectionBoundsAt(int32_t aSelectionNum,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ nsTArray<TextRange> ranges;
+ CroppedSelectionRanges(ranges);
+ if (aSelectionNum >= static_cast<int32_t>(ranges.Length())) {
+ return false;
+ }
+ TextRange& range = ranges[aSelectionNum];
+ Accessible* thisAcc = Acc();
+ if (range.StartContainer() == thisAcc) {
+ *aStartOffset = range.StartOffset();
+ } else {
+ bool ok;
+ // range.StartContainer() isn't a text leaf, so don't use its offset.
+ std::tie(ok, *aStartOffset) =
+ TransformOffset(range.StartContainer(), 0, /* aDescendToEnd */ false);
+ }
+ if (range.EndContainer() == thisAcc) {
+ *aEndOffset = range.EndOffset();
+ } else {
+ bool ok;
+ // range.EndContainer() isn't a text leaf, so don't use its offset. If
+ // range.EndOffset() is > 0, we want to include this container, so pas
+ // offset 1.
+ std::tie(ok, *aEndOffset) =
+ TransformOffset(range.EndContainer(), range.EndOffset() == 0 ? 0 : 1,
+ /* aDescendToEnd */ true);
+ }
+ return true;
+}
+
+bool HyperTextAccessibleBase::SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset) {
+ TextLeafRange range(ToTextLeafPoint(aStartOffset),
+ ToTextLeafPoint(aEndOffset, true));
+ if (!range) {
+ NS_ERROR("Wrong in offset");
+ return false;
+ }
+
+ return range.SetSelection(aSelectionNum);
+}
+
+void HyperTextAccessibleBase::ScrollSubstringTo(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aScrollType) {
+ TextLeafRange range(ToTextLeafPoint(aStartOffset),
+ ToTextLeafPoint(aEndOffset, true));
+ range.ScrollIntoView(aScrollType);
+}
+
+} // namespace mozilla::a11y
diff --git a/accessible/basetypes/HyperTextAccessibleBase.h b/accessible/basetypes/HyperTextAccessibleBase.h
new file mode 100644
index 0000000000..1afed52737
--- /dev/null
+++ b/accessible/basetypes/HyperTextAccessibleBase.h
@@ -0,0 +1,310 @@
+/* -*- 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 _HyperTextAccessibleBase_H_
+#define _HyperTextAccessibleBase_H_
+
+#include "AccAttributes.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+
+namespace mozilla::a11y {
+class Accessible;
+class TextLeafPoint;
+class TextRange;
+
+// This character marks where in the text returned via Text interface,
+// that embedded object characters exist
+const char16_t kEmbeddedObjectChar = 0xfffc;
+const char16_t kImaginaryEmbeddedObjectChar = ' ';
+const char16_t kForcedNewLineChar = '\n';
+
+/**
+ * An index type. Assert if out of range value was attempted to be used.
+ */
+class index_t {
+ public:
+ MOZ_IMPLICIT index_t(int32_t aVal) : mVal(aVal) {}
+
+ operator uint32_t() const {
+ MOZ_ASSERT(mVal >= 0, "Attempt to use wrong index!");
+ return mVal;
+ }
+
+ bool IsValid() const { return mVal >= 0; }
+
+ private:
+ int32_t mVal;
+};
+
+class HyperTextAccessibleBase {
+ public:
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ virtual int32_t GetChildIndexAtOffset(uint32_t aOffset) const;
+
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ virtual Accessible* GetChildAtOffset(uint32_t aOffset) const;
+
+ /**
+ * Return text offset of the given child accessible within hypertext
+ * accessible.
+ *
+ * @param aChild [in] accessible child to get text offset for
+ * @param aInvalidateAfter [in, optional] indicates whether to invalidate
+ * cached offsets for subsequent siblings of the
+ * child.
+ */
+ int32_t GetChildOffset(const Accessible* aChild,
+ bool aInvalidateAfter = false) const;
+
+ /**
+ * Return text offset for the child accessible index.
+ */
+ virtual int32_t GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter = false) const;
+
+ /**
+ * Return character count within the hypertext accessible.
+ */
+ uint32_t CharacterCount() const;
+
+ /**
+ * Get/set caret offset, if no caret then -1.
+ */
+ virtual int32_t CaretOffset() const;
+ virtual void SetCaretOffset(int32_t aOffset) = 0;
+
+ /**
+ * Provide the line number for the caret.
+ * @return 1-based index for the line number with the caret
+ */
+ virtual int32_t CaretLineNumber();
+
+ /**
+ * Transform magic offset into text offset.
+ */
+ index_t ConvertMagicOffset(int32_t aOffset) const;
+
+ /**
+ * Return text between given offsets.
+ */
+ void TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aText) const;
+
+ /**
+ * Get a character at the given offset (don't support magic offsets).
+ */
+ bool CharAt(int32_t aOffset, nsAString& aChar,
+ int32_t* aStartOffset = nullptr, int32_t* aEndOffset = nullptr);
+
+ char16_t CharAt(int32_t aOffset) {
+ nsAutoString charAtOffset;
+ CharAt(aOffset, charAtOffset);
+ return charAtOffset.CharAt(0);
+ }
+
+ /**
+ * Return a rect (in dev pixels) for character at given offset relative
+ * given coordinate system.
+ */
+ LayoutDeviceIntRect CharBounds(int32_t aOffset, uint32_t aCoordType);
+
+ /**
+ * Return a rect (in dev pixels) of the given text range relative given
+ * coordinate system.
+ */
+ LayoutDeviceIntRect TextBounds(
+ int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType =
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+
+ /**
+ * Return the offset of the char that contains the given coordinates.
+ */
+ virtual int32_t OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType);
+
+ /**
+ * Get a TextLeafPoint for a given offset in this HyperTextAccessible.
+ * If the offset points to an embedded object and aDescendToEnd is true,
+ * the point right at the end of this subtree will be returned instead of the
+ * start.
+ */
+ TextLeafPoint ToTextLeafPoint(int32_t aOffset, bool aDescendToEnd = false);
+
+ /**
+ * Return text before/at/after the given offset corresponding to
+ * the boundary type.
+ */
+ void TextBeforeOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+ void TextAtOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+ void TextAfterOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+
+ /**
+ * Return true if the given offset/range is valid.
+ */
+ bool IsValidOffset(int32_t aOffset);
+ bool IsValidRange(int32_t aStartOffset, int32_t aEndOffset);
+
+ /**
+ * Return link count within this hypertext accessible.
+ */
+ uint32_t LinkCount();
+
+ /**
+ * Return link accessible at the given index.
+ */
+ Accessible* LinkAt(uint32_t aIndex);
+
+ /**
+ * Return index for the given link accessible.
+ */
+ int32_t LinkIndexOf(Accessible* aLink);
+
+ /**
+ * Return link accessible at the given text offset.
+ */
+ int32_t LinkIndexAtOffset(uint32_t aOffset) {
+ Accessible* child = GetChildAtOffset(aOffset);
+ return child ? LinkIndexOf(child) : -1;
+ }
+
+ /**
+ * Return text attributes for the given text range.
+ */
+ already_AddRefed<AccAttributes> TextAttributes(bool aIncludeDefAttrs,
+ int32_t aOffset,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+ /**
+ * Return text attributes applied to the accessible.
+ */
+ virtual already_AddRefed<AccAttributes> DefaultTextAttributes() = 0;
+
+ /**
+ * Return an array of disjoint ranges for selected text within the text
+ * control or the document this accessible belongs to.
+ */
+ virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const = 0;
+
+ /**
+ * Return selected regions count within the accessible.
+ */
+ virtual int32_t SelectionCount();
+
+ /**
+ * Return the start and end offset of the specified selection.
+ */
+ virtual bool SelectionBoundsAt(int32_t aSelectionNum, int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+ /**
+ * Changes the start and end offset of the specified selection.
+ * @return true if succeeded
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual bool SetSelectionBoundsAt(
+ int32_t aSelectionNum, int32_t aStartOffset, int32_t aEndOffset);
+
+ /**
+ * Adds a selection bounded by the specified offsets.
+ * @return true if succeeded
+ */
+ bool AddToSelection(int32_t aStartOffset, int32_t aEndOffset) {
+ return SetSelectionBoundsAt(-1, aStartOffset, aEndOffset);
+ }
+
+ /**
+ * Removes the specified selection.
+ * @return true if succeeded
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual bool RemoveFromSelection(
+ int32_t aSelectionNum) = 0;
+
+ /**
+ * Scroll the given text range into view.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScrollSubstringTo(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aScrollType);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // EditableTextAccessible
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void ReplaceText(
+ const nsAString& aText) = 0;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void InsertText(const nsAString& aText,
+ int32_t aPosition) = 0;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void CopyText(int32_t aStartPos,
+ int32_t aEndPos) = 0;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void CutText(int32_t aStartPos,
+ int32_t aEndPos) = 0;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void DeleteText(int32_t aStartPos,
+ int32_t aEndPos) = 0;
+ MOZ_CAN_RUN_SCRIPT virtual void PasteText(int32_t aPosition) = 0;
+
+ protected:
+ virtual const Accessible* Acc() const = 0;
+ Accessible* Acc() {
+ const Accessible* acc =
+ const_cast<const HyperTextAccessibleBase*>(this)->Acc();
+ return const_cast<Accessible*>(acc);
+ }
+
+ /**
+ * Get the cached map of child indexes to HyperText offsets.
+ * This is an array which contains the exclusive end offset for each child.
+ * That is, the start offset for child c is array index c - 1.
+ */
+ virtual nsTArray<int32_t>& GetCachedHyperTextOffsets() = 0;
+
+ private:
+ /**
+ * Transform the given a11y point into an offset relative to this hypertext.
+ * Returns {success, offset}, where success is true if successful.
+ * If unsuccessful, the returned offset will be CharacterCount() if
+ * aIsEndOffset is true, 0 otherwise. This means most callers can ignore the
+ * success return value.
+ */
+ std::pair<bool, int32_t> TransformOffset(Accessible* aDescendant,
+ int32_t aOffset,
+ bool aIsEndOffset) const;
+
+ /**
+ * Helper method for TextBefore/At/AfterOffset.
+ * If BOUNDARY_LINE_END was requested and the origin is itself a line end
+ * boundary, we must use the line which ends at the origin. We must do
+ * similarly for BOUNDARY_WORD_END. This method adjusts the origin
+ * accordingly.
+ */
+ void AdjustOriginIfEndBoundary(TextLeafPoint& aOrigin,
+ AccessibleTextBoundary aBoundaryType,
+ bool aAtOffset = false) const;
+
+ /**
+ * Return text selection ranges cropped to this Accessible (rather than for
+ * the entire text control or document). This also excludes collapsed ranges.
+ */
+ virtual void CroppedSelectionRanges(nsTArray<TextRange>& aRanges) const;
+};
+
+} // namespace mozilla::a11y
+
+#endif
diff --git a/accessible/basetypes/TableAccessible.h b/accessible/basetypes/TableAccessible.h
new file mode 100644
index 0000000000..fe5b499f75
--- /dev/null
+++ b/accessible/basetypes/TableAccessible.h
@@ -0,0 +1,172 @@
+/* -*- 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 TABLE_ACCESSIBLE_H
+#define TABLE_ACCESSIBLE_H
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * Accessible table interface.
+ */
+class TableAccessible {
+ public:
+ /**
+ * Return the caption accessible if any for this table.
+ */
+ virtual Accessible* Caption() const { return nullptr; }
+
+ /**
+ * Get the summary for this table.
+ */
+ virtual void Summary(nsString& aSummary) { aSummary.Truncate(); }
+
+ /**
+ * Return the number of columns in the table.
+ */
+ virtual uint32_t ColCount() const { return 0; }
+
+ /**
+ * Return the number of rows in the table.
+ */
+ virtual uint32_t RowCount() { return 0; }
+
+ /**
+ * Return the accessible for the cell at the given row and column indices.
+ */
+ virtual Accessible* CellAt(uint32_t aRowIdx, uint32_t aColIdx) {
+ return nullptr;
+ }
+
+ /**
+ * Return the index of the cell at the given row and column.
+ */
+ virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) { return -1; }
+
+ /**
+ * Return the column index of the cell with the given index.
+ * This returns -1 if the column count is 0 or an invalid index is being
+ * passed in.
+ */
+ virtual int32_t ColIndexAt(uint32_t aCellIdx) { return -1; }
+
+ /**
+ * Return the row index of the cell with the given index.
+ * This returns -1 if the column count is 0 or an invalid index is being
+ * passed in.
+ */
+ virtual int32_t RowIndexAt(uint32_t aCellIdx) { return -1; }
+
+ /**
+ * Get the row and column indices for the cell at the given index.
+ * This returns -1 for both output parameters if the column count is 0 or an
+ * invalid index is being passed in.
+ */
+ virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx) {
+ *aRowIdx = -1;
+ *aColIdx = -1;
+ }
+
+ /**
+ * Return the number of columns occupied by the cell at the given row and
+ * column indices.
+ */
+ virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; }
+
+ /**
+ * Return the number of rows occupied by the cell at the given row and column
+ * indices.
+ */
+ virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; }
+
+ /**
+ * Get the description of the given column.
+ */
+ virtual void ColDescription(uint32_t aColIdx, nsString& aDescription) {
+ aDescription.Truncate();
+ }
+
+ /**
+ * Get the description for the given row.
+ */
+ virtual void RowDescription(uint32_t aRowIdx, nsString& aDescription) {
+ aDescription.Truncate();
+ }
+
+ /**
+ * Return true if the given column is selected.
+ */
+ virtual bool IsColSelected(uint32_t aColIdx) { return false; }
+
+ /**
+ * Return true if the given row is selected.
+ */
+ virtual bool IsRowSelected(uint32_t aRowIdx) { return false; }
+
+ /**
+ * Return true if the given cell is selected.
+ */
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) {
+ return false;
+ }
+
+ /**
+ * Return the number of selected cells.
+ */
+ virtual uint32_t SelectedCellCount() { return 0; }
+
+ /**
+ * Return the number of selected columns.
+ */
+ virtual uint32_t SelectedColCount() { return 0; }
+
+ /**
+ * Return the number of selected rows.
+ */
+ virtual uint32_t SelectedRowCount() { return 0; }
+
+ /**
+ * Get the set of selected cells.
+ */
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) {}
+
+ /**
+ * Get the set of selected cell indices.
+ */
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) {}
+
+ /**
+ * Get the set of selected column indices.
+ */
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) {}
+
+ /**
+ * Get the set of selected row indices.
+ */
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) {}
+
+ /**
+ * Return true if the table is probably for layout.
+ */
+ virtual bool IsProbablyLayoutTable() { return false; }
+
+ /**
+ * Convert the table to an Accessible*.
+ */
+ virtual Accessible* AsAccessible() = 0;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/basetypes/TableCellAccessible.h b/accessible/basetypes/TableCellAccessible.h
new file mode 100644
index 0000000000..3e92a7098b
--- /dev/null
+++ b/accessible/basetypes/TableCellAccessible.h
@@ -0,0 +1,68 @@
+/* -*- 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_TableCellAccessible_h__
+#define mozilla_a11y_TableCellAccessible_h__
+
+#include "nsTArray.h"
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class TableAccessible;
+
+/**
+ * Abstract interface implemented by table cell accessibles.
+ */
+class TableCellAccessible {
+ public:
+ /**
+ * Return the table this cell is in.
+ */
+ virtual TableAccessible* Table() const = 0;
+
+ /**
+ * Return the column of the table this cell is in.
+ */
+ virtual uint32_t ColIdx() const = 0;
+
+ /**
+ * Return the row of the table this cell is in.
+ */
+ virtual uint32_t RowIdx() const = 0;
+
+ /**
+ * Return the column extent of this cell.
+ */
+ virtual uint32_t ColExtent() const { return 1; }
+
+ /**
+ * Return the row extent of this cell.
+ */
+ virtual uint32_t RowExtent() const { return 1; }
+
+ /**
+ * Return the column header cells for this cell.
+ */
+ virtual void ColHeaderCells(nsTArray<Accessible*>* aCells) = 0;
+
+ /**
+ * Return the row header cells for this cell.
+ */
+ virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) = 0;
+
+ /**
+ * Returns true if this cell is selected.
+ */
+ virtual bool Selected() = 0;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_TableCellAccessible_h__
diff --git a/accessible/basetypes/moz.build b/accessible/basetypes/moz.build
new file mode 100644
index 0000000000..c0e3fdf9ac
--- /dev/null
+++ b/accessible/basetypes/moz.build
@@ -0,0 +1,25 @@
+# -*- 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/.
+
+EXPORTS.mozilla.a11y += [
+ "Accessible.h",
+ "HyperTextAccessibleBase.h",
+ "TableAccessible.h",
+ "TableCellAccessible.h",
+]
+
+UNIFIED_SOURCES += [
+ "Accessible.cpp",
+ "HyperTextAccessibleBase.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/accessible/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")