summaryrefslogtreecommitdiff
path: root/accessible/generic
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/generic')
-rw-r--r--accessible/generic/ARIAGridAccessible-inl.h39
-rw-r--r--accessible/generic/ARIAGridAccessible.cpp702
-rw-r--r--accessible/generic/ARIAGridAccessible.h138
-rw-r--r--accessible/generic/Accessible-inl.h135
-rw-r--r--accessible/generic/Accessible.cpp2856
-rw-r--r--accessible/generic/Accessible.h1269
-rw-r--r--accessible/generic/ApplicationAccessible.cpp201
-rw-r--r--accessible/generic/ApplicationAccessible.h120
-rw-r--r--accessible/generic/BaseAccessibles.cpp264
-rw-r--r--accessible/generic/BaseAccessibles.h132
-rw-r--r--accessible/generic/DocAccessible-inl.h189
-rw-r--r--accessible/generic/DocAccessible.cpp2387
-rw-r--r--accessible/generic/DocAccessible.h718
-rw-r--r--accessible/generic/FormControlAccessible.cpp193
-rw-r--r--accessible/generic/FormControlAccessible.h76
-rw-r--r--accessible/generic/HyperTextAccessible-inl.h180
-rw-r--r--accessible/generic/HyperTextAccessible.cpp2230
-rw-r--r--accessible/generic/HyperTextAccessible.h592
-rw-r--r--accessible/generic/ImageAccessible.cpp224
-rw-r--r--accessible/generic/ImageAccessible.h87
-rw-r--r--accessible/generic/OuterDocAccessible.cpp218
-rw-r--r--accessible/generic/OuterDocAccessible.h61
-rw-r--r--accessible/generic/RootAccessible.cpp732
-rw-r--r--accessible/generic/RootAccessible.h92
-rw-r--r--accessible/generic/TableAccessible.h187
-rw-r--r--accessible/generic/TableCellAccessible.cpp67
-rw-r--r--accessible/generic/TableCellAccessible.h70
-rw-r--r--accessible/generic/TextLeafAccessible.cpp54
-rw-r--r--accessible/generic/TextLeafAccessible.h51
-rw-r--r--accessible/generic/moz.build70
30 files changed, 14334 insertions, 0 deletions
diff --git a/accessible/generic/ARIAGridAccessible-inl.h b/accessible/generic/ARIAGridAccessible-inl.h
new file mode 100644
index 0000000000..bb2bc9705b
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible-inl.h
@@ -0,0 +1,39 @@
+/* -*- 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_ARIAGridAccessible_inl_h__
+#define mozilla_a11y_ARIAGridAccessible_inl_h__
+
+#include "ARIAGridAccessible.h"
+
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline int32_t
+ARIAGridCellAccessible::RowIndexFor(Accessible* aRow) const
+{
+ Accessible* table = nsAccUtils::TableFor(aRow);
+ if (table) {
+ int32_t rowIdx = 0;
+ Accessible* row = nullptr;
+ AccIterator rowIter(table, filters::GetRow);
+ while ((row = rowIter.Next()) && row != aRow)
+ rowIdx++;
+
+ if (row)
+ return rowIdx;
+ }
+
+ return -1;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/ARIAGridAccessible.cpp b/accessible/generic/ARIAGridAccessible.cpp
new file mode 100644
index 0000000000..48de9bbf07
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible.cpp
@@ -0,0 +1,702 @@
+/* -*- 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 "ARIAGridAccessible-inl.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor
+
+ARIAGridAccessible::
+ ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Table
+
+uint32_t
+ARIAGridAccessible::ColCount()
+{
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return 0;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ uint32_t colCount = 0;
+ while ((cell = cellIter.Next()))
+ colCount++;
+
+ return colCount;
+}
+
+uint32_t
+ARIAGridAccessible::RowCount()
+{
+ uint32_t rowCount = 0;
+ AccIterator rowIter(this, filters::GetRow);
+ while (rowIter.Next())
+ rowCount++;
+
+ return rowCount;
+}
+
+Accessible*
+ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
+{
+ Accessible* row = GetRowAt(aRowIndex);
+ if (!row)
+ return nullptr;
+
+ return GetCellInRowAt(row, aColumnIndex);
+}
+
+bool
+ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return false;
+
+ do {
+ if (!nsAccUtils::IsARIASelected(row)) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (!cell || !nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+ } while ((row = rowIter.Next()));
+
+ return true;
+}
+
+bool
+ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if(!row)
+ return false;
+
+ if (!nsAccUtils::IsARIASelected(row)) {
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ while ((cell = cellIter.Next())) {
+ if (!nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if(!row)
+ return false;
+
+ if (!nsAccUtils::IsARIASelected(row)) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (!cell || !nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+
+ return true;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedCellCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t count = 0, colCount = ColCount();
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+
+ while ((row = rowIter.Next())) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ count += colCount;
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ while ((cell = cellIter.Next())) {
+ if (nsAccUtils::IsARIASelected(cell))
+ count++;
+ }
+ }
+
+ return count;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedColCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t colCount = ColCount();
+ if (!colCount)
+ return 0;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return 0;
+
+ nsTArray<bool> isColSelArray(colCount);
+ isColSelArray.AppendElements(colCount);
+ memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
+
+ uint32_t selColCount = colCount;
+ do {
+ if (nsAccUtils::IsARIASelected(row))
+ continue;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0;
+ (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
+ isColSelArray[colIdx] = false;
+ selColCount--;
+ }
+ } while ((row = rowIter.Next()));
+
+ return selColCount;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedRowCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t count = 0;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+
+ while ((row = rowIter.Next())) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ count++;
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ if (!cell)
+ continue;
+
+ bool isRowSelected = true;
+ do {
+ if (!nsAccUtils::IsARIASelected(cell)) {
+ isRowSelected = false;
+ break;
+ }
+ } while ((cell = cellIter.Next()));
+
+ if (isRowSelected)
+ count++;
+ }
+
+ return count;
+}
+
+void
+ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ if (nsAccUtils::IsARIASelected(row)) {
+ while ((cell = cellIter.Next()))
+ aCells->AppendElement(cell);
+
+ continue;
+ }
+
+ while ((cell = cellIter.Next())) {
+ if (nsAccUtils::IsARIASelected(cell))
+ aCells->AppendElement(cell);
+ }
+ }
+}
+
+void
+ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ uint32_t colCount = ColCount();
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ aCells->AppendElement(rowIdx * colCount + colIdx);
+
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) {
+ if (nsAccUtils::IsARIASelected(cell))
+ aCells->AppendElement(rowIdx * colCount + colIdx);
+ }
+ }
+}
+
+void
+ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ uint32_t colCount = ColCount();
+ if (!colCount)
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return;
+
+ nsTArray<bool> isColSelArray(colCount);
+ isColSelArray.AppendElements(colCount);
+ memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
+
+ do {
+ if (nsAccUtils::IsARIASelected(row))
+ continue;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0;
+ (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
+ isColSelArray[colIdx] = false;
+ }
+ } while ((row = rowIter.Next()));
+
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx])
+ aCols->AppendElement(colIdx);
+}
+
+void
+ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ aRows->AppendElement(rowIdx);
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ if (!cell)
+ continue;
+
+ bool isRowSelected = true;
+ do {
+ if (!nsAccUtils::IsARIASelected(cell)) {
+ isRowSelected = false;
+ break;
+ }
+ } while ((cell = cellIter.Next()));
+
+ if (isRowSelected)
+ aRows->AppendElement(rowIdx);
+ }
+}
+
+void
+ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ DebugOnly<nsresult> rv = SetARIASelected(row, rowIdx == aRowIdx);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
+ }
+}
+
+void
+ARIAGridAccessible::SelectCol(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ // Unselect all cells in the row.
+ DebugOnly<nsresult> rv = SetARIASelected(row, false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
+
+ // Select cell at the column index.
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (cell)
+ SetARIASelected(cell, true);
+ }
+}
+
+void
+ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if (row)
+ SetARIASelected(row, false);
+}
+
+void
+ARIAGridAccessible::UnselectCol(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (cell)
+ SetARIASelected(cell, false);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected
+
+Accessible*
+ARIAGridAccessible::GetRowAt(int32_t aRow)
+{
+ int32_t rowIdx = aRow;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = rowIter.Next();
+ while (rowIdx != 0 && (row = rowIter.Next()))
+ rowIdx--;
+
+ return row;
+}
+
+Accessible*
+ARIAGridAccessible::GetCellInRowAt(Accessible* aRow, int32_t aColumn)
+{
+ int32_t colIdx = aColumn;
+
+ AccIterator cellIter(aRow, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ while (colIdx != 0 && (cell = cellIter.Next()))
+ colIdx--;
+
+ return cell;
+}
+
+nsresult
+ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
+ bool aIsSelected, bool aNotify)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return NS_OK;
+
+ nsIContent *content = aAccessible->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsresult rv = NS_OK;
+ if (aIsSelected)
+ rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("true"), aNotify);
+ else
+ rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("false"), aNotify);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No "smart" select/unselect for internal call.
+ if (!aNotify)
+ return NS_OK;
+
+ // If row or cell accessible was selected then we're able to not bother about
+ // selection of its cells or its row because our algorithm is row oriented,
+ // i.e. we check selection on row firstly and then on cells.
+ if (aIsSelected)
+ return NS_OK;
+
+ roles::Role role = aAccessible->Role();
+
+ // If the given accessible is row that was unselected then remove
+ // aria-selected from cell accessible.
+ if (role == roles::ROW) {
+ AccIterator cellIter(aAccessible, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ while ((cell = cellIter.Next())) {
+ rv = SetARIASelected(cell, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+
+ // If the given accessible is cell that was unselected and its row is selected
+ // then remove aria-selected from row and put aria-selected on
+ // siblings cells.
+ if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
+ role == roles::COLUMNHEADER) {
+ Accessible* row = aAccessible->Parent();
+
+ if (row && row->Role() == roles::ROW &&
+ nsAccUtils::IsARIASelected(row)) {
+ rv = SetARIASelected(row, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ while ((cell = cellIter.Next())) {
+ if (cell != aAccessible) {
+ rv = SetARIASelected(cell, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIARowAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+ARIARowAccessible::
+ ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eTableRow;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible)
+
+GroupPos
+ARIARowAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ Accessible* table = nsAccUtils::TableFor(this);
+ if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
+ nsGkAtoms::aria_rowcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return AccessibleWrap::GroupPosition();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor
+
+ARIAGridCellAccessible::
+ ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eTableCell;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// TableCell
+
+TableAccessible*
+ARIAGridCellAccessible::Table() const
+{
+ Accessible* table = nsAccUtils::TableFor(Row());
+ return table ? table->AsTable() : nullptr;
+}
+
+uint32_t
+ARIAGridCellAccessible::ColIdx() const
+{
+ Accessible* row = Row();
+ if (!row)
+ return 0;
+
+ int32_t indexInRow = IndexInParent();
+ uint32_t colIdx = 0;
+ for (int32_t idx = 0; idx < indexInRow; idx++) {
+ Accessible* cell = row->GetChildAt(idx);
+ roles::Role role = cell->Role();
+ if (role == roles::CELL || role == roles::GRID_CELL ||
+ role == roles::ROWHEADER || role == roles::COLUMNHEADER)
+ colIdx++;
+ }
+
+ return colIdx;
+}
+
+uint32_t
+ARIAGridCellAccessible::RowIdx() const
+{
+ return RowIndexFor(Row());
+}
+
+bool
+ARIAGridCellAccessible::Selected()
+{
+ Accessible* row = Row();
+ if (!row)
+ return false;
+
+ return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const
+{
+ HyperTextAccessibleWrap::ApplyARIAState(aState);
+
+ // Return if the gridcell has aria-selected="true".
+ if (*aState & states::SELECTED)
+ return;
+
+ // Check aria-selected="true" on the row.
+ Accessible* row = Parent();
+ if (!row || row->Role() != roles::ROW)
+ return;
+
+ nsIContent *rowContent = row->GetContent();
+ if (nsAccUtils::HasDefinedARIAToken(rowContent,
+ nsGkAtoms::aria_selected) &&
+ !rowContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_selected,
+ nsGkAtoms::_false, eCaseMatters))
+ *aState |= states::SELECTABLE | states::SELECTED;
+}
+
+already_AddRefed<nsIPersistentProperties>
+ARIAGridCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // Expose "table-cell-index" attribute.
+ Accessible* thisRow = Row();
+ if (!thisRow)
+ return attributes.forget();
+
+ int32_t colIdx = 0, colCount = 0;
+ uint32_t childCount = thisRow->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = thisRow->GetChildAt(childIdx);
+ if (child == this)
+ colIdx = colCount;
+
+ roles::Role role = child->Role();
+ if (role == roles::CELL || role == roles::GRID_CELL ||
+ role == roles::ROWHEADER || role == roles::COLUMNHEADER)
+ colCount++;
+ }
+
+ int32_t rowIdx = RowIndexFor(thisRow);
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(rowIdx * colCount + colIdx);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+#ifdef DEBUG
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
+ NS_LITERAL_STRING("ARIAGridCellAccessible"),
+ unused);
+#endif
+
+ return attributes.forget();
+}
+
+GroupPos
+ARIAGridCellAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ TableAccessible* table = Table();
+ if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
+ nsGkAtoms::aria_colcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return GroupPos();
+}
diff --git a/accessible/generic/ARIAGridAccessible.h b/accessible/generic/ARIAGridAccessible.h
new file mode 100644
index 0000000000..c9a36cc6ef
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible.h
@@ -0,0 +1,138 @@
+/* -*- 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_ARIAGridAccessible_h_
+#define MOZILLA_A11Y_ARIAGridAccessible_h_
+
+#include "HyperTextAccessibleWrap.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible for ARIA grid and treegrid.
+ */
+class ARIAGridAccessible : public AccessibleWrap,
+ public TableAccessible
+{
+public:
+ ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableAccessible* AsTable() override { return this; }
+
+ // TableAccessible
+ virtual uint32_t ColCount() override;
+ virtual uint32_t RowCount() override;
+ virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
+ virtual bool IsColSelected(uint32_t aColIdx) override;
+ virtual bool IsRowSelected(uint32_t aRowIdx) override;
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t SelectedCellCount() override;
+ virtual uint32_t SelectedColCount() override;
+ virtual uint32_t SelectedRowCount() override;
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
+ virtual 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 Accessible* AsAccessible() override { return this; }
+
+protected:
+ virtual ~ARIAGridAccessible() {}
+
+ /**
+ * Return row accessible at the given row index.
+ */
+ Accessible* GetRowAt(int32_t aRow);
+
+ /**
+ * Return cell accessible at the given column index in the row.
+ */
+ Accessible* GetCellInRowAt(Accessible* aRow, int32_t aColumn);
+
+ /**
+ * Set aria-selected attribute value on DOM node of the given accessible.
+ *
+ * @param aAccessible [in] accessible
+ * @param aIsSelected [in] new value of aria-selected attribute
+ * @param aNotify [in, optional] specifies if DOM should be notified
+ * about attribute change (used internally).
+ */
+ nsresult SetARIASelected(Accessible* aAccessible, bool aIsSelected,
+ bool aNotify = true);
+};
+
+
+/**
+ * Accessible for ARIA row.
+ */
+class ARIARowAccessible : public AccessibleWrap
+{
+public:
+ ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+protected:
+ virtual ~ARIARowAccessible() {}
+};
+
+
+/**
+ * Accessible for ARIA gridcell and rowheader/columnheader.
+ */
+class ARIAGridCellAccessible : public HyperTextAccessibleWrap,
+ public TableCellAccessible
+{
+public:
+ ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableCellAccessible* AsTableCell() override { return this; }
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+protected:
+ virtual ~ARIAGridCellAccessible() {}
+
+ /**
+ * Return a containing row.
+ */
+ Accessible* Row() const
+ {
+ Accessible* row = Parent();
+ return row && row->IsTableRow() ? row : nullptr;
+ }
+
+ /**
+ * Return index of the given row.
+ */
+ int32_t RowIndexFor(Accessible* aRow) const;
+
+ // TableCellAccessible
+ virtual TableAccessible* Table() const override;
+ virtual uint32_t ColIdx() const override;
+ virtual uint32_t RowIdx() const override;
+ virtual bool Selected() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/Accessible-inl.h b/accessible/generic/Accessible-inl.h
new file mode 100644
index 0000000000..f80056479e
--- /dev/null
+++ b/accessible/generic/Accessible-inl.h
@@ -0,0 +1,135 @@
+/* -*- 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_Accessible_inl_h_
+#define mozilla_a11y_Accessible_inl_h_
+
+#include "DocAccessible.h"
+#include "ARIAMap.h"
+#include "nsCoreUtils.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+inline mozilla::a11y::role
+Accessible::Role()
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole)
+ return ARIATransformRole(NativeRole());
+
+ return ARIATransformRole(roleMapEntry->role);
+}
+
+inline bool
+Accessible::HasARIARole() const
+{
+ return mRoleMapEntryIndex != aria::NO_ROLE_MAP_ENTRY_INDEX;
+}
+
+inline bool
+Accessible::IsARIARole(nsIAtom* aARIARole) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->Is(aARIARole);
+}
+
+inline bool
+Accessible::HasStrongARIARole() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->roleRule == kUseMapRole;
+}
+
+inline const nsRoleMapEntry*
+Accessible::ARIARoleMap() const
+{
+ return aria::GetRoleMapFromIndex(mRoleMapEntryIndex);
+}
+
+inline mozilla::a11y::role
+Accessible::ARIARole()
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole)
+ return mozilla::a11y::roles::NOTHING;
+
+ return ARIATransformRole(roleMapEntry->role);
+}
+
+inline void
+Accessible::SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry)
+{
+ mRoleMapEntryIndex = aria::GetIndexFromRoleMap(aRoleMapEntry);
+}
+
+inline bool
+Accessible::IsSearchbox() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return (roleMapEntry && roleMapEntry->Is(nsGkAtoms::searchbox)) ||
+ (mContent->IsHTMLElement(nsGkAtoms::input) &&
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::textInputType, eCaseMatters));
+}
+
+inline bool
+Accessible::HasGenericType(AccGenericType aType) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return (mGenericTypes & aType) ||
+ (roleMapEntry && roleMapEntry->IsOfType(aType));
+}
+
+inline bool
+Accessible::HasNumericValue() const
+{
+ if (mStateFlags & eHasNumericValue)
+ return true;
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->valueRule != eNoValue;
+}
+
+inline void
+Accessible::ScrollTo(uint32_t aHow) const
+{
+ if (mContent)
+ nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow);
+}
+
+inline bool
+Accessible::InsertAfter(Accessible* aNewChild, Accessible* aRefChild)
+{
+ MOZ_ASSERT(aNewChild, "No new child to insert");
+
+ if (aRefChild && aRefChild->Parent() != this) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("broken accessible tree", 0,
+ "parent", this, "prev sibling parent",
+ aRefChild->Parent(), "child", aNewChild, nullptr);
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Document tree", mDoc);
+ logging::DOMTree("TREE", "DOM document tree", mDoc);
+ }
+#endif
+ MOZ_ASSERT_UNREACHABLE("Broken accessible tree");
+ mDoc->UnbindFromDocument(aNewChild);
+ return false;
+ }
+
+ return InsertChildAt(aRefChild ? aRefChild->IndexInParent() + 1 : 0,
+ aNewChild);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp
new file mode 100644
index 0000000000..7ff2f82836
--- /dev/null
+++ b/accessible/generic/Accessible.cpp
@@ -0,0 +1,2856 @@
+/* -*- 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-inl.h"
+
+#include "nsIXBLAccessible.h"
+
+#include "EmbeddedObjCollector.h"
+#include "AccGroupInfo.h"
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+#include "nsAccessibilityService.h"
+#include "ApplicationAccessible.h"
+#include "NotificationController.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "DocAccessibleChild.h"
+#include "EventTree.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "States.h"
+#include "StyleInfo.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "TreeWalker.h"
+
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMTreeWalker.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULLabelElement.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsPIDOMWindow.h"
+
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIForm.h"
+#include "nsIFormControl.h"
+
+#include "nsDeckFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsIPresShell.h"
+#include "nsIStringBundle.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIScrollableFrame.h"
+#include "nsFocusManager.h"
+
+#include "nsXPIDLString.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "prdtoa.h"
+#include "nsIAtom.h"
+#include "nsIURI.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsAttrName.h"
+
+#ifdef DEBUG
+#include "nsIDOMCharacterData.h"
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/TreeWalker.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible: nsISupports and cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Accessible)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Accessible)
+ tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Accessible)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible)
+ if (aIID.Equals(NS_GET_IID(Accessible)))
+ foundInterface = this;
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, Accessible)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease())
+
+Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) :
+ mContent(aContent), mDoc(aDoc),
+ mParent(nullptr), mIndexInParent(-1),
+ mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX),
+ mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0),
+ mReorderEventTarget(false), mShowEventTarget(false), mHideEventTarget(false)
+{
+ mBits.groupInfo = nullptr;
+ mInt.mIndexOfEmbeddedChild = -1;
+}
+
+Accessible::~Accessible()
+{
+ NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
+}
+
+ENameValueFlag
+Accessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (!HasOwnContent())
+ return eNameOK;
+
+ ARIAName(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsCOMPtr<nsIXBLAccessible> xblAccessible(do_QueryInterface(mContent));
+ if (xblAccessible) {
+ xblAccessible->GetAccessibleName(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ ENameValueFlag nameFlag = NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // In the end get the name from tooltip.
+ if (mContent->IsHTMLElement()) {
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) {
+ aName.CompressWhitespace();
+ return eNameFromTooltip;
+ }
+ } else if (mContent->IsXULElement()) {
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
+ aName.CompressWhitespace();
+ return eNameFromTooltip;
+ }
+ } else if (mContent->IsSVGElement()) {
+ // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
+ // for processing, the user agent shall choose the first one.
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::desc)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
+ return eNameFromTooltip;
+ }
+ }
+ }
+
+ if (nameFlag != eNoNameOnPurpose)
+ aName.SetIsVoid(true);
+
+ return nameFlag;
+}
+
+void
+Accessible::Description(nsString& aDescription)
+{
+ // There are 4 conditions that make an accessible have no accDescription:
+ // 1. it's a text node; or
+ // 2. It has no DHTML describedby property
+ // 3. it doesn't have an accName; or
+ // 4. its title attribute already equals to its accName nsAutoString name;
+
+ if (!HasOwnContent() || mContent->IsNodeOfType(nsINode::eTEXT))
+ return;
+
+ nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
+ aDescription);
+
+ if (aDescription.IsEmpty()) {
+ NativeDescription(aDescription);
+
+ if (aDescription.IsEmpty()) {
+ // Keep the Name() method logic.
+ if (mContent->IsHTMLElement()) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aDescription);
+ } else if (mContent->IsXULElement()) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aDescription);
+ } else if (mContent->IsSVGElement()) {
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::desc)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
+ &aDescription);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!aDescription.IsEmpty()) {
+ aDescription.CompressWhitespace();
+ nsAutoString name;
+ Name(name);
+ // Don't expose a description if it is the same as the name.
+ if (aDescription.Equals(name))
+ aDescription.Truncate();
+ }
+}
+
+KeyBinding
+Accessible::AccessKey() const
+{
+ if (!HasOwnContent())
+ return KeyBinding();
+
+ uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
+ if (!key && mContent->IsElement()) {
+ Accessible* label = nullptr;
+
+ // Copy access key from label node.
+ if (mContent->IsHTMLElement()) {
+ // Unless it is labeled via an ancestor <label>, in which case that would
+ // be redundant.
+ HTMLLabelIterator iter(Document(), this,
+ HTMLLabelIterator::eSkipAncestorLabel);
+ label = iter.Next();
+
+ } else if (mContent->IsXULElement()) {
+ XULLabelIterator iter(Document(), mContent);
+ label = iter.Next();
+ }
+
+ if (label)
+ key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
+ }
+
+ if (!key)
+ return KeyBinding();
+
+ // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
+ switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) {
+ case -1:
+ break;
+ case nsIDOMKeyEvent::DOM_VK_SHIFT:
+ return KeyBinding(key, KeyBinding::kShift);
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ return KeyBinding(key, KeyBinding::kControl);
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ return KeyBinding(key, KeyBinding::kAlt);
+ case nsIDOMKeyEvent::DOM_VK_META:
+ return KeyBinding(key, KeyBinding::kMeta);
+ default:
+ return KeyBinding();
+ }
+
+ // Determine the access modifier used in this context.
+ nsIDocument* document = mContent->GetUncomposedDoc();
+ if (!document)
+ return KeyBinding();
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
+ if (!treeItem)
+ return KeyBinding();
+
+ nsresult rv = NS_ERROR_FAILURE;
+ int32_t modifierMask = 0;
+ switch (treeItem->ItemType()) {
+ case nsIDocShellTreeItem::typeChrome:
+ rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask);
+ break;
+ case nsIDocShellTreeItem::typeContent:
+ rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask);
+ break;
+ }
+
+ return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
+}
+
+KeyBinding
+Accessible::KeyboardShortcut() const
+{
+ return KeyBinding();
+}
+
+void
+Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
+{
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ services::GetStringBundleService();
+ if (!stringBundleService)
+ return;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/accessible.properties",
+ getter_AddRefs(stringBundle));
+ if (!stringBundle)
+ return;
+
+ nsXPIDLString xsValue;
+ nsresult rv = stringBundle->GetStringFromName(aKey.get(), getter_Copies(xsValue));
+ if (NS_SUCCEEDED(rv))
+ aStringOut.Assign(xsValue);
+}
+
+uint64_t
+Accessible::VisibilityState()
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return states::INVISIBLE;
+
+ // Walk the parent frame chain to see if there's invisible parent or the frame
+ // is in background tab.
+ if (!frame->StyleVisibility()->IsVisible())
+ return states::INVISIBLE;
+
+ nsIFrame* curFrame = frame;
+ do {
+ nsView* view = curFrame->GetView();
+ if (view && view->GetVisibility() == nsViewVisibility_kHide)
+ return states::INVISIBLE;
+
+ if (nsLayoutUtils::IsPopup(curFrame))
+ return 0;
+
+ // Offscreen state for background tab content and invisible for not selected
+ // deck panel.
+ nsIFrame* parentFrame = curFrame->GetParent();
+ nsDeckFrame* deckFrame = do_QueryFrame(parentFrame);
+ if (deckFrame && deckFrame->GetSelectedBox() != curFrame) {
+ if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels))
+ return states::OFFSCREEN;
+
+ NS_NOTREACHED("Children of not selected deck panel are not accessible.");
+ return states::INVISIBLE;
+ }
+
+ // If contained by scrollable frame then check that at least 12 pixels
+ // around the object is visible, otherwise the object is offscreen.
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
+ if (scrollableFrame) {
+ nsRect scrollPortRect = scrollableFrame->GetScrollPortRect();
+ nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, frame->GetRectRelativeToSelf(), parentFrame);
+ if (!scrollPortRect.Contains(frameRect)) {
+ const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
+ scrollPortRect.Deflate(kMinPixels, kMinPixels);
+ if (!scrollPortRect.Intersects(frameRect))
+ return states::OFFSCREEN;
+ }
+ }
+
+ if (!parentFrame) {
+ parentFrame = nsLayoutUtils::GetCrossDocParentFrame(curFrame);
+ if (parentFrame && !parentFrame->StyleVisibility()->IsVisible())
+ return states::INVISIBLE;
+ }
+
+ curFrame = parentFrame;
+ } while (curFrame);
+
+ // Zero area rects can occur in the first frame of a multi-frame text flow,
+ // in which case the rendered text is not empty and the frame should not be
+ // marked invisible.
+ // XXX Can we just remove this check? Why do we need to mark empty
+ // text invisible?
+ if (frame->GetType() == nsGkAtoms::textFrame &&
+ !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ frame->GetRect().IsEmpty()) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ if (text.mString.IsEmpty()) {
+ return states::INVISIBLE;
+ }
+ }
+
+ return 0;
+}
+
+uint64_t
+Accessible::NativeState()
+{
+ uint64_t state = 0;
+
+ if (!IsInDocument())
+ state |= states::STALE;
+
+ if (HasOwnContent() && mContent->IsElement()) {
+ EventStates elementState = mContent->AsElement()->State();
+
+ if (elementState.HasState(NS_EVENT_STATE_INVALID))
+ state |= states::INVALID;
+
+ if (elementState.HasState(NS_EVENT_STATE_REQUIRED))
+ state |= states::REQUIRED;
+
+ state |= NativeInteractiveState();
+ if (FocusMgr()->IsFocused(this))
+ state |= states::FOCUSED;
+ }
+
+ // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
+ state |= VisibilityState();
+
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
+ state |= states::FLOATING;
+
+ // XXX we should look at layout for non XUL box frames, but need to decide
+ // how that interacts with ARIA.
+ if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) {
+ const nsStyleXUL* xulStyle = frame->StyleXUL();
+ if (xulStyle && frame->IsXULBoxFrame()) {
+ // In XUL all boxes are either vertical or horizontal
+ if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical)
+ state |= states::VERTICAL;
+ else
+ state |= states::HORIZONTAL;
+ }
+ }
+ }
+
+ // Check if a XUL element has the popup attribute (an attached popup menu).
+ if (HasOwnContent() && mContent->IsXULElement() &&
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
+ state |= states::HASPOPUP;
+
+ // Bypass the link states specialization for non links.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
+ roleMapEntry->role == roles::LINK)
+ state |= NativeLinkState();
+
+ return state;
+}
+
+uint64_t
+Accessible::NativeInteractiveState() const
+{
+ if (!mContent->IsElement())
+ return 0;
+
+ if (NativelyUnavailable())
+ return states::UNAVAILABLE;
+
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->IsFocusable())
+ return states::FOCUSABLE;
+
+ return 0;
+}
+
+uint64_t
+Accessible::NativeLinkState() const
+{
+ return 0;
+}
+
+bool
+Accessible::NativelyUnavailable() const
+{
+ if (mContent->IsHTMLElement())
+ return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED);
+
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+Accessible*
+Accessible::FocusedChild()
+{
+ Accessible* focus = FocusMgr()->FocusedAccessible();
+ if (focus && (focus == this || focus->Parent() == this))
+ return focus;
+
+ return nullptr;
+}
+
+Accessible*
+Accessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ // If we can't find the point in a child, we will return the fallback answer:
+ // we return |this| if the point is within it, otherwise nullptr.
+ Accessible* fallbackAnswer = nullptr;
+ nsIntRect rect = Bounds();
+ if (aX >= rect.x && aX < rect.x + rect.width &&
+ aY >= rect.y && aY < rect.y + rect.height)
+ fallbackAnswer = this;
+
+ if (nsAccUtils::MustPrune(this)) // Do not dig any further
+ return fallbackAnswer;
+
+ // Search an accessible at the given point starting from accessible document
+ // because containing block (see CSS2) for out of flow element (for example,
+ // absolutely positioned element) may be different from its DOM parent and
+ // therefore accessible for containing block may be different from accessible
+ // for DOM parent but GetFrameForPoint() should be called for containing block
+ // to get an out of flow element.
+ DocAccessible* accDocument = Document();
+ NS_ENSURE_TRUE(accDocument, nullptr);
+
+ nsIFrame* rootFrame = accDocument->GetFrame();
+ NS_ENSURE_TRUE(rootFrame, nullptr);
+
+ nsIFrame* startFrame = rootFrame;
+
+ // Check whether the point is at popup content.
+ nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
+ NS_ENSURE_TRUE(rootWidget, nullptr);
+
+ LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
+
+ WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
+ WidgetMouseEvent::eSynthesized);
+ dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y);
+
+ nsIFrame* popupFrame = nsLayoutUtils::
+ GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(),
+ &dummyEvent);
+ if (popupFrame) {
+ // If 'this' accessible is not inside the popup then ignore the popup when
+ // searching an accessible at point.
+ DocAccessible* popupDoc =
+ GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
+ Accessible* popupAcc =
+ popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
+ Accessible* popupChild = this;
+ while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc)
+ popupChild = popupChild->Parent();
+
+ if (popupChild == popupAcc)
+ startFrame = popupFrame;
+ }
+
+ nsPresContext* presContext = startFrame->PresContext();
+ nsRect screenRect = startFrame->GetScreenRectInAppUnits();
+ nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x,
+ presContext->DevPixelsToAppUnits(aY) - screenRect.y);
+ nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset);
+
+ nsIContent* content = nullptr;
+ if (!foundFrame || !(content = foundFrame->GetContent()))
+ return fallbackAnswer;
+
+ // Get accessible for the node with the point or the first accessible in
+ // the DOM parent chain.
+ DocAccessible* contentDocAcc = GetAccService()->
+ GetDocAccessible(content->OwnerDoc());
+
+ // contentDocAcc in some circumstances can be nullptr. See bug 729861
+ NS_ASSERTION(contentDocAcc, "could not get the document accessible");
+ if (!contentDocAcc)
+ return fallbackAnswer;
+
+ Accessible* accessible = contentDocAcc->GetAccessibleOrContainer(content);
+ if (!accessible)
+ return fallbackAnswer;
+
+ // Hurray! We have an accessible for the frame that layout gave us.
+ // Since DOM node of obtained accessible may be out of flow then we should
+ // ensure obtained accessible is a child of this accessible.
+ Accessible* child = accessible;
+ while (child != this) {
+ Accessible* parent = child->Parent();
+ if (!parent) {
+ // Reached the top of the hierarchy. These bounds were inside an
+ // accessible that is not a descendant of this one.
+ return fallbackAnswer;
+ }
+
+ // If we landed on a legitimate child of |this|, and we want the direct
+ // child, return it here.
+ if (parent == this && aWhichChild == eDirectChild)
+ return child;
+
+ child = parent;
+ }
+
+ // Manually walk through accessible children and see if the are within this
+ // point. Skip offscreen or invisible accessibles. This takes care of cases
+ // where layout won't walk into things for us, such as image map areas and
+ // sub documents (XXX: subdocuments should be handled by methods of
+ // OuterDocAccessibles).
+ uint32_t childCount = accessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = accessible->GetChildAt(childIdx);
+
+ nsIntRect childRect = child->Bounds();
+ if (aX >= childRect.x && aX < childRect.x + childRect.width &&
+ aY >= childRect.y && aY < childRect.y + childRect.height &&
+ (child->State() & states::INVISIBLE) == 0) {
+
+ if (aWhichChild == eDeepestChild)
+ return child->ChildAtPoint(aX, aY, eDeepestChild);
+
+ return child;
+ }
+ }
+
+ return accessible;
+}
+
+nsRect
+Accessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ nsIFrame* frame = GetFrame();
+ if (frame && mContent) {
+ bool* hasHitRegionRect = static_cast<bool*>(mContent->GetProperty(nsGkAtoms::hitregion));
+
+ if (hasHitRegionRect && mContent->IsElement()) {
+ // This is for canvas fallback content
+ // Find a canvas frame the found hit region is relative to.
+ nsIFrame* canvasFrame = frame->GetParent();
+ if (canvasFrame) {
+ canvasFrame = nsLayoutUtils::GetClosestFrameOfType(canvasFrame, nsGkAtoms::HTMLCanvasFrame);
+ }
+
+ // make the canvas the bounding frame
+ if (canvasFrame) {
+ *aBoundingFrame = canvasFrame;
+ dom::HTMLCanvasElement *canvas =
+ dom::HTMLCanvasElement::FromContent(canvasFrame->GetContent());
+
+ // get the bounding rect of the hit region
+ nsRect bounds;
+ if (canvas && canvas->CountContexts() &&
+ canvas->GetContextAtIndex(0)->GetHitRegionRect(mContent->AsElement(), bounds)) {
+ return bounds;
+ }
+ }
+ }
+
+ *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
+ return nsLayoutUtils::
+ GetAllInFlowRectsUnion(frame, *aBoundingFrame,
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ }
+
+ return nsRect();
+}
+
+nsIntRect
+Accessible::Bounds() const
+{
+ nsIFrame* boundingFrame = nullptr;
+ nsRect unionRectTwips = RelativeBounds(&boundingFrame);
+ if (!boundingFrame)
+ return nsIntRect();
+
+ nsIntRect screenRect;
+ nsPresContext* presContext = mDoc->PresContext();
+ screenRect.x = presContext->AppUnitsToDevPixels(unionRectTwips.x);
+ screenRect.y = presContext->AppUnitsToDevPixels(unionRectTwips.y);
+ screenRect.width = presContext->AppUnitsToDevPixels(unionRectTwips.width);
+ screenRect.height = presContext->AppUnitsToDevPixels(unionRectTwips.height);
+
+ // We have the union of the rectangle, now we need to put it in absolute
+ // screen coords.
+ nsIntRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits().
+ ToNearestPixels(presContext->AppUnitsPerDevPixel());
+ screenRect.x += orgRectPixels.x;
+ screenRect.y += orgRectPixels.y;
+
+ return screenRect;
+}
+
+void
+Accessible::SetSelected(bool aSelect)
+{
+ if (!HasOwnContent())
+ return;
+
+ Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
+ if (select) {
+ if (select->State() & states::MULTISELECTABLE) {
+ if (ARIARoleMap()) {
+ if (aSelect) {
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("true"), true);
+ } else {
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, true);
+ }
+ }
+ return;
+ }
+
+ if (aSelect)
+ TakeFocus();
+ }
+}
+
+void
+Accessible::TakeSelection()
+{
+ Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
+ if (select) {
+ if (select->State() & states::MULTISELECTABLE)
+ select->UnselectAll();
+ SetSelected(true);
+ }
+}
+
+void
+Accessible::TakeFocus()
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIContent* focusContent = mContent;
+
+ // If the accessible focus is managed by container widget then focus the
+ // widget and set the accessible as its current item.
+ if (!frame->IsFocusable()) {
+ Accessible* widget = ContainerWidget();
+ if (widget && widget->AreItemsOperable()) {
+ nsIContent* widgetElm = widget->GetContent();
+ nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
+ if (widgetFrame && widgetFrame->IsFocusable()) {
+ focusContent = widgetElm;
+ widget->SetCurrentItem(this);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDOMElement> element(do_QueryInterface(focusContent));
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ fm->SetFocus(element, 0);
+}
+
+void
+Accessible::XULElmName(DocAccessible* aDocument,
+ nsIContent* aElm, nsString& aName)
+{
+ /**
+ * 3 main cases for XUL Controls to be labeled
+ * 1 - control contains label="foo"
+ * 2 - control has, as a child, a label element
+ * - label has either value="foo" or children
+ * 3 - non-child label contains control="controlID"
+ * - label has either value="foo" or children
+ * Once a label is found, the search is discontinued, so a control
+ * that has a label child as well as having a label external to
+ * the control that uses the control="controlID" syntax will use
+ * the child label for its Name.
+ */
+
+ // CASE #1 (via label attribute) -- great majority of the cases
+ nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl = do_QueryInterface(aElm);
+ if (labeledEl) {
+ labeledEl->GetLabel(aName);
+ } else {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm);
+ if (itemEl) {
+ itemEl->GetLabel(aName);
+ } else {
+ nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm);
+ // Use label if this is not a select control element which
+ // uses label attribute to indicate which option is selected
+ if (!select) {
+ nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aElm));
+ if (xulEl)
+ xulEl->GetAttribute(NS_LITERAL_STRING("label"), aName);
+ }
+ }
+ }
+
+ // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
+ if (aName.IsEmpty()) {
+ Accessible* labelAcc = nullptr;
+ XULLabelIterator iter(aDocument, aElm);
+ while ((labelAcc = iter.Next())) {
+ nsCOMPtr<nsIDOMXULLabelElement> xulLabel =
+ do_QueryInterface(labelAcc->GetContent());
+ // Check if label's value attribute is used
+ if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(aName)) && aName.IsEmpty()) {
+ // If no value attribute, a non-empty label must contain
+ // children that define its text -- possibly using HTML
+ nsTextEquivUtils::
+ AppendTextEquivFromContent(labelAcc, labelAcc->GetContent(), &aName);
+ }
+ }
+ }
+
+ aName.CompressWhitespace();
+ if (!aName.IsEmpty())
+ return;
+
+ // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem>
+ nsIContent *bindingParent = aElm->GetBindingParent();
+ nsIContent* parent =
+ bindingParent? bindingParent->GetParent() : aElm->GetParent();
+ nsAutoString ancestorTitle;
+ while (parent) {
+ if (parent->IsXULElement(nsGkAtoms::toolbaritem) &&
+ parent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, ancestorTitle)) {
+ // Before returning this, check if the element itself has a tooltip:
+ if (aElm->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
+ aName.CompressWhitespace();
+ return;
+ }
+
+ aName.Assign(ancestorTitle);
+ aName.CompressWhitespace();
+ return;
+ }
+ parent = parent->GetParent();
+ }
+}
+
+nsresult
+Accessible::HandleAccEvent(AccEvent* aEvent)
+{
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (IPCAccessibilityActive() && Document()) {
+ DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
+ MOZ_ASSERT(ipcDoc);
+ if (ipcDoc) {
+ uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 :
+ reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+
+ switch(aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_SHOW:
+ ipcDoc->ShowEvent(downcast_accEvent(aEvent));
+ break;
+
+ case nsIAccessibleEvent::EVENT_HIDE:
+ ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
+ break;
+
+ case nsIAccessibleEvent::EVENT_REORDER:
+ // reorder events on the application acc aren't necessary to tell the parent
+ // about new top level documents.
+ if (!aEvent->GetAccessible()->IsApplication())
+ ipcDoc->SendEvent(id, aEvent->GetEventType());
+ break;
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendStateChangeEvent(id, event->GetState(),
+ event->IsStateEnabled());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ AccCaretMoveEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendTextChangeEvent(id, event->ModifiedText(),
+ event->GetStartOffset(),
+ event->GetLength(),
+ event->IsTextInserted(),
+ event->IsFromUserInput());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
+ AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
+ uint64_t widgetID = selEvent->Widget()->IsDoc() ? 0 :
+ reinterpret_cast<uintptr_t>(selEvent->Widget());
+ ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
+ break;
+ }
+ default:
+ ipcDoc->SendEvent(id, aEvent->GetEventType());
+ }
+ }
+ }
+
+ if (nsCoreUtils::AccEventObserversExist()) {
+ nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIPersistentProperties>
+Accessible::Attributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes = NativeAttributes();
+ if (!HasOwnContent() || !mContent->IsElement())
+ return attributes.forget();
+
+ // 'xml-roles' attribute for landmark.
+ nsIAtom* landmark = LandmarkRole();
+ if (landmark) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, landmark);
+
+ } else {
+ // 'xml-roles' attribute coming from ARIA.
+ nsAutoString xmlRoles;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
+ }
+
+ // Expose object attributes from ARIA attributes.
+ nsAutoString unused;
+ aria::AttrIterator attribIter(mContent);
+ nsAutoString name, value;
+ while(attribIter.Next(name, value))
+ attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
+
+ if (IsARIAHidden()) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::hidden,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // If there is no aria-live attribute then expose default value of 'live'
+ // object attribute used for ARIA role of this accessible.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry) {
+ if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType,
+ NS_LITERAL_STRING("search"));
+ }
+
+ nsAutoString live;
+ nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live);
+ if (live.IsEmpty()) {
+ if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live);
+ }
+ }
+
+ return attributes.forget();
+}
+
+already_AddRefed<nsIPersistentProperties>
+Accessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ nsAutoString unused;
+
+ // We support values, so expose the string value as well, via the valuetext
+ // object attribute. We test for the value interface because we don't want
+ // to expose traditional Value() information such as URL's on links and
+ // documents, or text in an input.
+ if (HasNumericValue()) {
+ nsAutoString valuetext;
+ Value(valuetext);
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext,
+ unused);
+ }
+
+ // Expose checkable object attribute if the accessible has checkable state
+ if (State() & states::CHECKABLE) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::checkable,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // Expose 'explicit-name' attribute.
+ nsAutoString name;
+ if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("explicit-name"),
+ NS_LITERAL_STRING("true"), unused);
+ }
+
+ // Group attributes (level/setsize/posinset)
+ GroupPos groupPos = GroupPosition();
+ nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level,
+ groupPos.setSize, groupPos.posInSet);
+
+ // If the accessible doesn't have own content (such as list item bullet or
+ // xul tree item) then don't calculate content based attributes.
+ if (!HasOwnContent())
+ return attributes.forget();
+
+ nsEventShell::GetEventAttributes(GetNode(), attributes);
+
+ // Get container-foo computed live region properties based on the closest
+ // container with the live region attribute. Inner nodes override outer nodes
+ // within the same document. The inner nodes can be used to override live
+ // region behavior on more general outer nodes. However, nodes in outer
+ // documents override nodes in inner documents: outer doc author may want to
+ // override properties on a widget they used in an iframe.
+ nsIContent* startContent = mContent;
+ while (startContent) {
+ nsIDocument* doc = startContent->GetComposedDoc();
+ if (!doc)
+ break;
+
+ nsAccUtils::SetLiveContainerAttributes(attributes, startContent,
+ doc->GetRootElement());
+
+ // Allow ARIA live region markup from outer documents to override
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+ if (!docShellTreeItem)
+ break;
+
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+ docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+ if (!sameTypeParent || sameTypeParent == docShellTreeItem)
+ break;
+
+ nsIDocument* parentDoc = doc->GetParentDocument();
+ if (!parentDoc)
+ break;
+
+ startContent = parentDoc->FindContentForSubDocument(doc);
+ }
+
+ if (!mContent->IsElement())
+ return attributes.forget();
+
+ nsAutoString id;
+ if (nsCoreUtils::GetID(mContent, id))
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, unused);
+
+ // Expose class because it may have useful microformat information.
+ nsAutoString _class;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, _class))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::_class, _class);
+
+ // Expose tag.
+ nsAutoString tagName;
+ mContent->NodeInfo()->GetName(tagName);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tag, tagName);
+
+ // Expose draggable object attribute.
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent);
+ if (htmlElement) {
+ bool draggable = false;
+ htmlElement->GetDraggable(&draggable);
+ if (draggable) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::draggable,
+ NS_LITERAL_STRING("true"));
+ }
+ }
+
+ // Don't calculate CSS-based object attributes when no frame (i.e.
+ // the accessible is unattached from the tree).
+ if (!mContent->GetPrimaryFrame())
+ return attributes.forget();
+
+ // CSS style based object attributes.
+ nsAutoString value;
+ StyleInfo styleInfo(mContent->AsElement(), mDoc->PresShell());
+
+ // Expose 'display' attribute.
+ styleInfo.Display(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::display, value);
+
+ // Expose 'text-align' attribute.
+ styleInfo.TextAlign(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textAlign, value);
+
+ // Expose 'text-indent' attribute.
+ styleInfo.TextIndent(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textIndent, value);
+
+ // Expose 'margin-left' attribute.
+ styleInfo.MarginLeft(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginLeft, value);
+
+ // Expose 'margin-right' attribute.
+ styleInfo.MarginRight(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginRight, value);
+
+ // Expose 'margin-top' attribute.
+ styleInfo.MarginTop(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginTop, value);
+
+ // Expose 'margin-bottom' attribute.
+ styleInfo.MarginBottom(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginBottom, value);
+
+ return attributes.forget();
+}
+
+GroupPos
+Accessible::GroupPosition()
+{
+ GroupPos groupPos;
+ if (!HasOwnContent())
+ return groupPos;
+
+ // Get group position from ARIA attributes.
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level);
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize);
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &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) {
+ int32_t level = GetLevelInternal();
+ if (level != 0)
+ groupPos.level = level;
+ }
+
+ // Calculate position in group and group size if ARIA is missed.
+ if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
+ int32_t posInSet = 0, setSize = 0;
+ GetPositionAndSizeInternal(&posInSet, &setSize);
+ if (posInSet != 0 && setSize != 0) {
+ if (groupPos.posInSet == 0)
+ groupPos.posInSet = posInSet;
+
+ if (groupPos.setSize == 0)
+ groupPos.setSize = setSize;
+ }
+ }
+
+ return groupPos;
+}
+
+uint64_t
+Accessible::State()
+{
+ if (IsDefunct())
+ return states::DEFUNCT;
+
+ uint64_t state = NativeState();
+ // Apply ARIA states to be sure accessible states will be overridden.
+ ApplyARIAState(&state);
+
+ // 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 && !(state & states::SELECTED) &&
+ !mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_selected,
+ nsGkAtoms::_false, eCaseMatters)) {
+ // Special case for tabs: focused tab or focus inside related tab panel
+ // implies selected state.
+ if (roleMapEntry->role == roles::PAGETAB) {
+ if (state & states::FOCUSED) {
+ state |= 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))
+ state |= states::SELECTED;
+ }
+ }
+ } else if (state & states::FOCUSED) {
+ Accessible* container = nsAccUtils::GetSelectableContainer(this, state);
+ if (container &&
+ !nsAccUtils::HasDefinedARIAToken(container->GetContent(),
+ nsGkAtoms::aria_multiselectable)) {
+ state |= states::SELECTED;
+ }
+ }
+ }
+
+ const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
+ if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
+ // Cannot be both expanded and collapsed -- this happens in ARIA expanded
+ // combobox because of limitation of ARIAMap.
+ // XXX: Perhaps we will be able to make this less hacky if we support
+ // extended states in ARIAMap, e.g. derive COLLAPSED from
+ // EXPANDABLE && !EXPANDED.
+ state &= ~states::COLLAPSED;
+ }
+
+ if (!(state & states::UNAVAILABLE)) {
+ state |= states::ENABLED | states::SENSITIVE;
+
+ // If the object is a current item of container widget then mark it as
+ // ACTIVE. This allows screen reader virtual buffer modes to know which
+ // descendant is the current one that would get focus if the user navigates
+ // to the container widget.
+ Accessible* widget = ContainerWidget();
+ if (widget && widget->CurrentItem() == this)
+ state |= states::ACTIVE;
+ }
+
+ if ((state & states::COLLAPSED) || (state & states::EXPANDED))
+ state |= states::EXPANDABLE;
+
+ // For some reasons DOM node may have not a frame. We tract such accessibles
+ // as invisible.
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return state;
+
+ if (frame->StyleEffects()->mOpacity == 1.0f &&
+ !(state & states::INVISIBLE)) {
+ state |= states::OPAQUE1;
+ }
+
+ return state;
+}
+
+void
+Accessible::ApplyARIAState(uint64_t* aState) const
+{
+ if (!mContent->IsElement())
+ return;
+
+ dom::Element* element = mContent->AsElement();
+
+ // Test for universal states first
+ *aState |= aria::UniversalStatesFor(element);
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry) {
+
+ // We only force the readonly bit off if we have a real mapping for the aria
+ // role. This preserves the ability for screen readers to use readonly
+ // (primarily on the document) as the hint for creating a virtual buffer.
+ if (roleMapEntry->role != roles::NOTHING)
+ *aState &= ~states::READONLY;
+
+ if (mContent->HasID()) {
+ // If has a role & ID and aria-activedescendant on the container, assume
+ // focusable.
+ const Accessible* ancestor = this;
+ while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+ dom::Element* el = ancestor->Elm();
+ if (el &&
+ el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
+ *aState |= states::FOCUSABLE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (*aState & states::FOCUSABLE) {
+ // Propogate aria-disabled from ancestors down to any focusable descendant.
+ const Accessible* ancestor = this;
+ while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+ dom::Element* el = ancestor->Elm();
+ if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
+ nsGkAtoms::_true, eCaseMatters)) {
+ *aState |= states::UNAVAILABLE;
+ break;
+ }
+ }
+ }
+
+ // special case: A native button element whose role got transformed by ARIA to a toggle button
+ // Also applies to togglable button menus, like in the Dev Tools Web Console.
+ if (IsButton() || IsMenuButton())
+ aria::MapToState(aria::eARIAPressed, element, aState);
+
+ if (!roleMapEntry)
+ return;
+
+ *aState |= roleMapEntry->state;
+
+ if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
+ aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
+ aria::MapToState(roleMapEntry->attributeMap3, element, aState))
+ aria::MapToState(roleMapEntry->attributeMap4, element, aState);
+
+ // ARIA gridcell inherits editable/readonly states from the grid until it's
+ // overridden.
+ if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
+ roleMapEntry->Is(nsGkAtoms::columnheader) ||
+ roleMapEntry->Is(nsGkAtoms::rowheader)) &&
+ !(*aState & (states::READONLY | states::EDITABLE))) {
+ const TableCellAccessible* cell = AsTableCell();
+ if (cell) {
+ TableAccessible* table = cell->Table();
+ if (table) {
+ Accessible* grid = table->AsAccessible();
+ uint64_t gridState = 0;
+ grid->ApplyARIAState(&gridState);
+ *aState |= (gridState & (states::READONLY | states::EDITABLE));
+ }
+ }
+ }
+}
+
+void
+Accessible::Value(nsString& aValue)
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry)
+ return;
+
+ if (roleMapEntry->valueRule != eNoValue) {
+ // aria-valuenow is a number, and aria-valuetext is the optional text
+ // equivalent. For the string value, we will try the optional text
+ // equivalent first.
+ if (!mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_valuetext, aValue)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow,
+ aValue);
+ }
+ return;
+ }
+
+ // Value of textbox is a textified subtree.
+ if (roleMapEntry->Is(nsGkAtoms::textbox)) {
+ nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
+ return;
+ }
+
+ // Value of combobox is a text of current or selected item.
+ if (roleMapEntry->Is(nsGkAtoms::combobox)) {
+ Accessible* option = CurrentItem();
+ if (!option) {
+ uint32_t childCount = ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = mChildren.ElementAt(idx);
+ if (child->IsListControl()) {
+ option = child->GetSelectedItem(0);
+ break;
+ }
+ }
+ }
+
+ if (option)
+ nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue);
+ }
+}
+
+double
+Accessible::MaxValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuemax);
+}
+
+double
+Accessible::MinValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuemin);
+}
+
+double
+Accessible::Step() const
+{
+ return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
+}
+
+double
+Accessible::CurValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuenow);
+}
+
+bool
+Accessible::SetCurValue(double aValue)
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
+ return false;
+
+ const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
+ if (State() & kValueCannotChange)
+ return false;
+
+ double checkValue = MinValue();
+ if (!IsNaN(checkValue) && aValue < checkValue)
+ return false;
+
+ checkValue = MaxValue();
+ if (!IsNaN(checkValue) && aValue > checkValue)
+ return false;
+
+ nsAutoString strValue;
+ strValue.AppendFloat(aValue);
+
+ return NS_SUCCEEDED(
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
+}
+
+role
+Accessible::ARIATransformRole(role aRole)
+{
+ // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
+ // where the accessible role depends on both the role and ARIA state.
+ if (aRole == roles::PUSHBUTTON) {
+ if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
+ // For simplicity, any existing pressed attribute except "" or "undefined"
+ // indicates a toggle.
+ return roles::TOGGLE_BUTTON;
+ }
+
+ if (mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_haspopup,
+ nsGkAtoms::_true,
+ eCaseMatters)) {
+ // For button with aria-haspopup="true".
+ return roles::BUTTONMENU;
+ }
+
+ } else if (aRole == roles::LISTBOX) {
+ // A listbox inside of a combobox needs a special role because of ATK
+ // mapping to menu.
+ if (mParent && mParent->Role() == roles::COMBOBOX) {
+ return roles::COMBOBOX_LIST;
+ } else {
+ // Listbox is owned by a combobox
+ Relation rel = RelationByType(RelationType::NODE_CHILD_OF);
+ Accessible* targetAcc = nullptr;
+ while ((targetAcc = rel.Next()))
+ if (targetAcc->Role() == roles::COMBOBOX)
+ return roles::COMBOBOX_LIST;
+ }
+
+ } else if (aRole == roles::OPTION) {
+ if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
+ return roles::COMBOBOX_OPTION;
+
+ } else if (aRole == roles::MENUITEM) {
+ // Menuitem has a submenu.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_haspopup,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return roles::PARENT_MENUITEM;
+ }
+ }
+
+ return aRole;
+}
+
+nsIAtom*
+Accessible::LandmarkRole() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->IsOfType(eLandmark) ?
+ *(roleMapEntry->roleAtom) : nullptr;
+}
+
+role
+Accessible::NativeRole()
+{
+ return roles::NOTHING;
+}
+
+uint8_t
+Accessible::ActionCount()
+{
+ return GetActionRule() == eNoAction ? 0 : 1;
+}
+
+void
+Accessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ if (aIndex != 0)
+ return;
+
+ uint32_t actionRule = GetActionRule();
+
+ switch (actionRule) {
+ case eActivateAction:
+ aName.AssignLiteral("activate");
+ return;
+
+ case eClickAction:
+ aName.AssignLiteral("click");
+ return;
+
+ case ePressAction:
+ aName.AssignLiteral("press");
+ return;
+
+ case eCheckUncheckAction:
+ {
+ uint64_t state = State();
+ if (state & states::CHECKED)
+ aName.AssignLiteral("uncheck");
+ else if (state & states::MIXED)
+ aName.AssignLiteral("cycle");
+ else
+ aName.AssignLiteral("check");
+ return;
+ }
+
+ case eJumpAction:
+ aName.AssignLiteral("jump");
+ return;
+
+ case eOpenCloseAction:
+ if (State() & states::COLLAPSED)
+ aName.AssignLiteral("open");
+ else
+ aName.AssignLiteral("close");
+ return;
+
+ case eSelectAction:
+ aName.AssignLiteral("select");
+ return;
+
+ case eSwitchAction:
+ aName.AssignLiteral("switch");
+ return;
+
+ case eSortAction:
+ aName.AssignLiteral("sort");
+ return;
+
+ case eExpandAction:
+ if (State() & states::COLLAPSED)
+ aName.AssignLiteral("expand");
+ else
+ aName.AssignLiteral("collapse");
+ return;
+ }
+}
+
+bool
+Accessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ if (GetActionRule() != eNoAction) {
+ DoCommand();
+ return true;
+ }
+
+ return false;
+}
+
+nsIContent*
+Accessible::GetAtomicRegion() const
+{
+ nsIContent *loopContent = mContent;
+ nsAutoString atomic;
+ while (loopContent && !loopContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_atomic, atomic))
+ loopContent = loopContent->GetParent();
+
+ return atomic.EqualsLiteral("true") ? loopContent : nullptr;
+}
+
+Relation
+Accessible::RelationByType(RelationType aType)
+{
+ if (!HasOwnContent())
+ return Relation();
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+
+ // Relationships are defined on the same content node that the role would be
+ // defined on.
+ switch (aType) {
+ case RelationType::LABELLED_BY: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_labelledby));
+ if (mContent->IsHTMLElement()) {
+ rel.AppendIter(new HTMLLabelIterator(Document(), this));
+ } else if (mContent->IsXULElement()) {
+ rel.AppendIter(new XULLabelIterator(Document(), mContent));
+ }
+
+ return rel;
+ }
+
+ case RelationType::LABEL_FOR: {
+ Relation rel(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_labelledby));
+ if (mContent->IsXULElement(nsGkAtoms::label))
+ rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
+
+ return rel;
+ }
+
+ case RelationType::DESCRIBED_BY: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_describedby));
+ if (mContent->IsXULElement())
+ rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
+
+ return rel;
+ }
+
+ case RelationType::DESCRIPTION_FOR: {
+ Relation rel(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_describedby));
+
+ // This affectively adds an optional control attribute to xul:description,
+ // which only affects accessibility, by allowing the description to be
+ // tied to a control.
+ if (mContent->IsXULElement(nsGkAtoms::description))
+ rel.AppendIter(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::control));
+
+ return rel;
+ }
+
+ case RelationType::NODE_CHILD_OF: {
+ Relation rel;
+ // This is an ARIA tree or treegrid that doesn't use owns, so we need to
+ // get the parent the hard way.
+ if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW)) {
+ rel.AppendTarget(GetGroupInfo()->ConceptualParent());
+ }
+
+ // If accessible is in its own Window, or is the root of a document,
+ // then we should provide NODE_CHILD_OF relation so that MSAA clients
+ // can easily get to true parent instead of getting to oleacc's
+ // ROLE_WINDOW accessible which will prevent us from going up further
+ // (because it is system generated and has no idea about the hierarchy
+ // above it).
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ nsView *view = frame->GetView();
+ if (view) {
+ nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
+ if (scrollFrame || view->GetWidget() || !frame->GetParent())
+ rel.AppendTarget(Parent());
+ }
+ }
+
+ return rel;
+ }
+
+ case RelationType::NODE_PARENT_OF: {
+ // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
+ // also can be organized by groups.
+ if (roleMapEntry &&
+ (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW ||
+ roleMapEntry->role == roles::OUTLINE ||
+ roleMapEntry->role == roles::LIST ||
+ roleMapEntry->role == roles::TREE_TABLE)) {
+ return Relation(new ItemIterator(this));
+ }
+
+ return Relation();
+ }
+
+ case RelationType::CONTROLLED_BY:
+ return Relation(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_controls));
+
+ case RelationType::CONTROLLER_FOR: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_controls));
+ rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
+ return rel;
+ }
+
+ case RelationType::FLOWS_TO:
+ return Relation(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_flowto));
+
+ case RelationType::FLOWS_FROM:
+ return Relation(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_flowto));
+
+ case RelationType::MEMBER_OF:
+ return Relation(mDoc, GetAtomicRegion());
+
+ case RelationType::SUBWINDOW_OF:
+ case RelationType::EMBEDS:
+ case RelationType::EMBEDDED_BY:
+ case RelationType::POPUP_FOR:
+ case RelationType::PARENT_WINDOW_OF:
+ return Relation();
+
+ case RelationType::DEFAULT_BUTTON: {
+ if (mContent->IsHTMLElement()) {
+ // HTML form controls implements nsIFormControl interface.
+ nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
+ if (control) {
+ nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement()));
+ if (form) {
+ nsCOMPtr<nsIContent> formContent =
+ do_QueryInterface(form->GetDefaultSubmitElement());
+ return Relation(mDoc, formContent);
+ }
+ }
+ } else {
+ // In XUL, use first <button default="true" .../> in the document
+ nsCOMPtr<nsIDOMXULDocument> xulDoc =
+ do_QueryInterface(mContent->OwnerDoc());
+ nsCOMPtr<nsIDOMXULButtonElement> buttonEl;
+ if (xulDoc) {
+ nsCOMPtr<nsIDOMNodeList> possibleDefaultButtons;
+ xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"),
+ NS_LITERAL_STRING("true"),
+ getter_AddRefs(possibleDefaultButtons));
+ if (possibleDefaultButtons) {
+ uint32_t length;
+ possibleDefaultButtons->GetLength(&length);
+ nsCOMPtr<nsIDOMNode> possibleButton;
+ // Check for button in list of default="true" elements
+ for (uint32_t count = 0; count < length && !buttonEl; count ++) {
+ possibleDefaultButtons->Item(count, getter_AddRefs(possibleButton));
+ buttonEl = do_QueryInterface(possibleButton);
+ }
+ }
+ if (!buttonEl) { // Check for anonymous accept button in <dialog>
+ dom::Element* rootElm = mContent->OwnerDoc()->GetRootElement();
+ if (rootElm) {
+ nsIContent* possibleButtonEl = rootElm->OwnerDoc()->
+ GetAnonymousElementByAttribute(rootElm, nsGkAtoms::_default,
+ NS_LITERAL_STRING("true"));
+ buttonEl = do_QueryInterface(possibleButtonEl);
+ }
+ }
+ nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl));
+ return Relation(mDoc, relatedContent);
+ }
+ }
+ return Relation();
+ }
+
+ case RelationType::CONTAINING_DOCUMENT:
+ return Relation(mDoc);
+
+ case RelationType::CONTAINING_TAB_PANE: {
+ nsCOMPtr<nsIDocShell> docShell =
+ nsCoreUtils::GetDocShellFor(GetNode());
+ if (docShell) {
+ // Walk up the parent chain without crossing the boundary at which item
+ // types change, preventing us from walking up out of tab content.
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ if (root) {
+ // If the item type is typeContent, we assume we are in browser tab
+ // content. Note, this includes content such as about:addons,
+ // for consistency.
+ if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
+ return Relation(nsAccUtils::GetDocAccessibleFor(root));
+ }
+ }
+ }
+ return Relation();
+ }
+
+ case RelationType::CONTAINING_APPLICATION:
+ return Relation(ApplicationAcc());
+
+ case RelationType::DETAILS:
+ return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
+
+ case RelationType::DETAILS_FOR:
+ return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
+
+ case RelationType::ERRORMSG:
+ return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
+
+ case RelationType::ERRORMSG_FOR:
+ return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
+
+ default:
+ return Relation();
+ }
+}
+
+void
+Accessible::GetNativeInterface(void** aNativeAccessible)
+{
+}
+
+void
+Accessible::DoCommand(nsIContent *aContent, uint32_t aActionIndex)
+{
+ class Runnable final : public mozilla::Runnable
+ {
+ public:
+ Runnable(Accessible* aAcc, nsIContent* aContent, uint32_t aIdx) :
+ mAcc(aAcc), mContent(aContent), mIdx(aIdx) { }
+
+ NS_IMETHOD Run() override
+ {
+ if (mAcc)
+ mAcc->DispatchClickEvent(mContent, mIdx);
+
+ return NS_OK;
+ }
+
+ void Revoke()
+ {
+ mAcc = nullptr;
+ mContent = nullptr;
+ }
+
+ private:
+ RefPtr<Accessible> mAcc;
+ nsCOMPtr<nsIContent> mContent;
+ uint32_t mIdx;
+ };
+
+ nsIContent* content = aContent ? aContent : mContent.get();
+ nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+Accessible::DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex)
+{
+ if (IsDefunct())
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell();
+
+ // Scroll into view.
+ presShell->ScrollContentIntoView(aContent,
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+
+ nsWeakFrame frame = aContent->GetPrimaryFrame();
+ if (!frame)
+ return;
+
+ // Compute x and y coordinates.
+ nsPoint point;
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
+ if (!widget)
+ return;
+
+ nsSize size = frame->GetSize();
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
+ int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
+
+ // Simulate a touch interaction by dispatching touch events with mouse events.
+ nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame,
+ presShell, widget);
+}
+
+void
+Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIntPoint coords =
+ nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
+
+ nsIFrame* parentFrame = frame;
+ while ((parentFrame = parentFrame->GetParent()))
+ nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
+}
+
+void
+Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength)
+{
+ // Return text representation of non-text accessible within hypertext
+ // accessible. Text accessible overrides this method to return enclosed text.
+ if (aStartOffset != 0 || aLength == 0)
+ return;
+
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return;
+
+ NS_ASSERTION(mParent,
+ "Called on accessible unbound from tree. Result can be wrong.");
+
+ if (frame->GetType() == nsGkAtoms::brFrame) {
+ aText += kForcedNewLineChar;
+ } else if (mParent && nsAccUtils::MustPrune(mParent)) {
+ // Expose the embedded object accessible as imaginary embedded object
+ // character if its parent hypertext accessible doesn't expose children to
+ // AT.
+ aText += kImaginaryEmbeddedObjectChar;
+ } else {
+ aText += kEmbeddedObjectChar;
+ }
+}
+
+void
+Accessible::Shutdown()
+{
+ // Mark the accessible as defunct, invalidate the child count and pointers to
+ // other accessibles, also make sure none of its children point to this parent
+ mStateFlags |= eIsDefunct;
+
+ int32_t childCount = mChildren.Length();
+ for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mChildren.ElementAt(childIdx)->UnbindFromParent();
+ }
+ mChildren.Clear();
+
+ mEmbeddedObjCollector = nullptr;
+
+ if (mParent)
+ mParent->RemoveChild(this);
+
+ mContent = nullptr;
+ mDoc = nullptr;
+ if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this)
+ SelectionMgr()->ResetCaretOffset();
+}
+
+// Accessible protected
+void
+Accessible::ARIAName(nsString& aName)
+{
+ // aria-labelledby now takes precedence over aria-label
+ nsresult rv = nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_labelledby, aName);
+ if (NS_SUCCEEDED(rv)) {
+ aName.CompressWhitespace();
+ }
+
+ if (aName.IsEmpty() &&
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label, aName)) {
+ aName.CompressWhitespace();
+ }
+}
+
+// Accessible protected
+ENameValueFlag
+Accessible::NativeName(nsString& aName)
+{
+ if (mContent->IsHTMLElement()) {
+ Accessible* label = nullptr;
+ HTMLLabelIterator iter(Document(), this);
+ while ((label = iter.Next())) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
+ &aName);
+ aName.CompressWhitespace();
+ }
+
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ if (mContent->IsXULElement()) {
+ XULElmName(mDoc, mContent, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ if (mContent->IsSVGElement()) {
+ // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
+ // for processing, the user agent shall choose the first one.
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::title)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
+ return eNameOK;
+ }
+ }
+ }
+
+ return eNameOK;
+}
+
+// Accessible protected
+void
+Accessible::NativeDescription(nsString& aDescription)
+{
+ bool isXUL = mContent->IsXULElement();
+ if (isXUL) {
+ // Try XUL <description control="[id]">description text</description>
+ XULDescriptionIterator iter(Document(), mContent);
+ Accessible* descr = nullptr;
+ while ((descr = iter.Next())) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
+ &aDescription);
+ }
+ }
+}
+
+// Accessible protected
+void
+Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
+{
+ MOZ_ASSERT(aParent, "This method isn't used to set null parent");
+ MOZ_ASSERT(!mParent, "The child was expected to be moved");
+
+#ifdef A11Y_LOG
+ if (mParent) {
+ logging::TreeInfo("BindToParent: stealing accessible", 0,
+ "old parent", mParent,
+ "new parent", aParent,
+ "child", this, nullptr);
+ }
+#endif
+
+ mParent = aParent;
+ mIndexInParent = aIndexInParent;
+
+ // Note: this is currently only used for richlistitems and their children.
+ if (mParent->HasNameDependentParent() || mParent->IsXULListItem())
+ mContextFlags |= eHasNameDependentParent;
+ else
+ mContextFlags &= ~eHasNameDependentParent;
+
+ if (mParent->IsARIAHidden() || aria::HasDefinedARIAHidden(mContent))
+ SetARIAHidden(true);
+
+ mContextFlags |=
+ static_cast<uint32_t>((mParent->IsAlert() ||
+ mParent->IsInsideAlert())) & eInsideAlert;
+}
+
+// Accessible protected
+void
+Accessible::UnbindFromParent()
+{
+ mParent = nullptr;
+ mIndexInParent = -1;
+ mInt.mIndexOfEmbeddedChild = -1;
+ if (IsProxy())
+ MOZ_CRASH("this should never be called on proxy wrappers");
+
+ delete mBits.groupInfo;
+ mBits.groupInfo = nullptr;
+ mContextFlags &= ~eHasNameDependentParent & ~eInsideAlert;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public methods
+
+RootAccessible*
+Accessible::RootAccessible() const
+{
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
+ NS_ASSERTION(docShell, "No docshell for mContent");
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetRootTreeItem(getter_AddRefs(root));
+ NS_ASSERTION(root, "No root content tree item");
+ if (!root) {
+ return nullptr;
+ }
+
+ DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
+ return docAcc ? docAcc->AsRoot() : nullptr;
+}
+
+nsIFrame*
+Accessible::GetFrame() const
+{
+ return mContent ? mContent->GetPrimaryFrame() : nullptr;
+}
+
+nsINode*
+Accessible::GetNode() const
+{
+ return mContent;
+}
+
+void
+Accessible::Language(nsAString& aLanguage)
+{
+ aLanguage.Truncate();
+
+ if (!mDoc)
+ return;
+
+ nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
+ if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
+ mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
+ aLanguage);
+ }
+}
+
+bool
+Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ if (!aChild)
+ return false;
+
+ if (aIndex == mChildren.Length()) {
+ if (!mChildren.AppendElement(aChild))
+ return false;
+
+ } else {
+ if (!mChildren.InsertElementAt(aIndex, aChild))
+ return false;
+
+ MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
+
+ for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ }
+ }
+
+ if (aChild->IsText()) {
+ mStateFlags |= eHasTextKids;
+ }
+
+ aChild->BindToParent(this, aIndex);
+ return true;
+}
+
+bool
+Accessible::RemoveChild(Accessible* aChild)
+{
+ if (!aChild)
+ return false;
+
+ if (aChild->mParent != this || aChild->mIndexInParent == -1)
+ return false;
+
+ MOZ_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc(),
+ "Illicit children change");
+
+ int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
+ if (mChildren.SafeElementAt(index) != aChild) {
+ MOZ_ASSERT_UNREACHABLE("A wrong child index");
+ index = mChildren.IndexOf(aChild);
+ if (index == -1) {
+ MOZ_ASSERT_UNREACHABLE("No child was found");
+ return false;
+ }
+ }
+
+ aChild->UnbindFromParent();
+ mChildren.RemoveElementAt(index);
+
+ for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ }
+
+ return true;
+}
+
+void
+Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
+{
+ MOZ_ASSERT(aChild, "No child was given");
+ MOZ_ASSERT(aChild->mParent == this, "A child from different subtree was given");
+ MOZ_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
+ MOZ_ASSERT(static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
+ "No move, same index");
+ MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given");
+
+ RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
+ if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
+ aChild->SetHideEventTarget(true);
+ }
+
+ mEmbeddedObjCollector = nullptr;
+ mChildren.RemoveElementAt(aChild->mIndexInParent);
+
+ uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
+
+ // If the child is moved after its current position.
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
+ startIdx = aChild->mIndexInParent;
+ if (aNewIndex == mChildren.Length() + 1) {
+ // The child is moved to the end.
+ mChildren.AppendElement(aChild);
+ endIdx = mChildren.Length() - 1;
+ }
+ else {
+ mChildren.InsertElementAt(aNewIndex - 1, aChild);
+ endIdx = aNewIndex;
+ }
+ }
+ else {
+ // The child is moved prior its current position.
+ mChildren.InsertElementAt(aNewIndex, aChild);
+ }
+
+ for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ mChildren[idx]->mStateFlags |= eGroupInfoDirty;
+ mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
+ }
+
+ RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
+ DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
+ MOZ_ASSERT(added);
+ aChild->SetShowEventTarget(true);
+}
+
+Accessible*
+Accessible::GetChildAt(uint32_t aIndex) const
+{
+ Accessible* child = mChildren.SafeElementAt(aIndex, nullptr);
+ if (!child)
+ return nullptr;
+
+#ifdef DEBUG
+ Accessible* realParent = child->mParent;
+ NS_ASSERTION(!realParent || realParent == this,
+ "Two accessibles have the same first child accessible!");
+#endif
+
+ return child;
+}
+
+uint32_t
+Accessible::ChildCount() const
+{
+ return mChildren.Length();
+}
+
+int32_t
+Accessible::IndexInParent() const
+{
+ return mIndexInParent;
+}
+
+uint32_t
+Accessible::EmbeddedChildCount()
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector->Count();
+ }
+
+ return ChildCount();
+}
+
+Accessible*
+Accessible::GetEmbeddedChildAt(uint32_t aIndex)
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector.get() ?
+ mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr;
+ }
+
+ return GetChildAt(aIndex);
+}
+
+int32_t
+Accessible::GetIndexOfEmbeddedChild(Accessible* aChild)
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector.get() ?
+ mEmbeddedObjCollector->GetIndexAt(aChild) : -1;
+ }
+
+ return GetIndexOf(aChild);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperLinkAccessible methods
+
+bool
+Accessible::IsLink()
+{
+ // Every embedded accessible within hypertext accessible implements
+ // hyperlink interface.
+ return mParent && mParent->IsHyperText() && !IsText();
+}
+
+uint32_t
+Accessible::StartOffset()
+{
+ NS_PRECONDITION(IsLink(), "StartOffset is called not on hyper link!");
+
+ HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
+ return hyperText ? hyperText->GetChildOffset(this) : 0;
+}
+
+uint32_t
+Accessible::EndOffset()
+{
+ NS_PRECONDITION(IsLink(), "EndOffset is called on not hyper link!");
+
+ HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
+ return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
+}
+
+uint32_t
+Accessible::AnchorCount()
+{
+ NS_PRECONDITION(IsLink(), "AnchorCount is called on not hyper link!");
+ return 1;
+}
+
+Accessible*
+Accessible::AnchorAt(uint32_t aAnchorIndex)
+{
+ NS_PRECONDITION(IsLink(), "GetAnchor is called on not hyper link!");
+ return aAnchorIndex == 0 ? this : nullptr;
+}
+
+already_AddRefed<nsIURI>
+Accessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ NS_PRECONDITION(IsLink(), "AnchorURIAt is called on not hyper link!");
+ return nullptr;
+}
+
+void
+Accessible::ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+ bool aIsBefore) const
+{
+ if (IsHyperText()) {
+ *aContainer = const_cast<Accessible*>(this)->AsHyperText();
+ *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
+ return;
+ }
+
+ const Accessible* child = nullptr;
+ const Accessible* parent = this;
+ do {
+ child = parent;
+ parent = parent->Parent();
+ } while (parent && !parent->IsHyperText());
+
+ if (parent) {
+ *aContainer = const_cast<Accessible*>(parent)->AsHyperText();
+ *aOffset = (*aContainer)->GetChildOffset(
+ child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// SelectAccessible
+
+void
+Accessible::SelectedItems(nsTArray<Accessible*>* aItems)
+{
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()))
+ aItems->AppendElement(selected);
+}
+
+uint32_t
+Accessible::SelectedItemCount()
+{
+ uint32_t count = 0;
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()))
+ ++count;
+
+ return count;
+}
+
+Accessible*
+Accessible::GetSelectedItem(uint32_t aIndex)
+{
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+
+ uint32_t index = 0;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ return selected;
+}
+
+bool
+Accessible::IsItemSelected(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ return selected &&
+ selected->State() & states::SELECTED;
+}
+
+bool
+Accessible::AddItemToSelection(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ if (selected)
+ selected->SetSelected(true);
+
+ return static_cast<bool>(selected);
+}
+
+bool
+Accessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ if (selected)
+ selected->SetSelected(false);
+
+ return static_cast<bool>(selected);
+}
+
+bool
+Accessible::SelectAll()
+{
+ bool success = false;
+ Accessible* selectable = nullptr;
+
+ AccIterator iter(this, filters::GetSelectable);
+ while((selectable = iter.Next())) {
+ success = true;
+ selectable->SetSelected(true);
+ }
+ return success;
+}
+
+bool
+Accessible::UnselectAll()
+{
+ bool success = false;
+ Accessible* selected = nullptr;
+
+ AccIterator iter(this, filters::GetSelected);
+ while ((selected = iter.Next())) {
+ success = true;
+ selected->SetSelected(false);
+ }
+ return success;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Widgets
+
+bool
+Accessible::IsWidget() const
+{
+ return false;
+}
+
+bool
+Accessible::IsActiveWidget() const
+{
+ if (FocusMgr()->HasDOMFocus(mContent))
+ return true;
+
+ // If text entry of combobox widget has a focus then the combobox widget is
+ // active.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
+ uint32_t childCount = ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = mChildren.ElementAt(idx);
+ if (child->Role() == roles::ENTRY)
+ return FocusMgr()->HasDOMFocus(child->GetContent());
+ }
+ }
+
+ return false;
+}
+
+bool
+Accessible::AreItemsOperable() const
+{
+ return HasOwnContent() &&
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
+}
+
+Accessible*
+Accessible::CurrentItem()
+{
+ // Check for aria-activedescendant, which changes which element has focus.
+ // For activedescendant, the ARIA spec does not require that the user agent
+ // checks whether pointed node is actually a DOM descendant of the element
+ // with the aria-activedescendant attribute.
+ nsAutoString id;
+ if (HasOwnContent() &&
+ mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant, id)) {
+ nsIDocument* DOMDoc = mContent->OwnerDoc();
+ dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
+ if (activeDescendantElm) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(activeDescendantElm);
+ }
+ }
+ return nullptr;
+}
+
+void
+Accessible::SetCurrentItem(Accessible* aItem)
+{
+ nsIAtom* id = aItem->GetContent()->GetID();
+ if (id) {
+ nsAutoString idStr;
+ id->ToString(idStr);
+ mContent->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant, idStr, true);
+ }
+}
+
+Accessible*
+Accessible::ContainerWidget() const
+{
+ if (HasARIARole() && mContent->HasID()) {
+ for (Accessible* parent = Parent(); parent; parent = parent->Parent()) {
+ nsIContent* parentContent = parent->GetContent();
+ if (parentContent &&
+ parentContent->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant)) {
+ return parent;
+ }
+
+ // Don't cross DOM document boundaries.
+ if (parent->IsDoc())
+ break;
+ }
+ }
+ return nullptr;
+}
+
+void
+Accessible::SetARIAHidden(bool aIsDefined)
+{
+ if (aIsDefined)
+ mContextFlags |= eARIAHidden;
+ else
+ mContextFlags &= ~eARIAHidden;
+
+ uint32_t length = mChildren.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ mChildren[i]->SetARIAHidden(aIsDefined);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible protected methods
+
+void
+Accessible::LastRelease()
+{
+ // First cleanup if needed...
+ if (mDoc) {
+ Shutdown();
+ NS_ASSERTION(!mDoc,
+ "A Shutdown() impl forgot to call its parent's Shutdown?");
+ }
+ // ... then die.
+ delete this;
+}
+
+Accessible*
+Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
+{
+ if (!mParent || mIndexInParent == -1) {
+ if (aError)
+ *aError = NS_ERROR_UNEXPECTED;
+
+ return nullptr;
+ }
+
+ if (aError &&
+ mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
+ *aError = NS_OK; // fail peacefully
+ return nullptr;
+ }
+
+ Accessible* child = mParent->GetChildAt(mIndexInParent + aOffset);
+ if (aError && !child)
+ *aError = NS_ERROR_UNEXPECTED;
+
+ return child;
+}
+
+double
+Accessible::AttrNumericValue(nsIAtom* aAttr) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
+ return UnspecifiedNaN<double>();
+
+ nsAutoString attrValue;
+ if (!mContent->GetAttr(kNameSpaceID_None, aAttr, attrValue))
+ return UnspecifiedNaN<double>();
+
+ nsresult error = NS_OK;
+ double value = attrValue.ToDouble(&error);
+ return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
+}
+
+uint32_t
+Accessible::GetActionRule() const
+{
+ if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE))
+ return eNoAction;
+
+ // Return "click" action on elements that have an attached popup menu.
+ if (mContent->IsXULElement())
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
+ return eClickAction;
+
+ // Has registered 'click' event handler.
+ bool isOnclick = nsCoreUtils::HasClickListener(mContent);
+
+ if (isOnclick)
+ return eClickAction;
+
+ // Get an action based on ARIA role.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry &&
+ roleMapEntry->actionRule != eNoAction)
+ return roleMapEntry->actionRule;
+
+ // Get an action based on ARIA attribute.
+ if (nsAccUtils::HasDefinedARIAToken(mContent,
+ nsGkAtoms::aria_expanded))
+ return eExpandAction;
+
+ return eNoAction;
+}
+
+AccGroupInfo*
+Accessible::GetGroupInfo()
+{
+ if (IsProxy())
+ MOZ_CRASH("This should never be called on proxy wrappers");
+
+ if (mBits.groupInfo){
+ if (HasDirtyGroupInfo()) {
+ mBits.groupInfo->Update();
+ mStateFlags &= ~eGroupInfoDirty;
+ }
+
+ return mBits.groupInfo;
+ }
+
+ mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this);
+ return mBits.groupInfo;
+}
+
+void
+Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize)
+{
+ AccGroupInfo* groupInfo = GetGroupInfo();
+ if (groupInfo) {
+ *aPosInSet = groupInfo->PosInSet();
+ *aSetSize = groupInfo->SetSize();
+ }
+}
+
+int32_t
+Accessible::GetLevelInternal()
+{
+ int32_t level = nsAccUtils::GetDefaultLevel(this);
+
+ if (!IsBoundToParent())
+ 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;
+
+ Accessible* parent = this;
+ while ((parent = parent->Parent())) {
+ roles::Role parentRole = parent->Role();
+
+ if (parentRole == roles::OUTLINE)
+ break;
+ if (parentRole == roles::GROUPING)
+ ++ level;
+
+ }
+
+ } else if (role == roles::LISTITEM) {
+ // 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;
+ Accessible* parent = this;
+ while ((parent = parent->Parent())) {
+ 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->GetChildAt(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
+ }
+ }
+
+ return level;
+}
+
+void
+Accessible::StaticAsserts() const
+{
+ static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1,
+ "Accessible::mStateFlags was oversized by eLastStateFlag!");
+ static_assert(eLastAccType <= (1 << kTypeBits) - 1,
+ "Accessible::mType was oversized by eLastAccType!");
+ static_assert(eLastContextFlag <= (1 << kContextFlagsBits) - 1,
+ "Accessible::mContextFlags was oversized by eLastContextFlag!");
+ static_assert(eLastAccGenericType <= (1 << kGenericTypesBits) - 1,
+ "Accessible::mGenericType was oversized by eLastAccGenericType!");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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::services::GetStringBundleService();
+ if (stringBundleService)
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/platformKeys.properties",
+ getter_AddRefs(keyStringBundle));
+
+ if (!keyStringBundle)
+ return;
+
+ nsAutoString separator;
+ keyStringBundle->GetStringFromName(u"MODIFIER_SEPARATOR",
+ getter_Copies(separator));
+
+ nsAutoString modifierName;
+ if (mModifierMask & kControl) {
+ keyStringBundle->GetStringFromName(u"VK_CONTROL",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kAlt) {
+ keyStringBundle->GetStringFromName(u"VK_ALT",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kShift) {
+ keyStringBundle->GetStringFromName(u"VK_SHIFT",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kMeta) {
+ keyStringBundle->GetStringFromName(u"VK_META",
+ getter_Copies(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/generic/Accessible.h b/accessible/generic/Accessible.h
new file mode 100644
index 0000000000..eaf041920b
--- /dev/null
+++ b/accessible/generic/Accessible.h
@@ -0,0 +1,1269 @@
+/* -*- 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/AccTypes.h"
+#include "mozilla/a11y/RelationType.h"
+#include "mozilla/a11y/Role.h"
+#include "mozilla/a11y/States.h"
+
+#include "mozilla/UniquePtr.h"
+
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsRect.h"
+
+struct nsRoleMapEntry;
+
+struct nsRect;
+class nsIFrame;
+class nsIAtom;
+class nsIPersistentProperties;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class AccEvent;
+class AccGroupInfo;
+class ApplicationAccessible;
+class DocAccessible;
+class EmbeddedObjCollector;
+class EventTree;
+class HTMLImageMapAccessible;
+class HTMLLIAccessible;
+class HyperTextAccessible;
+class ImageAccessible;
+class KeyBinding;
+class OuterDocAccessible;
+class ProxyAccessible;
+class Relation;
+class RootAccessible;
+class TableAccessible;
+class TableCellAccessible;
+class TextLeafAccessible;
+class XULLabelAccessible;
+class XULTreeAccessible;
+
+#ifdef A11Y_LOG
+namespace logging {
+ typedef const char* (*GetTreePrefix)(void* aData, Accessible*);
+ void Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
+ GetTreePrefix aPrefixFunc, void* GetTreePrefixData);
+};
+#endif
+
+/**
+ * Name type flags.
+ */
+enum ENameValueFlag {
+ /**
+ * Name either
+ * a) present (not empty): !name.IsEmpty()
+ * b) no name (was missed): name.IsVoid()
+ */
+ eNameOK,
+
+ /**
+ * Name was left empty by the author on purpose:
+ * name.IsEmpty() && !name.IsVoid().
+ */
+ eNoNameOnPurpose,
+
+ /**
+ * 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;
+};
+
+/**
+ * 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;
+};
+
+typedef nsRefPtrHashtable<nsPtrHashKey<const void>, Accessible>
+ AccessibleHashtable;
+
+
+#define NS_ACCESSIBLE_IMPL_IID \
+{ /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */ \
+ 0x133c8bf4, \
+ 0x4913, \
+ 0x4355, \
+ { 0xbd, 0x50, 0x42, 0x6b, 0xd1, 0xd6, 0xe1, 0xad } \
+}
+
+class Accessible : public nsISupports
+{
+public:
+ Accessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(Accessible)
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLE_IMPL_IID)
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Public methods
+
+ /**
+ * Return the document accessible for this accessible.
+ */
+ DocAccessible* Document() const { return mDoc; }
+
+ /**
+ * Return the root document accessible for this accessible.
+ */
+ a11y::RootAccessible* RootAccessible() const;
+
+ /**
+ * Return frame for this accessible.
+ */
+ virtual nsIFrame* GetFrame() const;
+
+ /**
+ * Return DOM node associated with the accessible.
+ */
+ virtual nsINode* GetNode() const;
+ inline already_AddRefed<nsIDOMNode> DOMNode() const
+ {
+ nsCOMPtr<nsIDOMNode> DOMNode = do_QueryInterface(GetNode());
+ return DOMNode.forget();
+ }
+ nsIContent* GetContent() const { return mContent; }
+ mozilla::dom::Element* Elm() const
+ { return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr; }
+
+ /**
+ * Return node type information of DOM node associated with the accessible.
+ */
+ bool IsContent() const
+ { return GetNode() && GetNode()->IsNodeOfType(nsINode::eCONTENT); }
+
+ /**
+ * Return the unique identifier of the accessible.
+ */
+ void* UniqueID() { return static_cast<void*>(this); }
+
+ /**
+ * Return language associated with the accessible.
+ */
+ void Language(nsAString& aLocale);
+
+ /**
+ * Get the description of this accessible.
+ */
+ virtual void Description(nsString& aDescription);
+
+ /**
+ * Get the value of this accessible.
+ */
+ virtual void Value(nsString& aValue);
+
+ /**
+ * Get help string for the accessible.
+ */
+ void Help(nsString& aHelp) const { aHelp.Truncate(); }
+
+ /**
+ * Get the name of this accessible.
+ *
+ * Note: aName.IsVoid() when name was left empty by the author on purpose.
+ * aName.IsEmpty() when the author missed name, AT can try to repair a name.
+ */
+ virtual ENameValueFlag Name(nsString& aName);
+
+ /**
+ * Maps ARIA state attributes to state of accessible. Note the given state
+ * argument should hold states for accessible before you pass it into this
+ * method.
+ *
+ * @param [in/out] where to fill the states into.
+ */
+ virtual void ApplyARIAState(uint64_t* aState) const;
+
+ /**
+ * Return enumerated accessible role (see constants in Role.h).
+ */
+ mozilla::a11y::role Role();
+
+ /**
+ * Return true if ARIA role is specified on the element.
+ */
+ bool HasARIARole() const;
+ bool IsARIARole(nsIAtom* aARIARole) const;
+ bool HasStrongARIARole() const;
+
+ /**
+ * Retrun ARIA role map if any.
+ */
+ const nsRoleMapEntry* ARIARoleMap() const;
+
+ /**
+ * Return accessible role specified by ARIA (see constants in
+ * roles).
+ */
+ mozilla::a11y::role ARIARole();
+
+ /**
+ * Return a landmark role if applied.
+ */
+ virtual nsIAtom* LandmarkRole() const;
+
+ /**
+ * Returns enumerated accessible role from native markup (see constants in
+ * Role.h). Doesn't take into account ARIA roles.
+ */
+ virtual mozilla::a11y::role NativeRole();
+
+ /**
+ * Return all states of accessible (including ARIA states).
+ */
+ virtual uint64_t State();
+
+ /**
+ * Return interactive states present on the accessible
+ * (@see NativeInteractiveState).
+ */
+ uint64_t InteractiveState() const
+ {
+ uint64_t state = NativeInteractiveState();
+ ApplyARIAState(&state);
+ return state;
+ }
+
+ /**
+ * Return link states present on the accessible.
+ */
+ uint64_t LinkState() const
+ {
+ uint64_t state = NativeLinkState();
+ ApplyARIAState(&state);
+ return state;
+ }
+
+ /**
+ * Return if accessible is unavailable.
+ */
+ bool Unavailable() const
+ {
+ uint64_t state = NativelyUnavailable() ? states::UNAVAILABLE : 0;
+ ApplyARIAState(&state);
+ return state & states::UNAVAILABLE;
+ }
+
+ /**
+ * Return the states of accessible, not taking into account ARIA states.
+ * Use State() to get complete set of states.
+ */
+ virtual uint64_t NativeState();
+
+ /**
+ * Return native interactice state (unavailable, focusable or selectable).
+ */
+ virtual uint64_t NativeInteractiveState() const;
+
+ /**
+ * Return native link states present on the accessible.
+ */
+ virtual uint64_t NativeLinkState() const;
+
+ /**
+ * Return bit set of invisible and offscreen states.
+ */
+ uint64_t VisibilityState();
+
+ /**
+ * Return true if native unavailable state present.
+ */
+ virtual bool NativelyUnavailable() const;
+
+ /**
+ * Return object attributes for the accessible.
+ */
+ virtual already_AddRefed<nsIPersistentProperties> Attributes();
+
+ /**
+ * Return group position (level, position in set and set size).
+ */
+ virtual mozilla::a11y::GroupPos GroupPosition();
+
+ /**
+ * Used by ChildAtPoint() method to get direct or deepest child at point.
+ */
+ enum EWhichChildAtPoint {
+ eDirectChild,
+ eDeepestChild
+ };
+
+ /**
+ * Return direct or deepest child at the given point.
+ *
+ * @param aX [in] x coordinate relative screen
+ * @param aY [in] y coordinate relative screen
+ * @param aWhichChild [in] flag points if deepest or direct child
+ * should be returned
+ */
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild);
+
+ /**
+ * Return the focused child if any.
+ */
+ virtual Accessible* FocusedChild();
+
+ /**
+ * Return calculated group level based on accessible hierarchy.
+ */
+ virtual int32_t GetLevelInternal();
+
+ /**
+ * 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 GetPositionAndSizeInternal(int32_t *aPosInSet,
+ int32_t *aSetSize);
+
+ /**
+ * Get the relation of the given type.
+ */
+ virtual Relation RelationByType(RelationType aType);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Initializing methods
+
+ /**
+ * Shutdown this accessible object.
+ */
+ virtual void Shutdown();
+
+ /**
+ * Set the ARIA role map entry for a new accessible.
+ */
+ void SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry);
+
+ /**
+ * Append/insert/remove a child. Return true if operation was successful.
+ */
+ bool AppendChild(Accessible* aChild)
+ { return InsertChildAt(mChildren.Length(), aChild); }
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild);
+
+ /**
+ * Inserts a child after given sibling. If the child cannot be inserted,
+ * then the child is unbound from the document, and false is returned. Make
+ * sure to null out any references on the child object as it may be destroyed.
+ */
+ bool InsertAfter(Accessible* aNewChild, Accessible* aRefChild);
+
+ virtual bool RemoveChild(Accessible* aChild);
+
+ /**
+ * Reallocates the child withing its parent.
+ */
+ void MoveChild(uint32_t aNewIndex, Accessible* aChild);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Accessible tree traverse methods
+
+ /**
+ * Return parent accessible.
+ */
+ Accessible* Parent() const { return mParent; }
+
+ /**
+ * Return child accessible at the given index.
+ */
+ virtual Accessible* GetChildAt(uint32_t aIndex) const;
+
+ /**
+ * Return child accessible count.
+ */
+ virtual uint32_t ChildCount() const;
+
+ /**
+ * Return index of the given child accessible.
+ */
+ int32_t GetIndexOf(const Accessible* aChild) const
+ { return (aChild->mParent != this) ? -1 : aChild->IndexInParent(); }
+
+ /**
+ * Return index in parent accessible.
+ */
+ virtual int32_t IndexInParent() const;
+
+ /**
+ * Return true if accessible has children;
+ */
+ bool HasChildren() { return !!GetChildAt(0); }
+
+ /**
+ * Return first/last/next/previous sibling of the accessible.
+ */
+ inline Accessible* NextSibling() const
+ { return GetSiblingAtOffset(1); }
+ inline Accessible* PrevSibling() const
+ { return GetSiblingAtOffset(-1); }
+ inline Accessible* FirstChild()
+ { return GetChildAt(0); }
+ inline Accessible* LastChild()
+ {
+ uint32_t childCount = ChildCount();
+ return childCount != 0 ? GetChildAt(childCount - 1) : nullptr;
+ }
+
+ /**
+ * Return embedded accessible children count.
+ */
+ uint32_t EmbeddedChildCount();
+
+ /**
+ * Return embedded accessible child at the given index.
+ */
+ Accessible* GetEmbeddedChildAt(uint32_t aIndex);
+
+ /**
+ * Return index of the given embedded accessible child.
+ */
+ int32_t GetIndexOfEmbeddedChild(Accessible* aChild);
+
+ /**
+ * Return number of content children/content child at index. The content
+ * child is created from markup in contrast to it's never constructed by its
+ * parent accessible (like treeitem accessibles for XUL trees).
+ */
+ uint32_t ContentChildCount() const { return mChildren.Length(); }
+ Accessible* ContentChildAt(uint32_t aIndex) const
+ { return mChildren.ElementAt(aIndex); }
+
+ /**
+ * Return true if the accessible is attached to tree.
+ */
+ bool IsBoundToParent() const { return !!mParent; }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Miscellaneous methods
+
+ /**
+ * Handle accessible event, i.e. process it, notifies observers and fires
+ * platform specific event.
+ */
+ virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
+
+ /**
+ * Return true if the accessible is an acceptable child.
+ */
+ virtual bool IsAcceptableChild(nsIContent* aEl) const
+ { return true; }
+
+ /**
+ * 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 form start offset till the end is appended
+ */
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX);
+
+ /**
+ * Return boundaries in screen coordinates.
+ */
+ virtual nsIntRect Bounds() const;
+
+ /**
+ * Return boundaries rect relative the bounding frame.
+ */
+ virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const;
+
+ /**
+ * Selects the accessible within its container if applicable.
+ */
+ virtual void SetSelected(bool aSelect);
+
+ /**
+ * Select the accessible within its container.
+ */
+ void TakeSelection();
+
+ /**
+ * Focus the accessible.
+ */
+ virtual void TakeFocus();
+
+ /**
+ * Scroll the accessible into view.
+ */
+ void ScrollTo(uint32_t aHow) const;
+
+ /**
+ * Scroll the accessible to the given point.
+ */
+ void ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY);
+
+ /**
+ * Get a pointer to accessibility interface for this node, which is specific
+ * to the OS/accessibility toolkit we're running on.
+ */
+ virtual void GetNativeInterface(void** aNativeAccessible);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Downcasting and types
+
+ inline bool IsAbbreviation() const
+ {
+ return mContent->IsAnyOfHTMLElements(nsGkAtoms::abbr, nsGkAtoms::acronym);
+ }
+
+ bool IsAlert() const { return HasGenericType(eAlert); }
+
+ bool IsApplication() const { return mType == eApplicationType; }
+ ApplicationAccessible* AsApplication();
+
+ bool IsAutoComplete() const { return HasGenericType(eAutoComplete); }
+
+ bool IsAutoCompletePopup() const
+ { return HasGenericType(eAutoCompletePopup); }
+
+ bool IsButton() const { return HasGenericType(eButton); }
+
+ bool IsCombobox() const { return HasGenericType(eCombobox); }
+
+ bool IsDoc() const { return HasGenericType(eDocument); }
+ DocAccessible* AsDoc();
+
+ bool IsGenericHyperText() const { return mType == eHyperTextType; }
+ bool IsHyperText() const { return HasGenericType(eHyperText); }
+ HyperTextAccessible* AsHyperText();
+
+ 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; }
+ HTMLLIAccessible* AsHTMLListItem();
+
+ bool IsHTMLOptGroup() const { return mType == eHTMLOptGroupType; }
+
+ bool IsHTMLTable() const { return mType == eHTMLTableType; }
+ bool IsHTMLTableRow() const { return mType == eHTMLTableRowType; }
+
+ bool IsImage() const { return mType == eImageType; }
+ ImageAccessible* AsImage();
+
+ bool IsImageMap() const { return mType == eImageMapType; }
+ HTMLImageMapAccessible* AsImageMap();
+
+ 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 IsProxy() const { return mType == eProxyType; }
+ ProxyAccessible* Proxy() const
+ {
+ MOZ_ASSERT(IsProxy());
+ return mBits.proxy;
+ }
+ uint32_t ProxyInterfaces() const
+ {
+ MOZ_ASSERT(IsProxy());
+ return mInt.mProxyInterfaces;
+ }
+ void SetProxyInterfaces(uint32_t aInterfaces)
+ {
+ MOZ_ASSERT(IsProxy());
+ mInt.mProxyInterfaces = aInterfaces;
+ }
+
+ bool IsOuterDoc() const { return mType == eOuterDocType; }
+ OuterDocAccessible* AsOuterDoc();
+
+ bool IsProgress() const { return mType == eProgressType; }
+
+ bool IsRoot() const { return mType == eRootType; }
+ a11y::RootAccessible* AsRoot();
+
+ bool IsSearchbox() const;
+
+ bool IsSelect() const { return HasGenericType(eSelect); }
+
+ bool IsTable() const { return HasGenericType(eTable); }
+ virtual TableAccessible* AsTable() { return nullptr; }
+
+ bool IsTableCell() const { return HasGenericType(eTableCell); }
+ virtual TableCellAccessible* AsTableCell() { return nullptr; }
+ const TableCellAccessible* AsTableCell() const
+ { return const_cast<Accessible*>(this)->AsTableCell(); }
+
+ bool IsTableRow() const { return HasGenericType(eTableRow); }
+
+ bool IsTextField() const { return mType == eHTMLTextFieldType; }
+
+ bool IsText() const { return mGenericTypes & eText; }
+
+ bool IsTextLeaf() const { return mType == eTextLeafType; }
+ TextLeafAccessible* AsTextLeaf();
+
+ bool IsXULLabel() const { return mType == eXULLabelType; }
+ XULLabelAccessible* AsXULLabel();
+
+ bool IsXULListItem() const { return mType == eXULListItemType; }
+
+ bool IsXULTabpanels() const { return mType == eXULTabpanelsType; }
+
+ bool IsXULTree() const { return mType == eXULTreeType; }
+ XULTreeAccessible* AsXULTree();
+
+ /**
+ * Return true if the accessible belongs to the given accessible type.
+ */
+ bool HasGenericType(AccGenericType aType) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ActionAccessible
+
+ /**
+ * Return the number of actions that can be performed on this accessible.
+ */
+ virtual uint8_t ActionCount();
+
+ /**
+ * Return action name at given index.
+ */
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName);
+
+ /**
+ * 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);
+
+ /**
+ * Return access key, such as Alt+D.
+ */
+ virtual KeyBinding AccessKey() const;
+
+ /**
+ * Return global keyboard shortcut for default action, such as Ctrl+O for
+ * Open file menuitem.
+ */
+ virtual KeyBinding KeyboardShortcut() const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperLinkAccessible (any embedded object in text can implement HyperLink,
+ // which helps determine where it is located within containing text).
+
+ /**
+ * Return true if the accessible is hyper link accessible.
+ */
+ virtual bool IsLink();
+
+ /**
+ * Return the start offset of the link within the parent accessible.
+ */
+ virtual uint32_t StartOffset();
+
+ /**
+ * Return the end offset of the link within the parent accessible.
+ */
+ virtual uint32_t EndOffset();
+
+ /**
+ * Return true if the link is valid (e. g. points to a valid URL).
+ */
+ inline bool IsLinkValid()
+ {
+ NS_PRECONDITION(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));
+ }
+
+ /**
+ * Return the number of anchors within the link.
+ */
+ virtual uint32_t AnchorCount();
+
+ /**
+ * Returns an anchor accessible at the given index.
+ */
+ virtual Accessible* AnchorAt(uint32_t aAnchorIndex);
+
+ /**
+ * Returns an anchor URI at the given index.
+ */
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex);
+
+ /**
+ * Returns a text point for the accessible element.
+ */
+ void ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+ bool aIsBefore = true) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // SelectAccessible
+
+ /**
+ * Return an array of selected items.
+ */
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems);
+
+ /**
+ * Return the number of selected items.
+ */
+ virtual uint32_t SelectedItemCount();
+
+ /**
+ * Return selected item at the given index.
+ */
+ virtual Accessible* GetSelectedItem(uint32_t aIndex);
+
+ /**
+ * Determine if item at the given index is selected.
+ */
+ virtual bool IsItemSelected(uint32_t aIndex);
+
+ /**
+ * Add item at the given index the selection. Return true if success.
+ */
+ virtual bool AddItemToSelection(uint32_t aIndex);
+
+ /**
+ * Remove item at the given index from the selection. Return if success.
+ */
+ virtual bool RemoveItemFromSelection(uint32_t aIndex);
+
+ /**
+ * Select all items. Return true if success.
+ */
+ virtual bool SelectAll();
+
+ /**
+ * Unselect all items. Return true if success.
+ */
+ virtual bool UnselectAll();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Value (numeric value interface)
+
+ virtual double MaxValue() const;
+ virtual double MinValue() const;
+ virtual double CurValue() const;
+ virtual double Step() const;
+ virtual bool SetCurValue(double aValue);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Widgets
+
+ /**
+ * Return true if accessible is a widget, i.e. control or accessible that
+ * manages its items. Note, being a widget the accessible may be a part of
+ * composite widget.
+ */
+ virtual bool IsWidget() const;
+
+ /**
+ * Return true if the widget is active, i.e. has a focus within it.
+ */
+ virtual bool IsActiveWidget() const;
+
+ /**
+ * Return true if the widget has items and items are operable by user and
+ * can be activated.
+ */
+ virtual bool AreItemsOperable() const;
+
+ /**
+ * Return the current item of the widget, i.e. an item that has or will have
+ * keyboard focus when widget gets active.
+ */
+ virtual Accessible* CurrentItem();
+
+ /**
+ * Set the current item of the widget.
+ */
+ virtual void SetCurrentItem(Accessible* aItem);
+
+ /**
+ * Return container widget this accessible belongs to.
+ */
+ virtual Accessible* ContainerWidget() const;
+
+ /**
+ * Return the localized string for the given key.
+ */
+ static void TranslateString(const nsString& aKey, nsAString& aStringOut);
+
+ /**
+ * Return true if the accessible is defunct.
+ */
+ bool IsDefunct() const { return mStateFlags & eIsDefunct; }
+
+ /**
+ * Return false if the accessible is no longer in the document.
+ */
+ bool IsInDocument() const { return !(mStateFlags & eIsNotInDocument); }
+
+ /**
+ * Return true if the accessible should be contained by document node map.
+ */
+ bool IsNodeMapEntry() const
+ { return HasOwnContent() && !(mStateFlags & eNotNodeMapEntry); }
+
+ /**
+ * Return true if the accessible's group info needs to be updated.
+ */
+ inline bool HasDirtyGroupInfo() const { return mStateFlags & eGroupInfoDirty; }
+
+ /**
+ * Return true if the accessible has associated DOM content.
+ */
+ bool HasOwnContent() const
+ { return mContent && !(mStateFlags & eSharedNode); }
+
+ /**
+ * Return true if the accessible has a numeric value.
+ */
+ bool HasNumericValue() const;
+
+ /**
+ * Return true if the accessible state change is processed by handling proper
+ * DOM UI event, if otherwise then false. For example, HTMLCheckboxAccessible
+ * process nsIDocumentObserver::ContentStateChanged instead
+ * 'CheckboxStateChange' event.
+ */
+ bool NeedsDOMUIEvent() const
+ { return !(mStateFlags & eIgnoreDOMUIEvent); }
+
+ /**
+ * Get/set survivingInUpdate bit on child indicating that parent recollects
+ * its children.
+ */
+ bool IsSurvivingInUpdate() const { return mStateFlags & eSurvivingInUpdate; }
+ void SetSurvivingInUpdate(bool aIsSurviving)
+ {
+ if (aIsSurviving)
+ mStateFlags |= eSurvivingInUpdate;
+ else
+ mStateFlags &= ~eSurvivingInUpdate;
+ }
+
+ /**
+ * Get/set repositioned bit indicating that the accessible was moved in
+ * the accessible tree, i.e. the accessible tree structure differs from DOM.
+ */
+ bool IsRelocated() const { return mStateFlags & eRelocated; }
+ void SetRelocated(bool aRelocated)
+ {
+ if (aRelocated)
+ mStateFlags |= eRelocated;
+ else
+ mStateFlags &= ~eRelocated;
+ }
+
+ /**
+ * Return true if the accessible doesn't allow accessible children from XBL
+ * anonymous subtree.
+ */
+ bool NoXBLKids() const { return mStateFlags & eNoXBLKids; }
+
+ /**
+ * Return true if the accessible allows accessible children from subtree of
+ * a DOM element of this accessible.
+ */
+ bool KidsFromDOM() const { return !(mStateFlags & eNoKidsFromDOM); }
+
+ /**
+ * Return true if this accessible has a parent whose name depends on this
+ * accessible.
+ */
+ bool HasNameDependentParent() const
+ { return mContextFlags & eHasNameDependentParent; }
+
+ /**
+ * Return true if aria-hidden="true" is applied to the accessible or inherited
+ * from the parent.
+ */
+ bool IsARIAHidden() const { return mContextFlags & eARIAHidden; }
+ void SetARIAHidden(bool aIsDefined);
+
+ /**
+ * Return true if the element is inside an alert.
+ */
+ bool IsInsideAlert() const { return mContextFlags & eInsideAlert; }
+
+ /**
+ * Return true if there is a pending reorder event for this accessible.
+ */
+ bool ReorderEventTarget() const { return mReorderEventTarget; }
+
+ /**
+ * Return true if there is a pending show event for this accessible.
+ */
+ bool ShowEventTarget() const { return mShowEventTarget; }
+
+ /**
+ * Return true if there is a pending hide event for this accessible.
+ */
+ bool HideEventTarget() const { return mHideEventTarget; }
+
+ /**
+ * Set if there is a pending reorder event for this accessible.
+ */
+ void SetReorderEventTarget(bool aTarget) { mReorderEventTarget = aTarget; }
+
+ /**
+ * Set if this accessible is a show event target.
+ */
+ void SetShowEventTarget(bool aTarget) { mShowEventTarget = aTarget; }
+
+ /**
+ * Set if this accessible is a hide event target.
+ */
+ void SetHideEventTarget(bool aTarget) { mHideEventTarget = aTarget; }
+
+protected:
+ virtual ~Accessible();
+
+ /**
+ * Return the accessible name provided by native markup. It doesn't take
+ * into account ARIA markup used to specify the name.
+ */
+ virtual mozilla::a11y::ENameValueFlag NativeName(nsString& aName);
+
+ /**
+ * Return the accessible description provided by native markup. It doesn't take
+ * into account ARIA markup used to specify the description.
+ */
+ virtual void NativeDescription(nsString& aDescription);
+
+ /**
+ * Return object attributes provided by native markup. It doesn't take into
+ * account ARIA.
+ */
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Initializing, cache and tree traverse methods
+
+ /**
+ * Destroy the object.
+ */
+ void LastRelease();
+
+ /**
+ * Set accessible parent and index in parent.
+ */
+ void BindToParent(Accessible* aParent, uint32_t aIndexInParent);
+ void UnbindFromParent();
+
+ /**
+ * Return sibling accessible at the given offset.
+ */
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult *aError = nullptr) const;
+
+ /**
+ * Flags used to describe the state of this accessible.
+ */
+ enum StateFlags {
+ eIsDefunct = 1 << 0, // accessible is defunct
+ eIsNotInDocument = 1 << 1, // accessible is not in document
+ eSharedNode = 1 << 2, // accessible shares DOM node from another accessible
+ eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map
+ eHasNumericValue = 1 << 4, // accessible has a numeric value
+ eGroupInfoDirty = 1 << 5, // accessible needs to update group info
+ eKidsMutating = 1 << 6, // subtree is being mutated
+ eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
+ eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
+ eRelocated = 1 << 9, // accessible was moved in tree
+ eNoXBLKids = 1 << 10, // accessible don't allows XBL children
+ eNoKidsFromDOM = 1 << 11, // accessible doesn't allow children from DOM
+ eHasTextKids = 1 << 12, // accessible have a text leaf in children
+
+ eLastStateFlag = eNoKidsFromDOM
+ };
+
+ /**
+ * Flags used for contextual information about the accessible.
+ */
+ enum ContextFlags {
+ eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
+ eARIAHidden = 1 << 1,
+ eInsideAlert = 1 << 2,
+
+ eLastContextFlag = eInsideAlert
+ };
+
+protected:
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Miscellaneous helpers
+
+ /**
+ * Return ARIA role (helper method).
+ */
+ mozilla::a11y::role ARIATransformRole(mozilla::a11y::role aRole);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Name helpers
+
+ /**
+ * Returns the accessible name specified by ARIA.
+ */
+ void ARIAName(nsString& aName);
+
+ /**
+ * Return the name for XUL element.
+ */
+ static void XULElmName(DocAccessible* aDocument,
+ nsIContent* aElm, nsString& aName);
+
+ // helper method to verify frames
+ static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Action helpers
+
+ /**
+ * Prepares click action that will be invoked in timeout.
+ *
+ * @note DoCommand() prepares an action in timeout because when action
+ * command opens a modal dialog/window, it won't return until the
+ * dialog/window is closed. If executing action command directly in
+ * nsIAccessible::DoAction() method, it will block AT tools (e.g. GOK) that
+ * invoke action of mozilla accessibles direclty (see bug 277888 for details).
+ *
+ * @param aContent [in, optional] element to click
+ * @param aActionIndex [in, optional] index of accessible action
+ */
+ void DoCommand(nsIContent *aContent = nullptr, uint32_t aActionIndex = 0);
+
+ /**
+ * Dispatch click event.
+ */
+ virtual void DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ /**
+ * Get the container node for an atomic region, defined by aria-atomic="true"
+ * @return the container node
+ */
+ nsIContent* GetAtomicRegion() const;
+
+ /**
+ * Return numeric value of the given ARIA attribute, NaN if not applicable.
+ *
+ * @param aARIAProperty [in] the ARIA property we're using
+ * @return a numeric value
+ */
+ double AttrNumericValue(nsIAtom* aARIAAttr) const;
+
+ /**
+ * Return the action rule based on ARIA enum constants EActionRule
+ * (see ARIAMap.h). Used by ActionCount() and ActionNameAt().
+ */
+ uint32_t GetActionRule() const;
+
+ /**
+ * Return group info.
+ */
+ AccGroupInfo* GetGroupInfo();
+
+ // Data Members
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<DocAccessible> mDoc;
+
+ Accessible* mParent;
+ nsTArray<Accessible*> mChildren;
+ int32_t mIndexInParent;
+
+ static const uint8_t kStateFlagsBits = 13;
+ static const uint8_t kContextFlagsBits = 3;
+ static const uint8_t kTypeBits = 6;
+ static const uint8_t kGenericTypesBits = 16;
+
+ /**
+ * Non-NO_ROLE_MAP_ENTRY_INDEX indicates author-supplied role;
+ * possibly state & value as well
+ */
+ uint8_t mRoleMapEntryIndex;
+
+ /**
+ * Keep in sync with StateFlags, ContextFlags, and AccTypes.
+ */
+ uint32_t mStateFlags : kStateFlagsBits;
+ uint32_t mContextFlags : kContextFlagsBits;
+ uint32_t mType : kTypeBits;
+ uint32_t mGenericTypes : kGenericTypesBits;
+ uint32_t mReorderEventTarget : 1;
+ uint32_t mShowEventTarget : 1;
+ uint32_t mHideEventTarget : 1;
+
+ void StaticAsserts() const;
+
+#ifdef A11Y_LOG
+ friend void logging::Tree(const char* aTitle, const char* aMsgText,
+ Accessible* aRoot,
+ logging::GetTreePrefix aPrefixFunc,
+ void* aGetTreePrefixData);
+#endif
+ friend class DocAccessible;
+ friend class xpcAccessible;
+ friend class TreeMutation;
+
+ UniquePtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
+ union {
+ int32_t mIndexOfEmbeddedChild;
+ uint32_t mProxyInterfaces;
+ } mInt;
+
+ friend class EmbeddedObjCollector;
+
+ union
+ {
+ AccGroupInfo* groupInfo;
+ ProxyAccessible* proxy;
+ } mBits;
+ friend class AccGroupInfo;
+
+private:
+ Accessible() = delete;
+ Accessible(const Accessible&) = delete;
+ Accessible& operator =(const Accessible&) = delete;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Accessible,
+ NS_ACCESSIBLE_IMPL_IID)
+
+
+/**
+ * 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) {}
+
+ inline bool IsEmpty() const { return !mKey; }
+ inline uint32_t Key() const { return mKey; }
+ inline uint32_t ModifierMask() const { return mModifierMask; }
+
+ 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;
+
+ uint32_t mKey;
+ uint32_t mModifierMask;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/ApplicationAccessible.cpp b/accessible/generic/ApplicationAccessible.cpp
new file mode 100644
index 0000000000..ae8ca27e31
--- /dev/null
+++ b/accessible/generic/ApplicationAccessible.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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 "ApplicationAccessible.h"
+
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIComponentManager.h"
+#include "nsIDOMDocument.h"
+#include "nsIWindowMediator.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIStringBundle.h"
+
+using namespace mozilla::a11y;
+
+ApplicationAccessible::ApplicationAccessible() :
+ AccessibleWrap(nullptr, nullptr)
+{
+ mType = eApplicationType;
+ mAppInfo = do_GetService("@mozilla.org/xre/app-info;1");
+ MOZ_ASSERT(mAppInfo, "no application info");
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ApplicationAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+ENameValueFlag
+ApplicationAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ NS_ASSERTION(bundleService, "String bundle service must be present!");
+ if (!bundleService)
+ return eNameOK;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return eNameOK;
+
+ nsXPIDLString appName;
+ rv = bundle->GetStringFromName(u"brandShortName",
+ getter_Copies(appName));
+ if (NS_FAILED(rv) || appName.IsEmpty()) {
+ NS_WARNING("brandShortName not found, using default app name");
+ appName.AssignLiteral("Gecko based application");
+ }
+
+ aName.Assign(appName);
+ return eNameOK;
+}
+
+void
+ApplicationAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+}
+
+void
+ApplicationAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+}
+
+uint64_t
+ApplicationAccessible::State()
+{
+ return IsDefunct() ? states::DEFUNCT : 0;
+}
+
+already_AddRefed<nsIPersistentProperties>
+ApplicationAccessible::NativeAttributes()
+{
+ return nullptr;
+}
+
+GroupPos
+ApplicationAccessible::GroupPosition()
+{
+ return GroupPos();
+}
+
+Accessible*
+ApplicationAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ return nullptr;
+}
+
+Accessible*
+ApplicationAccessible::FocusedChild()
+{
+ Accessible* focus = FocusMgr()->FocusedAccessible();
+ if (focus && focus->Parent() == this)
+ return focus;
+
+ return nullptr;
+}
+
+Relation
+ApplicationAccessible::RelationByType(RelationType aRelationType)
+{
+ return Relation();
+}
+
+nsIntRect
+ApplicationAccessible::Bounds() const
+{
+ return nsIntRect();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public methods
+
+void
+ApplicationAccessible::Shutdown()
+{
+ mAppInfo = nullptr;
+}
+
+void
+ApplicationAccessible::ApplyARIAState(uint64_t* aState) const
+{
+}
+
+role
+ApplicationAccessible::NativeRole()
+{
+ return roles::APP_ROOT;
+}
+
+uint64_t
+ApplicationAccessible::NativeState()
+{
+ return 0;
+}
+
+KeyBinding
+ApplicationAccessible::AccessKey() const
+{
+ return KeyBinding();
+}
+
+void
+ApplicationAccessible::Init()
+{
+ // Basically children are kept updated by Append/RemoveChild method calls.
+ // However if there are open windows before accessibility was started
+ // then we need to make sure root accessibles for open windows are created so
+ // that all root accessibles are stored in application accessible children
+ // array.
+
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ nsresult rv = windowMediator->GetEnumerator(nullptr,
+ getter_AddRefs(windowEnumerator));
+ if (NS_FAILED(rv))
+ return;
+
+ bool hasMore = false;
+ windowEnumerator->HasMoreElements(&hasMore);
+ while (hasMore) {
+ nsCOMPtr<nsISupports> window;
+ windowEnumerator->GetNext(getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> DOMWindow = do_QueryInterface(window);
+ if (DOMWindow) {
+ nsCOMPtr<nsIDocument> docNode = DOMWindow->GetDoc();
+ if (docNode) {
+ GetAccService()->GetDocAccessible(docNode); // ensure creation
+ }
+ }
+ windowEnumerator->HasMoreElements(&hasMore);
+ }
+}
+
+Accessible*
+ApplicationAccessible::GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError) const
+{
+ if (aError)
+ *aError = NS_OK; // fail peacefully
+
+ return nullptr;
+}
diff --git a/accessible/generic/ApplicationAccessible.h b/accessible/generic/ApplicationAccessible.h
new file mode 100644
index 0000000000..7609a86e25
--- /dev/null
+++ b/accessible/generic/ApplicationAccessible.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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_ApplicationAccessible_h__
+#define mozilla_a11y_ApplicationAccessible_h__
+
+#include "AccessibleWrap.h"
+
+#include "nsIMutableArray.h"
+#include "nsIXULAppInfo.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * ApplicationAccessible is for the whole application of Mozilla.
+ * Only one instance of ApplicationAccessible exists for one Mozilla instance.
+ * And this one should be created when Mozilla Startup (if accessibility
+ * feature has been enabled) and destroyed when Mozilla Shutdown.
+ *
+ * All the accessibility objects for toplevel windows are direct children of
+ * the ApplicationAccessible instance.
+ */
+
+class ApplicationAccessible : public AccessibleWrap
+{
+public:
+
+ ApplicationAccessible();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual nsIntRect Bounds() const override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual GroupPos GroupPosition() override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual void Description(nsString& aDescription) override;
+ virtual void Value(nsString& aValue) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t State() override;
+ virtual uint64_t NativeState() override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+ virtual Accessible* FocusedChild() override;
+
+ // ActionAccessible
+ virtual KeyBinding AccessKey() const override;
+
+ // ApplicationAccessible
+ void Init();
+
+ void AppName(nsAString& aName) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cname;
+ mAppInfo->GetName(cname);
+ AppendUTF8toUTF16(cname, aName);
+ }
+ }
+
+ void AppVersion(nsAString& aVersion) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cversion;
+ mAppInfo->GetVersion(cversion);
+ AppendUTF8toUTF16(cversion, aVersion);
+ }
+ }
+
+ void PlatformName(nsAString& aName) const
+ {
+ aName.AssignLiteral("Gecko");
+ }
+
+ void PlatformVersion(nsAString& aVersion) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cversion;
+ mAppInfo->GetPlatformVersion(cversion);
+ AppendUTF8toUTF16(cversion, aVersion);
+ }
+ }
+
+protected:
+ virtual ~ApplicationAccessible() {}
+
+ // Accessible
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult *aError = nullptr) const override;
+
+private:
+ nsCOMPtr<nsIXULAppInfo> mAppInfo;
+};
+
+inline ApplicationAccessible*
+Accessible::AsApplication()
+{
+ return IsApplication() ? static_cast<ApplicationAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/BaseAccessibles.cpp b/accessible/generic/BaseAccessibles.cpp
new file mode 100644
index 0000000000..bcb262a97d
--- /dev/null
+++ b/accessible/generic/BaseAccessibles.cpp
@@ -0,0 +1,264 @@
+/* -*- 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 "BaseAccessibles.h"
+
+#include "Accessible-inl.h"
+#include "HyperTextAccessibleWrap.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "Role.h"
+#include "States.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// LeafAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+LeafAccessible::
+ LeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mStateFlags |= eNoKidsFromDOM;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(LeafAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// LeafAccessible: Accessible public
+
+Accessible*
+LeafAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ // Don't walk into leaf accessibles.
+ return this;
+}
+
+bool
+LeafAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ NS_NOTREACHED("InsertChildAt called on leaf accessible!");
+ return false;
+}
+
+bool
+LeafAccessible::RemoveChild(Accessible* aChild)
+{
+ NS_NOTREACHED("RemoveChild called on leaf accessible!");
+ return false;
+}
+
+bool
+LeafAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ // No children for leaf accessible.
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(LinkableAccessible, AccessibleWrap)
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible. nsIAccessible
+
+void
+LinkableAccessible::TakeFocus()
+{
+ if (Accessible* actionAcc = ActionWalk()) {
+ actionAcc->TakeFocus();
+ } else {
+ AccessibleWrap::TakeFocus();
+ }
+}
+
+uint64_t
+LinkableAccessible::NativeLinkState() const
+{
+ bool isLink;
+ Accessible* actionAcc =
+ const_cast<LinkableAccessible*>(this)->ActionWalk(&isLink);
+ if (isLink) {
+ return states::LINKED | (actionAcc->LinkState() & states::TRAVERSED);
+ }
+
+ return 0;
+}
+
+void
+LinkableAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ Accessible::Value(aValue);
+ if (!aValue.IsEmpty()) {
+ return;
+ }
+
+ bool isLink;
+ Accessible* actionAcc = ActionWalk(&isLink);
+ if (isLink) {
+ actionAcc->Value(aValue);
+ }
+}
+
+uint8_t
+LinkableAccessible::ActionCount()
+{
+ bool isLink, isOnclick, isLabelWithControl;
+ ActionWalk(&isLink, &isOnclick, &isLabelWithControl);
+ return (isLink || isOnclick || isLabelWithControl) ? 1 : 0;
+}
+
+Accessible*
+LinkableAccessible::ActionWalk(bool* aIsLink, bool* aIsOnclick,
+ bool* aIsLabelWithControl)
+{
+ if (aIsOnclick) {
+ *aIsOnclick = false;
+ }
+ if (aIsLink) {
+ *aIsLink = false;
+ }
+ if (aIsLabelWithControl) {
+ *aIsLabelWithControl = false;
+ }
+
+ if (nsCoreUtils::HasClickListener(mContent)) {
+ if (aIsOnclick) {
+ *aIsOnclick = true;
+ }
+ return nullptr;
+ }
+
+ // XXX: The logic looks broken since the click listener may be registered
+ // on non accessible node in parent chain but this node is skipped when tree
+ // is traversed.
+ Accessible* walkUpAcc = this;
+ while ((walkUpAcc = walkUpAcc->Parent()) && !walkUpAcc->IsDoc()) {
+ if (walkUpAcc->LinkState() & states::LINKED) {
+ if (aIsLink) {
+ *aIsLink = true;
+ }
+ return walkUpAcc;
+ }
+
+ if (nsCoreUtils::HasClickListener(walkUpAcc->GetContent())) {
+ if (aIsOnclick) {
+ *aIsOnclick = true;
+ }
+ return walkUpAcc;
+ }
+
+ if (nsCoreUtils::IsLabelWithControl(walkUpAcc->GetContent())) {
+ if (aIsLabelWithControl) {
+ *aIsLabelWithControl = true;
+ }
+ return walkUpAcc;
+ }
+ }
+ return nullptr;
+}
+
+void
+LinkableAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ // Action 0 (default action): Jump to link
+ if (aIndex == eAction_Jump) {
+ bool isOnclick, isLink, isLabelWithControl;
+ ActionWalk(&isLink, &isOnclick, &isLabelWithControl);
+ if (isLink) {
+ aName.AssignLiteral("jump");
+ } else if (isOnclick || isLabelWithControl) {
+ aName.AssignLiteral("click");
+ }
+ }
+}
+
+bool
+LinkableAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Jump) {
+ return false;
+ }
+
+ if (Accessible* actionAcc = ActionWalk()) {
+ return actionAcc->DoAction(aIndex);
+ }
+
+ return AccessibleWrap::DoAction(aIndex);
+}
+
+KeyBinding
+LinkableAccessible::AccessKey() const
+{
+ if (const Accessible* actionAcc =
+ const_cast<LinkableAccessible*>(this)->ActionWalk()) {
+ return actionAcc->AccessKey();
+ }
+
+ return Accessible::AccessKey();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible: HyperLinkAccessible
+
+already_AddRefed<nsIURI>
+LinkableAccessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ bool isLink;
+ Accessible* actionAcc = ActionWalk(&isLink);
+ if (isLink) {
+ NS_ASSERTION(actionAcc->IsLink(), "HyperLink isn't implemented.");
+
+ if (actionAcc->IsLink()) {
+ return actionAcc->AnchorURIAt(aAnchorIndex);
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DummyAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+uint64_t
+DummyAccessible::NativeState()
+{
+ return 0;
+}
+uint64_t
+DummyAccessible::NativeInteractiveState() const
+{
+ return 0;
+}
+
+uint64_t
+DummyAccessible::NativeLinkState() const
+{
+ return 0;
+}
+
+bool
+DummyAccessible::NativelyUnavailable() const
+{
+ return false;
+}
+
+void
+DummyAccessible::ApplyARIAState(uint64_t* aState) const
+{
+}
diff --git a/accessible/generic/BaseAccessibles.h b/accessible/generic/BaseAccessibles.h
new file mode 100644
index 0000000000..e4c71c423c
--- /dev/null
+++ b/accessible/generic/BaseAccessibles.h
@@ -0,0 +1,132 @@
+/* -*- 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_BaseAccessibles_h__
+#define mozilla_a11y_BaseAccessibles_h__
+
+#include "AccessibleWrap.h"
+#include "HyperTextAccessibleWrap.h"
+
+class nsIContent;
+
+/**
+ * This file contains a number of classes that are used as base
+ * classes for the different accessibility implementations of
+ * the HTML and XUL widget sets. --jgaunt
+ */
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Leaf version of DOM Accessible -- has no children
+ */
+class LeafAccessible : public AccessibleWrap
+{
+public:
+
+ LeafAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override final;
+ virtual bool RemoveChild(Accessible* aChild) override final;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+
+protected:
+ virtual ~LeafAccessible() {}
+};
+
+/**
+ * Used for text or image accessible nodes contained by link accessibles or
+ * accessibles for nodes with registered click event handler. It knows how to
+ * report the state of the host link (traveled or not) and can activate (click)
+ * the host accessible programmatically.
+ */
+class LinkableAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Jump = 0 };
+
+ LinkableAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual uint64_t NativeLinkState() const override;
+ virtual void TakeFocus() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t index) override;
+ virtual KeyBinding AccessKey() const override;
+
+ // ActionAccessible helpers
+ Accessible* ActionWalk(bool* aIsLink = nullptr,
+ bool* aIsOnclick = nullptr,
+ bool* aIsLabelWithControl = nullptr);
+ // HyperLinkAccessible
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
+
+protected:
+ virtual ~LinkableAccessible() {}
+
+};
+
+/**
+ * A simple accessible that gets its enumerated role.
+ */
+template<a11y::role R>
+class EnumRoleAccessible : public AccessibleWrap
+{
+public:
+ EnumRoleAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc) { }
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aPtr) override
+ { return Accessible::QueryInterface(aIID, aPtr); }
+
+ // Accessible
+ virtual a11y::role NativeRole() override { return R; }
+
+protected:
+ virtual ~EnumRoleAccessible() { }
+};
+
+
+/**
+ * A wrapper accessible around native accessible to connect it with
+ * crossplatform accessible tree.
+ */
+class DummyAccessible : public AccessibleWrap
+{
+public:
+ explicit DummyAccessible(DocAccessible* aDocument = nullptr) :
+ AccessibleWrap(nullptr, aDocument) { }
+
+ virtual uint64_t NativeState() override final;
+ virtual uint64_t NativeInteractiveState() const override final;
+ virtual uint64_t NativeLinkState() const override final;
+ virtual bool NativelyUnavailable() const override final;
+ virtual void ApplyARIAState(uint64_t* aState) const override final;
+
+protected:
+ virtual ~DummyAccessible() { }
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/DocAccessible-inl.h b/accessible/generic/DocAccessible-inl.h
new file mode 100644
index 0000000000..af660ff47f
--- /dev/null
+++ b/accessible/generic/DocAccessible-inl.h
@@ -0,0 +1,189 @@
+/* -*- 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_DocAccessible_inl_h_
+#define mozilla_a11y_DocAccessible_inl_h_
+
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsAccessiblePivot.h"
+#include "NotificationController.h"
+#include "States.h"
+#include "nsIScrollableFrame.h"
+#include "nsIDocumentInlines.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+inline Accessible*
+DocAccessible::AccessibleOrTrueContainer(nsINode* aNode) const
+{
+ // HTML comboboxes have no-content list accessible as an intermediate
+ // containing all options.
+ Accessible* container = GetAccessibleOrContainer(aNode);
+ if (container && container->IsHTMLCombobox()) {
+ return container->FirstChild();
+ }
+ return container;
+}
+
+inline nsIAccessiblePivot*
+DocAccessible::VirtualCursor()
+{
+ if (!mVirtualCursor) {
+ mVirtualCursor = new nsAccessiblePivot(this);
+ mVirtualCursor->AddObserver(this);
+ }
+ return mVirtualCursor;
+}
+
+inline void
+DocAccessible::FireDelayedEvent(AccEvent* aEvent)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoadEventFired(aEvent);
+#endif
+
+ mNotificationController->QueueEvent(aEvent);
+}
+
+inline void
+DocAccessible::FireDelayedEvent(uint32_t aEventType, Accessible* aTarget)
+{
+ RefPtr<AccEvent> event = new AccEvent(aEventType, aTarget);
+ FireDelayedEvent(event);
+}
+
+inline void
+DocAccessible::BindChildDocument(DocAccessible* aDocument)
+{
+ mNotificationController->ScheduleChildDocBinding(aDocument);
+}
+
+template<class Class, class Arg>
+inline void
+DocAccessible::HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg)
+{
+ if (mNotificationController) {
+ mNotificationController->HandleNotification<Class, Arg>(aInstance,
+ aMethod, aArg);
+ }
+}
+
+inline void
+DocAccessible::UpdateText(nsIContent* aTextNode)
+{
+ NS_ASSERTION(mNotificationController, "The document was shut down!");
+
+ // Ignore the notification if initial tree construction hasn't been done yet.
+ if (mNotificationController && HasLoadState(eTreeConstructed))
+ mNotificationController->ScheduleTextUpdate(aTextNode);
+}
+
+inline void
+DocAccessible::AddScrollListener()
+{
+ // Delay scroll initializing until the document has a root frame.
+ if (!mPresShell->GetRootFrame())
+ return;
+
+ mDocFlags |= eScrollInitialized;
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ sf->AddScrollPositionListener(this);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("add scroll listener");
+#endif
+ }
+}
+
+inline void
+DocAccessible::RemoveScrollListener()
+{
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (sf)
+ sf->RemoveScrollPositionListener(this);
+}
+
+inline void
+DocAccessible::NotifyOfLoad(uint32_t aLoadEventType)
+{
+ mLoadState |= eDOMLoaded;
+ mLoadEventType = aLoadEventType;
+
+ // If the document is loaded completely then network activity was presumingly
+ // caused by file loading. Fire busy state change event.
+ if (HasLoadState(eCompletelyLoaded) && IsLoadEventTarget()) {
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, false);
+ FireDelayedEvent(stateEvent);
+ }
+}
+
+inline void
+DocAccessible::MaybeNotifyOfValueChange(Accessible* aAccessible)
+{
+ a11y::role role = aAccessible->Role();
+ if (role == roles::ENTRY || role == roles::COMBOBOX)
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
+}
+
+inline Accessible*
+DocAccessible::GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const
+{
+ Accessible* acc = GetAccessibleEvenIfNotInMap(aNode);
+ return acc ? acc : GetContainerAccessible(aNode);
+}
+
+inline void
+DocAccessible::CreateSubtree(Accessible* aChild)
+{
+ // If a focused node has been shown then it could mean its frame was recreated
+ // while the node stays focused and we need to fire focus event on
+ // the accessible we just created. If the queue contains a focus event for
+ // this node already then it will be suppressed by this one.
+ Accessible* focusedAcc = nullptr;
+ CacheChildrenInSubtree(aChild, &focusedAcc);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Created subtree", aChild);
+ }
+#endif
+
+ // Fire events for ARIA elements.
+ if (aChild->HasARIARole()) {
+ roles::Role role = aChild->ARIARole();
+ if (role == roles::MENUPOPUP) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
+ }
+ else if (role == roles::ALERT) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
+ }
+ }
+
+ // XXX: do we really want to send focus to focused DOM node not taking into
+ // account active item?
+ if (focusedAcc) {
+ FocusMgr()->DispatchFocusEvent(this, focusedAcc);
+ SelectionMgr()->
+ SetControlSelectionListener(focusedAcc->GetNode()->AsElement());
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp
new file mode 100644
index 0000000000..c89aa189b6
--- /dev/null
+++ b/accessible/generic/DocAccessible.cpp
@@ -0,0 +1,2387 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "Accessible-inl.h"
+#include "AccIterator.h"
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "HTMLImageMapAccessible.h"
+#include "nsAccCache.h"
+#include "nsAccessiblePivot.h"
+#include "nsAccUtils.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "TreeWalker.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+#include "nsICommandManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMCharacterData.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMXULPopupElement.h"
+#include "nsIEditingSession.h"
+#include "nsIFrame.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsImageFrame.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsUnicharUtils.h"
+#include "nsIURI.h"
+#include "nsIWebNavigation.h"
+#include "nsFocusManager.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/Element.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Static member initialization
+
+static nsIAtom** kRelationAttrs[] =
+{
+ &nsGkAtoms::aria_labelledby,
+ &nsGkAtoms::aria_describedby,
+ &nsGkAtoms::aria_details,
+ &nsGkAtoms::aria_owns,
+ &nsGkAtoms::aria_controls,
+ &nsGkAtoms::aria_flowto,
+ &nsGkAtoms::aria_errormessage,
+ &nsGkAtoms::_for,
+ &nsGkAtoms::control
+};
+
+static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/desctructor
+
+DocAccessible::
+ DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ // XXX don't pass a document to the Accessible constructor so that we don't
+ // set mDoc until our vtable is fully setup. If we set mDoc before setting
+ // up the vtable we will call Accessible::AddRef() but not the overrides of
+ // it for subclasses. It is important to call those overrides to avoid
+ // confusing leak checking machinary.
+ HyperTextAccessibleWrap(nullptr, nullptr),
+ // XXX aaronl should we use an algorithm for the initial cache size?
+ mAccessibleCache(kDefaultCacheLength),
+ mNodeToAccessibleMap(kDefaultCacheLength),
+ mDocumentNode(aDocument),
+ mScrollPositionChangedTicks(0),
+ mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
+ mVirtualCursor(nullptr),
+ mPresShell(aPresShell), mIPCDoc(nullptr)
+{
+ mGenericTypes |= eDocument;
+ mStateFlags |= eNotNodeMapEntry;
+ mDoc = this;
+
+ MOZ_ASSERT(mPresShell, "should have been given a pres shell");
+ mPresShell->SetDocAccessible(this);
+
+ // If this is a XUL Document, it should not implement nsHyperText
+ if (mDocumentNode && mDocumentNode->IsXULDocument())
+ mGenericTypes &= ~eHyperText;
+}
+
+DocAccessible::~DocAccessible()
+{
+ NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
+ for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
+ AttrRelProviderArray* providers = iter.UserData();
+
+ for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "content of dependent ids hash entry of document accessible");
+
+ AttrRelProvider* provider = (*providers)[jdx];
+ cb.NoteXPCOMChild(provider->mContent);
+
+ NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
+ "Referred content is not in document!");
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
+ for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
+ nsTArray<RefPtr<Accessible> >* ar = it.UserData();
+ for (uint32_t i = 0; i < ar->Length(); i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mARIAOwnsHash entry item");
+ cb.NoteXPCOMChild(ar->ElementAt(i));
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
+ tmp->mDependentIDsHash.Clear();
+ tmp->mNodeToAccessibleMap.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
+ tmp->mARIAOwnsHash.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
+NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
+
+NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
+NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+ENameValueFlag
+DocAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (mParent) {
+ mParent->Name(aName); // Allow owning iframe to override the name
+ }
+ if (aName.IsEmpty()) {
+ // Allow name via aria-labelledby or title attribute
+ Accessible::Name(aName);
+ }
+ if (aName.IsEmpty()) {
+ Title(aName); // Try title element
+ }
+ if (aName.IsEmpty()) { // Last resort: use URL
+ URL(aName);
+ }
+
+ return eNameOK;
+}
+
+// Accessible public method
+role
+DocAccessible::NativeRole()
+{
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
+ if (docShell) {
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ int32_t itemType = docShell->ItemType();
+ if (sameTypeRoot == docShell) {
+ // Root of content or chrome tree
+ if (itemType == nsIDocShellTreeItem::typeChrome)
+ return roles::CHROME_WINDOW;
+
+ if (itemType == nsIDocShellTreeItem::typeContent) {
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
+ if (xulDoc)
+ return roles::APPLICATION;
+#endif
+ return roles::DOCUMENT;
+ }
+ }
+ else if (itemType == nsIDocShellTreeItem::typeContent) {
+ return roles::DOCUMENT;
+ }
+ }
+
+ return roles::PANE; // Fall back;
+}
+
+void
+DocAccessible::Description(nsString& aDescription)
+{
+ if (mParent)
+ mParent->Description(aDescription);
+
+ if (HasOwnContent() && aDescription.IsEmpty()) {
+ nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
+ aDescription);
+ }
+}
+
+// Accessible public method
+uint64_t
+DocAccessible::NativeState()
+{
+ // Document is always focusable.
+ uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
+ if (FocusMgr()->IsFocused(this))
+ state |= states::FOCUSED;
+
+ // Expose stale state until the document is ready (DOM is loaded and tree is
+ // constructed).
+ if (!HasLoadState(eReady))
+ state |= states::STALE;
+
+ // Expose state busy until the document and all its subdocuments is completely
+ // loaded.
+ if (!HasLoadState(eCompletelyLoaded))
+ state |= states::BUSY;
+
+ nsIFrame* frame = GetFrame();
+ if (!frame ||
+ !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+ state |= states::INVISIBLE | states::OFFSCREEN;
+ }
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ state |= editor ? states::EDITABLE : states::READONLY;
+
+ return state;
+}
+
+uint64_t
+DocAccessible::NativeInteractiveState() const
+{
+ // Document is always focusable.
+ return states::FOCUSABLE;
+}
+
+bool
+DocAccessible::NativelyUnavailable() const
+{
+ return false;
+}
+
+// Accessible public method
+void
+DocAccessible::ApplyARIAState(uint64_t* aState) const
+{
+ // Grab states from content element.
+ if (mContent)
+ Accessible::ApplyARIAState(aState);
+
+ // Allow iframe/frame etc. to have final state override via ARIA.
+ if (mParent)
+ mParent->ApplyARIAState(aState);
+}
+
+already_AddRefed<nsIPersistentProperties>
+DocAccessible::Attributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::Attributes();
+
+ // No attributes if document is not attached to the tree or if it's a root
+ // document.
+ if (!mParent || IsRoot())
+ return attributes.forget();
+
+ // Override ARIA object attributes from outerdoc.
+ aria::AttrIterator attribIter(mParent->GetContent());
+ nsAutoString name, value, unused;
+ while(attribIter.Next(name, value))
+ attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
+
+ return attributes.forget();
+}
+
+Accessible*
+DocAccessible::FocusedChild()
+{
+ // Return an accessible for the current global focus, which does not have to
+ // be contained within the current document.
+ return FocusMgr()->FocusedAccessible();
+}
+
+void
+DocAccessible::TakeFocus()
+{
+ // Focus the document.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ nsCOMPtr<nsIDOMElement> newFocus;
+ fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
+ nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
+}
+
+// HyperTextAccessible method
+already_AddRefed<nsIEditor>
+DocAccessible::GetEditor() const
+{
+ // Check if document is editable (designMode="on" case). Otherwise check if
+ // the html:body (for HTML document case) or document element is editable.
+ if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
+ (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
+ return nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIEditingSession> editingSession;
+ docShell->GetEditingSession(getter_AddRefs(editingSession));
+ if (!editingSession)
+ return nullptr; // No editing session interface
+
+ nsCOMPtr<nsIEditor> editor;
+ editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
+ if (!editor)
+ return nullptr;
+
+ bool isEditable = false;
+ editor->GetIsDocumentEditable(&isEditable);
+ if (isEditable)
+ return editor.forget();
+
+ return nullptr;
+}
+
+// DocAccessible public method
+
+void
+DocAccessible::URL(nsAString& aURL) const
+{
+ nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
+ nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
+ nsAutoCString theURL;
+ if (webNav) {
+ nsCOMPtr<nsIURI> pURI;
+ webNav->GetCurrentURI(getter_AddRefs(pURI));
+ if (pURI)
+ pURI->GetSpec(theURL);
+ }
+ CopyUTF8toUTF16(theURL, aURL);
+}
+
+void
+DocAccessible::DocType(nsAString& aType) const
+{
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
+ if (xulDoc) {
+ aType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
+ return;
+ }
+#endif
+ dom::DocumentType* docType = mDocumentNode->GetDoctype();
+ if (docType)
+ docType->GetPublicId(aType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+DocAccessible::Init()
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::DocCreate("document initialize", mDocumentNode, this);
+#endif
+
+ // Initialize notification controller.
+ mNotificationController = new NotificationController(this, mPresShell);
+
+ // Mark the document accessible as loaded if its DOM document was loaded at
+ // this point (this can happen because a11y is started late or DOM document
+ // having no container was loaded.
+ if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
+ mLoadState |= eDOMLoaded;
+
+ AddEventListeners();
+}
+
+void
+DocAccessible::Shutdown()
+{
+ if (!mPresShell) // already shutdown
+ return;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::DocDestroy("document shutdown", mDocumentNode, this);
+#endif
+
+ // Mark the document as shutdown before AT is notified about the document
+ // removal from its container (valid for root documents on ATK and due to
+ // some reason for MSAA, refer to bug 757392 for details).
+ mStateFlags |= eIsDefunct;
+
+ if (mNotificationController) {
+ mNotificationController->Shutdown();
+ mNotificationController = nullptr;
+ }
+
+ RemoveEventListeners();
+
+ nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
+ mDocumentNode = nullptr;
+
+ if (mParent) {
+ DocAccessible* parentDocument = mParent->Document();
+ if (parentDocument)
+ parentDocument->RemoveChildDocument(this);
+
+ mParent->RemoveChild(this);
+ }
+
+ // Walk the array backwards because child documents remove themselves from the
+ // array as they are shutdown.
+ int32_t childDocCount = mChildDocuments.Length();
+ for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
+ mChildDocuments[idx]->Shutdown();
+
+ mChildDocuments.Clear();
+
+ // XXX thinking about ordering?
+ if (mIPCDoc) {
+ MOZ_ASSERT(IPCAccessibilityActive());
+ mIPCDoc->Shutdown();
+ MOZ_ASSERT(!mIPCDoc);
+ }
+
+ if (mVirtualCursor) {
+ mVirtualCursor->RemoveObserver(this);
+ mVirtualCursor = nullptr;
+ }
+
+ mPresShell->SetDocAccessible(nullptr);
+ mPresShell = nullptr; // Avoid reentrancy
+
+ mDependentIDsHash.Clear();
+ mNodeToAccessibleMap.Clear();
+
+ for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
+ Accessible* accessible = iter.Data();
+ MOZ_ASSERT(accessible);
+ if (accessible && !accessible->IsDefunct()) {
+ // Unlink parent to avoid its cleaning overhead in shutdown.
+ accessible->mParent = nullptr;
+ accessible->Shutdown();
+ }
+ iter.Remove();
+ }
+
+ HyperTextAccessibleWrap::Shutdown();
+
+ GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
+}
+
+nsIFrame*
+DocAccessible::GetFrame() const
+{
+ nsIFrame* root = nullptr;
+ if (mPresShell)
+ root = mPresShell->GetRootFrame();
+
+ return root;
+}
+
+// DocAccessible protected member
+nsRect
+DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const
+{
+ *aRelativeFrame = GetFrame();
+
+ nsIDocument *document = mDocumentNode;
+ nsIDocument *parentDoc = nullptr;
+
+ nsRect bounds;
+ while (document) {
+ nsIPresShell *presShell = document->GetShell();
+ if (!presShell)
+ return nsRect();
+
+ nsRect scrollPort;
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
+ if (sf) {
+ scrollPort = sf->GetScrollPortRect();
+ } else {
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame)
+ return nsRect();
+
+ scrollPort = rootFrame->GetRect();
+ }
+
+ if (parentDoc) { // After first time thru loop
+ // XXXroc bogus code! scrollPort is relative to the viewport of
+ // this document, but we're intersecting rectangles derived from
+ // multiple documents and assuming they're all in the same coordinate
+ // system. See bug 514117.
+ bounds.IntersectRect(scrollPort, bounds);
+ }
+ else { // First time through loop
+ bounds = scrollPort;
+ }
+
+ document = parentDoc = document->GetParentDocument();
+ }
+
+ return bounds;
+}
+
+// DocAccessible protected member
+nsresult
+DocAccessible::AddEventListeners()
+{
+ nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
+
+ // We want to add a command observer only if the document is content and has
+ // an editor.
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
+ if (commandManager)
+ commandManager->AddCommandObserver(this, "obs_documentCreated");
+ }
+
+ SelectionMgr()->AddDocSelectionListener(mPresShell);
+
+ // Add document observer.
+ mDocumentNode->AddObserver(this);
+ return NS_OK;
+}
+
+// DocAccessible protected member
+nsresult
+DocAccessible::RemoveEventListeners()
+{
+ // Remove listeners associated with content documents
+ // Remove scroll position listener
+ RemoveScrollListener();
+
+ NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
+
+ if (mDocumentNode) {
+ mDocumentNode->RemoveObserver(this);
+
+ nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
+ NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
+
+ if (docShell) {
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
+ if (commandManager) {
+ commandManager->RemoveCommandObserver(this, "obs_documentCreated");
+ }
+ }
+ }
+ }
+
+ if (mScrollWatchTimer) {
+ mScrollWatchTimer->Cancel();
+ mScrollWatchTimer = nullptr;
+ NS_RELEASE_THIS(); // Kung fu death grip
+ }
+
+ SelectionMgr()->RemoveDocSelectionListener(mPresShell);
+ return NS_OK;
+}
+
+void
+DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
+
+ if (docAcc && docAcc->mScrollPositionChangedTicks &&
+ ++docAcc->mScrollPositionChangedTicks > 2) {
+ // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
+ // We only want to fire accessibilty scroll event when scrolling stops or pauses
+ // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
+ // That indicates a pause in scrolling, so we fire the accessibilty scroll event
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
+
+ docAcc->mScrollPositionChangedTicks = 0;
+ if (docAcc->mScrollWatchTimer) {
+ docAcc->mScrollWatchTimer->Cancel();
+ docAcc->mScrollWatchTimer = nullptr;
+ NS_RELEASE(docAcc); // Release kung fu death grip
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIScrollPositionListener
+
+void
+DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
+{
+ // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
+ // then the ::Notify() method will fire the accessibility event for scroll position changes
+ const uint32_t kScrollPosCheckWait = 50;
+ if (mScrollWatchTimer) {
+ mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
+ }
+ else {
+ mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mScrollWatchTimer) {
+ NS_ADDREF_THIS(); // Kung fu death grip
+ mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
+ kScrollPosCheckWait,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }
+ mScrollPositionChangedTicks = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIObserver
+
+NS_IMETHODIMP
+DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
+ // State editable will now be set, readonly is now clear
+ // Normally we only fire delayed events created from the node, not an
+ // accessible object. See the AccStateChangeEvent constructor for details
+ // about this exceptional case.
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(this, states::EDITABLE, true);
+ FireDelayedEvent(event);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessiblePivotObserver
+
+NS_IMETHODIMP
+DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
+ nsIAccessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ PivotMoveReason aReason,
+ bool aIsFromUserInput)
+{
+ RefPtr<AccEvent> event =
+ new AccVCChangeEvent(
+ this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
+ aOldStart, aOldEnd, aReason,
+ aIsFromUserInput ? eFromUserInput : eNoUserInput);
+ nsEventShell::FireEvent(event);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDocumentObserver
+
+NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
+NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
+NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
+
+void
+DocAccessible::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ Accessible* accessible = GetAccessible(aElement);
+ if (!accessible) {
+ if (aElement != mContent)
+ return;
+
+ accessible = this;
+ }
+
+ // Update dependent IDs cache. Take care of elements that are accessible
+ // because dependent IDs cache doesn't contain IDs from non accessible
+ // elements.
+ if (aModType != nsIDOMMutationEvent::ADDITION)
+ RemoveDependentIDsFor(accessible, aAttribute);
+
+ if (aAttribute == nsGkAtoms::id) {
+ RelocateARIAOwnedIfNeeded(aElement);
+ }
+
+ // Store the ARIA attribute old value so that it can be used after
+ // attribute change. Note, we assume there's no nested ARIA attribute
+ // changes. If this happens then we should end up with keeping a stack of
+ // old values.
+
+ // XXX TODO: bugs 472142, 472143.
+ // Here we will want to cache whatever attribute values we are interested
+ // in, such as the existence of aria-pressed for button (so we know if we
+ // need to newly expose it as a toggle button) etc.
+ if (aAttribute == nsGkAtoms::aria_checked ||
+ aAttribute == nsGkAtoms::aria_pressed) {
+ mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
+ nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_disabled ||
+ aAttribute == nsGkAtoms::disabled)
+ mStateBitWasOn = accessible->Unavailable();
+}
+
+void
+DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void
+DocAccessible::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ NS_ASSERTION(!IsDefunct(),
+ "Attribute changed called on defunct document accessible!");
+
+ // Proceed even if the element is not accessible because element may become
+ // accessible if it gets certain attribute.
+ if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
+ return;
+
+ // Ignore attribute change if the element doesn't have an accessible (at all
+ // or still) iff the element is not a root content of this document accessible
+ // (which is treated as attribute change on this document accessible).
+ // Note: we don't bail if all the content hasn't finished loading because
+ // these attributes are changing for a loaded part of the content.
+ Accessible* accessible = GetAccessible(aElement);
+ if (!accessible) {
+ if (mContent != aElement)
+ return;
+
+ accessible = this;
+ }
+
+ MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
+ "DOM attribute change on an accessible detached from the tree");
+
+ // Fire accessible events iff there's an accessible, otherwise we consider
+ // the accessible state wasn't changed, i.e. its state is initial state.
+ AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
+
+ // Update dependent IDs cache. Take care of accessible elements because no
+ // accessible element means either the element is not accessible at all or
+ // its accessible will be created later. It doesn't make sense to keep
+ // dependent IDs for non accessible elements. For the second case we'll update
+ // dependent IDs cache when its accessible is created.
+ if (aModType == nsIDOMMutationEvent::MODIFICATION ||
+ aModType == nsIDOMMutationEvent::ADDITION) {
+ AddDependentIDsFor(accessible, aAttribute);
+ }
+}
+
+// DocAccessible protected member
+void
+DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
+ int32_t aNameSpaceID, nsIAtom* aAttribute)
+{
+ // Fire accessible event after short timer, because we need to wait for
+ // DOM attribute & resulting layout to actually change. Otherwise,
+ // assistive technology will retrieve the wrong state/value/selection info.
+
+ // XXX todo
+ // We still need to handle special HTML cases here
+ // For example, if an <img>'s usemap attribute is modified
+ // Otherwise it may just be a state change, for example an object changing
+ // its visibility
+ //
+ // XXX todo: report aria state changes for "undefined" literal value changes
+ // filed as bug 472142
+ //
+ // XXX todo: invalidate accessible when aria state changes affect exposed role
+ // filed as bug 472143
+
+ // Universal boolean properties that don't require a role. Fire the state
+ // change when disabled or aria-disabled attribute is set.
+ // Note. Checking the XUL or HTML namespace would not seem to gain us
+ // anything, because disabled attribute really is going to mean the same
+ // thing in any namespace.
+ // Note. We use the attribute instead of the disabled state bit because
+ // ARIA's aria-disabled does not affect the disabled state bit.
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::aria_disabled) {
+ // Do nothing if state wasn't changed (like @aria-disabled was removed but
+ // @disabled is still presented).
+ if (aAccessible->Unavailable() == mStateBitWasOn)
+ return;
+
+ RefPtr<AccEvent> enabledChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
+ FireDelayedEvent(enabledChangeEvent);
+
+ RefPtr<AccEvent> sensitiveChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
+ FireDelayedEvent(sensitiveChangeEvent);
+ return;
+ }
+
+ // Check for namespaced ARIA attribute
+ if (aNameSpaceID == kNameSpaceID_None) {
+ // Check for hyphenated aria-foo property?
+ if (StringBeginsWith(nsDependentAtomString(aAttribute),
+ NS_LITERAL_STRING("aria-"))) {
+ ARIAAttributeChanged(aAccessible, aAttribute);
+ }
+ }
+
+ // Fire name change and description change events. XXX: it's not complete and
+ // dupes the code logic of accessible name and description calculation, we do
+ // that for performance reasons.
+ if (aAttribute == nsGkAtoms::aria_label) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_describedby) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
+ return;
+ }
+
+ nsIContent* elm = aAccessible->GetContent();
+ if (aAttribute == nsGkAtoms::aria_labelledby &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::alt &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::title) {
+ if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
+
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_busy) {
+ bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
+ eCaseMatters);
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::id) {
+ RelocateARIAOwnedIfNeeded(elm);
+ }
+
+ // ARIA or XUL selection
+ if ((aAccessible->GetContent()->IsXULElement() &&
+ aAttribute == nsGkAtoms::selected) ||
+ aAttribute == nsGkAtoms::aria_selected) {
+ Accessible* widget =
+ nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
+ if (widget) {
+ AccSelChangeEvent::SelChangeType selChangeType =
+ elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
+ AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
+
+ RefPtr<AccEvent> event =
+ new AccSelChangeEvent(widget, aAccessible, selChangeType);
+ FireDelayedEvent(event);
+ }
+
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::contenteditable) {
+ RefPtr<AccEvent> editableChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::EDITABLE);
+ FireDelayedEvent(editableChangeEvent);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::value) {
+ if (aAccessible->IsProgress())
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
+ }
+}
+
+// DocAccessible protected member
+void
+DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
+{
+ // Note: For universal/global ARIA states and properties we don't care if
+ // there is an ARIA role present or not.
+
+ if (aAttribute == nsGkAtoms::aria_required) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::REQUIRED);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_invalid) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::INVALID);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // The activedescendant universal property redirects accessible focus events
+ // to the element with the id that activedescendant points to. Make sure
+ // the tree up to date before processing.
+ if (aAttribute == nsGkAtoms::aria_activedescendant) {
+ mNotificationController->HandleNotification<DocAccessible, Accessible>
+ (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
+
+ return;
+ }
+
+ // We treat aria-expanded as a global ARIA state for historical reasons
+ if (aAttribute == nsGkAtoms::aria_expanded) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::EXPANDED);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // For aria attributes like drag and drop changes we fire a generic attribute
+ // change event; at least until native API comes up with a more meaningful event.
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
+ if (!(attrFlags & ATTR_BYPASSOBJ)) {
+ RefPtr<AccEvent> event =
+ new AccObjectAttrChangedEvent(aAccessible, aAttribute);
+ FireDelayedEvent(event);
+ }
+
+ nsIContent* elm = aAccessible->GetContent();
+
+ // Update aria-hidden flag for the whole subtree iff aria-hidden is changed
+ // on the root, i.e. ignore any affiliated aria-hidden changes in the subtree
+ // of top aria-hidden.
+ if (aAttribute == nsGkAtoms::aria_hidden) {
+ bool isDefined = aria::HasDefinedARIAHidden(elm);
+ if (isDefined != aAccessible->IsARIAHidden() &&
+ (!aAccessible->Parent() || !aAccessible->Parent()->IsARIAHidden())) {
+ aAccessible->SetARIAHidden(isDefined);
+
+ RefPtr<AccEvent> event =
+ new AccObjectAttrChangedEvent(aAccessible, aAttribute);
+ FireDelayedEvent(event);
+ }
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_checked ||
+ (aAccessible->IsButton() &&
+ aAttribute == nsGkAtoms::aria_pressed)) {
+ const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
+ states::CHECKED : states::PRESSED;
+ RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
+ FireDelayedEvent(event);
+
+ bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
+ bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
+ nsGkAtoms::mixed, eCaseMatters);
+ if (isMixed != wasMixed) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
+ FireDelayedEvent(event);
+ }
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_readonly) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::READONLY);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // Fire text value change event whenever aria-valuetext is changed.
+ if (aAttribute == nsGkAtoms::aria_valuetext) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
+ return;
+ }
+
+ // Fire numeric value change event when aria-valuenow is changed and
+ // aria-valuetext is empty
+ if (aAttribute == nsGkAtoms::aria_valuenow &&
+ (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
+ elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
+ nsGkAtoms::_empty, eCaseMatters))) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_owns) {
+ mNotificationController->ScheduleRelocation(aAccessible);
+ }
+}
+
+void
+DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
+{
+ nsIContent* elm = aAccessible->GetContent();
+ if (elm && aAccessible->IsActiveWidget()) {
+ nsAutoString id;
+ if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
+ dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
+ if (activeDescendantElm) {
+ Accessible* activeDescendant = GetAccessible(activeDescendantElm);
+ if (activeDescendant) {
+ FocusMgr()->ActiveItemChanged(activeDescendant, false);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
+ activeDescendant);
+#endif
+ }
+ }
+ }
+ }
+}
+
+void
+DocAccessible::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+}
+
+void
+DocAccessible::ContentStateChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ EventStates aStateMask)
+{
+ Accessible* accessible = GetAccessible(aContent);
+ if (!accessible)
+ return;
+
+ if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
+ Accessible* widget = accessible->ContainerWidget();
+ if (widget && widget->IsSelect()) {
+ AccSelChangeEvent::SelChangeType selChangeType =
+ aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
+ AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
+ RefPtr<AccEvent> event =
+ new AccSelChangeEvent(widget, accessible, selChangeType);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::CHECKED,
+ aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
+ FireDelayedEvent(event);
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::INVALID, true);
+ FireDelayedEvent(event);
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::TRAVERSED, true);
+ FireDelayedEvent(event);
+ }
+}
+
+void
+DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
+ EventStates aStateMask)
+{
+}
+
+void
+DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
+ nsIContent* aChild, int32_t /* unused */)
+{
+}
+
+void
+DocAccessible::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainerNode,
+ nsIContent* aChildNode, int32_t /* unused */,
+ nsIContent* aPreviousSiblingNode)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
+ logging::Node("container node", aContainerNode);
+ logging::Node("content node", aChildNode);
+ logging::MsgEnd();
+ }
+#endif
+ // This one and content removal notification from layout may result in
+ // double processing of same subtrees. If it pops up in profiling, then
+ // consider reusing a document node cache to reject these notifications early.
+ Accessible* container = GetAccessibleOrContainer(aContainerNode);
+ if (container) {
+ UpdateTreeOnRemoval(container, aChildNode);
+ }
+}
+
+void
+DocAccessible::ParentChainChanged(nsIContent* aContent)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+#ifdef A11Y_LOG
+nsresult
+DocAccessible::HandleAccEvent(AccEvent* aEvent)
+{
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoadEventHandled(aEvent);
+
+ return HyperTextAccessible::HandleAccEvent(aEvent);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Public members
+
+void*
+DocAccessible::GetNativeWindow() const
+{
+ if (!mPresShell)
+ return nullptr;
+
+ nsViewManager* vm = mPresShell->GetViewManager();
+ if (!vm)
+ return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ return widget->GetNativeData(NS_NATIVE_WINDOW);
+
+ return nullptr;
+}
+
+Accessible*
+DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
+{
+ Accessible* child = GetAccessibleByUniqueID(aUniqueID);
+ if (child)
+ return child;
+
+ uint32_t childDocCount = mChildDocuments.Length();
+ for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
+ DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
+ child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
+ if (child)
+ return child;
+ }
+
+ return nullptr;
+}
+
+Accessible*
+DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
+{
+ if (!aNode || !aNode->GetComposedDoc())
+ return nullptr;
+
+ nsINode* currNode = aNode;
+ Accessible* accessible = nullptr;
+ while (!(accessible = GetAccessible(currNode))) {
+ nsINode* parent = nullptr;
+
+ // If this is a content node, try to get a flattened parent content node.
+ // This will smartly skip from the shadow root to the host element,
+ // over parentless document fragment
+ if (currNode->IsContent())
+ parent = currNode->AsContent()->GetFlattenedTreeParent();
+
+ // Fallback to just get parent node, in case there is no parent content
+ // node. Or current node is not a content node.
+ if (!parent)
+ parent = currNode->GetParentNode();
+
+ if (!(currNode = parent)) break;
+ }
+
+ return accessible;
+}
+
+Accessible*
+DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
+{
+ Accessible* acc = GetAccessible(aNode);
+ if (acc)
+ return acc;
+
+ acc = GetContainerAccessible(aNode);
+ if (acc) {
+ uint32_t childCnt = acc->ChildCount();
+ for (uint32_t idx = 0; idx < childCnt; idx++) {
+ Accessible* child = acc->GetChildAt(idx);
+ for (nsIContent* elm = child->GetContent();
+ elm && elm != acc->GetContent();
+ elm = elm->GetFlattenedTreeParent()) {
+ if (elm == aNode)
+ return child;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+void
+DocAccessible::BindToDocument(Accessible* aAccessible,
+ const nsRoleMapEntry* aRoleMapEntry)
+{
+ // Put into DOM node cache.
+ if (aAccessible->IsNodeMapEntry())
+ mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
+
+ // Put into unique ID cache.
+ mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
+
+ aAccessible->SetRoleMapEntry(aRoleMapEntry);
+
+ AddDependentIDsFor(aAccessible);
+
+ if (aAccessible->HasOwnContent()) {
+ nsIContent* el = aAccessible->GetContent();
+ if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
+ mNotificationController->ScheduleRelocation(aAccessible);
+ }
+ }
+}
+
+void
+DocAccessible::UnbindFromDocument(Accessible* aAccessible)
+{
+ NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
+ "Unbinding the unbound accessible!");
+
+ // Fire focus event on accessible having DOM focus if active item was removed
+ // from the tree.
+ if (FocusMgr()->IsActiveItem(aAccessible)) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
+#endif
+ }
+
+ // Remove an accessible from node-to-accessible map if it exists there.
+ if (aAccessible->IsNodeMapEntry() &&
+ mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
+ mNodeToAccessibleMap.Remove(aAccessible->GetNode());
+
+ aAccessible->mStateFlags |= eIsNotInDocument;
+
+ // Update XPCOM part.
+ xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
+ if (xpcDoc)
+ xpcDoc->NotifyOfShutdown(aAccessible);
+
+ void* uniqueID = aAccessible->UniqueID();
+
+ NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
+ aAccessible->Shutdown();
+
+ mAccessibleCache.Remove(uniqueID);
+}
+
+void
+DocAccessible::ContentInserted(nsIContent* aContainerNode,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode)
+{
+ // Ignore content insertions until we constructed accessible tree. Otherwise
+ // schedule tree update on content insertion after layout.
+ if (mNotificationController && HasLoadState(eTreeConstructed)) {
+ // Update the whole tree of this document accessible when the container is
+ // null (document element is inserted or removed).
+ Accessible* container = aContainerNode ?
+ AccessibleOrTrueContainer(aContainerNode) : this;
+ if (container) {
+ // Ignore notification if the container node is no longer in the DOM tree.
+ mNotificationController->ScheduleContentInsertion(container,
+ aStartChildNode,
+ aEndChildNode);
+ }
+ }
+}
+
+void
+DocAccessible::RecreateAccessible(nsIContent* aContent)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "accessible recreated");
+ logging::Node("content", aContent);
+ logging::MsgEnd();
+ }
+#endif
+
+ // XXX: we shouldn't recreate whole accessible subtree, instead we should
+ // subclass hide and show events to handle them separately and implement their
+ // coalescence with normal hide and show events. Note, in this case they
+ // should be coalesced with normal show/hide events.
+
+ nsIContent* parent = aContent->GetFlattenedTreeParent();
+ ContentRemoved(parent, aContent);
+ ContentInserted(parent, aContent, aContent->GetNextSibling());
+}
+
+void
+DocAccessible::ProcessInvalidationList()
+{
+ // Invalidate children of container accessible for each element in
+ // invalidation list. Allow invalidation list insertions while container
+ // children are recached.
+ for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
+ nsIContent* content = mInvalidationList[idx];
+ if (!HasAccessible(content) && content->HasID()) {
+ Accessible* container = GetContainerAccessible(content);
+ if (container) {
+ // Check if the node is a target of aria-owns, and if so, don't process
+ // it here and let DoARIAOwnsRelocation process it.
+ AttrRelProviderArray* list =
+ mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
+ bool shouldProcess = !!list;
+ if (shouldProcess) {
+ for (uint32_t idx = 0; idx < list->Length(); idx++) {
+ if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
+ shouldProcess = false;
+ break;
+ }
+ }
+
+ if (shouldProcess) {
+ ProcessContentInserted(container, content);
+ }
+ }
+ }
+ }
+ }
+
+ mInvalidationList.Clear();
+}
+
+Accessible*
+DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
+{
+if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
+ return GetAccessible(aNode);
+
+ // XXX Bug 135040, incorrect when multiple images use the same map.
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ if (imageFrame) {
+ Accessible* parent = GetAccessible(imageFrame->GetContent());
+ if (parent) {
+ Accessible* area =
+ parent->AsImageMap()->GetChildAccessibleFor(aNode);
+ if (area)
+ return area;
+
+ return nullptr;
+ }
+ }
+
+ return GetAccessible(aNode);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected members
+
+void
+DocAccessible::NotifyOfLoading(bool aIsReloading)
+{
+ // Mark the document accessible as loading, if it stays alive then we'll mark
+ // it as loaded when we receive proper notification.
+ mLoadState &= ~eDOMLoaded;
+
+ if (!IsLoadEventTarget())
+ return;
+
+ if (aIsReloading) {
+ // Fire reload and state busy events on existing document accessible while
+ // event from user input flag can be calculated properly and accessible
+ // is alive. When new document gets loaded then this one is destroyed.
+ RefPtr<AccEvent> reloadEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
+ nsEventShell::FireEvent(reloadEvent);
+ }
+
+ // Fire state busy change event. Use delayed event since we don't care
+ // actually if event isn't delivered when the document goes away like a shot.
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, true);
+ FireDelayedEvent(stateEvent);
+}
+
+void
+DocAccessible::DoInitialUpdate()
+{
+ if (nsCoreUtils::IsTabDocument(mDocumentNode))
+ mDocFlags |= eTabDocument;
+
+ mLoadState |= eTreeConstructed;
+
+ // Set up a root element and ARIA role mapping.
+ UpdateRootElIfNeeded();
+
+ // Build initial tree.
+ CacheChildrenInSubtree(this);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Initial subtree", this);
+ }
+#endif
+
+ // Fire reorder event after the document tree is constructed. Note, since
+ // this reorder event is processed by parent document then events targeted to
+ // this document may be fired prior to this reorder event. If this is
+ // a problem then consider to keep event processing per tab document.
+ if (!IsRoot()) {
+ RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
+ ParentDocument()->FireDelayedEvent(reorderEvent);
+ }
+
+ TreeMutation mt(this);
+ uint32_t childCount = ChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ Accessible* child = GetChildAt(i);
+ mt.AfterInsertion(child);
+ }
+ mt.Done();
+}
+
+void
+DocAccessible::ProcessLoad()
+{
+ mLoadState |= eCompletelyLoaded;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocCompleteLoad(this, IsLoadEventTarget());
+#endif
+
+ // Do not fire document complete/stop events for root chrome document
+ // accessibles and for frame/iframe documents because
+ // a) screen readers start working on focus event in the case of root chrome
+ // documents
+ // b) document load event on sub documents causes screen readers to act is if
+ // entire page is reloaded.
+ if (!IsLoadEventTarget())
+ return;
+
+ // Fire complete/load stopped if the load event type is given.
+ if (mLoadEventType) {
+ RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
+ FireDelayedEvent(loadEvent);
+
+ mLoadEventType = 0;
+ }
+
+ // Fire busy state change event.
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, false);
+ FireDelayedEvent(stateEvent);
+}
+
+void
+DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
+{
+ dom::Element* relProviderEl = aRelProvider->Elm();
+ if (!relProviderEl)
+ return;
+
+ for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
+ nsIAtom* relAttr = *kRelationAttrs[idx];
+ if (aRelAttr && aRelAttr != relAttr)
+ continue;
+
+ if (relAttr == nsGkAtoms::_for) {
+ if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
+ nsGkAtoms::output))
+ continue;
+
+ } else if (relAttr == nsGkAtoms::control) {
+ if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
+ nsGkAtoms::description))
+ continue;
+ }
+
+ IDRefsIterator iter(this, relProviderEl, relAttr);
+ while (true) {
+ const nsDependentSubstring id = iter.NextID();
+ if (id.IsEmpty())
+ break;
+
+ AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+ if (!providers) {
+ providers = new AttrRelProviderArray();
+ if (providers) {
+ mDependentIDsHash.Put(id, providers);
+ }
+ }
+
+ if (providers) {
+ AttrRelProvider* provider =
+ new AttrRelProvider(relAttr, relProviderEl);
+ if (provider) {
+ providers->AppendElement(provider);
+
+ // We've got here during the children caching. If the referenced
+ // content is not accessible then store it to pend its container
+ // children invalidation (this happens immediately after the caching
+ // is finished).
+ nsIContent* dependentContent = iter.GetElem(id);
+ if (dependentContent) {
+ if (!HasAccessible(dependentContent)) {
+ mInvalidationList.AppendElement(dependentContent);
+ }
+ }
+ }
+ }
+ }
+
+ // If the relation attribute is given then we don't have anything else to
+ // check.
+ if (aRelAttr)
+ break;
+ }
+
+ // Make sure to schedule the tree update if needed.
+ mNotificationController->ScheduleProcessing();
+}
+
+void
+DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr)
+{
+ dom::Element* relProviderElm = aRelProvider->Elm();
+ if (!relProviderElm)
+ return;
+
+ for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
+ nsIAtom* relAttr = *kRelationAttrs[idx];
+ if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
+ continue;
+
+ IDRefsIterator iter(this, relProviderElm, relAttr);
+ while (true) {
+ const nsDependentSubstring id = iter.NextID();
+ if (id.IsEmpty())
+ break;
+
+ AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+ if (providers) {
+ for (uint32_t jdx = 0; jdx < providers->Length(); ) {
+ AttrRelProvider* provider = (*providers)[jdx];
+ if (provider->mRelAttr == relAttr &&
+ provider->mContent == relProviderElm)
+ providers->RemoveElement(provider);
+ else
+ jdx++;
+ }
+ if (providers->Length() == 0)
+ mDependentIDsHash.Remove(id);
+ }
+ }
+
+ // If the relation attribute is given then we don't have anything else to
+ // check.
+ if (aRelAttr)
+ break;
+ }
+}
+
+bool
+DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
+ nsIAtom* aAttribute)
+{
+ if (aAttribute == nsGkAtoms::role) {
+ // It is common for js libraries to set the role on the body element after
+ // the document has loaded. In this case we just update the role map entry.
+ if (mContent == aElement) {
+ SetRoleMapEntry(aria::GetRoleMap(aElement));
+ if (mIPCDoc) {
+ mIPCDoc->SendRoleChangedEvent(Role());
+ }
+
+ return true;
+ }
+
+ // Recreate the accessible when role is changed because we might require a
+ // different accessible class for the new role or the accessible may expose
+ // a different sets of interfaces (COM restriction).
+ RecreateAccessible(aElement);
+
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::href) {
+ // Not worth the expense to ensure which namespace these are in. It doesn't
+ // kill use to recreate the accessible even if the attribute was used in
+ // the wrong namespace or an element that doesn't support it.
+
+ // Make sure the accessible is recreated asynchronously to allow the content
+ // to handle the attribute change.
+ RecreateAccessible(aElement);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_multiselectable &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
+ // This affects whether the accessible supports SelectAccessible.
+ // COM says we cannot change what interfaces are supported on-the-fly,
+ // so invalidate this object. A new one will be created on demand.
+ RecreateAccessible(aElement);
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+DocAccessible::UpdateRootElIfNeeded()
+{
+ dom::Element* rootEl = mDocumentNode->GetBodyElement();
+ if (!rootEl) {
+ rootEl = mDocumentNode->GetRootElement();
+ }
+ if (rootEl != mContent) {
+ mContent = rootEl;
+ SetRoleMapEntry(aria::GetRoleMap(rootEl));
+ if (mIPCDoc) {
+ mIPCDoc->SendRoleChangedEvent(Role());
+ }
+ }
+}
+
+/**
+ * Content insertion helper.
+ */
+class InsertIterator final
+{
+public:
+ InsertIterator(Accessible* aContext,
+ const nsTArray<nsCOMPtr<nsIContent> >* aNodes) :
+ mChild(nullptr), mChildBefore(nullptr), mWalker(aContext),
+ mNodes(aNodes), mNodesIdx(0)
+ {
+ MOZ_ASSERT(aContext, "No context");
+ MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
+ MOZ_COUNT_CTOR(InsertIterator);
+ }
+ ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); }
+
+ Accessible* Context() const { return mWalker.Context(); }
+ Accessible* Child() const { return mChild; }
+ Accessible* ChildBefore() const { return mChildBefore; }
+ DocAccessible* Document() const { return mWalker.Document(); }
+
+ /**
+ * Iterates to a next accessible within the inserted content.
+ */
+ bool Next();
+
+ void Rejected()
+ {
+ mChild = nullptr;
+ mChildBefore = nullptr;
+ }
+
+private:
+ Accessible* mChild;
+ Accessible* mChildBefore;
+ TreeWalker mWalker;
+
+ const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
+ uint32_t mNodesIdx;
+};
+
+bool
+InsertIterator::Next()
+{
+ if (mNodesIdx > 0) {
+ Accessible* nextChild = mWalker.Next();
+ if (nextChild) {
+ mChildBefore = mChild;
+ mChild = nextChild;
+ return true;
+ }
+ }
+
+ while (mNodesIdx < mNodes->Length()) {
+ // Ignore nodes that are not contained by the container anymore.
+
+ // The container might be changed, for example, because of the subsequent
+ // overlapping content insertion (i.e. other content was inserted between
+ // this inserted content and its container or the content was reinserted
+ // into different container of unrelated part of tree). To avoid a double
+ // processing of the content insertion ignore this insertion notification.
+ // Note, the inserted content might be not in tree at all at this point
+ // what means there's no container. Ignore the insertion too.
+ nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
+ nsIContent* node = mNodes->ElementAt(mNodesIdx++);
+ Accessible* container = Document()->AccessibleOrTrueContainer(node);
+ if (container != Context()) {
+ continue;
+ }
+
+ // HTML comboboxes have no-content list accessible as an intermediate
+ // containing all options.
+ if (container->IsHTMLCombobox()) {
+ container = container->FirstChild();
+ }
+
+ if (!container->IsAcceptableChild(node)) {
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("traversing an inserted node", logging::eVerbose,
+ "container", container, "node", node);
+#endif
+
+ // If inserted nodes are siblings then just move the walker next.
+ if (mChild && prevNode && prevNode->GetNextSibling() == node) {
+ Accessible* nextChild = mWalker.Scope(node);
+ if (nextChild) {
+ mChildBefore = mChild;
+ mChild = nextChild;
+ return true;
+ }
+ }
+ else {
+ TreeWalker finder(container);
+ if (finder.Seek(node)) {
+ mChild = mWalker.Scope(node);
+ if (mChild) {
+ mChildBefore = finder.Prev();
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+DocAccessible::ProcessContentInserted(Accessible* aContainer,
+ const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
+{
+ // Process insertions if the container accessible is still in tree.
+ if (!aContainer->IsInDocument()) {
+ return;
+ }
+
+ // If new root content has been inserted then update it.
+ if (aContainer == this) {
+ UpdateRootElIfNeeded();
+ }
+
+ InsertIterator iter(aContainer, aNodes);
+ if (!iter.Next()) {
+ return;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children before insertion", logging::eVerbose,
+ aContainer);
+#endif
+
+ TreeMutation mt(aContainer);
+ do {
+ Accessible* parent = iter.Child()->Parent();
+ if (parent) {
+ if (parent != aContainer) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("stealing accessible", 0,
+ "old parent", parent, "new parent",
+ aContainer, "child", iter.Child(), nullptr);
+#endif
+ MOZ_ASSERT_UNREACHABLE("stealing accessible");
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("binding to same parent", logging::eVerbose,
+ "parent", aContainer, "child", iter.Child(), nullptr);
+#endif
+ continue;
+ }
+
+ if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("accessible was inserted", 0,
+ "container", aContainer, "child", iter.Child(), nullptr);
+#endif
+
+ CreateSubtree(iter.Child());
+ mt.AfterInsertion(iter.Child());
+ continue;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("accessible was rejected");
+ iter.Rejected();
+ } while (iter.Next());
+
+ mt.Done();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children after insertion", logging::eVerbose,
+ aContainer);
+#endif
+
+ FireEventsOnInsertion(aContainer);
+}
+
+void
+DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
+{
+ if (!aContainer->IsInDocument()) {
+ return;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
+#endif
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("traversing an inserted node", logging::eVerbose,
+ "container", aContainer, "node", aNode);
+#endif
+
+ TreeWalker walker(aContainer);
+ if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
+ Accessible* child = GetAccessible(aNode);
+ if (!child) {
+ child = GetAccService()->CreateAccessible(aNode, aContainer);
+ }
+
+ if (child) {
+ TreeMutation mt(aContainer);
+ if (!aContainer->InsertAfter(child, walker.Prev())) {
+ return;
+ }
+ CreateSubtree(child);
+ mt.AfterInsertion(child);
+ mt.Done();
+
+ FireEventsOnInsertion(aContainer);
+ }
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
+#endif
+}
+
+void
+DocAccessible::FireEventsOnInsertion(Accessible* aContainer)
+{
+ // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
+ // if it did.
+ if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
+ Accessible* ancestor = aContainer;
+ do {
+ if (ancestor->IsAlert()) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
+ break;
+ }
+ }
+ while ((ancestor = ancestor->Parent()));
+ }
+}
+
+void
+DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
+{
+ // If child node is not accessible then look for its accessible children.
+ Accessible* child = GetAccessible(aChildNode);
+#ifdef A11Y_LOG
+ logging::TreeInfo("process content removal", 0,
+ "container", aContainer, "child", aChildNode);
+#endif
+
+ TreeMutation mt(aContainer);
+ if (child) {
+ RefPtr<Accessible> kungFuDeathGripChild(child);
+ mt.BeforeRemoval(child);
+ if (child->IsDefunct()) {
+ return; // event coalescence may kill us
+ }
+
+ MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
+ aContainer->RemoveChild(child);
+ UncacheChildrenInSubtree(child);
+ mt.Done();
+ return;
+ }
+
+ TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
+ while (Accessible* child = walker.Next()) {
+ RefPtr<Accessible> kungFuDeathGripChild(child);
+ mt.BeforeRemoval(child);
+ if (child->IsDefunct()) {
+ return; // event coalescence may kill us
+ }
+
+ MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
+ aContainer->RemoveChild(child);
+ UncacheChildrenInSubtree(child);
+ }
+ mt.Done();
+}
+
+bool
+DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
+{
+ if (!aElement->HasID())
+ return false;
+
+ AttrRelProviderArray* list =
+ mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
+ if (list) {
+ for (uint32_t idx = 0; idx < list->Length(); idx++) {
+ if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
+ Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
+ if (owner) {
+ mNotificationController->ScheduleRelocation(owner);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+DocAccessible::ValidateARIAOwned()
+{
+ for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
+ Accessible* owner = it.Key();
+ nsTArray<RefPtr<Accessible> >* children = it.UserData();
+
+ // Owner is about to die, put children back if applicable.
+ if (!mAccessibleCache.GetWeak(reinterpret_cast<void*>(owner)) ||
+ !owner->IsInDocument()) {
+ PutChildrenBack(children, 0);
+ it.Remove();
+ continue;
+ }
+
+ for (uint32_t idx = 0; idx < children->Length(); idx++) {
+ Accessible* child = children->ElementAt(idx);
+ if (!child->IsInDocument()) {
+ children->RemoveElementAt(idx);
+ idx--;
+ continue;
+ }
+
+ NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
+
+ // If DOM node doesn't have a frame anymore then shutdown its accessible.
+ if (child->Parent() && !child->GetFrame()) {
+ UpdateTreeOnRemoval(child->Parent(), child->GetContent());
+ children->RemoveElementAt(idx);
+ idx--;
+ continue;
+ }
+
+ NS_ASSERTION(child->Parent() == owner,
+ "Illigally stolen ARIA owned child!");
+ }
+
+ if (children->Length() == 0) {
+ it.Remove();
+ }
+ }
+}
+
+void
+DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
+{
+ MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
+ MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
+#endif
+
+ nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
+ IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
+ uint32_t idx = 0;
+ while (nsIContent* childEl = iter.NextElem()) {
+ Accessible* child = GetAccessible(childEl);
+ auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
+
+ // Make an attempt to create an accessible if it wasn't created yet.
+ if (!child) {
+ if (aOwner->IsAcceptableChild(childEl)) {
+ child = GetAccService()->CreateAccessible(childEl, aOwner);
+ if (child) {
+ TreeMutation imut(aOwner);
+ aOwner->InsertChildAt(insertIdx, child);
+ imut.AfterInsertion(child);
+ imut.Done();
+
+ child->SetRelocated(true);
+ owned->InsertElementAt(idx, child);
+ idx++;
+
+ // Create subtree before adjusting the insertion index, since subtree
+ // creation may alter children in the container.
+ CreateSubtree(child);
+ FireEventsOnInsertion(aOwner);
+ }
+ }
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns traversal", logging::eVerbose,
+ "candidate", child, nullptr);
+#endif
+
+ // Same child on same position, no change.
+ if (child->Parent() == aOwner &&
+ child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
+ MOZ_ASSERT(owned->ElementAt(idx) == child, "Not in sync!");
+ idx++;
+ continue;
+ }
+
+ MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
+ if (owned->IndexOf(child) < idx) {
+ continue; // ignore second entry of same ID
+ }
+
+ // A new child is found, check for loops.
+ if (child->Parent() != aOwner) {
+ Accessible* parent = aOwner;
+ while (parent && parent != child && !parent->IsDoc()) {
+ parent = parent->Parent();
+ }
+ // A referred child cannot be a parent of the owner.
+ if (parent == child) {
+ continue;
+ }
+ }
+
+ if (MoveChild(child, aOwner, insertIdx)) {
+ child->SetRelocated(true);
+ MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
+ owned = mARIAOwnsHash.LookupOrAdd(aOwner);
+ owned->InsertElementAt(idx, child);
+ idx++;
+ }
+ }
+
+ // Put back children that are not seized anymore.
+ PutChildrenBack(owned, idx);
+ if (owned->Length() == 0) {
+ mARIAOwnsHash.Remove(aOwner);
+ }
+}
+
+void
+DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
+ uint32_t aStartIdx)
+{
+ MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
+
+ nsTArray<RefPtr<Accessible> > containers;
+ for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
+ Accessible* child = aChildren->ElementAt(idx);
+ if (!child->IsInDocument()) {
+ continue;
+ }
+
+ // Remove the child from the owner
+ Accessible* owner = child->Parent();
+ if (!owner) {
+ NS_ERROR("Cannot put the child back. No parent, a broken tree.");
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns put child back", 0,
+ "old parent", owner, "child", child, nullptr);
+#endif
+
+ // Unset relocated flag to find an insertion point for the child.
+ child->SetRelocated(false);
+
+ int32_t idxInParent = -1;
+ Accessible* origContainer = GetContainerAccessible(child->GetContent());
+ if (origContainer) {
+ TreeWalker walker(origContainer);
+ if (walker.Seek(child->GetContent())) {
+ Accessible* prevChild = walker.Prev();
+ if (prevChild) {
+ idxInParent = prevChild->IndexInParent() + 1;
+ MOZ_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
+ origContainer = prevChild->Parent();
+ }
+ else {
+ idxInParent = 0;
+ }
+ }
+ }
+ MoveChild(child, origContainer, idxInParent);
+ }
+
+ aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
+}
+
+bool
+DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
+ int32_t aIdxInParent)
+{
+ MOZ_ASSERT(aChild, "No child");
+ MOZ_ASSERT(aChild->Parent(), "No parent");
+ MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
+ "Wrong insertion point for a moving child");
+
+ Accessible* curParent = aChild->Parent();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child", 0,
+ "old parent", curParent, "new parent", aNewParent,
+ "child", aChild, nullptr);
+#endif
+
+ // Forget aria-owns info in case of ARIA owned element. The caller is expected
+ // to update it if needed.
+ if (aChild->IsRelocated()) {
+ aChild->SetRelocated(false);
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
+ children->RemoveElement(aChild);
+ }
+
+ NotificationController::MoveGuard mguard(mNotificationController);
+
+ if (curParent == aNewParent) {
+ MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
+ curParent->MoveChild(aIdxInParent, aChild);
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child: parent tree after",
+ logging::eVerbose, curParent);
+#endif
+ return true;
+ }
+
+ if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
+ return false;
+ }
+
+ TreeMutation rmut(curParent);
+ rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
+ curParent->RemoveChild(aChild);
+ rmut.Done();
+
+ // No insertion point for the child.
+ if (aIdxInParent == -1) {
+ return true;
+ }
+
+ if (aIdxInParent > static_cast<int32_t>(aNewParent->ChildCount())) {
+ MOZ_ASSERT_UNREACHABLE("Wrong insertion point for a moving child");
+ return true;
+ }
+
+ TreeMutation imut(aNewParent);
+ aNewParent->InsertChildAt(aIdxInParent, aChild);
+ imut.AfterInsertion(aChild);
+ imut.Done();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child: old parent tree after",
+ logging::eVerbose, curParent);
+ logging::TreeInfo("move child: new parent tree after",
+ logging::eVerbose, aNewParent);
+#endif
+
+ return true;
+}
+
+
+void
+DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
+ Accessible** aFocusedAcc)
+{
+ // If the accessible is focused then report a focus event after all related
+ // mutation events.
+ if (aFocusedAcc && !*aFocusedAcc &&
+ FocusMgr()->HasDOMFocus(aRoot->GetContent()))
+ *aFocusedAcc = aRoot;
+
+ Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
+ if (root->KidsFromDOM()) {
+ TreeMutation mt(root, TreeMutation::kNoEvents);
+ TreeWalker walker(root);
+ while (Accessible* child = walker.Next()) {
+ if (child->IsBoundToParent()) {
+ MoveChild(child, root, root->ChildCount());
+ continue;
+ }
+
+ root->AppendChild(child);
+ mt.AfterInsertion(child);
+
+ CacheChildrenInSubtree(child, aFocusedAcc);
+ }
+ mt.Done();
+ }
+
+ // Fire events for ARIA elements.
+ if (!aRoot->HasARIARole()) {
+ return;
+ }
+
+ // XXX: we should delay document load complete event if the ARIA document
+ // has aria-busy.
+ roles::Role role = aRoot->ARIARole();
+ if (!aRoot->IsDoc() && (role == roles::DIALOG || role == roles::DOCUMENT)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
+ }
+}
+
+void
+DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
+{
+ aRoot->mStateFlags |= eIsNotInDocument;
+ RemoveDependentIDsFor(aRoot);
+
+ uint32_t count = aRoot->ContentChildCount();
+ for (uint32_t idx = 0; idx < count; idx++) {
+ Accessible* child = aRoot->ContentChildAt(idx);
+
+ // Removing this accessible from the document doesn't mean anything about
+ // accessibles for subdocuments, so skip removing those from the tree.
+ if (!child->IsDoc()) {
+ UncacheChildrenInSubtree(child);
+ }
+ }
+
+ if (aRoot->IsNodeMapEntry() &&
+ mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
+ mNodeToAccessibleMap.Remove(aRoot->GetNode());
+}
+
+void
+DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
+{
+ // Traverse through children and shutdown them before this accessible. When
+ // child gets shutdown then it removes itself from children array of its
+ //parent. Use jdx index to process the cases if child is not attached to the
+ // parent and as result doesn't remove itself from its children.
+ uint32_t count = aAccessible->ContentChildCount();
+ for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
+ Accessible* child = aAccessible->ContentChildAt(jdx);
+ if (!child->IsBoundToParent()) {
+ NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
+ jdx++;
+ }
+
+ // Don't cross document boundaries. The outerdoc shutdown takes care about
+ // its subdocument.
+ if (!child->IsDoc())
+ ShutdownChildrenInSubtree(child);
+ }
+
+ UnbindFromDocument(aAccessible);
+}
+
+bool
+DocAccessible::IsLoadEventTarget() const
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
+ NS_ASSERTION(treeItem, "No document shell for document!");
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ // Not a root document.
+ if (parentTreeItem) {
+ // Return true if it's either:
+ // a) tab document;
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ if (parentTreeItem == rootTreeItem)
+ return true;
+
+ // b) frame/iframe document and its parent document is not in loading state
+ // Note: we can get notifications while document is loading (and thus
+ // while there's no parent document yet).
+ DocAccessible* parentDoc = ParentDocument();
+ return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
+ }
+
+ // It's content (not chrome) root document.
+ return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
+}
diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h
new file mode 100644
index 0000000000..4bc6f03f08
--- /dev/null
+++ b/accessible/generic/DocAccessible.h
@@ -0,0 +1,718 @@
+/* -*- 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_DocAccessible_h__
+#define mozilla_a11y_DocAccessible_h__
+
+#include "nsIAccessiblePivot.h"
+
+#include "HyperTextAccessibleWrap.h"
+#include "AccEvent.h"
+
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsIDocument.h"
+#include "nsIDocumentObserver.h"
+#include "nsIEditor.h"
+#include "nsIObserver.h"
+#include "nsIScrollPositionListener.h"
+#include "nsITimer.h"
+#include "nsIWeakReference.h"
+
+class nsAccessiblePivot;
+
+const uint32_t kDefaultCacheLength = 128;
+
+namespace mozilla {
+namespace a11y {
+
+class DocManager;
+class NotificationController;
+class DocAccessibleChild;
+class RelatedAccIterator;
+template<class Class, class ... Args>
+class TNotification;
+
+class DocAccessible : public HyperTextAccessibleWrap,
+ public nsIDocumentObserver,
+ public nsIObserver,
+ public nsIScrollPositionListener,
+ public nsSupportsWeakReference,
+ public nsIAccessiblePivotObserver
+{
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, Accessible)
+
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIACCESSIBLEPIVOTOBSERVER
+
+public:
+
+ DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell);
+
+ // nsIScrollPositionListener
+ virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override {}
+ virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER
+
+ // Accessible
+ virtual void Init();
+ virtual void Shutdown() override;
+ virtual nsIFrame* GetFrame() const override;
+ virtual nsINode* GetNode() const override { return mDocumentNode; }
+ nsIDocument* DocumentNode() const { return mDocumentNode; }
+
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual void Description(nsString& aDescription) override;
+ virtual Accessible* FocusedChild() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual bool NativelyUnavailable() const override;
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
+
+ virtual void TakeFocus() override;
+
+#ifdef A11Y_LOG
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+#endif
+
+ virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const override;
+
+ // HyperTextAccessible
+ virtual already_AddRefed<nsIEditor> GetEditor() const override;
+
+ // DocAccessible
+
+ /**
+ * Return document URL.
+ */
+ void URL(nsAString& aURL) const;
+
+ /**
+ * Return DOM document title.
+ */
+ void Title(nsString& aTitle) const { mDocumentNode->GetTitle(aTitle); }
+
+ /**
+ * Return DOM document mime type.
+ */
+ void MimeType(nsAString& aType) const { mDocumentNode->GetContentType(aType); }
+
+ /**
+ * Return DOM document type.
+ */
+ void DocType(nsAString& aType) const;
+
+ /**
+ * Return virtual cursor associated with the document.
+ */
+ nsIAccessiblePivot* VirtualCursor();
+
+ /**
+ * Return presentation shell for this document accessible.
+ */
+ nsIPresShell* PresShell() const { return mPresShell; }
+
+ /**
+ * Return the presentation shell's context.
+ */
+ nsPresContext* PresContext() const { return mPresShell->GetPresContext(); }
+
+ /**
+ * Return true if associated DOM document was loaded and isn't unloading.
+ */
+ bool IsContentLoaded() const
+ {
+ // eDOMLoaded flag check is used for error pages as workaround to make this
+ // method return correct result since error pages do not receive 'pageshow'
+ // event and as consequence nsIDocument::IsShowing() returns false.
+ return mDocumentNode && mDocumentNode->IsVisible() &&
+ (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded));
+ }
+
+ /**
+ * Document load states.
+ */
+ enum LoadState {
+ // initial tree construction is pending
+ eTreeConstructionPending = 0,
+ // initial tree construction done
+ eTreeConstructed = 1,
+ // DOM document is loaded.
+ eDOMLoaded = 1 << 1,
+ // document is ready
+ eReady = eTreeConstructed | eDOMLoaded,
+ // document and all its subdocuments are ready
+ eCompletelyLoaded = eReady | 1 << 2
+ };
+
+ /**
+ * Return true if the document has given document state.
+ */
+ bool HasLoadState(LoadState aState) const
+ { return (mLoadState & static_cast<uint32_t>(aState)) ==
+ static_cast<uint32_t>(aState); }
+
+ /**
+ * Return a native window handler or pointer depending on platform.
+ */
+ virtual void* GetNativeWindow() const;
+
+ /**
+ * Return the parent document.
+ */
+ DocAccessible* ParentDocument() const
+ { return mParent ? mParent->Document() : nullptr; }
+
+ /**
+ * Return the child document count.
+ */
+ uint32_t ChildDocumentCount() const
+ { return mChildDocuments.Length(); }
+
+ /**
+ * Return the child document at the given index.
+ */
+ DocAccessible* GetChildDocumentAt(uint32_t aIndex) const
+ { return mChildDocuments.SafeElementAt(aIndex, nullptr); }
+
+ /**
+ * Fire accessible event asynchronously.
+ */
+ void FireDelayedEvent(AccEvent* aEvent);
+ void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
+ void FireEventsOnInsertion(Accessible* aContainer);
+
+ /**
+ * Fire value change event on the given accessible if applicable.
+ */
+ void MaybeNotifyOfValueChange(Accessible* aAccessible);
+
+ /**
+ * Get/set the anchor jump.
+ */
+ Accessible* AnchorJump()
+ { return GetAccessibleOrContainer(mAnchorJumpElm); }
+
+ void SetAnchorJump(nsIContent* aTargetNode)
+ { mAnchorJumpElm = aTargetNode; }
+
+ /**
+ * Bind the child document to the tree.
+ */
+ void BindChildDocument(DocAccessible* aDocument);
+
+ /**
+ * Process the generic notification.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * notification is processed.
+ * @see NotificationController::HandleNotification
+ */
+ template<class Class, class Arg>
+ void HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg);
+
+ /**
+ * Return the cached accessible by the given DOM node if it's in subtree of
+ * this document accessible or the document accessible itself, otherwise null.
+ *
+ * @return the accessible object
+ */
+ Accessible* GetAccessible(nsINode* aNode) const
+ {
+ return aNode == mDocumentNode ?
+ const_cast<DocAccessible*>(this) : mNodeToAccessibleMap.Get(aNode);
+ }
+
+ /**
+ * Return an accessible for the given node even if the node is not in
+ * document's node map cache (like HTML area element).
+ *
+ * XXX: it should be really merged with GetAccessible().
+ */
+ Accessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const;
+ Accessible* GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const;
+
+ /**
+ * Return whether the given DOM node has an accessible or not.
+ */
+ bool HasAccessible(nsINode* aNode) const
+ { return GetAccessible(aNode); }
+
+ /**
+ * Return the cached accessible by the given unique ID within this document.
+ *
+ * @note the unique ID matches with the uniqueID() of Accessible
+ *
+ * @param aUniqueID [in] the unique ID used to cache the node.
+ */
+ Accessible* GetAccessibleByUniqueID(void* aUniqueID)
+ {
+ return UniqueID() == aUniqueID ?
+ this : mAccessibleCache.GetWeak(aUniqueID);
+ }
+
+ /**
+ * Return the cached accessible by the given unique ID looking through
+ * this and nested documents.
+ */
+ Accessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID);
+
+ /**
+ * Return an accessible for the given DOM node or container accessible if
+ * the node is not accessible.
+ */
+ Accessible* GetAccessibleOrContainer(nsINode* aNode) const;
+
+ /**
+ * Return a container accessible for the given DOM node.
+ */
+ Accessible* GetContainerAccessible(nsINode* aNode) const
+ {
+ return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr;
+ }
+
+ /**
+ * Return an accessible for the given node if any, or an immediate accessible
+ * container for it.
+ */
+ Accessible* AccessibleOrTrueContainer(nsINode* aNode) const;
+
+ /**
+ * Return an accessible for the given node or its first accessible descendant.
+ */
+ Accessible* GetAccessibleOrDescendant(nsINode* aNode) const;
+
+ /**
+ * Returns aria-owns seized child at the given index.
+ */
+ Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
+ {
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
+ if (children) {
+ return children->SafeElementAt(aIndex);
+ }
+ return nullptr;
+ }
+ uint32_t ARIAOwnedCount(Accessible* aParent) const
+ {
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
+ return children ? children->Length() : 0;
+ }
+
+ /**
+ * Return true if the given ID is referred by relation attribute.
+ *
+ * @note Different elements may share the same ID if they are hosted inside
+ * XBL bindings. Be careful the result of this method may be senseless
+ * while it's called for XUL elements (where XBL is used widely).
+ */
+ bool IsDependentID(const nsAString& aID) const
+ { return mDependentIDsHash.Get(aID, nullptr); }
+
+ /**
+ * Initialize the newly created accessible and put it into document caches.
+ *
+ * @param aAccessible [in] created accessible
+ * @param aRoleMapEntry [in] the role map entry role the ARIA role or nullptr
+ * if none
+ */
+ void BindToDocument(Accessible* aAccessible,
+ const nsRoleMapEntry* aRoleMapEntry);
+
+ /**
+ * Remove from document and shutdown the given accessible.
+ */
+ void UnbindFromDocument(Accessible* aAccessible);
+
+ /**
+ * Notify the document accessible that content was inserted.
+ */
+ void ContentInserted(nsIContent* aContainerNode,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode);
+
+ /**
+ * Notify the document accessible that content was removed.
+ */
+ void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
+ {
+ // Update the whole tree of this document accessible when the container is
+ // null (document element is removed).
+ UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
+ }
+ void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
+ {
+ ContentRemoved(GetAccessibleOrContainer(aContainerNode), aChildNode);
+ }
+
+ /**
+ * Updates accessible tree when rendered text is changed.
+ */
+ void UpdateText(nsIContent* aTextNode);
+
+ /**
+ * Recreate an accessible, results in hide/show events pair.
+ */
+ void RecreateAccessible(nsIContent* aContent);
+
+ /**
+ * Schedule ARIA owned element relocation if needed. Return true if relocation
+ * was scheduled.
+ */
+ bool RelocateARIAOwnedIfNeeded(nsIContent* aEl);
+
+ /**
+ * Return a notification controller associated with the document.
+ */
+ NotificationController* Controller() const { return mNotificationController; }
+
+ /**
+ * If this document is in a content process return the object responsible for
+ * communicating with the main process for it.
+ */
+ DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
+
+protected:
+ virtual ~DocAccessible();
+
+ void LastRelease();
+
+ // DocAccessible
+ virtual nsresult AddEventListeners();
+ virtual nsresult RemoveEventListeners();
+
+ /**
+ * Marks this document as loaded or loading.
+ */
+ void NotifyOfLoad(uint32_t aLoadEventType);
+ void NotifyOfLoading(bool aIsReloading);
+
+ friend class DocManager;
+
+ /**
+ * Perform initial update (create accessible tree).
+ * Can be overridden by wrappers to prepare initialization work.
+ */
+ virtual void DoInitialUpdate();
+
+ /**
+ * Updates root element and picks up ARIA role on it if any.
+ */
+ void UpdateRootElIfNeeded();
+
+ /**
+ * Process document load notification, fire document load and state busy
+ * events if applicable.
+ */
+ void ProcessLoad();
+
+ /**
+ * Add/remove scroll listeners, @see nsIScrollPositionListener interface.
+ */
+ void AddScrollListener();
+ void RemoveScrollListener();
+
+ /**
+ * Append the given document accessible to this document's child document
+ * accessibles.
+ */
+ bool AppendChildDocument(DocAccessible* aChildDocument)
+ {
+ return mChildDocuments.AppendElement(aChildDocument);
+ }
+
+ /**
+ * Remove the given document accessible from this document's child document
+ * accessibles.
+ */
+ void RemoveChildDocument(DocAccessible* aChildDocument)
+ {
+ mChildDocuments.RemoveElement(aChildDocument);
+ }
+
+ /**
+ * Add dependent IDs pointed by accessible element by relation attribute to
+ * cache. If the relation attribute is missed then all relation attributes
+ * are checked.
+ *
+ * @param aRelProvider [in] accessible that element has relation attribute
+ * @param aRelAttr [in, optional] relation attribute
+ */
+ void AddDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr = nullptr);
+
+ /**
+ * Remove dependent IDs pointed by accessible element by relation attribute
+ * from cache. If the relation attribute is absent then all relation
+ * attributes are checked.
+ *
+ * @param aRelProvider [in] accessible that element has relation attribute
+ * @param aRelAttr [in, optional] relation attribute
+ */
+ void RemoveDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr = nullptr);
+
+ /**
+ * Update or recreate an accessible depending on a changed attribute.
+ *
+ * @param aElement [in] the element the attribute was changed on
+ * @param aAttribute [in] the changed attribute
+ * @return true if an action was taken on the attribute change
+ */
+ bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement,
+ nsIAtom* aAttribute);
+
+ /**
+ * Fire accessible events when attribute is changed.
+ *
+ * @param aAccessible [in] accessible the DOM attribute is changed for
+ * @param aNameSpaceID [in] namespace of changed attribute
+ * @param aAttribute [in] changed attribute
+ */
+ void AttributeChangedImpl(Accessible* aAccessible,
+ int32_t aNameSpaceID, nsIAtom* aAttribute);
+
+ /**
+ * Fire accessible events when ARIA attribute is changed.
+ *
+ * @param aAccessible [in] accesislbe the DOM attribute is changed for
+ * @param aAttribute [in] changed attribute
+ */
+ void ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute);
+
+ /**
+ * Process ARIA active-descendant attribute change.
+ */
+ void ARIAActiveDescendantChanged(Accessible* aAccessible);
+
+ /**
+ * Update the accessible tree for inserted content.
+ */
+ void ProcessContentInserted(Accessible* aContainer,
+ const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent);
+ void ProcessContentInserted(Accessible* aContainer,
+ nsIContent* aInsertedContent);
+
+ /**
+ * Used to notify the document to make it process the invalidation list.
+ *
+ * While children are cached we may encounter the case there's no accessible
+ * for referred content by related accessible. Store these related nodes to
+ * invalidate their containers later.
+ */
+ void ProcessInvalidationList();
+
+ /**
+ * Update the accessible tree for content removal.
+ */
+ void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
+
+ /**
+ * Validates all aria-owns connections and updates the tree accordingly.
+ */
+ void ValidateARIAOwned();
+
+ /**
+ * Steals or puts back accessible subtrees.
+ */
+ void DoARIAOwnsRelocation(Accessible* aOwner);
+
+ /**
+ * Moves children back under their original parents.
+ */
+ void PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
+ uint32_t aStartIdx);
+
+ bool MoveChild(Accessible* aChild, Accessible* aNewParent,
+ int32_t aIdxInParent);
+
+ /**
+ * Create accessible tree.
+ *
+ * @param aRoot [in] a root of subtree to create
+ * @param aFocusedAcc [in, optional] a focused accessible under created
+ * subtree if any
+ */
+ void CacheChildrenInSubtree(Accessible* aRoot,
+ Accessible** aFocusedAcc = nullptr);
+ void CreateSubtree(Accessible* aRoot);
+
+ /**
+ * Remove accessibles in subtree from node to accessible map.
+ */
+ void UncacheChildrenInSubtree(Accessible* aRoot);
+
+ /**
+ * Shutdown any cached accessible in the subtree.
+ *
+ * @param aAccessible [in] the root of the subrtee to invalidate accessible
+ * child/parent refs in
+ */
+ void ShutdownChildrenInSubtree(Accessible* aAccessible);
+
+ /**
+ * Return true if the document is a target of document loading events
+ * (for example, state busy change or document reload events).
+ *
+ * Rules: The root chrome document accessible is never an event target
+ * (for example, Firefox UI window). If the sub document is loaded within its
+ * parent document then the parent document is a target only (aka events
+ * coalescence).
+ */
+ bool IsLoadEventTarget() const;
+
+ /*
+ * Set the object responsible for communicating with the main process on
+ * behalf of this document.
+ */
+ void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = aIPCDoc; }
+
+ friend class DocAccessibleChildBase;
+
+ /**
+ * Used to fire scrolling end event after page scroll.
+ *
+ * @param aTimer [in] the timer object
+ * @param aClosure [in] the document accessible where scrolling happens
+ */
+ static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
+
+protected:
+
+ /**
+ * State and property flags, kept by mDocFlags.
+ */
+ enum {
+ // Whether scroll listeners were added.
+ eScrollInitialized = 1 << 0,
+
+ // Whether the document is a tab document.
+ eTabDocument = 1 << 1
+ };
+
+ /**
+ * Cache of accessibles within this document accessible.
+ */
+ AccessibleHashtable mAccessibleCache;
+ nsDataHashtable<nsPtrHashKey<const nsINode>, Accessible*>
+ mNodeToAccessibleMap;
+
+ nsIDocument* mDocumentNode;
+ nsCOMPtr<nsITimer> mScrollWatchTimer;
+ uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events
+
+ /**
+ * Bit mask of document load states (@see LoadState).
+ */
+ uint32_t mLoadState : 3;
+
+ /**
+ * Bit mask of other states and props.
+ */
+ uint32_t mDocFlags : 28;
+
+ /**
+ * Type of document load event fired after the document is loaded completely.
+ */
+ uint32_t mLoadEventType;
+
+ /**
+ * Reference to anchor jump element.
+ */
+ nsCOMPtr<nsIContent> mAnchorJumpElm;
+
+ /**
+ * A generic state (see items below) before the attribute value was changed.
+ * @see AttributeWillChange and AttributeChanged notifications.
+ */
+ union {
+ // ARIA attribute value
+ nsIAtom* mARIAAttrOldValue;
+
+ // True if the accessible state bit was on
+ bool mStateBitWasOn;
+ };
+
+ nsTArray<RefPtr<DocAccessible> > mChildDocuments;
+
+ /**
+ * The virtual cursor of the document.
+ */
+ RefPtr<nsAccessiblePivot> mVirtualCursor;
+
+ /**
+ * A storage class for pairing content with one of its relation attributes.
+ */
+ class AttrRelProvider
+ {
+ public:
+ AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) :
+ mRelAttr(aRelAttr), mContent(aContent) { }
+
+ nsIAtom* mRelAttr;
+ nsCOMPtr<nsIContent> mContent;
+
+ private:
+ AttrRelProvider();
+ AttrRelProvider(const AttrRelProvider&);
+ AttrRelProvider& operator =(const AttrRelProvider&);
+ };
+
+ /**
+ * The cache of IDs pointed by relation attributes.
+ */
+ typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
+ nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
+ mDependentIDsHash;
+
+ friend class RelatedAccIterator;
+
+ /**
+ * Used for our caching algorithm. We store the list of nodes that should be
+ * invalidated.
+ *
+ * @see ProcessInvalidationList
+ */
+ nsTArray<RefPtr<nsIContent>> mInvalidationList;
+
+ /**
+ * Holds a list of aria-owns relocations.
+ */
+ nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<RefPtr<Accessible> > >
+ mARIAOwnsHash;
+
+ /**
+ * Used to process notification from core and accessible events.
+ */
+ RefPtr<NotificationController> mNotificationController;
+ friend class EventTree;
+ friend class NotificationController;
+
+private:
+
+ nsIPresShell* mPresShell;
+
+ // Exclusively owned by IPDL so don't manually delete it!
+ DocAccessibleChild* mIPCDoc;
+};
+
+inline DocAccessible*
+Accessible::AsDoc()
+{
+ return IsDoc() ? static_cast<DocAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/FormControlAccessible.cpp b/accessible/generic/FormControlAccessible.cpp
new file mode 100644
index 0000000000..0f27500702
--- /dev/null
+++ b/accessible/generic/FormControlAccessible.cpp
@@ -0,0 +1,193 @@
+/* -*- 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/. */
+
+// NOTE: alphabetically ordered
+
+#include "FormControlAccessible.h"
+#include "Role.h"
+
+#include "mozilla/FloatingPoint.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULControlElement.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+template class mozilla::a11y::ProgressMeterAccessible<1>;
+template class mozilla::a11y::ProgressMeterAccessible<100>;
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+template<int Max>
+role
+ProgressMeterAccessible<Max>::NativeRole()
+{
+ return roles::PROGRESSBAR;
+}
+
+template<int Max>
+uint64_t
+ProgressMeterAccessible<Max>::NativeState()
+{
+ uint64_t state = LeafAccessible::NativeState();
+
+ // An undetermined progressbar (i.e. without a value) has a mixed state.
+ nsAutoString attrValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+
+ if (attrValue.IsEmpty())
+ state |= states::MIXED;
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible<Max>: Widgets
+
+template<int Max>
+bool
+ProgressMeterAccessible<Max>::IsWidget() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible<Max>: Value
+
+template<int Max>
+void
+ProgressMeterAccessible<Max>::Value(nsString& aValue)
+{
+ 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('%');
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::MaxValue() const
+{
+ double value = LeafAccessible::MaxValue();
+ if (!IsNaN(value))
+ return value;
+
+ nsAutoString strValue;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::max, strValue)) {
+ nsresult result = NS_OK;
+ value = strValue.ToDouble(&result);
+ if (NS_SUCCEEDED(result))
+ return value;
+ }
+
+ return Max;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::MinValue() const
+{
+ double value = LeafAccessible::MinValue();
+ return IsNaN(value) ? 0 : value;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::Step() const
+{
+ double value = LeafAccessible::Step();
+ return IsNaN(value) ? 0 : value;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::CurValue() const
+{
+ double value = LeafAccessible::CurValue();
+ if (!IsNaN(value))
+ return value;
+
+ nsAutoString attrValue;
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue))
+ return UnspecifiedNaN<double>();
+
+ nsresult error = NS_OK;
+ value = attrValue.ToDouble(&error);
+ return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
+}
+
+template<int Max>
+bool
+ProgressMeterAccessible<Max>::SetCurValue(double aValue)
+{
+ return false; // progress meters are readonly.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+RadioButtonAccessible::
+ RadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+uint8_t
+RadioButtonAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+RadioButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("select");
+}
+
+bool
+RadioButtonAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+role
+RadioButtonAccessible::NativeRole()
+{
+ return roles::RADIOBUTTON;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButtonAccessible: Widgets
+
+bool
+RadioButtonAccessible::IsWidget() const
+{
+ return true;
+}
diff --git a/accessible/generic/FormControlAccessible.h b/accessible/generic/FormControlAccessible.h
new file mode 100644
index 0000000000..59844e5530
--- /dev/null
+++ b/accessible/generic/FormControlAccessible.h
@@ -0,0 +1,76 @@
+/* -*- 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_FormControlAccessible_H_
+#define MOZILLA_A11Y_FormControlAccessible_H_
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Generic class used for progress meters.
+ */
+template<int Max>
+class ProgressMeterAccessible : public LeafAccessible
+{
+public:
+ ProgressMeterAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+ {
+ // Ignore 'ValueChange' DOM event in lieu of @value attribute change
+ // notifications.
+ mStateFlags |= eHasNumericValue | eIgnoreDOMUIEvent;
+ mType = eProgressType;
+ }
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() 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 ~ProgressMeterAccessible() {}
+};
+
+/**
+ * Generic class used for radio buttons.
+ */
+class RadioButtonAccessible : public LeafAccessible
+{
+
+public:
+ RadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ enum { eAction_Click = 0 };
+
+ // Widgets
+ virtual bool IsWidget() const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/HyperTextAccessible-inl.h b/accessible/generic/HyperTextAccessible-inl.h
new file mode 100644
index 0000000000..1e8deac5da
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible-inl.h
@@ -0,0 +1,180 @@
+/* -*- 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_HyperTextAccessible_inl_h__
+#define mozilla_a11y_HyperTextAccessible_inl_h__
+
+#include "HyperTextAccessible.h"
+
+#include "nsAccUtils.h"
+
+#include "nsIClipboard.h"
+#include "nsIEditor.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPlaintextEditor.h"
+#include "nsFrameSelection.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline bool
+HyperTextAccessible::IsValidOffset(int32_t aOffset)
+{
+ index_t offset = ConvertMagicOffset(aOffset);
+ return offset.IsValid() && offset <= CharacterCount();
+}
+
+inline bool
+HyperTextAccessible::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();
+}
+
+inline void
+HyperTextAccessible::SetCaretOffset(int32_t aOffset)
+{
+ SetSelectionRange(aOffset, aOffset);
+ // XXX: Force cache refresh until a good solution for AT emulation of user
+ // input is implemented (AccessFu caret movement).
+ SelectionMgr()->UpdateCaretOffset(this, aOffset);
+}
+
+inline bool
+HyperTextAccessible::AddToSelection(int32_t aStartOffset, int32_t aEndOffset)
+{
+ dom::Selection* domSel = DOMSelection();
+ return domSel &&
+ SetSelectionBoundsAt(domSel->RangeCount(), aStartOffset, aEndOffset);
+}
+
+inline void
+HyperTextAccessible::ReplaceText(const nsAString& aText)
+{
+ // We need to call DeleteText() even if there is no contents because we need
+ // to ensure to move focus to the editor via SetSelectionRange() called in
+ // DeleteText().
+ DeleteText(0, CharacterCount());
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ nsCOMPtr<nsIPlaintextEditor> plaintextEditor(do_QueryInterface(editor));
+ if (!plaintextEditor) {
+ return;
+ }
+
+ // DeleteText() may cause inserting <br> element in some cases. Let's
+ // select all again and replace whole contents.
+ editor->SelectAll();
+
+ plaintextEditor->InsertText(aText);
+}
+
+inline void
+HyperTextAccessible::InsertText(const nsAString& aText, int32_t aPosition)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
+ if (peditor) {
+ SetSelectionRange(aPosition, aPosition);
+ peditor->InsertText(aText);
+ }
+}
+
+inline void
+HyperTextAccessible::CopyText(int32_t aStartPos, int32_t aEndPos)
+ {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->Copy();
+ }
+ }
+
+inline void
+HyperTextAccessible::CutText(int32_t aStartPos, int32_t aEndPos)
+ {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->Cut();
+ }
+ }
+
+inline void
+HyperTextAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+ }
+}
+
+inline void
+HyperTextAccessible::PasteText(int32_t aPosition)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aPosition, aPosition);
+ editor->Paste(nsIClipboard::kGlobalClipboard);
+ }
+}
+
+inline index_t
+HyperTextAccessible::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;
+}
+
+inline uint32_t
+HyperTextAccessible::AdjustCaretOffset(uint32_t aOffset) const
+{
+ // It is the same character offset when the caret is visually at the very
+ // end of a line or the start of a new line (soft line break). Getting text
+ // at the line should provide the line with the visual caret, otherwise
+ // screen readers will announce the wrong line as the user presses up or
+ // down arrow and land at the end of a line.
+ if (aOffset > 0 && IsCaretAtEndOfLine())
+ return aOffset - 1;
+
+ return aOffset;
+}
+
+inline bool
+HyperTextAccessible::IsCaretAtEndOfLine() const
+{
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ return frameSelection &&
+ frameSelection->GetHint() == CARET_ASSOCIATE_BEFORE;
+}
+
+inline already_AddRefed<nsFrameSelection>
+HyperTextAccessible::FrameSelection() const
+{
+ nsIFrame* frame = GetFrame();
+ return frame ? frame->GetFrameSelection() : nullptr;
+}
+
+inline dom::Selection*
+HyperTextAccessible::DOMSelection() const
+{
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ return frameSelection ? frameSelection->GetSelection(SelectionType::eNormal) :
+ nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp
new file mode 100644
index 0000000000..059c273726
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -0,0 +1,2230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* 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 "HyperTextAccessible-inl.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsIAccessibleTypes.h"
+#include "DocAccessible.h"
+#include "HTMLListAccessible.h"
+#include "Role.h"
+#include "States.h"
+#include "TextAttrs.h"
+#include "TextRange.h"
+#include "TreeWalker.h"
+
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsIDOMRange.h"
+#include "nsIEditingSession.h"
+#include "nsContainerFrame.h"
+#include "nsFrameSelection.h"
+#include "nsILineIterator.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIScrollableFrame.h"
+#include "nsIServiceManager.h"
+#include "nsITextControlElement.h"
+#include "nsIMathMLFrame.h"
+#include "nsTextFragment.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/MathAlgorithms.h"
+#include "gfxSkipChars.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HyperTextAccessible::
+ HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) :
+ AccessibleWrap(aNode, aDoc)
+{
+ mType = eHyperTextType;
+ mGenericTypes |= eHyperText;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessible, Accessible)
+
+role
+HyperTextAccessible::NativeRole()
+{
+ a11y::role r = GetAccService()->MarkupRole(mContent);
+ if (r != roles::NOTHING)
+ return r;
+
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->GetType() == nsGkAtoms::inlineFrame)
+ return roles::TEXT;
+
+ return roles::TEXT_CONTAINER;
+}
+
+uint64_t
+HyperTextAccessible::NativeState()
+{
+ uint64_t states = AccessibleWrap::NativeState();
+
+ if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
+ states |= states::EDITABLE;
+
+ } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
+ // We want <article> to behave like a document in terms of readonly state.
+ states |= states::READONLY;
+ }
+
+ if (HasChildren())
+ states |= states::SELECTABLE_TEXT;
+
+ return states;
+}
+
+nsIntRect
+HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame,
+ uint32_t aStartRenderedOffset,
+ uint32_t aEndRenderedOffset)
+{
+ nsPresContext* presContext = mDoc->PresContext();
+ if (aFrame->GetType() != nsGkAtoms::textFrame) {
+ return aFrame->GetScreenRectInAppUnits().
+ ToNearestPixels(presContext->AppUnitsPerDevPixel());
+ }
+
+ // Substring must be entirely within the same text node.
+ int32_t startContentOffset, endContentOffset;
+ nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+ rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ nsIFrame *frame;
+ int32_t startContentOffsetInFrame;
+ // Get the right frame continuation -- not really a child, but a sibling of
+ // the primary frame passed in
+ rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false,
+ &startContentOffsetInFrame, &frame);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ nsRect screenRect;
+ while (frame && startContentOffset < endContentOffset) {
+ // Start with this frame's screen rect, which we will shrink based on
+ // the substring we care about within it. We will then add that frame to
+ // the total screenRect we are returning.
+ nsRect frameScreenRect = frame->GetScreenRectInAppUnits();
+
+ // Get the length of the substring in this frame that we want the bounds for
+ int32_t startFrameTextOffset, endFrameTextOffset;
+ frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
+ int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
+ int32_t seekLength = endContentOffset - startContentOffset;
+ int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength);
+
+ // Add the point where the string starts to the frameScreenRect
+ nsPoint frameTextStartPoint;
+ rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ // Use the point for the end offset to calculate the width
+ nsPoint frameTextEndPoint;
+ rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
+ frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
+
+ screenRect.UnionRect(frameScreenRect, screenRect);
+
+ // Get ready to loop back for next frame continuation
+ startContentOffset += frameSubStringLength;
+ startContentOffsetInFrame = 0;
+ frame = frame->GetNextContinuation();
+ }
+
+ return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
+}
+
+void
+HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aText)
+{
+ 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;
+
+ if (startChildIdx == endChildIdx) {
+ int32_t childOffset = GetChildOffset(startChildIdx);
+ if (childOffset == -1)
+ return;
+
+ Accessible* child = GetChildAt(startChildIdx);
+ child->AppendTextTo(aText, startOffset - childOffset,
+ endOffset - startOffset);
+ return;
+ }
+
+ int32_t startChildOffset = GetChildOffset(startChildIdx);
+ if (startChildOffset == -1)
+ return;
+
+ Accessible* startChild = GetChildAt(startChildIdx);
+ startChild->AppendTextTo(aText, startOffset - startChildOffset);
+
+ for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
+ Accessible* child = GetChildAt(childIdx);
+ child->AppendTextTo(aText);
+ }
+
+ int32_t endChildOffset = GetChildOffset(endChildIdx);
+ if (endChildOffset == -1)
+ return;
+
+ Accessible* endChild = GetChildAt(endChildIdx);
+ endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
+}
+
+uint32_t
+HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+ bool aIsEndOffset) const
+{
+ if (!aNode)
+ return 0;
+
+ uint32_t offset = 0;
+ nsINode* findNode = nullptr;
+
+ if (aNodeOffset == -1) {
+ findNode = aNode;
+
+ } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ // For text nodes, aNodeOffset comes in as a character offset
+ // Text offset will be added at the end, if we find the offset in this hypertext
+ // We want the "skipped" offset into the text (rendered text without the extra whitespace)
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ NS_ENSURE_TRUE(frame, 0);
+
+ nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ findNode = aNode;
+
+ } else {
+ // findNode could be null if aNodeOffset == # of child nodes, which means
+ // one of two things:
+ // 1) there are no children, and the passed-in node is not mContent -- use
+ // parentContent for the node to find
+ // 2) there are no children and the passed-in node is mContent, which means
+ // we're an empty nsIAccessibleText
+ // 3) there are children and we're at the end of the children
+
+ findNode = aNode->GetChildAt(aNodeOffset);
+ if (!findNode) {
+ if (aNodeOffset == 0) {
+ if (aNode == GetNode()) {
+ // Case #1: this accessible has no children and thus has empty text,
+ // we can only be at hypertext offset 0.
+ return 0;
+ }
+
+ // Case #2: there are no children, we're at this node.
+ findNode = aNode;
+ } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
+ // Case #3: we're after the last child, get next node to this one.
+ for (nsINode* tmpNode = aNode;
+ !findNode && tmpNode && tmpNode != mContent;
+ tmpNode = tmpNode->GetParent()) {
+ findNode = tmpNode->GetNextSibling();
+ }
+ }
+ }
+ }
+
+ // Get accessible for this findNode, or if that node isn't accessible, use the
+ // accessible for the next DOM node which has one (based on forward depth first search)
+ Accessible* descendant = nullptr;
+ if (findNode) {
+ nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
+ if (findContent && findContent->IsHTMLElement() &&
+ findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
+ findContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::mozeditorbogusnode,
+ nsGkAtoms::_true,
+ eIgnoreCase)) {
+ // This <br> is the hacky "bogus node" used when there is no text in a control
+ return 0;
+ }
+
+ descendant = mDoc->GetAccessible(findNode);
+ if (!descendant && findNode->IsContent()) {
+ Accessible* container = mDoc->GetContainerAccessible(findNode);
+ if (container) {
+ TreeWalker walker(container, findNode->AsContent(),
+ TreeWalker::eWalkContextTree);
+ descendant = walker.Next();
+ if (!descendant)
+ descendant = container;
+ }
+ }
+ }
+
+ return TransformOffset(descendant, offset, aIsEndOffset);
+}
+
+uint32_t
+HyperTextAccessible::TransformOffset(Accessible* aDescendant,
+ uint32_t aOffset, bool aIsEndOffset) const
+{
+ // From the descendant, go up and get the immediate child of this hypertext.
+ uint32_t offset = aOffset;
+ Accessible* descendant = aDescendant;
+ while (descendant) {
+ Accessible* parent = descendant->Parent();
+ if (parent == this)
+ return 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;
+ }
+
+ // If the given a11y point cannot be mapped into offset relative this hypertext
+ // offset then return length as fallback value.
+ return CharacterCount();
+}
+
+/**
+ * GetElementAsContentOf() returns a content representing an element which is
+ * or includes aNode.
+ *
+ * XXX This method is enough to retrieve ::before or ::after pseudo element.
+ * So, if you want to use this for other purpose, you might need to check
+ * ancestors too.
+ */
+static nsIContent* GetElementAsContentOf(nsINode* aNode)
+{
+ if (aNode->IsElement()) {
+ return aNode->AsContent();
+ }
+ nsIContent* parent = aNode->GetParent();
+ return parent && parent->IsElement() ? parent : nullptr;
+}
+
+bool
+HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
+ nsRange* aRange)
+{
+ DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
+ if (!startPoint.node)
+ return false;
+
+ // HyperTextAccessible manages pseudo elements generated by ::before or
+ // ::after. However, contents of them are not in the DOM tree normally.
+ // Therefore, they are not selectable and editable. So, when this creates
+ // a DOM range, it should not start from nor end in any pseudo contents.
+
+ nsIContent* container = GetElementAsContentOf(startPoint.node);
+ DOMPoint startPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(startPoint, container);
+ aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
+
+ // If the caller wants collapsed range, let's collapse the range to its start.
+ if (aStartOffset == aEndOffset) {
+ aRange->Collapse(true);
+ return true;
+ }
+
+ DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
+ if (!endPoint.node)
+ return false;
+
+ if (startPoint.node != endPoint.node) {
+ container = GetElementAsContentOf(endPoint.node);
+ }
+
+ DOMPoint endPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(endPoint, container);
+ aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
+ return true;
+}
+
+DOMPoint
+HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
+{
+ // 0 offset is valid even if no children. In this case the associated editor
+ // is empty so return a DOM point for editor root element.
+ if (aOffset == 0) {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ bool isEmpty = false;
+ editor->GetDocumentIsEmpty(&isEmpty);
+ if (isEmpty) {
+ nsCOMPtr<nsIDOMElement> editorRootElm;
+ editor->GetRootElement(getter_AddRefs(editorRootElm));
+
+ nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
+ return DOMPoint(editorRoot, 0);
+ }
+ }
+ }
+
+ int32_t childIdx = GetChildIndexAtOffset(aOffset);
+ if (childIdx == -1)
+ return DOMPoint();
+
+ Accessible* child = GetChildAt(childIdx);
+ int32_t innerOffset = aOffset - GetChildOffset(childIdx);
+
+ // A text leaf case.
+ if (child->IsTextLeaf()) {
+ // The point is inside the text node. This is always true for any text leaf
+ // except a last child one. See assertion below.
+ if (aOffset < GetChildOffset(childIdx + 1)) {
+ nsIContent* content = child->GetContent();
+ int32_t idx = 0;
+ if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
+ innerOffset, &idx)))
+ return DOMPoint();
+
+ return DOMPoint(content, idx);
+ }
+
+ // Set the DOM point right after the text node.
+ MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
+ innerOffset = 1;
+ }
+
+ // Case of embedded object. The point is either before or after the element.
+ NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
+ nsINode* node = child->GetNode();
+ nsINode* parentNode = node->GetParentNode();
+ return parentNode ?
+ DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
+ DOMPoint();
+}
+
+DOMPoint
+HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
+ nsIContent* aElementContent)
+{
+ MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
+
+ // ::before pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForBefore()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::before must have parent element");
+ // The first child of its parent (i.e., immediately after the ::before) is
+ // good point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(), 0);
+ }
+
+ // ::after pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForAfter()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::after must have parent element");
+ // The end of its parent (i.e., immediately before the ::after) is good
+ // point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(),
+ aElementContent->GetParent()->GetChildCount());
+ }
+
+ return aDOMPoint;
+}
+
+uint32_t
+HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection,
+ nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType)
+{
+ NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine,
+ "eSelectBeginLine should only be used with eDirPrevious");
+
+ // Find a leaf accessible frame to start with. PeekOffset wants this.
+ HyperTextAccessible* text = this;
+ Accessible* child = nullptr;
+ int32_t innerOffset = aOffset;
+
+ do {
+ int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
+
+ // We can have an empty text leaf as our only child. Since empty text
+ // leaves are not accessible we then have no children, but 0 is a valid
+ // innerOffset.
+ if (childIdx == -1) {
+ NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
+ return DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext);
+ }
+
+ child = text->GetChildAt(childIdx);
+
+ // HTML list items may need special processing because PeekOffset doesn't
+ // work with list bullets.
+ if (text->IsHTMLListItem()) {
+ HTMLLIAccessible* li = text->AsHTMLListItem();
+ if (child == li->Bullet()) {
+ // XXX: the logic is broken for multichar bullets in moving by
+ // char/cluster/word cases.
+ if (text != this) {
+ return aDirection == eDirPrevious ?
+ TransformOffset(text, 0, false) :
+ TransformOffset(text, 1, true);
+ }
+ if (aDirection == eDirPrevious)
+ return 0;
+
+ uint32_t nextOffset = GetChildOffset(1);
+ if (nextOffset == 0)
+ return 0;
+
+ switch (aAmount) {
+ case eSelectLine:
+ case eSelectEndLine:
+ // Ask a text leaf next (if not empty) to the bullet for an offset
+ // since list item may be multiline.
+ return nextOffset < CharacterCount() ?
+ FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) :
+ nextOffset;
+
+ default:
+ return nextOffset;
+ }
+ }
+ }
+
+ innerOffset -= text->GetChildOffset(childIdx);
+
+ text = child->AsHyperText();
+ } while (text);
+
+ nsIFrame* childFrame = child->GetFrame();
+ if (!childFrame) {
+ NS_ERROR("No child frame");
+ return 0;
+ }
+
+ int32_t innerContentOffset = innerOffset;
+ if (child->IsTextLeaf()) {
+ NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!");
+ RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
+ }
+
+ nsIFrame* frameAtOffset = childFrame;
+ int32_t unusedOffsetInFrame = 0;
+ childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
+ &unusedOffsetInFrame,
+ &frameAtOffset);
+
+ const bool kIsJumpLinesOk = true; // okay to jump lines
+ const bool kIsScrollViewAStop = false; // do not stop at scroll views
+ const bool kIsKeyboardSelect = true; // is keyboard selection
+ const bool kIsVisualBidi = false; // use visual order for bidi text
+ nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset,
+ nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop,
+ kIsKeyboardSelect, kIsVisualBidi,
+ false, aWordMovementType);
+ nsresult rv = frameAtOffset->PeekOffset(&pos);
+
+ // PeekOffset fails on last/first lines of the text in certain cases.
+ if (NS_FAILED(rv) && aAmount == eSelectLine) {
+ pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
+ frameAtOffset->PeekOffset(&pos);
+ }
+ if (!pos.mResultContent) {
+ NS_ERROR("No result content!");
+ return 0;
+ }
+
+ // Turn the resulting DOM point into an offset.
+ uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
+ pos.mContentOffset,
+ aDirection == eDirNext);
+
+ if (aDirection == eDirPrevious) {
+ // If we reached the end during search, this means we didn't find the DOM point
+ // and we're actually at the start of the paragraph
+ if (hyperTextOffset == CharacterCount())
+ return 0;
+
+ // PeekOffset stops right before bullet so return 0 to workaround it.
+ if (IsHTMLListItem() && aAmount == eSelectBeginLine &&
+ hyperTextOffset > 0) {
+ Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1);
+ if (prevOffsetChild == AsHTMLListItem()->Bullet())
+ return 0;
+ }
+ }
+
+ return hyperTextOffset;
+}
+
+uint32_t
+HyperTextAccessible::FindLineBoundary(uint32_t aOffset,
+ EWhichLineBoundary aWhichLineBoundary)
+{
+ // Note: empty last line doesn't have own frame (a previous line contains '\n'
+ // character instead) thus when it makes a difference we need to process this
+ // case separately (otherwise operations are performed on previous line).
+ switch (aWhichLineBoundary) {
+ case ePrevLineBegin: {
+ // Fetch a previous line and move to its start (as arrow up and home keys
+ // were pressed).
+ if (IsEmptyLastLineOffset(aOffset))
+ return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+
+ uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
+ return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
+ }
+
+ case ePrevLineEnd: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset - 1;
+
+ // If offset is at first line then return 0 (first line start).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+ if (tmpOffset == 0)
+ return 0;
+
+ // Otherwise move to end of previous line (as arrow up and end keys were
+ // pressed).
+ tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
+ return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
+ }
+
+ case eThisLineBegin:
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to begin of the current line (as home key was pressed).
+ return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+
+ case eThisLineEnd:
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to end of the current line (as end key was pressed).
+ return FindOffset(aOffset, eDirNext, eSelectEndLine);
+
+ case eNextLineBegin: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to begin of the next line if any (arrow down and home keys),
+ // otherwise end of the current line (arrow down only).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
+ if (tmpOffset == CharacterCount())
+ return tmpOffset;
+
+ return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
+ }
+
+ case eNextLineEnd: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to next line end (as down arrow and end key were pressed).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
+ if (tmpOffset == CharacterCount())
+ return tmpOffset;
+
+ return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
+ }
+ }
+
+ return 0;
+}
+
+void
+HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ index_t convertedOffset = ConvertMagicOffset(aOffset);
+ if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return;
+ }
+
+ uint32_t adjustedOffset = convertedOffset;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ if (convertedOffset != 0)
+ CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START: {
+ // If the offset is a word start (except text length offset) then move
+ // backward to find a start offset (end offset is the given offset).
+ // Otherwise move backward twice to find both start and end offsets.
+ if (adjustedOffset == CharacterCount()) {
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ } else {
+ *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
+ if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) {
+ *aEndOffset = *aStartOffset;
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ }
+ }
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+
+ case nsIAccessibleText::BOUNDARY_WORD_END: {
+ // Move word backward twice to find start and end offsets.
+ *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END: {
+ *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
+ int32_t tmpOffset = *aEndOffset;
+ // Adjust offset if line is wrapped.
+ if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset))
+ tmpOffset--;
+
+ *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+ }
+}
+
+void
+HyperTextAccessible::TextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
+ if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
+ NS_ERROR("Wrong given offset!");
+ return;
+ }
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ // Return no char if caret is at the end of wrapped line (case of no line
+ // end character). Returning a next line char is confusing for AT.
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine())
+ *aStartOffset = *aEndOffset = adjustedOffset;
+ else
+ CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_END:
+ // Ignore the spec and follow what WebKitGtk does because Orca expects it,
+ // i.e. return a next word at word end offset of the current word
+ // (WebKitGtk behavior) instead the current word (AKT spec).
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ // In contrast to word end boundary we follow the spec here.
+ *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+}
+
+void
+HyperTextAccessible::TextAfterOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ index_t convertedOffset = ConvertMagicOffset(aOffset);
+ if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return;
+ }
+
+ uint32_t adjustedOffset = convertedOffset;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ // If caret is at the end of wrapped line (case of no line end character)
+ // then char after the offset is a first char at next line.
+ if (adjustedOffset >= CharacterCount())
+ *aStartOffset = *aEndOffset = CharacterCount();
+ else
+ CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START:
+ // Move word forward twice to find start and end offsets.
+ *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_END:
+ // If the offset is a word end (except 0 offset) then move forward to find
+ // end offset (start offset is the given offset). Otherwise move forward
+ // twice to find both start and end offsets.
+ if (convertedOffset == 0) {
+ *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
+ } else {
+ *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ if (*aStartOffset != static_cast<int32_t>(convertedOffset)) {
+ *aStartOffset = *aEndOffset;
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
+ }
+ }
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
+ *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END:
+ *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ // 1. Get each attribute and its ranges one after another.
+ // 2. As we get each new attribute, we pass the current start and end offsets
+ // as in/out parameters. In other words, as attributes are collected,
+ // the attribute range itself can only stay the same or get smaller.
+
+ *aStartOffset = *aEndOffset = 0;
+ index_t offset = ConvertMagicOffset(aOffset);
+ if (!offset.IsValid() || offset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ Accessible* accAtOffset = GetChildAtOffset(offset);
+ if (!accAtOffset) {
+ // 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) {
+ TextAttrsMgr textAttrsMgr(this);
+ textAttrsMgr.GetAttributes(attributes);
+ }
+ return attributes.forget();
+ }
+ return nullptr;
+ }
+
+ int32_t accAtOffsetIdx = accAtOffset->IndexInParent();
+ uint32_t startOffset = GetChildOffset(accAtOffsetIdx);
+ uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1);
+ int32_t offsetInAcc = offset - startOffset;
+
+ TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset,
+ accAtOffsetIdx);
+ textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset);
+
+ // Compute spelling attributes on text accessible only.
+ nsIFrame *offsetFrame = accAtOffset->GetFrame();
+ if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) {
+ int32_t nodeOffset = 0;
+ RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset);
+
+ // Set 'misspelled' text attribute.
+ GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset,
+ &startOffset, &endOffset, attributes);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+ return attributes.forget();
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::DefaultTextAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ TextAttrsMgr textAttrsMgr(this);
+ textAttrsMgr.GetAttributes(attributes);
+ return attributes.forget();
+}
+
+int32_t
+HyperTextAccessible::GetLevelInternal()
+{
+ if (mContent->IsHTMLElement(nsGkAtoms::h1))
+ return 1;
+ if (mContent->IsHTMLElement(nsGkAtoms::h2))
+ return 2;
+ if (mContent->IsHTMLElement(nsGkAtoms::h3))
+ return 3;
+ if (mContent->IsHTMLElement(nsGkAtoms::h4))
+ return 4;
+ if (mContent->IsHTMLElement(nsGkAtoms::h5))
+ return 5;
+ if (mContent->IsHTMLElement(nsGkAtoms::h6))
+ return 6;
+
+ return AccessibleWrap::GetLevelInternal();
+}
+
+void
+HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes)
+{
+ // Add MathML xmlroles based on the position inside the parent.
+ Accessible* parent = Parent();
+ if (parent) {
+ switch (parent->Role()) {
+ case roles::MATHML_CELL:
+ case roles::MATHML_ENCLOSED:
+ case roles::MATHML_ERROR:
+ case roles::MATHML_MATH:
+ case roles::MATHML_ROW:
+ case roles::MATHML_SQUARE_ROOT:
+ case roles::MATHML_STYLE:
+ if (Role() == roles::MATHML_OPERATOR) {
+ // This is an operator inside an <mrow> (or an inferred <mrow>).
+ // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
+ // XXX We should probably do something similar for MATHML_FENCED, but
+ // operators do not appear in the accessible tree. See bug 1175747.
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
+ if (mathMLFrame) {
+ nsEmbellishData embellishData;
+ mathMLFrame->GetEmbellishData(embellishData);
+ if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) {
+ if (!PrevSibling()) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::open_fence);
+ } else if (!NextSibling()) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::close_fence);
+ }
+ }
+ if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::separator_);
+ }
+ }
+ }
+ break;
+ case roles::MATHML_FRACTION:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ?
+ nsGkAtoms::numerator :
+ nsGkAtoms::denominator);
+ break;
+ case roles::MATHML_ROOT:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::root_index);
+ break;
+ case roles::MATHML_SUB:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::subscript);
+ break;
+ case roles::MATHML_SUP:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::superscript);
+ break;
+ case roles::MATHML_SUB_SUP: {
+ int32_t index = IndexInParent();
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ index == 0 ? nsGkAtoms::base :
+ (index == 1 ? nsGkAtoms::subscript :
+ nsGkAtoms::superscript));
+ } break;
+ case roles::MATHML_UNDER:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::underscript);
+ break;
+ case roles::MATHML_OVER:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::overscript);
+ break;
+ case roles::MATHML_UNDER_OVER: {
+ int32_t index = IndexInParent();
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ index == 0 ? nsGkAtoms::base :
+ (index == 1 ? nsGkAtoms::underscript :
+ nsGkAtoms::overscript));
+ } break;
+ case roles::MATHML_MULTISCRIPTS: {
+ // Get the <multiscripts> base.
+ nsIContent* child;
+ bool baseFound = false;
+ for (child = parent->GetContent()->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsMathMLElement()) {
+ baseFound = true;
+ break;
+ }
+ }
+ if (baseFound) {
+ nsIContent* content = GetContent();
+ if (child == content) {
+ // We are the base.
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::base);
+ } else {
+ // Browse the list of scripts to find us and determine our type.
+ bool postscript = true;
+ bool subscript = true;
+ for (child = child->GetNextSibling(); child;
+ child = child->GetNextSibling()) {
+ if (!child->IsMathMLElement())
+ continue;
+ if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) {
+ postscript = false;
+ subscript = true;
+ continue;
+ }
+ if (child == content) {
+ if (postscript) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ subscript ?
+ nsGkAtoms::subscript :
+ nsGkAtoms::superscript);
+ } else {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ subscript ?
+ nsGkAtoms::presubscript :
+ nsGkAtoms::presuperscript);
+ }
+ break;
+ }
+ subscript = !subscript;
+ }
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ AccessibleWrap::NativeAttributes();
+
+ // 'formatting' attribute is deprecated, 'display' attribute should be
+ // instead.
+ nsIFrame *frame = GetFrame();
+ if (frame && frame->GetType() == nsGkAtoms::blockFrame) {
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
+ NS_LITERAL_STRING("block"), unused);
+ }
+
+ if (FocusMgr()->IsFocused(this)) {
+ int32_t lineNumber = CaretLineNumber();
+ if (lineNumber >= 1) {
+ nsAutoString strLineNumber;
+ strLineNumber.AppendInt(lineNumber);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
+ }
+ }
+
+ if (HasOwnContent()) {
+ GetAccService()->MarkupAttributes(mContent, attributes);
+ if (mContent->IsMathMLElement())
+ SetMathMLXMLRoles(attributes);
+ }
+
+ return attributes.forget();
+}
+
+nsIAtom*
+HyperTextAccessible::LandmarkRole() const
+{
+ if (!HasOwnContent())
+ return nullptr;
+
+ // For the html landmark elements we expose them like we do ARIA landmarks to
+ // make AT navigation schemes "just work".
+ if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
+ return nsGkAtoms::navigation;
+ }
+
+ if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
+ nsGkAtoms::footer)) {
+ // Only map header and footer if they are not descendants of an article
+ // or section tag.
+ nsIContent* parent = mContent->GetParent();
+ while (parent) {
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) {
+ break;
+ }
+ parent = parent->GetParent();
+ }
+
+ // No article or section elements found.
+ if (!parent) {
+ if (mContent->IsHTMLElement(nsGkAtoms::header)) {
+ return nsGkAtoms::banner;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
+ return nsGkAtoms::contentinfo;
+ }
+ }
+ return nullptr;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::aside)) {
+ return nsGkAtoms::complementary;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::main)) {
+ return nsGkAtoms::main;
+ }
+
+ return nullptr;
+}
+
+int32_t
+HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
+{
+ nsIFrame* hyperFrame = GetFrame();
+ if (!hyperFrame)
+ return -1;
+
+ nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
+ this);
+
+ nsPresContext* presContext = mDoc->PresContext();
+ nsPoint coordsInAppUnits =
+ ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
+
+ nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
+ if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
+ return -1; // Not found
+
+ nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
+ coordsInAppUnits.y - frameScreenRect.y);
+
+ // Go through the frames to check if each one has the point.
+ // When one does, add up the character offsets until we have a match
+
+ // We have an point in an accessible child of this, now we need to add up the
+ // offsets before it to what we already have
+ int32_t offset = 0;
+ uint32_t childCount = ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* childAcc = mChildren[childIdx];
+
+ nsIFrame *primaryFrame = childAcc->GetFrame();
+ NS_ENSURE_TRUE(primaryFrame, -1);
+
+ nsIFrame *frame = primaryFrame;
+ while (frame) {
+ nsIContent *content = frame->GetContent();
+ NS_ENSURE_TRUE(content, -1);
+ nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
+ nsSize frameSize = frame->GetSize();
+ if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
+ // Finished
+ if (frame->GetType() == nsGkAtoms::textFrame) {
+ nsIFrame::ContentOffsets contentOffsets =
+ frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
+ if (contentOffsets.IsNull() || contentOffsets.content != content) {
+ return -1; // Not found
+ }
+ uint32_t addToOffset;
+ nsresult rv = ContentToRenderedOffset(primaryFrame,
+ contentOffsets.offset,
+ &addToOffset);
+ NS_ENSURE_SUCCESS(rv, -1);
+ offset += addToOffset;
+ }
+ return offset;
+ }
+ frame = frame->GetNextContinuation();
+ }
+
+ offset += nsAccUtils::TextLength(childAcc);
+ }
+
+ return -1; // Not found
+}
+
+nsIntRect
+HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType)
+{
+ 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 nsIntRect();
+ }
+
+
+ int32_t childIdx = GetChildIndexAtOffset(startOffset);
+ if (childIdx == -1)
+ return nsIntRect();
+
+ nsIntRect bounds;
+ int32_t prevOffset = GetChildOffset(childIdx);
+ int32_t offset1 = startOffset - prevOffset;
+
+ while (childIdx < static_cast<int32_t>(ChildCount())) {
+ nsIFrame* frame = GetChildAt(childIdx++)->GetFrame();
+ if (!frame) {
+ NS_NOTREACHED("No frame for a child!");
+ continue;
+ }
+
+ int32_t nextOffset = GetChildOffset(childIdx);
+ if (nextOffset >= static_cast<int32_t>(endOffset)) {
+ bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
+ endOffset - prevOffset));
+ break;
+ }
+
+ bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
+ nextOffset - prevOffset));
+
+ prevOffset = nextOffset;
+ offset1 = 0;
+ }
+
+ nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
+ return bounds;
+}
+
+already_AddRefed<nsIEditor>
+HyperTextAccessible::GetEditor() const
+{
+ if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
+ // If we're inside an editable container, then return that container's editor
+ Accessible* ancestor = Parent();
+ while (ancestor) {
+ HyperTextAccessible* hyperText = ancestor->AsHyperText();
+ if (hyperText) {
+ // Recursion will stop at container doc because it has its own impl
+ // of GetEditor()
+ return hyperText->GetEditor();
+ }
+
+ ancestor = ancestor->Parent();
+ }
+
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
+ nsCOMPtr<nsIEditingSession> editingSession;
+ docShell->GetEditingSession(getter_AddRefs(editingSession));
+ if (!editingSession)
+ return nullptr; // No editing session interface
+
+ nsCOMPtr<nsIEditor> editor;
+ nsIDocument* docNode = mDoc->DocumentNode();
+ editingSession->GetEditorForWindow(docNode->GetWindow(),
+ getter_AddRefs(editor));
+ return editor.forget();
+}
+
+/**
+ * =================== Caret & Selection ======================
+ */
+
+nsresult
+HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos)
+{
+ // Before setting the selection range, we need to ensure that the editor
+ // is initialized. (See bug 804927.)
+ // Otherwise, it's possible that lazy editor initialization will override
+ // the selection we set here and leave the caret at the end of the text.
+ // By calling GetEditor here, we ensure that editor initialization is
+ // completed before we set the selection.
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+
+ bool isFocusable = InteractiveState() & states::FOCUSABLE;
+
+ // If accessible is focusable then focus it before setting the selection to
+ // neglect control's selection changes on focus if any (for example, inputs
+ // that do select all on focus).
+ // some input controls
+ if (isFocusable)
+ TakeFocus();
+
+ dom::Selection* domSel = DOMSelection();
+ NS_ENSURE_STATE(domSel);
+
+ // Set up the selection.
+ for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--)
+ domSel->RemoveRange(domSel->GetRangeAt(idx));
+ SetSelectionBoundsAt(0, aStartPos, aEndPos);
+
+ // When selection is done, move the focus to the selection if accessible is
+ // not focusable. That happens when selection is set within hypertext
+ // accessible.
+ if (isFocusable)
+ return NS_OK;
+
+ nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
+ if (DOMFocusManager) {
+ NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
+ nsIDocument* docNode = mDoc->DocumentNode();
+ NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow();
+ nsCOMPtr<nsIDOMElement> result;
+ DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
+ nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
+ }
+
+ return NS_OK;
+}
+
+int32_t
+HyperTextAccessible::CaretOffset() const
+{
+ // Not focused focusable accessible except document accessible doesn't have
+ // a caret.
+ if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
+ (InteractiveState() & states::FOCUSABLE)) {
+ return -1;
+ }
+
+ // Check cached value.
+ int32_t caretOffset = -1;
+ HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
+
+ // Use cached value if it corresponds to this accessible.
+ if (caretOffset != -1) {
+ if (text == this)
+ return caretOffset;
+
+ nsINode* textNode = text->GetNode();
+ // Ignore offset if cached accessible isn't a text leaf.
+ if (nsCoreUtils::IsAncestorOf(GetNode(), textNode))
+ return TransformOffset(text,
+ textNode->IsNodeOfType(nsINode::eTEXT) ? caretOffset : 0, false);
+ }
+
+ // No caret if the focused node is not inside this DOM node and this DOM node
+ // is not inside of focused node.
+ FocusManager::FocusDisposition focusDisp =
+ FocusMgr()->IsInOrContainsFocus(this);
+ if (focusDisp == FocusManager::eNone)
+ return -1;
+
+ // Turn the focus node and offset of the selection into caret hypretext
+ // offset.
+ dom::Selection* domSel = DOMSelection();
+ NS_ENSURE_TRUE(domSel, -1);
+
+ nsINode* focusNode = domSel->GetFocusNode();
+ uint32_t focusOffset = domSel->FocusOffset();
+
+ // No caret if this DOM node is inside of focused node but the selection's
+ // focus point is not inside of this DOM node.
+ if (focusDisp == FocusManager::eContainedByFocus) {
+ nsINode* resultNode =
+ nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
+
+ nsINode* thisNode = GetNode();
+ if (resultNode != thisNode &&
+ !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
+ return -1;
+ }
+
+ return DOMPointToOffset(focusNode, focusOffset);
+}
+
+int32_t
+HyperTextAccessible::CaretLineNumber()
+{
+ // Provide the line number for the caret, relative to the
+ // currently focused node. Use a 1-based index
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (!frameSelection)
+ return -1;
+
+ dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal);
+ if (!domSel)
+ return - 1;
+
+ nsINode* caretNode = domSel->GetFocusNode();
+ if (!caretNode || !caretNode->IsContent())
+ return -1;
+
+ nsIContent* caretContent = caretNode->AsContent();
+ if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
+ return -1;
+
+ int32_t returnOffsetUnused;
+ uint32_t caretOffset = domSel->FocusOffset();
+ CaretAssociationHint hint = frameSelection->GetHint();
+ nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
+ hint, &returnOffsetUnused);
+ NS_ENSURE_TRUE(caretFrame, -1);
+
+ int32_t lineNumber = 1;
+ nsAutoLineIterator lineIterForCaret;
+ nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr;
+ while (caretFrame) {
+ if (hyperTextContent == caretFrame->GetContent()) {
+ return lineNumber; // Must be in a single line hyper text, there is no line iterator
+ }
+ nsContainerFrame *parentFrame = caretFrame->GetParent();
+ if (!parentFrame)
+ break;
+
+ // Add lines for the sibling frames before the caret
+ nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild();
+ while (sibling && sibling != caretFrame) {
+ nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
+ if (lineIterForSibling) {
+ // For the frames before that grab all the lines
+ int32_t addLines = lineIterForSibling->GetNumLines();
+ lineNumber += addLines;
+ }
+ sibling = sibling->GetNextSibling();
+ }
+
+ // Get the line number relative to the container with lines
+ if (!lineIterForCaret) { // Add the caret line just once
+ lineIterForCaret = parentFrame->GetLineIterator();
+ if (lineIterForCaret) {
+ // Ancestor of caret
+ int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
+ lineNumber += addLines;
+ }
+ }
+
+ caretFrame = parentFrame;
+ }
+
+ NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
+ return lineNumber;
+}
+
+LayoutDeviceIntRect
+HyperTextAccessible::GetCaretRect(nsIWidget** aWidget)
+{
+ *aWidget = nullptr;
+
+ RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret();
+ NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
+
+ bool isVisible = caret->IsVisible();
+ if (!isVisible)
+ return LayoutDeviceIntRect();
+
+ nsRect rect;
+ nsIFrame* frame = caret->GetGeometry(&rect);
+ if (!frame || rect.IsEmpty())
+ return LayoutDeviceIntRect();
+
+ nsPoint offset;
+ // Offset from widget origin to the frame origin, which includes chrome
+ // on the widget.
+ *aWidget = frame->GetNearestWidget(offset);
+ NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect());
+ rect.MoveBy(offset);
+
+ LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect(
+ rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()));
+ // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
+ caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset());
+
+ // Correct for character size, so that caret always matches the size of
+ // the character. This is important for font size transitions, and is
+ // necessary because the Gecko caret uses the previous character's size as
+ // the user moves forward in the text by character.
+ nsIntRect charRect = CharBounds(CaretOffset(),
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+ if (!charRect.IsEmpty()) {
+ caretRect.height -= charRect.y - caretRect.y;
+ caretRect.y = charRect.y;
+ }
+ return caretRect;
+}
+
+void
+HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
+ nsTArray<nsRange*>* aRanges)
+{
+ // Ignore selection if it is not visible.
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (!frameSelection ||
+ frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN)
+ return;
+
+ dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
+ if (!domSel)
+ return;
+
+ nsCOMPtr<nsINode> startNode = GetNode();
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ nsCOMPtr<nsIDOMElement> editorRoot;
+ editor->GetRootElement(getter_AddRefs(editorRoot));
+ startNode = do_QueryInterface(editorRoot);
+ }
+
+ if (!startNode)
+ return;
+
+ uint32_t childCount = startNode->GetChildCount();
+ nsresult rv = domSel->
+ GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Remove collapsed ranges
+ uint32_t numRanges = aRanges->Length();
+ for (uint32_t idx = 0; idx < numRanges; idx ++) {
+ if ((*aRanges)[idx]->Collapsed()) {
+ aRanges->RemoveElementAt(idx);
+ --numRanges;
+ --idx;
+ }
+ }
+}
+
+int32_t
+HyperTextAccessible::SelectionCount()
+{
+ nsTArray<nsRange*> ranges;
+ GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
+ return ranges.Length();
+}
+
+bool
+HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aStartOffset = *aEndOffset = 0;
+
+ nsTArray<nsRange*> ranges;
+ GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
+
+ uint32_t rangeCount = ranges.Length();
+ if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount))
+ return false;
+
+ nsRange* range = ranges[aSelectionNum];
+
+ // Get start and end points.
+ nsINode* startNode = range->GetStartParent();
+ nsINode* endNode = range->GetEndParent();
+ int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset();
+
+ // Make sure start is before end, by swapping DOM points. This occurs when
+ // the user selects backwards in the text.
+ int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset,
+ startNode, startOffset);
+ if (rangeCompare < 0) {
+ nsINode* tempNode = startNode;
+ startNode = endNode;
+ endNode = tempNode;
+ int32_t tempOffset = startOffset;
+ startOffset = endOffset;
+ endOffset = tempOffset;
+ }
+
+ if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
+ *aStartOffset = 0;
+ else
+ *aStartOffset = DOMPointToOffset(startNode, startOffset);
+
+ if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
+ *aEndOffset = CharacterCount();
+ else
+ *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
+ return true;
+}
+
+bool
+HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ 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 false;
+ }
+
+ dom::Selection* domSel = DOMSelection();
+ if (!domSel)
+ return false;
+
+ RefPtr<nsRange> range;
+ uint32_t rangeCount = domSel->RangeCount();
+ if (aSelectionNum == static_cast<int32_t>(rangeCount))
+ range = new nsRange(mContent);
+ else
+ range = domSel->GetRangeAt(aSelectionNum);
+
+ if (!range)
+ return false;
+
+ if (!OffsetsToDOMRange(startOffset, endOffset, range))
+ return false;
+
+ // If new range was created then add it, otherwise notify selection listeners
+ // that existing selection range was changed.
+ if (aSelectionNum == static_cast<int32_t>(rangeCount))
+ return NS_SUCCEEDED(domSel->AddRange(range));
+
+ domSel->RemoveRange(range);
+ return NS_SUCCEEDED(domSel->AddRange(range));
+}
+
+bool
+HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
+{
+ dom::Selection* domSel = DOMSelection();
+ if (!domSel)
+ return false;
+
+ if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount()))
+ return false;
+
+ domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum));
+ return true;
+}
+
+void
+HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType)
+{
+ RefPtr<nsRange> range = new nsRange(mContent);
+ if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
+ nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
+}
+
+void
+HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY)
+{
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
+ this);
+
+ RefPtr<nsRange> range = new nsRange(mContent);
+ if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
+ return;
+
+ nsPresContext* presContext = frame->PresContext();
+ nsPoint coordsInAppUnits =
+ ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
+
+ bool initialScrolled = false;
+ nsIFrame *parentFrame = frame;
+ while ((parentFrame = parentFrame->GetParent())) {
+ nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
+ if (scrollableFrame) {
+ if (!initialScrolled) {
+ // Scroll substring to the given point. Turn the point into percents
+ // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
+ nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
+ nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
+ nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
+
+ nsSize size(parentFrame->GetSize());
+
+ // avoid divide by zero
+ size.width = size.width ? size.width : 1;
+ size.height = size.height ? size.height : 1;
+
+ int16_t hPercent = offsetPointX * 100 / size.width;
+ int16_t vPercent = offsetPointY * 100 / size.height;
+
+ nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range,
+ nsIPresShell::ScrollAxis(vPercent),
+ nsIPresShell::ScrollAxis(hPercent));
+ if (NS_FAILED(rv))
+ return;
+
+ initialScrolled = true;
+ } else {
+ // Substring was scrolled to the given point already inside its closest
+ // scrollable area. If there are nested scrollable areas then make
+ // sure we scroll lower areas to the given point inside currently
+ // traversed scrollable area.
+ nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
+ }
+ }
+ frame = parentFrame;
+ }
+}
+
+void
+HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
+{
+ if (IsTextField()) {
+ aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
+ const_cast<HyperTextAccessible*>(this), CharacterCount());
+ } else {
+ aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount());
+ }
+}
+
+void
+HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
+{
+ MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
+
+ dom::Selection* sel = DOMSelection();
+ if (!sel)
+ return;
+
+ aRanges->SetCapacity(sel->RangeCount());
+
+ for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
+ nsRange* DOMRange = sel->GetRangeAt(idx);
+ HyperTextAccessible* startParent =
+ nsAccUtils::GetTextContainer(DOMRange->GetStartParent());
+ HyperTextAccessible* endParent =
+ nsAccUtils::GetTextContainer(DOMRange->GetEndParent());
+ if (!startParent || !endParent)
+ continue;
+
+ int32_t startOffset =
+ startParent->DOMPointToOffset(DOMRange->GetStartParent(),
+ DOMRange->StartOffset(), false);
+ int32_t endOffset =
+ endParent->DOMPointToOffset(DOMRange->GetEndParent(),
+ DOMRange->EndOffset(), true);
+
+ TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
+ startParent, startOffset, endParent, endOffset);
+ *(aRanges->AppendElement()) = Move(tr);
+ }
+}
+
+void
+HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
+{
+}
+
+void
+HyperTextAccessible::RangeByChild(Accessible* aChild,
+ a11y::TextRange& aRange) const
+{
+ HyperTextAccessible* ht = aChild->AsHyperText();
+ if (ht) {
+ aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount());
+ return;
+ }
+
+ Accessible* child = aChild;
+ Accessible* parent = nullptr;
+ while ((parent = child->Parent()) && !(ht = parent->AsHyperText()))
+ child = parent;
+
+ // If no text then return collapsed text range, otherwise return a range
+ // containing the text enclosed by the given child.
+ if (ht) {
+ int32_t childIdx = child->IndexInParent();
+ int32_t startOffset = ht->GetChildOffset(childIdx);
+ int32_t endOffset = child->IsTextLeaf() ?
+ ht->GetChildOffset(childIdx + 1) : startOffset;
+ aRange.Set(mDoc, ht, startOffset, ht, endOffset);
+ }
+}
+
+void
+HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
+ a11y::TextRange& aRange) const
+{
+ Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
+ if (!child)
+ return;
+
+ Accessible* parent = nullptr;
+ while ((parent = child->Parent()) && !parent->IsHyperText())
+ child = parent;
+
+ // Return collapsed text range for the point.
+ if (parent) {
+ HyperTextAccessible* ht = parent->AsHyperText();
+ int32_t offset = ht->GetChildOffset(child);
+ aRange.Set(mDoc, ht, offset, ht, offset);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+// Accessible protected
+ENameValueFlag
+HyperTextAccessible::NativeName(nsString& aName)
+{
+ // Check @alt attribute for invalid img elements.
+ bool hasImgAlt = false;
+ if (mContent->IsHTMLElement(nsGkAtoms::img)) {
+ hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // Get name from title attribute for HTML abbr and acronym elements making it
+ // a valid name from markup. Otherwise their name isn't picked up by recursive
+ // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
+ if (IsAbbreviation() &&
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
+ aName.CompressWhitespace();
+
+ return hasImgAlt ? eNoNameOnPurpose : eNameOK;
+}
+
+void
+HyperTextAccessible::Shutdown()
+{
+ mOffsets.Clear();
+ AccessibleWrap::Shutdown();
+}
+
+bool
+HyperTextAccessible::RemoveChild(Accessible* aAccessible)
+{
+ int32_t childIndex = aAccessible->IndexInParent();
+ int32_t count = mOffsets.Length() - childIndex;
+ if (count > 0)
+ mOffsets.RemoveElementsAt(childIndex, count);
+
+ return AccessibleWrap::RemoveChild(aAccessible);
+}
+
+bool
+HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ int32_t count = mOffsets.Length() - aIndex;
+ if (count > 0 ) {
+ mOffsets.RemoveElementsAt(aIndex, count);
+ }
+ return AccessibleWrap::InsertChildAt(aIndex, aChild);
+}
+
+Relation
+HyperTextAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = Accessible::RelationByType(aType);
+
+ switch (aType) {
+ case RelationType::NODE_CHILD_OF:
+ if (HasOwnContent() && mContent->IsMathMLElement()) {
+ Accessible* parent = Parent();
+ if (parent) {
+ nsIContent* parentContent = parent->GetContent();
+ if (parentContent &&
+ parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+ // Add a relation pointing to the parent <mroot>.
+ rel.AppendTarget(parent);
+ }
+ }
+ }
+ break;
+ case RelationType::NODE_PARENT_OF:
+ if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+ Accessible* base = GetChildAt(0);
+ Accessible* index = GetChildAt(1);
+ if (base && index) {
+ // Append the <mroot> children in the order index, base.
+ rel.AppendTarget(index);
+ rel.AppendTarget(base);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible public static
+
+nsresult
+HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset,
+ uint32_t* aRenderedOffset) const
+{
+ if (!aFrame) {
+ // Current frame not rendered -- this can happen if text is set on
+ // something with display: none
+ *aRenderedOffset = 0;
+ return NS_OK;
+ }
+
+ if (IsTextField()) {
+ *aRenderedOffset = aContentOffset;
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
+ "Need text frame for offset conversion");
+ NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
+ "Call on primary frame only");
+
+ nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
+ aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
+
+ return NS_OK;
+}
+
+nsresult
+HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
+ int32_t* aContentOffset) const
+{
+ if (IsTextField()) {
+ *aContentOffset = aRenderedOffset;
+ return NS_OK;
+ }
+
+ *aContentOffset = 0;
+ NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
+
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
+ "Need text frame for offset conversion");
+ NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
+ "Call on primary frame only");
+
+ nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
+ aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ *aContentOffset = text.mOffsetWithinNodeText;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible public
+
+int32_t
+HyperTextAccessible::GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter) const
+{
+ if (aChildIndex == 0) {
+ if (aInvalidateAfter)
+ mOffsets.Clear();
+
+ return aChildIndex;
+ }
+
+ int32_t count = mOffsets.Length() - aChildIndex;
+ if (count > 0) {
+ if (aInvalidateAfter)
+ mOffsets.RemoveElementsAt(aChildIndex, count);
+
+ return mOffsets[aChildIndex - 1];
+ }
+
+ uint32_t lastOffset = mOffsets.IsEmpty() ?
+ 0 : mOffsets[mOffsets.Length() - 1];
+
+ while (mOffsets.Length() < aChildIndex) {
+ Accessible* child = mChildren[mOffsets.Length()];
+ lastOffset += nsAccUtils::TextLength(child);
+ mOffsets.AppendElement(lastOffset);
+ }
+
+ return mOffsets[aChildIndex - 1];
+}
+
+int32_t
+HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const
+{
+ uint32_t lastOffset = 0;
+ const uint32_t offsetCount = mOffsets.Length();
+
+ if (offsetCount > 0) {
+ lastOffset = mOffsets[offsetCount - 1];
+ if (aOffset < lastOffset) {
+ size_t index;
+ if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) {
+ return (index < (offsetCount - 1)) ? index + 1 : index;
+ }
+
+ return (index == offsetCount) ? -1 : index;
+ }
+ }
+
+ uint32_t childCount = ChildCount();
+ while (mOffsets.Length() < childCount) {
+ Accessible* child = GetChildAt(mOffsets.Length());
+ lastOffset += nsAccUtils::TextLength(child);
+ mOffsets.AppendElement(lastOffset);
+ if (aOffset < lastOffset)
+ return mOffsets.Length() - 1;
+ }
+
+ if (aOffset == lastOffset)
+ return mOffsets.Length() - 1;
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible protected
+
+nsresult
+HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
+ Accessible* aAccessible,
+ DOMPoint* aPoint)
+{
+ NS_ENSURE_ARG(aAccessible);
+
+ if (!aFrame) {
+ // If the given frame is null then set offset after the DOM node of the
+ // given accessible.
+ NS_ASSERTION(!aAccessible->IsDoc(),
+ "Shouldn't be called on document accessible!");
+
+ nsIContent* content = aAccessible->GetContent();
+ NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
+
+ nsIContent* parent = content->GetParent();
+
+ aPoint->idx = parent->IndexOf(content) + 1;
+ aPoint->node = parent;
+
+ } else if (aFrame->GetType() == nsGkAtoms::textFrame) {
+ nsIContent* content = aFrame->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsIFrame *primaryFrame = content->GetPrimaryFrame();
+ nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPoint->node = content;
+
+ } else {
+ nsIContent* content = aFrame->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsIContent* parent = content->GetParent();
+ NS_ENSURE_STATE(parent);
+
+ aPoint->idx = parent->IndexOf(content);
+ aPoint->node = parent;
+ }
+
+ return NS_OK;
+}
+
+// HyperTextAccessible
+void
+HyperTextAccessible::GetSpellTextAttr(nsINode* aNode,
+ int32_t aNodeOffset,
+ uint32_t* aStartOffset,
+ uint32_t* aEndOffset,
+ nsIPersistentProperties* aAttributes)
+{
+ RefPtr<nsFrameSelection> fs = FrameSelection();
+ if (!fs)
+ return;
+
+ dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck);
+ if (!domSel)
+ return;
+
+ int32_t rangeCount = domSel->RangeCount();
+ if (rangeCount <= 0)
+ return;
+
+ uint32_t startOffset = 0, endOffset = 0;
+ for (int32_t idx = 0; idx < rangeCount; idx++) {
+ nsRange* range = domSel->GetRangeAt(idx);
+ if (range->Collapsed())
+ continue;
+
+ // See if the point comes after the range in which case we must continue in
+ // case there is another range after this one.
+ nsINode* endNode = range->GetEndParent();
+ int32_t endNodeOffset = range->EndOffset();
+ if (nsContentUtils::ComparePoints(aNode, aNodeOffset,
+ endNode, endNodeOffset) >= 0)
+ continue;
+
+ // At this point our point is either in this range or before it but after
+ // the previous range. So we check to see if the range starts before the
+ // point in which case the point is in the missspelled range, otherwise it
+ // must be before the range and after the previous one if any.
+ nsINode* startNode = range->GetStartParent();
+ int32_t startNodeOffset = range->StartOffset();
+ if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode,
+ aNodeOffset) <= 0) {
+ startOffset = DOMPointToOffset(startNode, startNodeOffset);
+
+ endOffset = DOMPointToOffset(endNode, endNodeOffset);
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+
+ if (endOffset < *aEndOffset)
+ *aEndOffset = endOffset;
+
+ if (aAttributes) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("spelling"));
+ }
+
+ return;
+ }
+
+ // This range came after the point.
+ endOffset = DOMPointToOffset(startNode, startNodeOffset);
+
+ if (idx > 0) {
+ nsRange* prevRange = domSel->GetRangeAt(idx - 1);
+ startOffset = DOMPointToOffset(prevRange->GetEndParent(),
+ prevRange->EndOffset());
+ }
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+
+ if (endOffset < *aEndOffset)
+ *aEndOffset = endOffset;
+
+ return;
+ }
+
+ // We never found a range that ended after the point, therefore we know that
+ // the point is not in a range, that we do not need to compute an end offset,
+ // and that we should use the end offset of the last range to compute the
+ // start offset of the text attribute range.
+ nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1);
+ startOffset = DOMPointToOffset(prevRange->GetEndParent(),
+ prevRange->EndOffset());
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+}
+
+bool
+HyperTextAccessible::IsTextRole()
+{
+ 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;
+}
diff --git a/accessible/generic/HyperTextAccessible.h b/accessible/generic/HyperTextAccessible.h
new file mode 100644
index 0000000000..78e73f042a
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible.h
@@ -0,0 +1,592 @@
+/* -*- 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_HyperTextAccessible_h__
+#define mozilla_a11y_HyperTextAccessible_h__
+
+#include "AccessibleWrap.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "nsDirection.h"
+#include "WordMovementType.h"
+#include "nsIFrame.h"
+
+#include "nsISelectionController.h"
+
+class nsFrameSelection;
+class nsRange;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace dom {
+class Selection;
+}
+
+namespace a11y {
+
+class TextRange;
+
+struct DOMPoint {
+ DOMPoint() : node(nullptr), idx(0) { }
+ DOMPoint(nsINode* aNode, int32_t aIdx) : node(aNode), idx(aIdx) { }
+
+ nsINode* node;
+ int32_t idx;
+};
+
+// 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';
+
+/**
+ * Special Accessible that knows how contain both text and embedded objects
+ */
+class HyperTextAccessible : public AccessibleWrap
+{
+public:
+ HyperTextAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual nsIAtom* LandmarkRole() const override;
+ virtual int32_t GetLevelInternal() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ virtual void Shutdown() override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ // HyperTextAccessible (static helper method)
+
+ // Convert content offset to rendered text offset
+ nsresult ContentToRenderedOffset(nsIFrame *aFrame, int32_t aContentOffset,
+ uint32_t *aRenderedOffset) const;
+
+ // Convert rendered text offset to content offset
+ nsresult RenderedToContentOffset(nsIFrame *aFrame, uint32_t aRenderedOffset,
+ int32_t *aContentOffset) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperLinkAccessible
+
+ /**
+ * Return link count within this hypertext accessible.
+ */
+ uint32_t LinkCount()
+ { return EmbeddedChildCount(); }
+
+ /**
+ * Return link accessible at the given index.
+ */
+ Accessible* LinkAt(uint32_t aIndex)
+ {
+ return GetEmbeddedChildAt(aIndex);
+ }
+
+ /**
+ * Return index for the given link accessible.
+ */
+ int32_t LinkIndexOf(Accessible* aLink)
+ {
+ return GetIndexOfEmbeddedChild(aLink);
+ }
+
+ /**
+ * Return link accessible at the given text offset.
+ */
+ int32_t LinkIndexAtOffset(uint32_t aOffset)
+ {
+ Accessible* child = GetChildAtOffset(aOffset);
+ return child ? LinkIndexOf(child) : -1;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperTextAccessible: DOM point to text offset conversions.
+
+ /**
+ * Turn a DOM point (node and offset) into a character offset of this
+ * hypertext. Will look for closest match when the DOM node does not have
+ * an accessible object associated with it. Will return an offset for the end
+ * of the string if the node is not found.
+ *
+ * @param aNode [in] the node to look for
+ * @param aNodeOffset [in] the offset to look for
+ * if -1 just look directly for the node
+ * if >=0 and aNode is text, this represents a char offset
+ * if >=0 and aNode is not text, this represents a child node offset
+ * @param aIsEndOffset [in] if true, then then this offset is not inclusive. The character
+ * indicated by the offset returned is at [offset - 1]. This means
+ * if the passed-in offset is really in a descendant, then the offset returned
+ * will come just after the relevant embedded object characer.
+ * If false, then the offset is inclusive. The character indicated
+ * by the offset returned is at [offset]. If the passed-in offset in inside a
+ * descendant, then the returned offset will be on the relevant embedded object char.
+ */
+ uint32_t DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+ bool aIsEndOffset = false) const;
+
+ /**
+ * Transform the given a11y point into the offset relative this hypertext.
+ */
+ uint32_t TransformOffset(Accessible* aDescendant, uint32_t aOffset,
+ bool aIsEndOffset) const;
+
+ /**
+ * Convert start and end hypertext offsets into DOM range. Note that if
+ * aStartOffset and/or aEndOffset is in generated content such as ::before or
+ * ::after, the result range excludes the generated content. See also
+ * ClosestNotGeneratedDOMPoint() for more information.
+ *
+ * @param aStartOffset [in] the given start hypertext offset
+ * @param aEndOffset [in] the given end hypertext offset
+ * @param aRange [in, out] the range whose bounds to set
+ * @return true if conversion was successful
+ */
+ bool OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
+ nsRange* aRange);
+
+ /**
+ * Convert the given offset into DOM point.
+ *
+ * If offset is at text leaf then DOM point is (text node, offsetInTextNode),
+ * if before embedded object then (parent node, indexInParent), if after then
+ * (parent node, indexInParent + 1).
+ */
+ DOMPoint OffsetToDOMPoint(int32_t aOffset);
+
+ /**
+ * Return true if the used ARIA role (if any) allows the hypertext accessible
+ * to expose text interfaces.
+ */
+ bool IsTextRole();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // TextAccessible
+
+ /**
+ * Return character count within the hypertext accessible.
+ */
+ uint32_t CharacterCount() const
+ { return GetChildOffset(ChildCount()); }
+
+ /**
+ * 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)
+ {
+ NS_ASSERTION(!aStartOffset == !aEndOffset,
+ "Offsets should be both defined or both undefined!");
+
+ int32_t childIdx = GetChildIndexAtOffset(aOffset);
+ if (childIdx == -1)
+ return false;
+
+ Accessible* child = GetChildAt(childIdx);
+ child->AppendTextTo(aChar, aOffset - GetChildOffset(childIdx), 1);
+
+ if (aStartOffset && aEndOffset) {
+ *aStartOffset = aOffset;
+ *aEndOffset = aOffset + aChar.Length();
+ }
+ return true;
+ }
+
+ char16_t CharAt(int32_t aOffset)
+ {
+ nsAutoString charAtOffset;
+ CharAt(aOffset, charAtOffset);
+ return charAtOffset.CharAt(0);
+ }
+
+ /**
+ * Return true if char at the given offset equals to given char.
+ */
+ bool IsCharAt(int32_t aOffset, char16_t aChar)
+ { return CharAt(aOffset) == aChar; }
+
+ /**
+ * Return true if terminal char is at the given offset.
+ */
+ bool IsLineEndCharAt(int32_t aOffset)
+ { return IsCharAt(aOffset, '\n'); }
+
+ /**
+ * Return text between given offsets.
+ */
+ void TextSubstring(int32_t aStartOffset, int32_t aEndOffset, nsAString& aText);
+
+ /**
+ * 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 text attributes for the given text range.
+ */
+ already_AddRefed<nsIPersistentProperties>
+ TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
+ int32_t* aStartOffset, int32_t* aEndOffset);
+
+ /**
+ * Return text attributes applied to the accessible.
+ */
+ already_AddRefed<nsIPersistentProperties> DefaultTextAttributes();
+
+ /**
+ * 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 invalidate
+ * cached offsets for next siblings of the child
+ */
+ int32_t GetChildOffset(const Accessible* aChild,
+ bool aInvalidateAfter = false) const
+ {
+ int32_t index = GetIndexOf(aChild);
+ return index == -1 ? -1 : GetChildOffset(index, aInvalidateAfter);
+ }
+
+ /**
+ * Return text offset for the child accessible index.
+ */
+ int32_t GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter = false) const;
+
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ int32_t GetChildIndexAtOffset(uint32_t aOffset) const;
+
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ Accessible* GetChildAtOffset(uint32_t aOffset) const
+ {
+ return GetChildAt(GetChildIndexAtOffset(aOffset));
+ }
+
+ /**
+ * Return true if the given offset/range is valid.
+ */
+ bool IsValidOffset(int32_t aOffset);
+ bool IsValidRange(int32_t aStartOffset, int32_t aEndOffset);
+
+ /**
+ * Return an offset at the given point.
+ */
+ int32_t OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType);
+
+ /**
+ * Return a rect of the given text range relative given coordinate system.
+ */
+ nsIntRect TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+
+ /**
+ * Return a rect for character at given offset relative given coordinate
+ * system.
+ */
+ nsIntRect CharBounds(int32_t aOffset, uint32_t aCoordType)
+ {
+ int32_t endOffset = aOffset == static_cast<int32_t>(CharacterCount()) ?
+ aOffset : aOffset + 1;
+ return TextBounds(aOffset, endOffset, aCoordType);
+ }
+
+ /**
+ * Get/set caret offset, if no caret then -1.
+ */
+ int32_t CaretOffset() const;
+ void SetCaretOffset(int32_t aOffset);
+
+ /**
+ * Provide the line number for the caret.
+ * @return 1-based index for the line number with the caret
+ */
+ int32_t CaretLineNumber();
+
+ /**
+ * Return the caret rect and the widget containing the caret within this
+ * text accessible.
+ *
+ * @param [out] the widget containing the caret
+ * @return the caret rect
+ */
+ mozilla::LayoutDeviceIntRect GetCaretRect(nsIWidget** aWidget);
+
+ /**
+ * Return selected regions count within the accessible.
+ */
+ int32_t SelectionCount();
+
+ /**
+ * Return the start and end offset of the specified selection.
+ */
+ 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
+ */
+ 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);
+
+ /*
+ * Removes the specified selection.
+ * @return true if succeeded
+ */
+ bool RemoveFromSelection(int32_t aSelectionNum);
+
+ /**
+ * Scroll the given text range into view.
+ */
+ void ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType);
+
+ /**
+ * Scroll the given text range to the given point.
+ */
+ void ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY);
+
+ /**
+ * Return a range that encloses the text control or the document this
+ * accessible belongs to.
+ */
+ void EnclosingRange(TextRange& aRange) const;
+
+ /**
+ * Return an array of disjoint ranges for selected text within the text control
+ * or the document this accessible belongs to.
+ */
+ void SelectionRanges(nsTArray<TextRange>* aRanges) const;
+
+ /**
+ * Return an array of disjoint ranges of visible text within the text control
+ * or the document this accessible belongs to.
+ */
+ void VisibleRanges(nsTArray<TextRange>* aRanges) const;
+
+ /**
+ * Return a range containing the given accessible.
+ */
+ void RangeByChild(Accessible* aChild, TextRange& aRange) const;
+
+ /**
+ * Return a range containing an accessible at the given point.
+ */
+ void RangeAtPoint(int32_t aX, int32_t aY, TextRange& aRange) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // EditableTextAccessible
+
+ void ReplaceText(const nsAString& aText);
+ void InsertText(const nsAString& aText, int32_t aPosition);
+ void CopyText(int32_t aStartPos, int32_t aEndPos);
+ void CutText(int32_t aStartPos, int32_t aEndPos);
+ void DeleteText(int32_t aStartPos, int32_t aEndPos);
+ void PasteText(int32_t aPosition);
+
+ /**
+ * Return the editor associated with the accessible.
+ */
+ virtual already_AddRefed<nsIEditor> GetEditor() const;
+
+ /**
+ * Return DOM selection object for the accessible.
+ */
+ dom::Selection* DOMSelection() const;
+
+protected:
+ virtual ~HyperTextAccessible() { }
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // HyperTextAccessible
+
+ /**
+ * Transform magic offset into text offset.
+ */
+ index_t ConvertMagicOffset(int32_t aOffset) const;
+
+ /**
+ * Adjust an offset the caret stays at to get a text by line boundary.
+ */
+ uint32_t AdjustCaretOffset(uint32_t aOffset) const;
+
+ /**
+ * Return true if caret is at end of line.
+ */
+ bool IsCaretAtEndOfLine() const;
+
+ /**
+ * Return true if the given offset points to terminal empty line if any.
+ */
+ bool IsEmptyLastLineOffset(int32_t aOffset)
+ {
+ return aOffset == static_cast<int32_t>(CharacterCount()) &&
+ IsLineEndCharAt(aOffset - 1);
+ }
+
+ /**
+ * Return an offset of the found word boundary.
+ */
+ uint32_t FindWordBoundary(uint32_t aOffset, nsDirection aDirection,
+ EWordMovementType aWordMovementType)
+ {
+ return FindOffset(aOffset, aDirection, eSelectWord, aWordMovementType);
+ }
+
+ /**
+ * Used to get begin/end of previous/this/next line. Note: end of line
+ * is an offset right before '\n' character if any, the offset is right after
+ * '\n' character is begin of line. In case of wrap word breaks these offsets
+ * are equal.
+ */
+ enum EWhichLineBoundary {
+ ePrevLineBegin,
+ ePrevLineEnd,
+ eThisLineBegin,
+ eThisLineEnd,
+ eNextLineBegin,
+ eNextLineEnd
+ };
+
+ /**
+ * Return an offset for requested line boundary. See constants above.
+ */
+ uint32_t FindLineBoundary(uint32_t aOffset,
+ EWhichLineBoundary aWhichLineBoundary);
+
+ /**
+ * Return an offset corresponding to the given direction and selection amount
+ * relative the given offset. A helper used to find word or line boundaries.
+ */
+ uint32_t FindOffset(uint32_t aOffset, nsDirection aDirection,
+ nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType = eDefaultBehavior);
+
+ /**
+ * Return the boundaries of the substring in case of textual frame or
+ * frame boundaries in case of non textual frame, offsets are ignored.
+ */
+ nsIntRect GetBoundsInFrame(nsIFrame* aFrame,
+ uint32_t aStartRenderedOffset,
+ uint32_t aEndRenderedOffset);
+
+ // Selection helpers
+
+ /**
+ * Return frame selection object for the accessible.
+ */
+ already_AddRefed<nsFrameSelection> FrameSelection() const;
+
+ /**
+ * Return selection ranges within the accessible subtree.
+ */
+ void GetSelectionDOMRanges(SelectionType aSelectionType,
+ nsTArray<nsRange*>* aRanges);
+
+ nsresult SetSelectionRange(int32_t aStartPos, int32_t aEndPos);
+
+ /**
+ * Convert the given DOM point to a DOM point in non-generated contents.
+ *
+ * If aDOMPoint is in ::before, the result is immediately after it.
+ * If aDOMPoint is in ::after, the result is immediately before it.
+ *
+ * @param aDOMPoint [in] A DOM node and an index of its child. This may
+ * be in a generated content such as ::before or
+ * ::after.
+ * @param aElementContent [in] An nsIContent representing an element of
+ * aDOMPoint.node.
+ * @return An DOM point which must not be in generated
+ * contents.
+ */
+ DOMPoint ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
+ nsIContent* aElementContent);
+
+ // Helpers
+ nsresult GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
+ Accessible* aAccessible,
+ mozilla::a11y::DOMPoint* aPoint);
+
+ /**
+ * Set 'misspelled' text attribute and return range offsets where the
+ * attibute is stretched. If the text is not misspelled at the given offset
+ * then we expose only range offsets where text is not misspelled. The method
+ * is used by TextAttributes() method.
+ *
+ * @param aIncludeDefAttrs [in] points whether text attributes having default
+ * values of attributes should be included
+ * @param aSourceNode [in] the node we start to traverse from
+ * @param aStartOffset [in, out] the start offset
+ * @param aEndOffset [in, out] the end offset
+ * @param aAttributes [out, optional] result attributes
+ */
+ void GetSpellTextAttr(nsINode* aNode, int32_t aNodeOffset,
+ uint32_t* aStartOffset, uint32_t* aEndOffset,
+ nsIPersistentProperties* aAttributes);
+
+ /**
+ * Set xml-roles attributes for MathML elements.
+ * @param aAttributes
+ */
+ void SetMathMLXMLRoles(nsIPersistentProperties* aAttributes);
+
+private:
+ /**
+ * End text offsets array.
+ */
+ mutable nsTArray<uint32_t> mOffsets;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline HyperTextAccessible*
+Accessible::AsHyperText()
+{
+ return IsHyperText() ? static_cast<HyperTextAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/ImageAccessible.cpp b/accessible/generic/ImageAccessible.cpp
new file mode 100644
index 0000000000..c6556b04da
--- /dev/null
+++ b/accessible/generic/ImageAccessible.cpp
@@ -0,0 +1,224 @@
+/* -*- 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 "ImageAccessible.h"
+
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "AccIterator.h"
+#include "States.h"
+
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDocument.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIPersistentProperties2.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+ImageAccessible::
+ ImageAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LinkableAccessible(aContent, aDoc)
+{
+ mType = eImageType;
+}
+
+ImageAccessible::~ImageAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+uint64_t
+ImageAccessible::NativeState()
+{
+ // The state is a bitfield, get our inherited state, then logically OR it with
+ // states::ANIMATED if this is an animated image.
+
+ uint64_t state = LinkableAccessible::NativeState();
+
+ nsCOMPtr<nsIImageLoadingContent> content(do_QueryInterface(mContent));
+ nsCOMPtr<imgIRequest> imageRequest;
+
+ if (content)
+ content->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imageRequest));
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ if (imageRequest)
+ imageRequest->GetImage(getter_AddRefs(imgContainer));
+
+ if (imgContainer) {
+ bool animated;
+ imgContainer->GetAnimated(&animated);
+ if (animated)
+ state |= states::ANIMATED;
+ }
+
+ return state;
+}
+
+ENameValueFlag
+ImageAccessible::NativeName(nsString& aName)
+{
+ bool hasAltAttrib =
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // No accessible name but empty 'alt' attribute is present. If further name
+ // computation algorithm doesn't provide non empty name then it means
+ // an empty 'alt' attribute was used to indicate a decorative image (see
+ // Accessible::Name() method for details).
+ return hasAltAttrib ? eNoNameOnPurpose : eNameOK;
+}
+
+role
+ImageAccessible::NativeRole()
+{
+ return roles::GRAPHIC;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+uint8_t
+ImageAccessible::ActionCount()
+{
+ uint8_t actionCount = LinkableAccessible::ActionCount();
+ return HasLongDesc() ? actionCount + 1 : actionCount;
+}
+
+void
+ImageAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+ if (IsLongDescIndex(aIndex) && HasLongDesc())
+ aName.AssignLiteral("showlongdesc");
+ else
+ LinkableAccessible::ActionNameAt(aIndex, aName);
+}
+
+bool
+ImageAccessible::DoAction(uint8_t aIndex)
+{
+ // Get the long description uri and open in a new window.
+ if (!IsLongDescIndex(aIndex))
+ return LinkableAccessible::DoAction(aIndex);
+
+ nsCOMPtr<nsIURI> uri = GetLongDescURI();
+ if (!uri)
+ return false;
+
+ nsAutoCString utf8spec;
+ uri->GetSpec(utf8spec);
+ NS_ConvertUTF8toUTF16 spec(utf8spec);
+
+ nsIDocument* document = mContent->OwnerDoc();
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = document->GetWindow();
+ if (!piWindow)
+ return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> tmp;
+ return NS_SUCCEEDED(piWindow->Open(spec, EmptyString(), EmptyString(),
+ /* aLoadInfo = */ nullptr,
+ /* aForceNoOpener = */ false,
+ getter_AddRefs(tmp)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageAccessible
+
+nsIntPoint
+ImageAccessible::Position(uint32_t aCoordType)
+{
+ nsIntRect rect = Bounds();
+ nsAccUtils::ConvertScreenCoordsTo(&rect.x, &rect.y, aCoordType, this);
+ return rect.TopLeft();
+}
+
+nsIntSize
+ImageAccessible::Size()
+{
+ return Bounds().Size();
+}
+
+// Accessible
+already_AddRefed<nsIPersistentProperties>
+ImageAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ LinkableAccessible::NativeAttributes();
+
+ nsAutoString src;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
+ if (!src.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::src, src);
+
+ return attributes.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private methods
+
+already_AddRefed<nsIURI>
+ImageAccessible::GetLongDescURI() const
+{
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::longdesc)) {
+ // To check if longdesc contains an invalid url.
+ nsAutoString longdesc;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::longdesc, longdesc);
+ if (longdesc.FindChar(' ') != -1 || longdesc.FindChar('\t') != -1 ||
+ longdesc.FindChar('\r') != -1 || longdesc.FindChar('\n') != -1) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), longdesc,
+ mContent->OwnerDoc(), baseURI);
+ return uri.forget();
+ }
+
+ DocAccessible* document = Document();
+ if (document) {
+ IDRefsIterator iter(document, mContent, nsGkAtoms::aria_describedby);
+ while (nsIContent* target = iter.NextElem()) {
+ if ((target->IsHTMLElement(nsGkAtoms::a) ||
+ target->IsHTMLElement(nsGkAtoms::area)) &&
+ target->HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ nsGenericHTMLElement* element =
+ nsGenericHTMLElement::FromContent(target);
+
+ nsCOMPtr<nsIURI> uri;
+ element->GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
+ return uri.forget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+ImageAccessible::IsLongDescIndex(uint8_t aIndex)
+{
+ return aIndex == LinkableAccessible::ActionCount();
+}
+
diff --git a/accessible/generic/ImageAccessible.h b/accessible/generic/ImageAccessible.h
new file mode 100644
index 0000000000..f2324ae4cd
--- /dev/null
+++ b/accessible/generic/ImageAccessible.h
@@ -0,0 +1,87 @@
+/* -*- 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_ImageAccessible_h__
+#define mozilla_a11y_ImageAccessible_h__
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/* Accessible for supporting images
+ * supports:
+ * - gets name, role
+ * - support basic state
+ */
+class ImageAccessible : public LinkableAccessible
+{
+public:
+ ImageAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // ImageAccessible
+ nsIntPoint Position(uint32_t aCoordType);
+ nsIntSize Size();
+
+protected:
+ virtual ~ImageAccessible();
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+private:
+ /**
+ * Return whether the element has a longdesc URI.
+ */
+ bool HasLongDesc() const
+ {
+ nsCOMPtr<nsIURI> uri = GetLongDescURI();
+ return uri;
+ }
+
+ /**
+ * Return an URI for showlongdesc action if any.
+ */
+ already_AddRefed<nsIURI> GetLongDescURI() const;
+
+ /**
+ * Used by ActionNameAt and DoAction to ensure the index for opening the
+ * longdesc URL is valid.
+ * It is always assumed that the highest possible index opens the longdesc.
+ * This doesn't check that there is actually a longdesc, just that the index
+ * would be correct if there was one.
+ *
+ * @param aIndex The 0-based index to be tested.
+ *
+ * @returns true if index is valid for longdesc action.
+ */
+ inline bool IsLongDescIndex(uint8_t aIndex);
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline ImageAccessible*
+Accessible::AsImage()
+{
+ return IsImage() ? static_cast<ImageAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/OuterDocAccessible.cpp b/accessible/generic/OuterDocAccessible.cpp
new file mode 100644
index 0000000000..a63069355d
--- /dev/null
+++ b/accessible/generic/OuterDocAccessible.cpp
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OuterDocAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "DocAccessible-inl.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "Role.h"
+#include "States.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// OuterDocAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+OuterDocAccessible::
+ OuterDocAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mType = eOuterDocType;
+
+ // Request document accessible for the content document to make sure it's
+ // created. It will appended to outerdoc accessible children asynchronously.
+ nsIDocument* outerDoc = mContent->GetUncomposedDoc();
+ if (outerDoc) {
+ nsIDocument* innerDoc = outerDoc->GetSubDocumentFor(mContent);
+ if (innerDoc)
+ GetAccService()->GetDocAccessible(innerDoc);
+ }
+}
+
+OuterDocAccessible::~OuterDocAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(OuterDocAccessible,
+ Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public (DON'T add methods here)
+
+role
+OuterDocAccessible::NativeRole()
+{
+ return roles::INTERNAL_FRAME;
+}
+
+Accessible*
+OuterDocAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ nsIntRect docRect = Bounds();
+ if (aX < docRect.x || aX >= docRect.x + docRect.width ||
+ aY < docRect.y || aY >= docRect.y + docRect.height)
+ return nullptr;
+
+ // Always return the inner doc as direct child accessible unless bounds
+ // outside of it.
+ Accessible* child = GetChildAt(0);
+ NS_ENSURE_TRUE(child, nullptr);
+
+ if (aWhichChild == eDeepestChild)
+ return child->ChildAtPoint(aX, aY, eDeepestChild);
+ return child;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+void
+OuterDocAccessible::Shutdown()
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::OuterDocDestroy(this);
+#endif
+
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ if (child) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy)) {
+ logging::DocDestroy("outerdoc's child document rebind is scheduled",
+ child->AsDoc()->DocumentNode());
+ }
+#endif
+ RemoveChild(child);
+
+ // XXX: sometimes outerdoc accessible is shutdown because of layout style
+ // change however the presshell of underlying document isn't destroyed and
+ // the document doesn't get pagehide events. Schedule a document rebind
+ // to its parent document. Otherwise a document accessible may be lost if
+ // its outerdoc has being recreated (see bug 862863 for details).
+ if (!mDoc->IsDefunct()) {
+ mDoc->BindChildDocument(child->AsDoc());
+ }
+ }
+
+ AccessibleWrap::Shutdown();
+}
+
+bool
+OuterDocAccessible::InsertChildAt(uint32_t aIdx, Accessible* aAccessible)
+{
+ MOZ_RELEASE_ASSERT(aAccessible->IsDoc(),
+ "OuterDocAccessible can have a document child only!");
+
+ // We keep showing the old document for a bit after creating the new one,
+ // and while building the new DOM and frame tree. That's done on purpose
+ // to avoid weird flashes of default background color.
+ // The old viewer will be destroyed after the new one is created.
+ // For a11y, it should be safe to shut down the old document now.
+ if (mChildren.Length())
+ mChildren[0]->Shutdown();
+
+ if (!AccessibleWrap::InsertChildAt(0, aAccessible))
+ return false;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate)) {
+ logging::DocCreate("append document to outerdoc",
+ aAccessible->AsDoc()->DocumentNode());
+ logging::Address("outerdoc", this);
+ }
+#endif
+
+ return true;
+}
+
+bool
+OuterDocAccessible::RemoveChild(Accessible* aAccessible)
+{
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ if (child != aAccessible) {
+ NS_ERROR("Wrong child to remove!");
+ return false;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy)) {
+ logging::DocDestroy("remove document from outerdoc",
+ child->AsDoc()->DocumentNode(), child->AsDoc());
+ logging::Address("outerdoc", this);
+ }
+#endif
+
+ bool wasRemoved = AccessibleWrap::RemoveChild(child);
+
+ NS_ASSERTION(!mChildren.Length(),
+ "This child document of outerdoc accessible wasn't removed!");
+
+ return wasRemoved;
+}
+
+bool
+OuterDocAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ // outer document accessible doesn't not participate in ordinal tree
+ // mutations.
+ return false;
+}
+
+#if defined(XP_WIN)
+
+// On Windows e10s, since we don't cache in the chrome process, these next two
+// functions must be implemented so that we properly cross the chrome-to-content
+// boundary when traversing.
+
+uint32_t
+OuterDocAccessible::ChildCount() const
+{
+ uint32_t result = mChildren.Length();
+ if (!result && RemoteChildDoc()) {
+ result = 1;
+ }
+ return result;
+}
+
+Accessible*
+OuterDocAccessible::GetChildAt(uint32_t aIndex) const
+{
+ Accessible* result = AccessibleWrap::GetChildAt(aIndex);
+ if (result || aIndex) {
+ return result;
+ }
+ // If we are asking for child 0 and GetChildAt doesn't return anything, try
+ // to get the remote child doc and return that instead.
+ ProxyAccessible* remoteChild = RemoteChildDoc();
+ if (!remoteChild) {
+ return nullptr;
+ }
+ return WrapperFor(remoteChild);
+}
+
+#endif // defined(XP_WIN)
+
+ProxyAccessible*
+OuterDocAccessible::RemoteChildDoc() const
+{
+ dom::TabParent* tab = dom::TabParent::GetFrom(GetContent());
+ if (!tab)
+ return nullptr;
+
+ return tab->GetTopLevelDocAccessible();
+}
diff --git a/accessible/generic/OuterDocAccessible.h b/accessible/generic/OuterDocAccessible.h
new file mode 100644
index 0000000000..bfa02ba14c
--- /dev/null
+++ b/accessible/generic/OuterDocAccessible.h
@@ -0,0 +1,61 @@
+/* -*- 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_OUTERDOCACCESSIBLE_H_
+#define MOZILLA_A11Y_OUTERDOCACCESSIBLE_H_
+
+#include "AccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+class ProxyAccessible;
+
+/**
+ * Used for <browser>, <frame>, <iframe>, <page> or editor> elements.
+ *
+ * In these variable names, "outer" relates to the OuterDocAccessible as
+ * opposed to the DocAccessibleWrap which is "inner". The outer node is
+ * a something like tags listed above, whereas the inner node corresponds to
+ * the inner document root.
+ */
+
+class OuterDocAccessible final : public AccessibleWrap
+{
+public:
+ OuterDocAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ProxyAccessible* RemoteChildDoc() const;
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+
+ virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+
+#if defined(XP_WIN)
+ virtual uint32_t ChildCount() const override;
+ virtual Accessible* GetChildAt(uint32_t aIndex) const override;
+#endif // defined(XP_WIN)
+
+protected:
+ virtual ~OuterDocAccessible() override;
+};
+
+inline OuterDocAccessible*
+Accessible::AsOuterDoc()
+{
+ return IsOuterDoc() ? static_cast<OuterDocAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/RootAccessible.cpp b/accessible/generic/RootAccessible.cpp
new file mode 100644
index 0000000000..5817f2da9c
--- /dev/null
+++ b/accessible/generic/RootAccessible.cpp
@@ -0,0 +1,732 @@
+/* -*- 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 "RootAccessible.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#define CreateEvent CreateEventA
+#include "nsIDOMDocument.h"
+
+#include "Accessible-inl.h"
+#include "DocAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#ifdef MOZ_XUL
+#include "XULTreeAccessible.h"
+#endif
+
+#include "mozilla/dom/Element.h"
+
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "nsIDOMCustomEvent.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPropertyBag2.h"
+#include "nsIServiceManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsReadableUtils.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindow.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#include "nsIXULWindow.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(RootAccessible, DocAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/destructor
+
+RootAccessible::
+ RootAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessibleWrap(aDocument, aPresShell)
+{
+ mType = eRootType;
+}
+
+RootAccessible::~RootAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+ENameValueFlag
+RootAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (ARIARoleMap()) {
+ Accessible::Name(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ mDocumentNode->GetTitle(aName);
+ return eNameOK;
+}
+
+role
+RootAccessible::NativeRole()
+{
+ // If it's a <dialog> or <wizard>, use roles::DIALOG instead
+ dom::Element* rootElm = mDocumentNode->GetRootElement();
+ if (rootElm && rootElm->IsAnyOfXULElements(nsGkAtoms::dialog,
+ nsGkAtoms::wizard))
+ return roles::DIALOG;
+
+ return DocAccessibleWrap::NativeRole();
+}
+
+// RootAccessible protected member
+#ifdef MOZ_XUL
+uint32_t
+RootAccessible::GetChromeFlags()
+{
+ // Return the flag set for the top level window as defined
+ // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME]
+ // Not simple: nsIXULWindow is not just a QI from nsIDOMWindow
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
+ NS_ENSURE_TRUE(docShell, 0);
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_ENSURE_TRUE(treeOwner, 0);
+ nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwner));
+ if (!xulWin) {
+ return 0;
+ }
+ uint32_t chromeFlags;
+ xulWin->GetChromeFlags(&chromeFlags);
+ return chromeFlags;
+}
+#endif
+
+uint64_t
+RootAccessible::NativeState()
+{
+ uint64_t state = DocAccessibleWrap::NativeState();
+ if (state & states::DEFUNCT)
+ return state;
+
+#ifdef MOZ_XUL
+ uint32_t chromeFlags = GetChromeFlags();
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE)
+ state |= states::SIZEABLE;
+ // If it has a titlebar it's movable
+ // XXX unless it's minimized or maximized, but not sure
+ // how to detect that
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR)
+ state |= states::MOVEABLE;
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)
+ state |= states::MODAL;
+#endif
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow())
+ state |= states::ACTIVE;
+
+ return state;
+}
+
+const char* const kEventTypes[] = {
+#ifdef DEBUG_DRAGDROPSTART
+ // Capture mouse over events and fire fake DRAGDROPSTART event to simplify
+ // debugging a11y objects with event viewers.
+ "mouseover",
+#endif
+ // Fired when list or tree selection changes.
+ "select",
+ // Fired when value changes immediately, wether or not focused changed.
+ "ValueChange",
+ "AlertActive",
+ "TreeRowCountChanged",
+ "TreeInvalidated",
+ // add ourself as a OpenStateChange listener (custom event fired in tree.xml)
+ "OpenStateChange",
+ // add ourself as a CheckboxStateChange listener (custom event fired in HTMLInputElement.cpp)
+ "CheckboxStateChange",
+ // add ourself as a RadioStateChange Listener ( custom event fired in in HTMLInputElement.cpp & radio.xml)
+ "RadioStateChange",
+ "popupshown",
+ "popuphiding",
+ "DOMMenuInactive",
+ "DOMMenuItemActive",
+ "DOMMenuItemInactive",
+ "DOMMenuBarActive",
+ "DOMMenuBarInactive"
+};
+
+nsresult
+RootAccessible::AddEventListeners()
+{
+ // EventTarget interface allows to register event listeners to
+ // receive untrusted events (synthetic events generated by untrusted code).
+ // For example, XBL bindings implementations for elements that are hosted in
+ // non chrome document fire untrusted events.
+ nsCOMPtr<EventTarget> nstarget = mDocumentNode;
+
+ if (nstarget) {
+ for (const char* const* e = kEventTypes,
+ * const* e_end = ArrayEnd(kEventTypes);
+ e < e_end; ++e) {
+ nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e),
+ this, true, true, 2);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return DocAccessible::AddEventListeners();
+}
+
+nsresult
+RootAccessible::RemoveEventListeners()
+{
+ nsCOMPtr<EventTarget> target = mDocumentNode;
+ if (target) {
+ for (const char* const* e = kEventTypes,
+ * const* e_end = ArrayEnd(kEventTypes);
+ e < e_end; ++e) {
+ nsresult rv = target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Do this before removing clearing caret accessible, so that it can use
+ // shutdown the caret accessible's selection listener
+ DocAccessible::RemoveEventListeners();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public
+
+void
+RootAccessible::DocumentActivated(DocAccessible* aDocument)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+RootAccessible::HandleEvent(nsIDOMEvent* aDOMEvent)
+{
+ MOZ_ASSERT(aDOMEvent);
+ Event* event = aDOMEvent->InternalDOMEvent();
+ nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
+ if (!origTargetNode)
+ return NS_OK;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDOMEvents)) {
+ nsAutoString eventType;
+ aDOMEvent->GetType(eventType);
+ logging::DOMEvent("handled", origTargetNode, eventType);
+ }
+#endif
+
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
+
+ if (document) {
+ // Root accessible exists longer than any of its descendant documents so
+ // that we are guaranteed notification is processed before root accessible
+ // is destroyed.
+ document->HandleNotification<RootAccessible, nsIDOMEvent>
+ (this, &RootAccessible::ProcessDOMEvent, aDOMEvent);
+ }
+
+ return NS_OK;
+}
+
+// RootAccessible protected
+void
+RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
+{
+ MOZ_ASSERT(aDOMEvent);
+ Event* event = aDOMEvent->InternalDOMEvent();
+ nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
+
+ nsAutoString eventType;
+ aDOMEvent->GetType(eventType);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDOMEvents))
+ logging::DOMEvent("processed", origTargetNode, eventType);
+#endif
+
+ if (eventType.EqualsLiteral("popuphiding")) {
+ HandlePopupHidingEvent(origTargetNode);
+ return;
+ }
+
+ DocAccessible* targetDocument = GetAccService()->
+ GetDocAccessible(origTargetNode->OwnerDoc());
+ NS_ASSERTION(targetDocument, "No document while accessible is in document?!");
+
+ Accessible* accessible =
+ targetDocument->GetAccessibleOrContainer(origTargetNode);
+ if (!accessible)
+ return;
+
+#ifdef MOZ_XUL
+ XULTreeAccessible* treeAcc = accessible->AsXULTree();
+ if (treeAcc) {
+ if (eventType.EqualsLiteral("TreeRowCountChanged")) {
+ HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc);
+ return;
+ }
+
+ if (eventType.EqualsLiteral("TreeInvalidated")) {
+ HandleTreeInvalidatedEvent(aDOMEvent, treeAcc);
+ return;
+ }
+ }
+#endif
+
+ if (eventType.EqualsLiteral("RadioStateChange")) {
+ uint64_t state = accessible->State();
+ bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0;
+
+ if (accessible->NeedsDOMUIEvent()) {
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ }
+
+ if (isEnabled) {
+ FocusMgr()->ActiveItemChanged(accessible);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("RadioStateChange", accessible);
+#endif
+ }
+
+ return;
+ }
+
+ if (eventType.EqualsLiteral("CheckboxStateChange")) {
+ if (accessible->NeedsDOMUIEvent()) {
+ uint64_t state = accessible->State();
+ bool isEnabled = !!(state & states::CHECKED);
+
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ }
+ return;
+ }
+
+ Accessible* treeItemAcc = nullptr;
+#ifdef MOZ_XUL
+ // If it's a tree element, need the currently selected item.
+ if (treeAcc) {
+ treeItemAcc = accessible->CurrentItem();
+ if (treeItemAcc)
+ accessible = treeItemAcc;
+ }
+
+ if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) {
+ uint64_t state = accessible->State();
+ bool isEnabled = (state & states::EXPANDED) != 0;
+
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ return;
+ }
+
+ nsINode* targetNode = accessible->GetNode();
+ if (treeItemAcc && eventType.EqualsLiteral("select")) {
+ // XXX: We shouldn't be based on DOM select event which doesn't provide us
+ // any context info. We should integrate into nsTreeSelection instead.
+ // If multiselect tree, we should fire selectionadd or selection removed
+ if (FocusMgr()->HasDOMFocus(targetNode)) {
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
+ do_QueryInterface(targetNode);
+ nsAutoString selType;
+ multiSel->GetSelType(selType);
+ if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
+ // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
+ // for each tree item. Perhaps each tree item will need to cache its
+ // selection state and fire an event after a DOM "select" event when
+ // that state changes. XULTreeAccessible::UpdateTreeSelection();
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
+ accessible);
+ return;
+ }
+
+ RefPtr<AccSelChangeEvent> selChangeEvent =
+ new AccSelChangeEvent(treeAcc, treeItemAcc,
+ AccSelChangeEvent::eSelectionAdd);
+ nsEventShell::FireEvent(selChangeEvent);
+ return;
+ }
+ }
+ else
+#endif
+ if (eventType.EqualsLiteral("AlertActive")) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible);
+ }
+ else if (eventType.EqualsLiteral("popupshown")) {
+ HandlePopupShownEvent(accessible);
+ }
+ else if (eventType.EqualsLiteral("DOMMenuInactive")) {
+ if (accessible->Role() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ accessible);
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
+ FocusMgr()->ActiveItemChanged(accessible);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible);
+#endif
+ }
+ else if (eventType.EqualsLiteral("DOMMenuItemInactive")) {
+ // Process DOMMenuItemInactive event for autocomplete only because this is
+ // unique widget that may acquire focus from autocomplete popup while popup
+ // stays open and has no active item. In case of XUL tree autocomplete
+ // popup this event is fired for tree accessible.
+ Accessible* widget =
+ accessible->IsWidget() ? accessible : accessible->ContainerWidget();
+ if (widget && widget->IsAutoCompletePopup()) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible);
+#endif
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START,
+ accessible, eFromUserInput);
+
+ // Notify of active item change when menubar gets active and if it has
+ // current item. This is a case of mouseover (set current menuitem) and
+ // mouse click (activate the menubar). If menubar doesn't have current item
+ // (can be a case of menubar activation from keyboard) then ignore this
+ // notification because later we'll receive DOMMenuItemActive event after
+ // current menuitem is set.
+ Accessible* activeItem = accessible->CurrentItem();
+ if (activeItem) {
+ FocusMgr()->ActiveItemChanged(activeItem);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible);
+#endif
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END,
+ accessible, eFromUserInput);
+
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible);
+#endif
+ }
+ else if (accessible->NeedsDOMUIEvent() &&
+ eventType.EqualsLiteral("ValueChange")) {
+ uint32_t event = accessible->HasNumericValue()
+ ? nsIAccessibleEvent::EVENT_VALUE_CHANGE
+ : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE;
+ targetDocument->FireDelayedEvent(event, accessible);
+ }
+#ifdef DEBUG_DRAGDROPSTART
+ else if (eventType.EqualsLiteral("mouseover")) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START,
+ accessible);
+ }
+#endif
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+RootAccessible::Shutdown()
+{
+ // Called manually or by Accessible::LastRelease()
+ if (!PresShell())
+ return; // Already shutdown
+
+ DocAccessibleWrap::Shutdown();
+}
+
+Relation
+RootAccessible::RelationByType(RelationType aType)
+{
+ if (!mDocumentNode || aType != RelationType::EMBEDS)
+ return DocAccessibleWrap::RelationByType(aType);
+
+ if (nsPIDOMWindowOuter* rootWindow = mDocumentNode->GetWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent();
+ if (contentWindow) {
+ nsCOMPtr<nsIDocument> contentDocumentNode = contentWindow->GetDoc();
+ if (contentDocumentNode) {
+ DocAccessible* contentDocument =
+ GetAccService()->GetDocAccessible(contentDocumentNode);
+ if (contentDocument)
+ return Relation(contentDocument);
+ }
+ }
+ }
+
+ return Relation();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected members
+
+void
+RootAccessible::HandlePopupShownEvent(Accessible* aAccessible)
+{
+ roles::Role role = aAccessible->Role();
+
+ if (role == roles::MENUPOPUP) {
+ // Don't fire menupopup events for combobox and autocomplete lists.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
+ aAccessible);
+ return;
+ }
+
+ if (role == roles::TOOLTIP) {
+ // There is a single <xul:tooltip> node which Mozilla moves around.
+ // The accessible for it stays the same no matter where it moves.
+ // AT's expect to get an EVENT_SHOW for the tooltip.
+ // In event callback the tooltip's accessible will be ready.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SHOW, aAccessible);
+ return;
+ }
+
+ if (role == roles::COMBOBOX_LIST) {
+ // Fire expanded state change event for comboboxes and autocompeletes.
+ Accessible* combobox = aAccessible->Parent();
+ if (!combobox)
+ return;
+
+ roles::Role comboboxRole = combobox->Role();
+ if (comboboxRole == roles::COMBOBOX ||
+ comboboxRole == roles::AUTOCOMPLETE) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(combobox, states::EXPANDED, true);
+ if (event)
+ nsEventShell::FireEvent(event);
+ }
+ }
+}
+
+void
+RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode)
+{
+ // Get popup accessible. There are cases when popup element isn't accessible
+ // but an underlying widget is and behaves like popup, an example is
+ // autocomplete popups.
+ DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode);
+ if (!document)
+ return;
+
+ Accessible* popup = document->GetAccessible(aPopupNode);
+ if (!popup) {
+ Accessible* popupContainer = document->GetContainerAccessible(aPopupNode);
+ if (!popupContainer)
+ return;
+
+ uint32_t childCount = popupContainer->ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = popupContainer->GetChildAt(idx);
+ if (child->IsAutoCompletePopup()) {
+ popup = child;
+ break;
+ }
+ }
+
+ // No popup no events. Focus is managed by DOM. This is a case for
+ // menupopups of menus on Linux since there are no accessible for popups.
+ if (!popup)
+ return;
+ }
+
+ // In case of autocompletes and comboboxes fire state change event for
+ // expanded state. Note, HTML form autocomplete isn't a subject of state
+ // change event because they aren't autocompletes strictly speaking.
+ // When popup closes (except nested popups and menus) then fire focus event to
+ // where it was. The focus event is expected even if popup didn't take a focus.
+
+ static const uint32_t kNotifyOfFocus = 1;
+ static const uint32_t kNotifyOfState = 2;
+ uint32_t notifyOf = 0;
+
+ // HTML select is target of popuphidding event. Otherwise get container
+ // widget. No container widget means this is either tooltip or menupopup.
+ // No events in the former case.
+ Accessible* widget = nullptr;
+ if (popup->IsCombobox()) {
+ widget = popup;
+ } else {
+ widget = popup->ContainerWidget();
+ if (!widget) {
+ if (!popup->IsMenuPopup())
+ return;
+
+ widget = popup;
+ }
+ }
+
+ if (popup->IsAutoCompletePopup()) {
+ // No focus event for autocomplete because it's managed by
+ // DOMMenuItemInactive events.
+ if (widget->IsAutoComplete())
+ notifyOf = kNotifyOfState;
+
+ } else if (widget->IsCombobox()) {
+ // Fire focus for active combobox, otherwise the focus is managed by DOM
+ // focus notifications. Always fire state change event.
+ if (widget->IsActiveWidget())
+ notifyOf = kNotifyOfFocus;
+ notifyOf |= kNotifyOfState;
+
+ } else if (widget->IsMenuButton()) {
+ // Can be a part of autocomplete.
+ Accessible* compositeWidget = widget->ContainerWidget();
+ if (compositeWidget && compositeWidget->IsAutoComplete()) {
+ widget = compositeWidget;
+ notifyOf = kNotifyOfState;
+ }
+
+ // Autocomplete (like searchbar) can be inactive when popup hiddens
+ notifyOf |= kNotifyOfFocus;
+
+ } else if (widget == popup) {
+ // Top level context menus and alerts.
+ // Ignore submenus and menubar. When submenu is closed then sumbenu
+ // container menuitem takes a focus via DOMMenuItemActive notification.
+ // For menubars processing we listen DOMMenubarActive/Inactive
+ // notifications.
+ notifyOf = kNotifyOfFocus;
+ }
+
+ // Restore focus to where it was.
+ if (notifyOf & kNotifyOfFocus) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("popuphiding", popup);
+#endif
+ }
+
+ // Fire expanded state change event.
+ if (notifyOf & kNotifyOfState) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(widget, states::EXPANDED, false);
+ document->FireDelayedEvent(event);
+ }
+}
+
+#ifdef MOZ_XUL
+void
+RootAccessible::HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible)
+{
+ nsCOMPtr<nsIDOMCustomEvent> customEvent(do_QueryInterface(aEvent));
+ if (!customEvent)
+ return;
+
+ nsCOMPtr<nsIVariant> detailVariant;
+ customEvent->GetDetail(getter_AddRefs(detailVariant));
+ if (!detailVariant)
+ return;
+
+ nsCOMPtr<nsISupports> supports;
+ detailVariant->GetAsISupports(getter_AddRefs(supports));
+ nsCOMPtr<nsIPropertyBag2> propBag(do_QueryInterface(supports));
+ if (!propBag)
+ return;
+
+ nsresult rv;
+ int32_t index, count;
+ rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("index"), &index);
+ if (NS_FAILED(rv))
+ return;
+
+ rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("count"), &count);
+ if (NS_FAILED(rv))
+ return;
+
+ aAccessible->InvalidateCache(index, count);
+}
+
+void
+RootAccessible::HandleTreeInvalidatedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible)
+{
+ nsCOMPtr<nsIDOMCustomEvent> customEvent(do_QueryInterface(aEvent));
+ if (!customEvent)
+ return;
+
+ nsCOMPtr<nsIVariant> detailVariant;
+ customEvent->GetDetail(getter_AddRefs(detailVariant));
+ if (!detailVariant)
+ return;
+
+ nsCOMPtr<nsISupports> supports;
+ detailVariant->GetAsISupports(getter_AddRefs(supports));
+ nsCOMPtr<nsIPropertyBag2> propBag(do_QueryInterface(supports));
+ if (!propBag)
+ return;
+
+ int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1;
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startrow"),
+ &startRow);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endrow"),
+ &endRow);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"),
+ &startCol);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"),
+ &endCol);
+
+ aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol);
+}
+#endif
+
+ProxyAccessible*
+RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const
+{
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner));
+ NS_ENSURE_TRUE(owner, nullptr);
+
+ nsCOMPtr<nsITabParent> tabParent;
+ owner->GetPrimaryTabParent(getter_AddRefs(tabParent));
+ if (!tabParent) {
+ return nullptr;
+ }
+
+ auto tab = static_cast<dom::TabParent*>(tabParent.get());
+ return tab->GetTopLevelDocAccessible();
+}
diff --git a/accessible/generic/RootAccessible.h b/accessible/generic/RootAccessible.h
new file mode 100644
index 0000000000..beb74cf4b6
--- /dev/null
+++ b/accessible/generic/RootAccessible.h
@@ -0,0 +1,92 @@
+/* -*- 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_RootAccessible_h__
+#define mozilla_a11y_RootAccessible_h__
+
+#include "HyperTextAccessible.h"
+#include "DocAccessibleWrap.h"
+
+#include "nsIDOMEventListener.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessible : public DocAccessibleWrap,
+ public nsIDOMEventListener
+{
+ NS_DECL_ISUPPORTS_INHERITED
+
+public:
+ RootAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell);
+
+ // nsIDOMEventListener
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual Relation RelationByType(RelationType aType) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // RootAccessible
+
+ /**
+ * Notify that the sub document presshell was activated.
+ */
+ virtual void DocumentActivated(DocAccessible* aDocument);
+
+ /**
+ * Return the primary remote top level document if any.
+ */
+ ProxyAccessible* GetPrimaryRemoteTopLevelContentDoc() const;
+
+protected:
+ virtual ~RootAccessible();
+
+ /**
+ * Add/remove DOM event listeners.
+ */
+ virtual nsresult AddEventListeners() override;
+ virtual nsresult RemoveEventListeners() override;
+
+ /**
+ * Process the DOM event.
+ */
+ void ProcessDOMEvent(nsIDOMEvent* aEvent);
+
+ /**
+ * Process "popupshown" event. Used by HandleEvent().
+ */
+ void HandlePopupShownEvent(Accessible* aAccessible);
+
+ /*
+ * Process "popuphiding" event. Used by HandleEvent().
+ */
+ void HandlePopupHidingEvent(nsINode* aNode);
+
+#ifdef MOZ_XUL
+ void HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible);
+ void HandleTreeInvalidatedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible);
+
+ uint32_t GetChromeFlags();
+#endif
+};
+
+inline RootAccessible*
+Accessible::AsRoot()
+{
+ return IsRoot() ? static_cast<mozilla::a11y::RootAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/TableAccessible.h b/accessible/generic/TableAccessible.h
new file mode 100644
index 0000000000..fbb8393b67
--- /dev/null
+++ b/accessible/generic/TableAccessible.h
@@ -0,0 +1,187 @@
+/* -*- 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() { 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 ColCount() * aRowIdx + aColIdx; }
+
+ /**
+ * Return the column index of the cell with the given index.
+ */
+ virtual int32_t ColIndexAt(uint32_t aCellIdx)
+ { return aCellIdx % ColCount(); }
+
+ /**
+ * Return the row index of the cell with the given index.
+ */
+ virtual int32_t RowIndexAt(uint32_t aCellIdx)
+ { return aCellIdx / ColCount(); }
+
+ /**
+ * Get the row and column indices for the cell at the given index.
+ */
+ virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx)
+ {
+ uint32_t colCount = ColCount();
+ *aRowIdx = aCellIdx / colCount;
+ *aColIdx = aCellIdx % colCount;
+ }
+
+ /**
+ * 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) = 0;
+
+ /**
+ * Get the set of selected cell indices.
+ */
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) = 0;
+
+ /**
+ * Get the set of selected column indices.
+ */
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) = 0;
+
+ /**
+ * Get the set of selected row indices.
+ */
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) = 0;
+
+ /**
+ * Select the given column unselecting any other selected columns.
+ */
+ virtual void SelectCol(uint32_t aColIdx) {}
+
+ /**
+ * Select the given row unselecting all other previously selected rows.
+ */
+ virtual void SelectRow(uint32_t aRowIdx) {}
+
+ /**
+ * Unselect the given column leaving other selected columns selected.
+ */
+ virtual void UnselectCol(uint32_t aColIdx) {}
+
+ /**
+ * Unselect the given row leaving other selected rows selected.
+ */
+ virtual void UnselectRow(uint32_t aRowIdx) {}
+
+ /**
+ * 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/generic/TableCellAccessible.cpp b/accessible/generic/TableCellAccessible.cpp
new file mode 100644
index 0000000000..3c840127cb
--- /dev/null
+++ b/accessible/generic/TableCellAccessible.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "TableCellAccessible.h"
+
+#include "Accessible-inl.h"
+#include "TableAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void
+TableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ TableAccessible* table = Table();
+ if (!table)
+ return;
+
+ // Move to the left to find row header cells
+ for (uint32_t curColIdx = colIdx - 1; curColIdx < colIdx; curColIdx--) {
+ Accessible* cell = table->CellAt(rowIdx, curColIdx);
+ if (!cell)
+ continue;
+
+ // CellAt should always return a TableCellAccessible (XXX Bug 587529)
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ NS_ASSERTION(tableCell, "cell should be a table cell!");
+ if (!tableCell)
+ continue;
+
+ // Avoid addding cells multiple times, if this cell spans more columns
+ // we'll get it later.
+ if (tableCell->ColIdx() == curColIdx && cell->Role() == roles::ROWHEADER)
+ aCells->AppendElement(cell);
+ }
+}
+
+void
+TableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ TableAccessible* table = Table();
+ if (!table)
+ return;
+
+ // Move up to find column header cells
+ for (uint32_t curRowIdx = rowIdx - 1; curRowIdx < rowIdx; curRowIdx--) {
+ Accessible* cell = table->CellAt(curRowIdx, colIdx);
+ if (!cell)
+ continue;
+
+ // CellAt should always return a TableCellAccessible (XXX Bug 587529)
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ NS_ASSERTION(tableCell, "cell should be a table cell!");
+ if (!tableCell)
+ continue;
+
+ // Avoid addding cells multiple times, if this cell spans more rows
+ // we'll get it later.
+ if (tableCell->RowIdx() == curRowIdx && cell->Role() == roles::COLUMNHEADER)
+ aCells->AppendElement(cell);
+ }
+}
diff --git a/accessible/generic/TableCellAccessible.h b/accessible/generic/TableCellAccessible.h
new file mode 100644
index 0000000000..327552fe82
--- /dev/null
+++ b/accessible/generic/TableCellAccessible.h
@@ -0,0 +1,70 @@
+/* -*- 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);
+
+ /**
+ * Return the row header cells for this cell.
+ */
+ virtual void RowHeaderCells(nsTArray<Accessible*>* aCells);
+
+ /**
+ * Returns true if this cell is selected.
+ */
+ virtual bool Selected() = 0;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_TableCellAccessible_h__
diff --git a/accessible/generic/TextLeafAccessible.cpp b/accessible/generic/TextLeafAccessible.cpp
new file mode 100644
index 0000000000..9808833e10
--- /dev/null
+++ b/accessible/generic/TextLeafAccessible.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "TextLeafAccessible.h"
+
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TextLeafAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+TextLeafAccessible::
+ TextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LinkableAccessible(aContent, aDoc)
+{
+ mType = eTextLeafType;
+ mGenericTypes |= eText;
+ mStateFlags |= eNoKidsFromDOM;
+}
+
+TextLeafAccessible::~TextLeafAccessible()
+{
+}
+
+role
+TextLeafAccessible::NativeRole()
+{
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->IsGeneratedContentFrame())
+ return roles::STATICTEXT;
+
+ return roles::TEXT_LEAF;
+}
+
+void
+TextLeafAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength)
+{
+ aText.Append(Substring(mText, aStartOffset, aLength));
+}
+
+ENameValueFlag
+TextLeafAccessible::Name(nsString& aName)
+{
+ // Text node, ARIA can't be used.
+ aName = mText;
+ return eNameOK;
+}
diff --git a/accessible/generic/TextLeafAccessible.h b/accessible/generic/TextLeafAccessible.h
new file mode 100644
index 0000000000..555929fbf2
--- /dev/null
+++ b/accessible/generic/TextLeafAccessible.h
@@ -0,0 +1,51 @@
+/* -*- 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_TextLeafAccessible_h__
+#define mozilla_a11y_TextLeafAccessible_h__
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Generic class used for text nodes.
+ */
+class TextLeafAccessible : public LinkableAccessible
+{
+public:
+ TextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~TextLeafAccessible();
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX) override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+
+ // TextLeafAccessible
+ void SetText(const nsAString& aText) { mText = aText; }
+ const nsString& Text() const { return mText; }
+
+protected:
+ nsString mText;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcast method
+
+inline TextLeafAccessible*
+Accessible::AsTextLeaf()
+{
+ return IsTextLeaf() ? static_cast<TextLeafAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build
new file mode 100644
index 0000000000..a9e97acf04
--- /dev/null
+++ b/accessible/generic/moz.build
@@ -0,0 +1,70 @@
+# -*- 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',
+ 'DocAccessible.h',
+ 'HyperTextAccessible.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Accessible.cpp',
+ 'ApplicationAccessible.cpp',
+ 'ARIAGridAccessible.cpp',
+ 'BaseAccessibles.cpp',
+ 'DocAccessible.cpp',
+ 'FormControlAccessible.cpp',
+ 'HyperTextAccessible.cpp',
+ 'ImageAccessible.cpp',
+ 'OuterDocAccessible.cpp',
+ 'RootAccessible.cpp',
+ 'TableCellAccessible.cpp',
+ 'TextLeafAccessible.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/html',
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/dom/base',
+ '/layout/generic',
+ '/layout/xul',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/win',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/other',
+ ]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ 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',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']