diff -Nru thunderbird-115.3.1+build1/accessible/atk/AccessibleWrap.cpp thunderbird-115.4.1+build1/accessible/atk/AccessibleWrap.cpp --- thunderbird-115.3.1+build1/accessible/atk/AccessibleWrap.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/atk/AccessibleWrap.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -16,8 +16,8 @@ #include "RemoteAccessible.h" #include "DocAccessibleParent.h" #include "RootAccessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsMai.h" #include "nsMaiHyperlink.h" #include "nsString.h" @@ -1442,7 +1442,7 @@ } // static -Accessible* AccessibleWrap::GetColumnHeader(TableAccessibleBase* aAccessible, +Accessible* AccessibleWrap::GetColumnHeader(TableAccessible* aAccessible, int32_t aColIdx) { if (!aAccessible) { return nullptr; @@ -1460,7 +1460,7 @@ } // otherwise get column header for the data cell at the first row. - TableCellAccessibleBase* tableCell = cell->AsTableCellBase(); + TableCellAccessible* tableCell = cell->AsTableCell(); if (!tableCell) { return nullptr; } @@ -1475,7 +1475,7 @@ } // static -Accessible* AccessibleWrap::GetRowHeader(TableAccessibleBase* aAccessible, +Accessible* AccessibleWrap::GetRowHeader(TableAccessible* aAccessible, int32_t aRowIdx) { if (!aAccessible) { return nullptr; @@ -1493,7 +1493,7 @@ } // otherwise get row header for the data cell at the first column. - TableCellAccessibleBase* tableCell = cell->AsTableCellBase(); + TableCellAccessible* tableCell = cell->AsTableCell(); if (!tableCell) { return nullptr; } diff -Nru thunderbird-115.3.1+build1/accessible/atk/AccessibleWrap.h thunderbird-115.4.1+build1/accessible/atk/AccessibleWrap.h --- thunderbird-115.3.1+build1/accessible/atk/AccessibleWrap.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/atk/AccessibleWrap.h 2023-10-24 21:13:27.000000000 +0000 @@ -73,9 +73,9 @@ static void GetKeyBinding(Accessible* aAccessible, nsAString& aResult); - static Accessible* GetColumnHeader(TableAccessibleBase* aAccessible, + static Accessible* GetColumnHeader(TableAccessible* aAccessible, int32_t aColIdx); - static Accessible* GetRowHeader(TableAccessibleBase* aAccessible, + static Accessible* GetRowHeader(TableAccessible* aAccessible, int32_t aRowIdx); protected: diff -Nru thunderbird-115.3.1+build1/accessible/atk/nsMaiInterfaceTableCell.cpp thunderbird-115.4.1+build1/accessible/atk/nsMaiInterfaceTableCell.cpp --- thunderbird-115.3.1+build1/accessible/atk/nsMaiInterfaceTableCell.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/atk/nsMaiInterfaceTableCell.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -6,8 +6,8 @@ #include "InterfaceInitFuncs.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsAccessibilityService.h" #include "nsMai.h" #include "RemoteAccessible.h" @@ -24,7 +24,7 @@ if (!acc) { return 0; } - return static_cast(acc->AsTableCellBase()->ColExtent()); + return static_cast(acc->AsTableCell()->ColExtent()); } static gint GetRowSpanCB(AtkTableCell* aCell) { @@ -32,7 +32,7 @@ if (!acc) { return 0; } - return static_cast(acc->AsTableCellBase()->RowExtent()); + return static_cast(acc->AsTableCell()->RowExtent()); } static gboolean GetPositionCB(AtkTableCell* aCell, gint* aRow, gint* aCol) { @@ -40,7 +40,7 @@ if (!acc) { return false; } - TableCellAccessibleBase* cell = acc->AsTableCellBase(); + TableCellAccessible* cell = acc->AsTableCell(); if (!cell) { return false; } @@ -55,7 +55,7 @@ if (!acc) { return false; } - TableCellAccessibleBase* cellAcc = acc->AsTableCellBase(); + TableCellAccessible* cellAcc = acc->AsTableCell(); if (!cellAcc) { return false; } @@ -71,7 +71,11 @@ if (!acc) { return nullptr; } - TableAccessibleBase* table = acc->AsTableCellBase()->Table(); + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } + TableAccessible* table = cell->Table(); if (!table) { return nullptr; } @@ -84,8 +88,12 @@ if (!acc) { return nullptr; } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } AutoTArray headers; - acc->AsTableCellBase()->ColHeaderCells(&headers); + cell->ColHeaderCells(&headers); if (headers.IsEmpty()) { return nullptr; } @@ -105,8 +113,12 @@ if (!acc) { return nullptr; } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } AutoTArray headers; - acc->AsTableCellBase()->RowHeaderCells(&headers); + cell->RowHeaderCells(&headers); if (headers.IsEmpty()) { return nullptr; } diff -Nru thunderbird-115.3.1+build1/accessible/atk/nsMaiInterfaceTable.cpp thunderbird-115.4.1+build1/accessible/atk/nsMaiInterfaceTable.cpp --- thunderbird-115.3.1+build1/accessible/atk/nsMaiInterfaceTable.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/atk/nsMaiInterfaceTable.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -7,7 +7,7 @@ #include "InterfaceInitFuncs.h" #include "AccessibleWrap.h" -#include "mozilla/a11y/TableAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" #include "nsAccessibilityService.h" #include "nsMai.h" #include "RemoteAccessible.h" @@ -29,7 +29,7 @@ if (!acc) { return nullptr; } - Accessible* cell = acc->AsTableBase()->CellAt(aRowIdx, aColIdx); + Accessible* cell = acc->AsTable()->CellAt(aRowIdx, aColIdx); if (!cell) { return nullptr; } @@ -52,7 +52,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->CellIndexAt(aRowIdx, aColIdx)); + return static_cast(acc->AsTable()->CellIndexAt(aRowIdx, aColIdx)); } static gint getColumnAtIndexCB(AtkTable* aTable, gint aIdx) { @@ -64,7 +64,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->ColIndexAt(aIdx)); + return static_cast(acc->AsTable()->ColIndexAt(aIdx)); } static gint getRowAtIndexCB(AtkTable* aTable, gint aIdx) { @@ -76,7 +76,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->RowIndexAt(aIdx)); + return static_cast(acc->AsTable()->RowIndexAt(aIdx)); } static gint getColumnCountCB(AtkTable* aTable) { @@ -84,7 +84,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->ColCount()); + return static_cast(acc->AsTable()->ColCount()); } static gint getRowCountCB(AtkTable* aTable) { @@ -92,7 +92,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->RowCount()); + return static_cast(acc->AsTable()->RowCount()); } static gint getColumnExtentAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { @@ -104,7 +104,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->ColExtentAt(aRowIdx, aColIdx)); + return static_cast(acc->AsTable()->ColExtentAt(aRowIdx, aColIdx)); } static gint getRowExtentAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { @@ -112,7 +112,7 @@ if (!acc) { return -1; } - return static_cast(acc->AsTableBase()->RowExtentAt(aRowIdx, aColIdx)); + return static_cast(acc->AsTable()->RowExtentAt(aRowIdx, aColIdx)); } static AtkObject* getCaptionCB(AtkTable* aTable) { @@ -120,7 +120,7 @@ if (!acc) { return nullptr; } - Accessible* caption = acc->AsTableBase()->Caption(); + Accessible* caption = acc->AsTable()->Caption(); return caption ? GetWrapperFor(caption) : nullptr; } @@ -130,7 +130,7 @@ return nullptr; } nsAutoString autoStr; - acc->AsTableBase()->ColDescription(aColumn, autoStr); + acc->AsTable()->ColDescription(aColumn, autoStr); return AccessibleWrap::ReturnString(autoStr); } @@ -139,8 +139,7 @@ if (!acc) { return nullptr; } - Accessible* header = - AccessibleWrap::GetColumnHeader(acc->AsTableBase(), aColIdx); + Accessible* header = AccessibleWrap::GetColumnHeader(acc->AsTable(), aColIdx); return header ? GetWrapperFor(header) : nullptr; } @@ -150,7 +149,7 @@ return nullptr; } nsAutoString autoStr; - acc->AsTableBase()->RowDescription(aRow, autoStr); + acc->AsTable()->RowDescription(aRow, autoStr); return AccessibleWrap::ReturnString(autoStr); } @@ -159,8 +158,7 @@ if (!acc) { return nullptr; } - Accessible* header = - AccessibleWrap::GetRowHeader(acc->AsTableBase(), aRowIdx); + Accessible* header = AccessibleWrap::GetRowHeader(acc->AsTable(), aRowIdx); return header ? GetWrapperFor(header) : nullptr; } @@ -180,7 +178,7 @@ return 0; } AutoTArray cols; - acc->AsTableBase()->SelectedColIndices(&cols); + acc->AsTable()->SelectedColIndices(&cols); if (cols.IsEmpty()) return 0; @@ -201,7 +199,7 @@ return 0; } AutoTArray rows; - acc->AsTableBase()->SelectedRowIndices(&rows); + acc->AsTable()->SelectedRowIndices(&rows); gint* atkRows = g_new(gint, rows.Length()); if (!atkRows) { @@ -219,7 +217,7 @@ if (!acc) { return FALSE; } - return static_cast(acc->AsTableBase()->IsColSelected(aColIdx)); + return static_cast(acc->AsTable()->IsColSelected(aColIdx)); } static gboolean isRowSelectedCB(AtkTable* aTable, gint aRowIdx) { @@ -227,7 +225,7 @@ if (!acc) { return FALSE; } - return static_cast(acc->AsTableBase()->IsRowSelected(aRowIdx)); + return static_cast(acc->AsTable()->IsRowSelected(aRowIdx)); } static gboolean isCellSelectedCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { @@ -236,7 +234,7 @@ return FALSE; } return static_cast( - acc->AsTableBase()->IsCellSelected(aRowIdx, aColIdx)); + acc->AsTable()->IsCellSelected(aRowIdx, aColIdx)); } } diff -Nru thunderbird-115.3.1+build1/accessible/base/AccGroupInfo.cpp thunderbird-115.4.1+build1/accessible/base/AccGroupInfo.cpp --- thunderbird-115.3.1+build1/accessible/base/AccGroupInfo.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/AccGroupInfo.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -4,7 +4,7 @@ #include "AccGroupInfo.h" #include "mozilla/a11y/Accessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" #include "nsAccUtils.h" #include "nsIAccessiblePivot.h" @@ -14,8 +14,6 @@ using namespace mozilla::a11y; -static bool IsGenericContainer(role aRole); -static Accessible* GetRelevantParent(const Accessible* aAcc); static role BaseRole(role aRole); // This rule finds candidate siblings for compound widget children. @@ -37,7 +35,7 @@ // Ignore generic accessibles, but keep searching through the subtree for // siblings. - if (IsGenericContainer(accRole)) { + if (aAcc->IsGeneric()) { return nsIAccessibleTraversalRule::FILTER_IGNORE; } @@ -57,7 +55,7 @@ void AccGroupInfo::Update() { mParent = nullptr; - Accessible* parent = GetRelevantParent(mItem); + Accessible* parent = mItem->GetNonGenericParent(); if (!parent) { return; } @@ -173,7 +171,7 @@ if (mRole == roles::OUTLINEITEM) { // Find the relevant grandparent of the item. Use that parent as the root // and find the previous outline item sibling within that root. - Accessible* grandParent = GetRelevantParent(parent); + Accessible* grandParent = parent->GetNonGenericParent(); MOZ_ASSERT(grandParent); Pivot pivot{grandParent}; CompoundWidgetSiblingRule parentSiblingRule{mRole}; @@ -188,7 +186,7 @@ // the parent of the item will be a group and containing item of the group is // a conceptual parent of the item. if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { - Accessible* grandParent = GetRelevantParent(parent); + Accessible* grandParent = parent->GetNonGenericParent(); if (grandParent && grandParent->Role() == mRole) { mParent = grandParent; } @@ -271,7 +269,7 @@ return *val; } } - if (TableAccessibleBase* tableAcc = aContainer->AsTableBase()) { + if (TableAccessible* tableAcc = aContainer->AsTable()) { return tableAcc->RowCount(); } break; @@ -282,7 +280,7 @@ return *val; } } - if (TableAccessibleBase* tableAcc = table->AsTableBase()) { + if (TableAccessible* tableAcc = table->AsTable()) { return tableAcc->ColCount(); } } @@ -373,23 +371,6 @@ return aAccessible->GetLevel(true); } -static bool IsGenericContainer(role aRole) { - return aRole == roles::TEXT || aRole == roles::TEXT_CONTAINER || - aRole == roles::SECTION; -} - -static Accessible* GetRelevantParent(const Accessible* aAcc) { - MOZ_ASSERT(aAcc); - - // Search through ancestors until we find a relevant parent, skipping generic - // accessibles. - Accessible* parent = aAcc->Parent(); - while (parent && IsGenericContainer(parent->Role())) { - parent = parent->Parent(); - } - return parent; -} - static role BaseRole(role aRole) { if (aRole == roles::CHECK_MENU_ITEM || aRole == roles::PARENT_MENUITEM || aRole == roles::RADIO_MENU_ITEM) { diff -Nru thunderbird-115.3.1+build1/accessible/base/CachedTableAccessible.cpp thunderbird-115.4.1+build1/accessible/base/CachedTableAccessible.cpp --- thunderbird-115.3.1+build1/accessible/base/CachedTableAccessible.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/CachedTableAccessible.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -8,6 +8,7 @@ #include "AccIterator.h" #include "DocAccessibleParent.h" +#include "HTMLTableAccessible.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "mozilla/UniquePtr.h" @@ -15,8 +16,6 @@ #include "nsIAccessiblePivot.h" #include "Pivot.h" #include "RemoteAccessible.h" -#include "TableAccessible.h" -#include "TableCellAccessible.h" namespace mozilla::a11y { @@ -216,44 +215,14 @@ return mCells[cellIdx].Acc(mAcc); } -void CachedTableAccessible::SelectCol(uint32_t aColIdx) { - if (LocalAccessible* localAcc = mAcc->AsLocal()) { - TableAccessible* table = localAcc->AsTable(); - table->SelectCol(aColIdx); - } - // XXX Implement support for RemoteAccessible. -} - -void CachedTableAccessible::UnselectCol(uint32_t aColIdx) { - if (LocalAccessible* localAcc = mAcc->AsLocal()) { - TableAccessible* table = localAcc->AsTable(); - table->UnselectCol(aColIdx); - } - // XXX Implement support for RemoteAccessible. -} - -void CachedTableAccessible::SelectRow(uint32_t aRowIdx) { - if (LocalAccessible* localAcc = mAcc->AsLocal()) { - TableAccessible* table = localAcc->AsTable(); - table->SelectRow(aRowIdx); - } - // XXX Implement support for RemoteAccessible. -} - -void CachedTableAccessible::UnselectRow(uint32_t aRowIdx) { - if (LocalAccessible* localAcc = mAcc->AsLocal()) { - TableAccessible* table = localAcc->AsTable(); - table->UnselectRow(aRowIdx); - } - // XXX Implement support for RemoteAccessible. -} - bool CachedTableAccessible::IsProbablyLayoutTable() { if (RemoteAccessible* remoteAcc = mAcc->AsRemote()) { return remoteAcc->TableIsProbablyForLayout(); } - TableAccessible* localTable = mAcc->AsLocal()->AsTable(); - return localTable->IsProbablyLayoutTable(); + if (auto* localTable = HTMLTableAccessible::GetFrom(mAcc->AsLocal())) { + return localTable->IsProbablyLayoutTable(); + } + return false; } /* static */ @@ -261,8 +230,7 @@ Accessible* aAcc) { MOZ_ASSERT(aAcc->IsTableCell()); for (Accessible* parent = aAcc; parent; parent = parent->Parent()) { - if (auto* table = - static_cast(parent->AsTableBase())) { + if (auto* table = static_cast(parent->AsTable())) { if (auto cellIdx = table->mAccToCellIdx.Lookup(aAcc)) { return &table->mCells[*cellIdx]; } @@ -278,12 +246,11 @@ return acc; } -TableAccessibleBase* CachedTableCellAccessible::Table() const { +TableAccessible* CachedTableCellAccessible::Table() const { for (const Accessible* acc = mAcc; acc; acc = acc->Parent()) { // Since the caller has this cell, the table is already created, so it's // okay to ignore the const restriction here. - if (TableAccessibleBase* table = - const_cast(acc)->AsTableBase()) { + if (TableAccessible* table = const_cast(acc)->AsTable()) { return table; } } @@ -299,13 +266,11 @@ return *colSpan; } } - } else if (LocalAccessible* localAcc = mAcc->AsLocal()) { + } else if (auto* cell = HTMLTableCellAccessible::GetFrom(mAcc->AsLocal())) { // For HTML table cells, we must use the HTMLTableCellAccessible // GetColExtent method rather than using the DOM attributes directly. // This is because of things like rowspan="0" which depend on knowing // about thead, tbody, etc., which is info we don't have in the a11y tree. - TableCellAccessible* cell = localAcc->AsTableCell(); - MOZ_ASSERT(cell); uint32_t colExtent = cell->ColExtent(); MOZ_ASSERT(colExtent > 0); if (colExtent > 0) { @@ -324,13 +289,11 @@ return *rowSpan; } } - } else if (LocalAccessible* localAcc = mAcc->AsLocal()) { + } else if (auto* cell = HTMLTableCellAccessible::GetFrom(mAcc->AsLocal())) { // For HTML table cells, we must use the HTMLTableCellAccessible // GetRowExtent method rather than using the DOM attributes directly. // This is because of things like rowspan="0" which depend on knowing // about thead, tbody, etc., which is info we don't have in the a11y tree. - TableCellAccessible* cell = localAcc->AsTableCell(); - MOZ_ASSERT(cell); uint32_t rowExtent = cell->RowExtent(); MOZ_ASSERT(rowExtent > 0); if (rowExtent > 0) { diff -Nru thunderbird-115.3.1+build1/accessible/base/CachedTableAccessible.h thunderbird-115.4.1+build1/accessible/base/CachedTableAccessible.h --- thunderbird-115.3.1+build1/accessible/base/CachedTableAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/CachedTableAccessible.h 2023-10-24 21:13:26.000000000 +0000 @@ -7,8 +7,8 @@ #ifndef CACHED_TABLE_ACCESSIBLE_H #define CACHED_TABLE_ACCESSIBLE_H -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "mozilla/UniquePtr.h" #include "nsTHashMap.h" @@ -20,11 +20,11 @@ class CachedTableAccessible; -class CachedTableCellAccessible final : public TableCellAccessibleBase { +class CachedTableCellAccessible final : public TableCellAccessible { public: static CachedTableCellAccessible* GetFrom(Accessible* aAcc); - virtual TableAccessibleBase* Table() const override; + virtual TableAccessible* Table() const override; virtual uint32_t ColIdx() const override { return static_cast(mColIdx); @@ -76,7 +76,7 @@ /** * TableAccessible implementation which builds and queries a cache. */ -class CachedTableAccessible final : public TableAccessibleBase { +class CachedTableAccessible final : public TableAccessible { public: static CachedTableAccessible* GetFrom(Accessible* aAcc); @@ -256,11 +256,6 @@ } } - 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 mAcc; } virtual bool IsProbablyLayoutTable() override; diff -Nru thunderbird-115.3.1+build1/accessible/base/HTMLMarkupMap.h thunderbird-115.4.1+build1/accessible/base/HTMLMarkupMap.h --- thunderbird-115.3.1+build1/accessible/base/HTMLMarkupMap.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/HTMLMarkupMap.h 2023-10-24 21:13:26.000000000 +0000 @@ -349,28 +349,9 @@ MARKUPMAP( table, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - if (!aElement->GetPrimaryFrame() || - aElement->GetPrimaryFrame()->AccessibleType() != eHTMLTableType) { - return new ARIAGridAccessible(aElement, aContext->Document()); - } - - // Make sure that our children are proper layout table parts - for (nsIContent* child = aElement->GetFirstChild(); child; - child = child->GetNextSibling()) { - if (child->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tfoot, - nsGkAtoms::tbody, nsGkAtoms::tr)) { - // These children elements need to participate in the layout table - // and need table row(group) frames. - nsIFrame* childFrame = child->GetPrimaryFrame(); - if (childFrame && (!childFrame->IsTableRowGroupFrame() && - !childFrame->IsTableRowFrame())) { - return new ARIAGridAccessible(aElement, aContext->Document()); - } - } - } - return nullptr; + return new HTMLTableAccessible(aElement, aContext->Document()); }, - 0) + roles::TABLE) MARKUPMAP(time, New_HyperText, 0, Attr(xmlroles, time), AttrFromDOM(datetime, datetime)) @@ -380,24 +361,14 @@ MARKUPMAP( td, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - if (aContext->IsTableRow() && - aContext->GetContent() == aElement->GetParent()) { - // If HTML:td element is part of its HTML:table, which has CSS - // display style other than 'table', then create a generic table - // cell accessible, because there's no underlying table layout and - // thus native HTML table cell class doesn't work. The same is - // true if the cell itself has CSS display:block;. - if (!aContext->IsHTMLTableRow() || !aElement->GetPrimaryFrame() || - aElement->GetPrimaryFrame()->AccessibleType() != - eHTMLTableCellType) { - return new ARIAGridCellAccessible(aElement, aContext->Document()); - } - if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::scope)) { - return new HTMLTableHeaderCellAccessible(aElement, - aContext->Document()); - } + if (!aContext->IsHTMLTableRow()) { + return nullptr; } - return nullptr; + if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::scope)) { + return new HTMLTableHeaderCellAccessible(aElement, + aContext->Document()); + } + return new HTMLTableCellAccessible(aElement, aContext->Document()); }, 0) @@ -406,22 +377,10 @@ MARKUPMAP( th, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - if (aContext->IsTableRow() && - aContext->GetContent() == aElement->GetParent()) { - // If HTML:th element is part of its HTML:table, which has CSS - // display style other than 'table', then create a generic table - // cell accessible, because there's no underlying table layout and - // thus native HTML table cell class doesn't work. The same is - // true if the cell itself has CSS display:block;. - if (!aContext->IsHTMLTableRow() || !aElement->GetPrimaryFrame() || - aElement->GetPrimaryFrame()->AccessibleType() != - eHTMLTableCellType) { - return new ARIAGridCellAccessible(aElement, aContext->Document()); - } - return new HTMLTableHeaderCellAccessible(aElement, - aContext->Document()); + if (!aContext->IsHTMLTableRow()) { + return nullptr; } - return nullptr; + return new HTMLTableHeaderCellAccessible(aElement, aContext->Document()); }, 0) @@ -430,37 +389,21 @@ MARKUPMAP( tr, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - // If HTML:tr element is part of its HTML:table, which has CSS - // display style other than 'table', then create a generic table row - // accessible, because there's no underlying table layout and thus - // native HTML table row class doesn't work. Refer to - // CreateAccessibleByFrameType dual logic. - LocalAccessible* table = aContext->IsTable() ? aContext : nullptr; - if (!table && aContext->LocalParent() && - aContext->LocalParent()->IsTable()) { - table = aContext->LocalParent(); - } - if (table) { - nsIContent* parentContent = aElement->GetParent(); - nsIFrame* parentFrame = parentContent->GetPrimaryFrame(); - if (!parentFrame || !parentFrame->IsTableWrapperFrame()) { - parentContent = parentContent->GetParent(); - // parentContent can be null if this tr is at the top level of a - // shadow root (with the table outside the shadow root). - parentFrame = - parentContent ? parentContent->GetPrimaryFrame() : nullptr; - if (table->GetContent() == parentContent && - ((!parentFrame || !parentFrame->IsTableWrapperFrame()) || - !aElement->GetPrimaryFrame() || - aElement->GetPrimaryFrame()->AccessibleType() != - eHTMLTableRowType)) { - return new ARIARowAccessible(aElement, aContext->Document()); - } - } + if (aContext->IsTableRow()) { + // A within a row isn't valid. + return nullptr; + } + // Check if this is within a table. We check the grandparent because + // it might be inside a rowgroup. We don't specifically check for an HTML + // table because there are cases where there is a inside a + //
such as Monorail. + if (aContext->IsTable() || + (aContext->LocalParent() && aContext->LocalParent()->IsTable())) { + return new HTMLTableRowAccessible(aElement, aContext->Document()); } return nullptr; }, - 0) + roles::ROW) MARKUPMAP( ul, diff -Nru thunderbird-115.3.1+build1/accessible/base/MathMLMarkupMap.h thunderbird-115.4.1+build1/accessible/base/MathMLMarkupMap.h --- thunderbird-115.3.1+build1/accessible/base/MathMLMarkupMap.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/MathMLMarkupMap.h 2023-10-24 21:13:27.000000000 +0000 @@ -59,12 +59,6 @@ MARKUPMAP( mtable_, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - // If we're not a table according to layout, use a generic accessible. - if (!aElement->GetPrimaryFrame() || - aElement->GetPrimaryFrame()->AccessibleType() != eHTMLTableType) { - return new ARIAGridAccessible(aElement, aContext->Document()); - } - return new HTMLTableAccessible(aElement, aContext->Document()); }, roles::MATHML_TABLE, AttrFromDOM(align, align), @@ -80,40 +74,16 @@ MARKUPMAP( mtr_, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - if (aContext->IsHTMLTable() && aElement->GetPrimaryFrame() && - aElement->GetPrimaryFrame()->AccessibleType() == eHTMLTableRowType) { - return new HTMLTableRowAccessible(aElement, aContext->Document()); - } - - // If the mtr element in a MathML table has a display style other than - // 'table', create a generic table row accessible, since there's no - // underlying table layout. - if (aContext->IsTable()) { - return new ARIARowAccessible(aElement, aContext->Document()); - } - - return nullptr; + return new HTMLTableRowAccessible(aElement, aContext->Document()); }, roles::MATHML_TABLE_ROW) MARKUPMAP( mtd_, [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* { - if (aContext->IsHTMLTableRow() && aElement->GetPrimaryFrame() && - aElement->GetPrimaryFrame()->AccessibleType() == eHTMLTableCellType) { - return new HTMLTableCellAccessible(aElement, aContext->Document()); - } - - // If the mtd element in a MathML table has a display style other than - // 'table', create a generic table cell accessible, since there's no - // underlying table layout. - if (aContext->IsTableRow()) { - return new ARIAGridCellAccessible(aElement, aContext->Document()); - } - - return nullptr; + return new HTMLTableCellAccessible(aElement, aContext->Document()); }, - roles::MATHML_CELL) + 0) MARKUPMAP(maction_, New_HyperText, roles::MATHML_ACTION, AttrFromDOM(actiontype_, actiontype_), diff -Nru thunderbird-115.3.1+build1/accessible/base/nsAccessibilityService.cpp thunderbird-115.4.1+build1/accessible/base/nsAccessibilityService.cpp --- thunderbird-115.3.1+build1/accessible/base/nsAccessibilityService.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/nsAccessibilityService.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -29,6 +29,7 @@ #include "nsDOMTokenList.h" #include "nsCRT.h" #include "nsEventShell.h" +#include "nsGkAtoms.h" #include "nsIFrameInlines.h" #include "nsServiceManagerUtils.h" #include "nsTextFormatter.h" @@ -61,6 +62,7 @@ #include "nsTreeBodyFrame.h" #include "nsTreeColumns.h" #include "nsTreeUtils.h" +#include "mozilla/a11y/AccTypes.h" #include "mozilla/ArrayUtils.h" #include "mozilla/dom/DOMStringList.h" #include "mozilla/dom/EventTarget.h" @@ -68,6 +70,7 @@ #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/ProfilerMarkers.h" +#include "mozilla/RefPtr.h" #include "mozilla/Services.h" #include "XULAlertAccessible.h" @@ -97,39 +100,38 @@ //////////////////////////////////////////////////////////////////////////////// /** - * Return true if the role map entry is an ARIA table part. + * If the element has an ARIA attribute that requires a specific Accessible + * class, create and return it. Otherwise, return null. */ -static bool IsARIATablePart(const nsRoleMapEntry* aRoleMapEntry) { - return aRoleMapEntry && - (aRoleMapEntry->accTypes & (eTableCell | eTableRow | eTable)); -} - -/** - * Create and return an Accessible for the given content depending on which - * table part we think it is. - */ -static LocalAccessible* CreateARIATablePartAcc( +static LocalAccessible* MaybeCreateSpecificARIAAccessible( const nsRoleMapEntry* aRoleMapEntry, const LocalAccessible* aContext, nsIContent* aContent, DocAccessible* aDocument) { - // In case of ARIA grid or table use table-specific classes if it's not - // native table based. - if ((aRoleMapEntry->accTypes & eTableCell)) { - if (aContext->IsTableRow()) { - return new ARIAGridCellAccessible(aContent, aDocument); + if (aRoleMapEntry && aRoleMapEntry->accTypes & eTableCell) { + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) && + aContext->IsHTMLTableRow()) { + // Don't use ARIAGridCellAccessible for a valid td/th because + // HTMLTableCellAccessible can provide additional info; e.g. row/col span + // from the layout engine. + return nullptr; + } + // A cell must be in a row. + if (aContext->Role() != roles::ROW) { + return nullptr; + } + // That row must be in a table, though there may be an intervening rowgroup. + Accessible* parent = aContext->GetNonGenericParent(); + if (!parent) { + return nullptr; + } + if (!parent->IsTable() && parent->Role() == roles::GROUPING) { + parent = parent->GetNonGenericParent(); + if (!parent) { + return nullptr; + } } - } else if (aRoleMapEntry->IsOfType(eTableRow)) { - if (aContext->IsTable() || - // There can be an Accessible between a row and its table, but it - // can only be a row group or a generic container. This is - // consistent with Filters::GetRow and CachedTableAccessible's - // TablePartRule. - ((aContext->Role() == roles::GROUPING || - (aContext->IsGenericHyperText() && !aContext->ARIARoleMap())) && - aContext->LocalParent() && aContext->LocalParent()->IsTable())) { - return new ARIARowAccessible(aContent, aDocument); + if (parent->IsTable()) { + return new ARIAGridCellAccessible(aContent, aDocument); } - } else if (aRoleMapEntry->IsOfType(eTable)) { - return new ARIAGridAccessible(aContent, aDocument); } return nullptr; } @@ -181,16 +183,27 @@ * marked presentational with role="presentation", etc. MustBeAccessible causes * an Accessible to be created as if it weren't marked presentational at all; * e.g. will expose roles::TABLE and - * support TableAccessibleBase. In contrast, this function causes a generic + * support TableAccessible. In contrast, this function causes a generic * Accessible to be created; e.g.
will expose roles::TEXT_CONTAINER and will not support - * TableAccessibleBase. This is necessary in certain cases for the + * TableAccessible. This is necessary in certain cases for the * RemoteAccessible cache. */ static bool MustBeGenericAccessible(nsIContent* aContent, DocAccessible* aDocument) { + if (aContent->IsInNativeAnonymousSubtree() || aContent->IsSVGElement()) { + // We should not force create accs for anonymous content. + // This is an issue for inputs, which have an intermediate + // container with relevant overflow styling between the input + // and its internal input content. + // We should also avoid this for SVG elements (ie. ``s + // which have default overflow:hidden styling). + return false; + } nsIFrame* frame = aContent->GetPrimaryFrame(); MOZ_ASSERT(frame); + nsAutoCString overflow; + frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow); // If the frame has been transformed, and the content has any children, we // should create an Accessible so that we can account for the transform when // calculating the Accessible's bounds using the parent process cache. @@ -203,7 +216,9 @@ ((aContent->HasChildren() && frame->IsTransformed()) || frame->IsStickyPositioned() || (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && - nsLayoutUtils::IsReallyFixedPos(frame))); + nsLayoutUtils::IsReallyFixedPos(frame)) || + overflow.Equals("auto"_ns) || overflow.Equals("scroll"_ns) || + overflow.Equals("hidden"_ns)); } /** @@ -517,12 +532,14 @@ // If the content has children and its frame has a transform, create an // Accessible so that we can account for the transform when calculating // the Accessible's bounds using the parent process cache. Ditto for - // position: fixed/sticky. + // position: fixed/sticky and content with overflow styling (hidden, auto, + // scroll) if (const nsIFrame* frame = aContent->GetPrimaryFrame()) { const auto& disp = *frame->StyleDisplay(); if (disp.HasTransform(frame) || disp.mPosition == StylePositionProperty::Fixed || - disp.mPosition == StylePositionProperty::Sticky) { + disp.mPosition == StylePositionProperty::Sticky || + disp.IsScrollableOverflow()) { document->ContentInserted(aContent, aContent->GetNextSibling()); } } @@ -1058,28 +1075,25 @@ } else if (nsCoreUtils::CanCreateAccessibleWithoutFrame(content)) { // display:contents element doesn't have a frame, but retains the // semantics. All its children are unaffected. - const MarkupMapInfo* markupMap = GetMarkupMapInfoFor(content); - RefPtr newAcc; - if (markupMap && markupMap->new_func) { - newAcc = markupMap->new_func(content->AsElement(), aContext); + const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement()); + RefPtr newAcc = MaybeCreateSpecificARIAAccessible( + roleMapEntry, aContext, content, document); + const MarkupMapInfo* markupMap = nullptr; + if (!newAcc) { + markupMap = GetMarkupMapInfoFor(content); + if (markupMap && markupMap->new_func) { + newAcc = markupMap->new_func(content->AsElement(), aContext); + } } // Check whether this element has an ARIA role or attribute that requires // us to create an Accessible. - const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement()); const bool hasNonPresentationalARIARole = roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) && !roleMapEntry->Is(nsGkAtoms::none); if (!newAcc && (hasNonPresentationalARIARole || AttributesMustBeAccessible(content, document))) { - // If this element is an ARIA table part, create the proper table part - // Accessible. Otherwise, create a generic HyperTextAccessible. - if (IsARIATablePart(roleMapEntry)) { - newAcc = - CreateARIATablePartAcc(roleMapEntry, aContext, content, document); - } else { - newAcc = new HyperTextAccessibleWrap(content, document); - } + newAcc = new HyperTextAccessibleWrap(content, document); } // If there's still no Accessible but we do have an entry in the markup @@ -1214,14 +1228,14 @@ } if (!newAcc && content->IsHTMLElement()) { // HTML accessibles - const bool isARIATablePart = IsARIATablePart(roleMapEntry); + // We should always use OuterDocAccessible for OuterDocs, even if there's a + // specific ARIA class we would otherwise use. + if (frame->AccessibleType() != eOuterDocType) { + newAcc = MaybeCreateSpecificARIAAccessible(roleMapEntry, aContext, + content, document); + } - if (!isARIATablePart || frame->AccessibleType() == eHTMLTableCellType || - frame->AccessibleType() == eHTMLTableRowType || - frame->AccessibleType() == eHTMLTableType || - // We should always use OuterDocAccessible for OuterDocs, even for - // ARIA table roles. - frame->AccessibleType() == eOuterDocType) { + if (!newAcc) { // Prefer to use markup to decide if and what kind of accessible to // create, const MarkupMapInfo* markupMap = @@ -1235,15 +1249,6 @@ } } - // In case of ARIA grid or table use table-specific classes if it's not - // native table based. - if (isARIATablePart && (!newAcc || newAcc->IsGenericHyperText())) { - if (LocalAccessible* tablePartAcc = CreateARIATablePartAcc( - roleMapEntry, aContext, content, document)) { - newAcc = tablePartAcc; - } - } - // If table has strong ARIA role then all table descendants shouldn't // expose their native roles. if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) { @@ -1595,56 +1600,14 @@ newAcc = new HTMLSpinnerAccessible(aContent, document); break; case eHTMLTableType: - if (aContent->IsHTMLElement(nsGkAtoms::table)) { - newAcc = new HTMLTableAccessible(aContent, document); - } else { - newAcc = new HyperTextAccessibleWrap(aContent, document); - } - break; case eHTMLTableCellType: - // LocalAccessible HTML table cell should be a child of accessible HTML - // table or its row (CSS HTML tables are polite to the used markup at - // certain degree). - // Otherwise create a generic text accessible to avoid text jamming - // when reading by AT. - if (aContext->IsHTMLTableRow() || aContext->IsHTMLTable()) { - newAcc = new HTMLTableCellAccessible(aContent, document); - } else { - newAcc = new HyperTextAccessibleWrap(aContent, document); - } + // We handle markup and ARIA tables elsewhere. If we reach here, this is + // a CSS table part. Just create a generic text container. + newAcc = new HyperTextAccessibleWrap(aContent, document); break; - - case eHTMLTableRowType: { - // LocalAccessible HTML table row may be a child of tbody/tfoot/thead of - // accessible HTML table or a direct child of accessible of HTML table. - LocalAccessible* table = aContext->IsTable() ? aContext : nullptr; - if (!table && aContext->LocalParent() && - aContext->LocalParent()->IsTable()) { - table = aContext->LocalParent(); - } - - if (table) { - nsIContent* parentContent = - aContent->GetParentOrShadowHostNode()->AsContent(); - nsIFrame* parentFrame = nullptr; - if (parentContent) { - parentFrame = parentContent->GetPrimaryFrame(); - if (!parentFrame || !parentFrame->IsTableWrapperFrame()) { - parentContent = - parentContent->GetParentOrShadowHostNode()->AsContent(); - if (parentContent) { - parentFrame = parentContent->GetPrimaryFrame(); - } - } - } - - if (parentFrame && parentFrame->IsTableWrapperFrame() && - table->GetContent() == parentContent) { - newAcc = new HTMLTableRowAccessible(aContent, document); - } - } + case eHTMLTableRowType: + // This is a CSS table row. Don't expose it at all. break; - } case eHTMLTextFieldType: newAcc = new HTMLTextFieldAccessible(aContent, document); break; diff -Nru thunderbird-115.3.1+build1/accessible/base/nsAccUtils.h thunderbird-115.4.1+build1/accessible/base/nsAccUtils.h --- thunderbird-115.3.1+build1/accessible/base/nsAccUtils.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/base/nsAccUtils.h 2023-10-24 21:13:26.000000000 +0000 @@ -106,6 +106,10 @@ static Accessible* TableFor(Accessible* aRow); static LocalAccessible* TableFor(LocalAccessible* aRow); + static const LocalAccessible* TableFor(const LocalAccessible* aAcc) { + return TableFor(const_cast(aAcc)); + } + /** * Return true if the DOM node of a given accessible has a given attribute * with a value of "true". diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/Accessible.h thunderbird-115.4.1+build1/accessible/basetypes/Accessible.h --- thunderbird-115.3.1+build1/accessible/basetypes/Accessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/Accessible.h 2023-10-24 21:13:26.000000000 +0000 @@ -29,8 +29,8 @@ class Relation; enum class RelationType; class RemoteAccessible; -class TableAccessibleBase; -class TableCellAccessibleBase; +class TableAccessible; +class TableCellAccessible; /** * Name type flags. @@ -475,20 +475,19 @@ bool IsDoc() const { return HasGenericType(eDocument); } - /** - * Note: The eTable* types defined in the ARIA map are used in - * nsAccessibilityService::CreateAccessible to determine which ARIAGrid* - * classes to use for accessible object creation. However, an invalid table - * structure might cause these classes not to be used after all. - * - * To make sure we're really dealing with a table/row/cell, only check the - * generic type defined by the class, not the type defined in the ARIA map. - */ - bool IsTableRow() const { return mGenericTypes & eTableRow; } + bool IsTableRow() const { return HasGenericType(eTableRow); } - bool IsTableCell() const { return mGenericTypes & eTableCell; } + bool IsTableCell() const { + // The eTableCell type defined in the ARIA map is used in + // nsAccessibilityService::CreateAccessible to specify when + // ARIAGridCellAccessible should be used for object creation. However, an + // invalid table structure might cause this class not to be used after all. + // To make sure we're really dealing with a cell, only check the generic + // type defined by the class, not the type defined in the ARIA map. + return mGenericTypes & eTableCell; + } - bool IsTable() const { return mGenericTypes & eTable; } + bool IsTable() const { return HasGenericType(eTable); } bool IsHyperText() const { return HasGenericType(eHyperText); } @@ -532,6 +531,7 @@ bool IsHTMLRadioButton() const { return mType == eHTMLRadioButtonType; } bool IsHTMLTable() const { return mType == eHTMLTableType; } + bool IsHTMLTableCell() const { return mType == eHTMLTableCellType; } bool IsHTMLTableRow() const { return mType == eHTMLTableRowType; } bool IsImageMap() const { return mType == eImageMapType; } @@ -579,6 +579,28 @@ virtual bool HasNumericValue() const = 0; /** + * Returns true if this is a generic container element that has no meaning on + * its own. + */ + bool IsGeneric() const { + role accRole = Role(); + return accRole == roles::TEXT || accRole == roles::TEXT_CONTAINER || + accRole == roles::SECTION; + } + + /** + * Returns the nearest ancestor which is not a generic element. + */ + Accessible* GetNonGenericParent() const { + for (Accessible* parent = Parent(); parent; parent = parent->Parent()) { + if (!parent->IsGeneric()) { + return parent; + } + } + return nullptr; + } + + /** * Return true if the link is valid (e. g. points to a valid URL). */ bool IsLinkValid(); @@ -608,8 +630,8 @@ virtual HyperTextAccessibleBase* AsHyperTextBase() { return nullptr; } - virtual TableAccessibleBase* AsTableBase() { return nullptr; } - virtual TableCellAccessibleBase* AsTableCellBase() { return nullptr; } + virtual TableAccessible* AsTable() { return nullptr; } + virtual TableCellAccessible* AsTableCell() { return nullptr; } #ifdef A11Y_LOG /** diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/HyperTextAccessibleBase.cpp thunderbird-115.4.1+build1/accessible/basetypes/HyperTextAccessibleBase.cpp --- thunderbird-115.3.1+build1/accessible/basetypes/HyperTextAccessibleBase.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/HyperTextAccessibleBase.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -292,6 +292,19 @@ nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc); if (!thisAcc->Bounds().Contains(coords.x, coords.y)) { // The requested point does not exist in this accessible. + // Check if we used fuzzy hittesting to get here and, if + // so, return 0 to indicate this text leaf is a valid match. + LayoutDeviceIntPoint p(aX, aY); + if (aCoordType != nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) { + p = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc); + } + if (Accessible* doc = nsAccUtils::DocumentFor(thisAcc)) { + Accessible* hittestMatch = doc->ChildAtPoint( + p.x, p.y, Accessible::EWhichChildAtPoint::DeepestChild); + if (hittestMatch && thisAcc == hittestMatch->Parent()) { + return 0; + } + } return -1; } diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/moz.build thunderbird-115.4.1+build1/accessible/basetypes/moz.build --- thunderbird-115.3.1+build1/accessible/basetypes/moz.build 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/moz.build 2023-10-24 21:13:27.000000000 +0000 @@ -7,8 +7,8 @@ EXPORTS.mozilla.a11y += [ "Accessible.h", "HyperTextAccessibleBase.h", - "TableAccessibleBase.h", - "TableCellAccessibleBase.h", + "TableAccessible.h", + "TableCellAccessible.h", ] UNIFIED_SOURCES += [ diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/TableAccessibleBase.h thunderbird-115.4.1+build1/accessible/basetypes/TableAccessibleBase.h --- thunderbird-115.3.1+build1/accessible/basetypes/TableAccessibleBase.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/TableAccessibleBase.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,192 +0,0 @@ -/* -*- 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_BASE_H -#define TABLE_ACCESSIBLE_BASE_H - -#include "nsString.h" -#include "nsTArray.h" - -namespace mozilla { -namespace a11y { - -class Accessible; - -/** - * Accessible table interface. - */ -class TableAccessibleBase { - public: - /** - * Return the caption accessible if any for this table. - */ - virtual Accessible* Caption() const { return nullptr; } - - /** - * Get the summary for this table. - */ - virtual void Summary(nsString& aSummary) { aSummary.Truncate(); } - - /** - * Return the number of columns in the table. - */ - virtual uint32_t ColCount() const { return 0; } - - /** - * Return the number of rows in the table. - */ - virtual uint32_t RowCount() { return 0; } - - /** - * Return the accessible for the cell at the given row and column indices. - */ - virtual Accessible* CellAt(uint32_t aRowIdx, uint32_t aColIdx) { - return nullptr; - } - - /** - * Return the index of the cell at the given row and column. - */ - virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) { return -1; } - - /** - * Return the column index of the cell with the given index. - * This returns -1 if the column count is 0 or an invalid index is being - * passed in. - */ - virtual int32_t ColIndexAt(uint32_t aCellIdx) { return -1; } - - /** - * Return the row index of the cell with the given index. - * This returns -1 if the column count is 0 or an invalid index is being - * passed in. - */ - virtual int32_t RowIndexAt(uint32_t aCellIdx) { return -1; } - - /** - * Get the row and column indices for the cell at the given index. - * This returns -1 for both output parameters if the column count is 0 or an - * invalid index is being passed in. - */ - virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, - int32_t* aColIdx) { - *aRowIdx = -1; - *aColIdx = -1; - } - - /** - * Return the number of columns occupied by the cell at the given row and - * column indices. - */ - virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; } - - /** - * Return the number of rows occupied by the cell at the given row and column - * indices. - */ - virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; } - - /** - * Get the description of the given column. - */ - virtual void ColDescription(uint32_t aColIdx, nsString& aDescription) { - aDescription.Truncate(); - } - - /** - * Get the description for the given row. - */ - virtual void RowDescription(uint32_t aRowIdx, nsString& aDescription) { - aDescription.Truncate(); - } - - /** - * Return true if the given column is selected. - */ - virtual bool IsColSelected(uint32_t aColIdx) { return false; } - - /** - * Return true if the given row is selected. - */ - virtual bool IsRowSelected(uint32_t aRowIdx) { return false; } - - /** - * Return true if the given cell is selected. - */ - virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { - return false; - } - - /** - * Return the number of selected cells. - */ - virtual uint32_t SelectedCellCount() { return 0; } - - /** - * Return the number of selected columns. - */ - virtual uint32_t SelectedColCount() { return 0; } - - /** - * Return the number of selected rows. - */ - virtual uint32_t SelectedRowCount() { return 0; } - - /** - * Get the set of selected cells. - */ - virtual void SelectedCells(nsTArray* aCells) {} - - /** - * Get the set of selected cell indices. - */ - virtual void SelectedCellIndices(nsTArray* aCells) {} - - /** - * Get the set of selected column indices. - */ - virtual void SelectedColIndices(nsTArray* aCols) {} - - /** - * Get the set of selected row indices. - */ - virtual void SelectedRowIndices(nsTArray* aRows) {} - - /** - * 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 -Nru thunderbird-115.3.1+build1/accessible/basetypes/TableAccessible.h thunderbird-115.4.1+build1/accessible/basetypes/TableAccessible.h --- thunderbird-115.3.1+build1/accessible/basetypes/TableAccessible.h 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/TableAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TABLE_ACCESSIBLE_H +#define TABLE_ACCESSIBLE_H + +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace a11y { + +class Accessible; + +/** + * Accessible table interface. + */ +class TableAccessible { + public: + /** + * Return the caption accessible if any for this table. + */ + virtual Accessible* Caption() const { return nullptr; } + + /** + * Get the summary for this table. + */ + virtual void Summary(nsString& aSummary) { aSummary.Truncate(); } + + /** + * Return the number of columns in the table. + */ + virtual uint32_t ColCount() const { return 0; } + + /** + * Return the number of rows in the table. + */ + virtual uint32_t RowCount() { return 0; } + + /** + * Return the accessible for the cell at the given row and column indices. + */ + virtual Accessible* CellAt(uint32_t aRowIdx, uint32_t aColIdx) { + return nullptr; + } + + /** + * Return the index of the cell at the given row and column. + */ + virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) { return -1; } + + /** + * Return the column index of the cell with the given index. + * This returns -1 if the column count is 0 or an invalid index is being + * passed in. + */ + virtual int32_t ColIndexAt(uint32_t aCellIdx) { return -1; } + + /** + * Return the row index of the cell with the given index. + * This returns -1 if the column count is 0 or an invalid index is being + * passed in. + */ + virtual int32_t RowIndexAt(uint32_t aCellIdx) { return -1; } + + /** + * Get the row and column indices for the cell at the given index. + * This returns -1 for both output parameters if the column count is 0 or an + * invalid index is being passed in. + */ + virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) { + *aRowIdx = -1; + *aColIdx = -1; + } + + /** + * Return the number of columns occupied by the cell at the given row and + * column indices. + */ + virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; } + + /** + * Return the number of rows occupied by the cell at the given row and column + * indices. + */ + virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; } + + /** + * Get the description of the given column. + */ + virtual void ColDescription(uint32_t aColIdx, nsString& aDescription) { + aDescription.Truncate(); + } + + /** + * Get the description for the given row. + */ + virtual void RowDescription(uint32_t aRowIdx, nsString& aDescription) { + aDescription.Truncate(); + } + + /** + * Return true if the given column is selected. + */ + virtual bool IsColSelected(uint32_t aColIdx) { return false; } + + /** + * Return true if the given row is selected. + */ + virtual bool IsRowSelected(uint32_t aRowIdx) { return false; } + + /** + * Return true if the given cell is selected. + */ + virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { + return false; + } + + /** + * Return the number of selected cells. + */ + virtual uint32_t SelectedCellCount() { return 0; } + + /** + * Return the number of selected columns. + */ + virtual uint32_t SelectedColCount() { return 0; } + + /** + * Return the number of selected rows. + */ + virtual uint32_t SelectedRowCount() { return 0; } + + /** + * Get the set of selected cells. + */ + virtual void SelectedCells(nsTArray* aCells) {} + + /** + * Get the set of selected cell indices. + */ + virtual void SelectedCellIndices(nsTArray* aCells) {} + + /** + * Get the set of selected column indices. + */ + virtual void SelectedColIndices(nsTArray* aCols) {} + + /** + * Get the set of selected row indices. + */ + virtual void SelectedRowIndices(nsTArray* aRows) {} + + /** + * Return true if the table is probably for layout. + */ + virtual bool IsProbablyLayoutTable() { return false; } + + /** + * Convert the table to an Accessible*. + */ + virtual Accessible* AsAccessible() = 0; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/TableCellAccessibleBase.h thunderbird-115.4.1+build1/accessible/basetypes/TableCellAccessibleBase.h --- thunderbird-115.3.1+build1/accessible/basetypes/TableCellAccessibleBase.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/TableCellAccessibleBase.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -/* -*- 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_TableCellAccessibleBase_h__ -#define mozilla_a11y_TableCellAccessibleBase_h__ - -#include "nsTArray.h" -#include - -namespace mozilla { -namespace a11y { - -class Accessible; -class TableAccessibleBase; - -/** - * Abstract interface implemented by table cell accessibles. - */ -class TableCellAccessibleBase { - public: - /** - * Return the table this cell is in. - */ - virtual TableAccessibleBase* 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* aCells) = 0; - - /** - * Return the row header cells for this cell. - */ - virtual void RowHeaderCells(nsTArray* aCells) = 0; - - /** - * Returns true if this cell is selected. - */ - virtual bool Selected() = 0; -}; - -} // namespace a11y -} // namespace mozilla - -#endif // mozilla_a11y_TableCellAccessibleBase_h__ diff -Nru thunderbird-115.3.1+build1/accessible/basetypes/TableCellAccessible.h thunderbird-115.4.1+build1/accessible/basetypes/TableCellAccessible.h --- thunderbird-115.3.1+build1/accessible/basetypes/TableCellAccessible.h 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/basetypes/TableCellAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_TableCellAccessible_h__ +#define mozilla_a11y_TableCellAccessible_h__ + +#include "nsTArray.h" +#include + +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* aCells) = 0; + + /** + * Return the row header cells for this cell. + */ + virtual void RowHeaderCells(nsTArray* aCells) = 0; + + /** + * Returns true if this cell is selected. + */ + virtual bool Selected() = 0; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_TableCellAccessible_h__ diff -Nru thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible.cpp thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible.cpp --- thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -3,509 +3,24 @@ * 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 "ARIAGridAccessible.h" +#include #include "LocalAccessible-inl.h" #include "AccAttributes.h" #include "AccIterator.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "nsAccessibilityService.h" #include "nsAccUtils.h" +#include "nsGkAtoms.h" #include "Role.h" #include "States.h" -#include "mozilla/dom/Element.h" -#include "nsComponentManagerUtils.h" - using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// -// ARIAGridAccessible -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Constructor - -ARIAGridAccessible::ARIAGridAccessible(nsIContent* aContent, - DocAccessible* aDoc) - : HyperTextAccessibleWrap(aContent, aDoc) { - mGenericTypes |= eTable; -} - -role ARIAGridAccessible::NativeRole() const { - a11y::role r = GetAccService()->MarkupRole(mContent); - return r != roles::NOTHING ? r : roles::TABLE; -} - -already_AddRefed ARIAGridAccessible::NativeAttributes() { - RefPtr attributes = AccessibleWrap::NativeAttributes(); - - if (IsProbablyLayoutTable()) { - attributes->SetAttribute(nsGkAtoms::layout_guess, true); - } - - return attributes.forget(); -} - -//////////////////////////////////////////////////////////////////////////////// -// Table - -uint32_t ARIAGridAccessible::ColCount() const { - AccIterator rowIter(this, filters::GetRow); - LocalAccessible* row = rowIter.Next(); - if (!row) return 0; - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* cell = nullptr; - - uint32_t colCount = 0; - while ((cell = cellIter.Next())) { - MOZ_ASSERT(cell->IsTableCell(), "No table or grid cell!"); - colCount += cell->AsTableCell()->ColExtent(); - } - - return colCount; -} - -uint32_t ARIAGridAccessible::RowCount() { - uint32_t rowCount = 0; - AccIterator rowIter(this, filters::GetRow); - while (rowIter.Next()) rowCount++; - - return rowCount; -} - -LocalAccessible* ARIAGridAccessible::CellAt(uint32_t aRowIndex, - uint32_t aColumnIndex) { - LocalAccessible* row = RowAt(aRowIndex); - if (!row) return nullptr; - - return CellInRowAt(row, aColumnIndex); -} - -bool ARIAGridAccessible::IsColSelected(uint32_t aColIdx) { - if (IsARIARole(nsGkAtoms::table)) return false; - - AccIterator rowIter(this, filters::GetRow); - LocalAccessible* row = rowIter.Next(); - if (!row) return false; - - do { - if (!nsAccUtils::IsARIASelected(row)) { - LocalAccessible* cell = CellInRowAt(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; - - LocalAccessible* row = RowAt(aRowIdx); - if (!row) return false; - - if (!nsAccUtils::IsARIASelected(row)) { - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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; - - LocalAccessible* row = RowAt(aRowIdx); - if (!row) return false; - - if (!nsAccUtils::IsARIASelected(row)) { - LocalAccessible* cell = CellInRowAt(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); - LocalAccessible* row = nullptr; - - while ((row = rowIter.Next())) { - if (nsAccUtils::IsARIASelected(row)) { - count += colCount; - continue; - } - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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); - LocalAccessible* row = rowIter.Next(); - if (!row) return 0; - - nsTArray 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); - LocalAccessible* 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); - LocalAccessible* row = nullptr; - - while ((row = rowIter.Next())) { - if (nsAccUtils::IsARIASelected(row)) { - count++; - continue; - } - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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* aCells) { - if (IsARIARole(nsGkAtoms::table)) return; - - AccIterator rowIter(this, filters::GetRow); - - LocalAccessible* row = nullptr; - while ((row = rowIter.Next())) { - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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* aCells) { - if (IsARIARole(nsGkAtoms::table)) return; - - uint32_t colCount = ColCount(); - - AccIterator rowIter(this, filters::GetRow); - LocalAccessible* 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); - LocalAccessible* cell = nullptr; - for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) { - if (nsAccUtils::IsARIASelected(cell)) { - aCells->AppendElement(rowIdx * colCount + colIdx); - } - } - } -} - -void ARIAGridAccessible::SelectedColIndices(nsTArray* aCols) { - if (IsARIARole(nsGkAtoms::table)) return; - - uint32_t colCount = ColCount(); - if (!colCount) return; - - AccIterator rowIter(this, filters::GetRow); - LocalAccessible* row = rowIter.Next(); - if (!row) return; - - nsTArray isColSelArray(colCount); - isColSelArray.AppendElements(colCount); - memset(isColSelArray.Elements(), true, colCount * sizeof(bool)); - - do { - if (nsAccUtils::IsARIASelected(row)) continue; - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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* aRows) { - if (IsARIARole(nsGkAtoms::table)) return; - - AccIterator rowIter(this, filters::GetRow); - LocalAccessible* row = nullptr; - for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { - if (nsAccUtils::IsARIASelected(row)) { - aRows->AppendElement(rowIdx); - continue; - } - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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); - - LocalAccessible* row = nullptr; - for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) { - DebugOnly 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); - - LocalAccessible* row = nullptr; - while ((row = rowIter.Next())) { - // Unselect all cells in the row. - DebugOnly rv = SetARIASelected(row, false); - NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!"); - - // Select cell at the column index. - LocalAccessible* cell = CellInRowAt(row, aColIdx); - if (cell) SetARIASelected(cell, true); - } -} - -void ARIAGridAccessible::UnselectRow(uint32_t aRowIdx) { - if (IsARIARole(nsGkAtoms::table)) return; - - LocalAccessible* row = RowAt(aRowIdx); - if (row) SetARIASelected(row, false); -} - -void ARIAGridAccessible::UnselectCol(uint32_t aColIdx) { - if (IsARIARole(nsGkAtoms::table)) return; - - AccIterator rowIter(this, filters::GetRow); - - LocalAccessible* row = nullptr; - while ((row = rowIter.Next())) { - LocalAccessible* cell = CellInRowAt(row, aColIdx); - if (cell) SetARIASelected(cell, false); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Protected - -nsresult ARIAGridAccessible::SetARIASelected(LocalAccessible* 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 (content->IsElement()) { - if (aIsSelected) { - rv = content->AsElement()->SetAttr( - kNameSpaceID_None, nsGkAtoms::aria_selected, u"true"_ns, aNotify); - } else { - rv = content->AsElement()->SetAttr( - kNameSpaceID_None, nsGkAtoms::aria_selected, u"false"_ns, 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); - LocalAccessible* 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) { - LocalAccessible* row = aAccessible->LocalParent(); - - if (row && row->Role() == roles::ROW && nsAccUtils::IsARIASelected(row)) { - rv = SetARIASelected(row, false, false); - NS_ENSURE_SUCCESS(rv, rv); - - AccIterator cellIter(row, filters::GetCell); - LocalAccessible* 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) - : HyperTextAccessibleWrap(aContent, aDoc) { - mGenericTypes |= eTableRow; -} - -role ARIARowAccessible::NativeRole() const { - a11y::role r = GetAccService()->MarkupRole(mContent); - return r != roles::NOTHING ? r : roles::ROW; -} - -GroupPos ARIARowAccessible::GroupPosition() { - int32_t count = 0, index = 0; - LocalAccessible* table = nsAccUtils::TableFor(this); - if (table) { - if (nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_rowcount, - &count) && - nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) { - return GroupPos(0, index, count); - } - - // Deal with the special case here that tables and grids can have rows - // which are wrapped in generic text container elements. Exclude tree grids - // because these are dealt with elsewhere. - if (table->Role() == roles::TABLE) { - LocalAccessible* row = nullptr; - AccIterator rowIter(table, filters::GetRow); - while ((row = rowIter.Next())) { - index++; - if (row == this) { - break; - } - } - - if (row) { - count = table->AsTable()->RowCount(); - return GroupPos(0, index, count); - } - } - } - - return AccessibleWrap::GroupPosition(); -} - -// LocalAccessible protected -ENameValueFlag ARIARowAccessible::NativeName(nsString& aName) const { - // We want to calculate the name from content only if an ARIA role is - // present. ARIARowAccessible might also be used by tables with - // display:block; styling, in which case we do not want the name from - // content. - if (HasStrongARIARole()) { - return AccessibleWrap::NativeName(aName); - } - - return eNameOK; -} - -//////////////////////////////////////////////////////////////////////////////// // ARIAGridCellAccessible //////////////////////////////////////////////////////////////////////////////// @@ -518,53 +33,6 @@ mGenericTypes |= eTableCell; } -role ARIAGridCellAccessible::NativeRole() const { - const a11y::role r = GetAccService()->MarkupRole(mContent); - if (r != role::NOTHING) { - return r; - } - - // Special case to handle th elements mapped to ARIA grid cells. - if (GetContent() && GetContent()->IsHTMLElement(nsGkAtoms::th)) { - return GetHeaderCellRole(this); - } - - return role::CELL; -} - -//////////////////////////////////////////////////////////////////////////////// -// TableCell - -TableAccessible* ARIAGridCellAccessible::Table() const { - LocalAccessible* table = nsAccUtils::TableFor(Row()); - return table ? table->AsTable() : nullptr; -} - -uint32_t ARIAGridCellAccessible::ColIdx() const { - LocalAccessible* row = Row(); - if (!row) return 0; - - int32_t indexInRow = IndexInParent(); - uint32_t colIdx = 0; - for (int32_t idx = 0; idx < indexInRow; idx++) { - LocalAccessible* cell = row->LocalChildAt(idx); - if (cell->IsTableCell()) { - colIdx += cell->AsTableCell()->ColExtent(); - } - } - - return colIdx; -} - -uint32_t ARIAGridCellAccessible::RowIdx() const { return RowIndexFor(Row()); } - -bool ARIAGridCellAccessible::Selected() { - LocalAccessible* row = Row(); - if (!row) return false; - - return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this); -} - //////////////////////////////////////////////////////////////////////////////// // LocalAccessible @@ -591,35 +59,21 @@ RefPtr attributes = HyperTextAccessibleWrap::NativeAttributes(); - // Expose "table-cell-index" attribute. - LocalAccessible* thisRow = Row(); - if (!thisRow) return attributes.forget(); - - int32_t rowIdx = RowIndexFor(thisRow); - if (rowIdx == -1) { // error - return attributes.forget(); - } - - int32_t colIdx = 0, colCount = 0; - uint32_t childCount = thisRow->ChildCount(); - for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { - LocalAccessible* child = thisRow->LocalChildAt(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++; + // We only need to expose table-cell-index to clients. If we're in the content + // process, we don't need this, so building a CachedTableAccessible is very + // wasteful. This will be exposed by RemoteAccessible in the parent process + // instead. + if (!IPCAccessibilityActive()) { + if (const TableCellAccessible* cell = AsTableCell()) { + TableAccessible* table = cell->Table(); + const uint32_t row = cell->RowIdx(); + const uint32_t col = cell->ColIdx(); + const int32_t cellIdx = table->CellIndexAt(row, col); + if (cellIdx != -1) { + attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx); + } } } - attributes->SetAttribute(nsGkAtoms::tableCellIndex, - rowIdx * colCount + colIdx); - -#ifdef DEBUG - RefPtr cppClass = NS_Atomize(u"cppclass"_ns); - attributes->SetAttributeStringCopy(cppClass, u"ARIAGridCellAccessible"_ns); -#endif - return attributes.forget(); } diff -Nru thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible.h thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible.h --- thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible.h 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible.h 2023-10-24 21:13:26.000000000 +0000 @@ -7,90 +7,14 @@ #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 HyperTextAccessibleWrap, - public TableAccessible { - public: - ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc); - - NS_INLINE_DECL_REFCOUNTING_INHERITED(ARIAGridAccessible, - HyperTextAccessibleWrap) - - // LocalAccessible - virtual a11y::role NativeRole() const override; - virtual already_AddRefed NativeAttributes() override; - virtual TableAccessible* AsTable() override { return this; } - - // TableAccessible - virtual uint32_t ColCount() const override; - virtual uint32_t RowCount() override; - virtual LocalAccessible* CellAt(uint32_t aRowIndex, - uint32_t aColumnIndex) override; - virtual bool IsColSelected(uint32_t aColIdx) override; - virtual bool IsRowSelected(uint32_t aRowIdx) override; - virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override; - virtual uint32_t SelectedCellCount() override; - virtual uint32_t SelectedColCount() override; - virtual uint32_t SelectedRowCount() override; - virtual void SelectedCells(nsTArray* aCells) override; - virtual void SelectedCellIndices(nsTArray* aCells) override; - virtual void SelectedColIndices(nsTArray* aCols) override; - virtual void SelectedRowIndices(nsTArray* aRows) override; - virtual void SelectCol(uint32_t aColIdx) override; - virtual void SelectRow(uint32_t aRowIdx) override; - virtual void UnselectCol(uint32_t aColIdx) override; - virtual void UnselectRow(uint32_t aRowIdx) override; - virtual LocalAccessible* AsAccessible() override { return this; } - - protected: - virtual ~ARIAGridAccessible() {} - - /** - * 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(LocalAccessible* aAccessible, bool aIsSelected, - bool aNotify = true); -}; - -/** - * Accessible for ARIA row. - */ -class ARIARowAccessible : public HyperTextAccessibleWrap { - public: - ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc); - - NS_INLINE_DECL_REFCOUNTING_INHERITED(ARIARowAccessible, - HyperTextAccessibleWrap) - - // LocalAccessible - virtual a11y::role NativeRole() const override; - virtual mozilla::a11y::GroupPos GroupPosition() override; - - protected: - virtual ~ARIARowAccessible() {} - - // LocalAccessible - virtual ENameValueFlag NativeName(nsString& aName) const override; -}; - -/** * Accessible for ARIA gridcell and rowheader/columnheader. */ -class ARIAGridCellAccessible : public HyperTextAccessibleWrap, - public TableCellAccessible { +class ARIAGridCellAccessible : public HyperTextAccessibleWrap { public: ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc); @@ -98,33 +22,11 @@ HyperTextAccessibleWrap) // LocalAccessible - virtual a11y::role NativeRole() const override; - virtual TableCellAccessible* AsTableCell() override { return this; } virtual void ApplyARIAState(uint64_t* aState) const override; virtual already_AddRefed NativeAttributes() override; protected: virtual ~ARIAGridCellAccessible() {} - - /** - * Return a containing row. - */ - LocalAccessible* Row() const { - LocalAccessible* row = LocalParent(); - return row && row->IsTableRow() ? row : nullptr; - } - - /** - * Return index of the given row. - * Returns -1 upon error. - */ - int32_t RowIndexFor(LocalAccessible* 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 diff -Nru thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible-inl.h thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible-inl.h --- thunderbird-115.3.1+build1/accessible/generic/ARIAGridAccessible-inl.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/ARIAGridAccessible-inl.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -/* -*- 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( - LocalAccessible* aRow) const { - LocalAccessible* table = nsAccUtils::TableFor(aRow); - if (table) { - int32_t rowIdx = 0; - LocalAccessible* 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 -Nru thunderbird-115.3.1+build1/accessible/generic/LocalAccessible.cpp thunderbird-115.4.1+build1/accessible/generic/LocalAccessible.cpp --- thunderbird-115.3.1+build1/accessible/generic/LocalAccessible.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/LocalAccessible.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -6,6 +6,7 @@ #include "AccEvent.h" #include "LocalAccessible-inl.h" +#include #include "EmbeddedObjCollector.h" #include "AccAttributes.h" #include "AccGroupInfo.h" @@ -14,6 +15,8 @@ #include "CachedTableAccessible.h" #include "DocAccessible-inl.h" #include "mozilla/a11y/AccAttributes.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsAccUtils.h" #include "nsAccessibilityService.h" #include "ApplicationAccessible.h" @@ -33,14 +36,14 @@ #include "StyleInfo.h" #include "TextLeafRange.h" #include "TextRange.h" -#include "TableAccessible.h" -#include "TableCellAccessible.h" #include "TreeWalker.h" #include "HTMLElementAccessibles.h" #include "HTMLSelectAccessible.h" +#include "HTMLTableAccessible.h" #include "ImageAccessible.h" #include "nsComputedDOMStyle.h" +#include "nsGkAtoms.h" #include "nsIDOMXULButtonElement.h" #include "nsIDOMXULSelectCntrlEl.h" #include "nsIDOMXULSelectCntrlItemEl.h" @@ -1624,16 +1627,15 @@ if ((roleMapEntry->Is(nsGkAtoms::gridcell) || roleMapEntry->Is(nsGkAtoms::columnheader) || roleMapEntry->Is(nsGkAtoms::rowheader)) && + // Don't recurse infinitely for an authoring error like + //
. Without this check, we'd call TableFor(this) + // below, which would return this. + !IsTable() && !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) { - const TableCellAccessible* cell = AsTableCell(); - if (cell) { - TableAccessible* table = cell->Table(); - if (table) { - LocalAccessible* grid = table->AsAccessible(); - uint64_t gridState = 0; - grid->ApplyARIAState(&gridState); - *aState |= gridState & states::READONLY; - } + if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) { + uint64_t gridState = 0; + grid->ApplyARIAState(&gridState); + *aState |= gridState & states::READONLY; } } } @@ -1820,12 +1822,9 @@ // A cell inside an ancestor table element that has a grid role needs a // gridcell role // (https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings). - const TableCellAccessible* cell = AsTableCell(); - if (cell) { - TableAccessible* table = cell->Table(); - if (table && table->AsAccessible()->IsARIARole(nsGkAtoms::grid)) { - return roles::GRID_CELL; - } + const LocalAccessible* table = nsAccUtils::TableFor(this); + if (table && table->IsARIARole(nsGkAtoms::grid)) { + return roles::GRID_CELL; } } @@ -3325,6 +3324,7 @@ } bool boundsChanged = false; + nsIFrame* frame = GetFrame(); if (aCacheDomain & CacheDomain::Bounds) { nsRect newBoundsRect = ParentRelativeBounds(); @@ -3374,6 +3374,12 @@ fields->SetAttribute(nsGkAtoms::relativeBounds, std::move(boundsArray)); } + + if (frame && frame->ScrollableOverflowRect().IsEmpty()) { + fields->SetAttribute(nsGkAtoms::clip_rule, true); + } else if (aUpdateType != CacheUpdateType::Initial) { + fields->SetAttribute(nsGkAtoms::clip_rule, DeleteEntry()); + } } if (aCacheDomain & CacheDomain::Text) { @@ -3407,7 +3413,6 @@ } } - nsIFrame* frame = GetFrame(); if (aCacheDomain & (CacheDomain::Text | CacheDomain::Bounds) && !HasChildren()) { // We cache line start offsets for both text and non-text leaf Accessibles @@ -3630,16 +3635,27 @@ } else if (aUpdateType != CacheUpdateType::Initial) { fields->SetAttribute(nsGkAtoms::position, DeleteEntry()); } + + if (frame) { + nsAutoCString overflow; + frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow); + RefPtr overflowAtom = NS_Atomize(overflow); + if (overflowAtom == nsGkAtoms::hidden) { + fields->SetAttribute(nsGkAtoms::overflow, nsGkAtoms::hidden); + } else if (aUpdateType != CacheUpdateType::Initial) { + fields->SetAttribute(nsGkAtoms::overflow, DeleteEntry()); + } + } } if (aCacheDomain & CacheDomain::Table) { - if (TableAccessible* table = AsTable()) { + if (auto* table = HTMLTableAccessible::GetFrom(this)) { if (table->IsProbablyLayoutTable()) { fields->SetAttribute(nsGkAtoms::layout_guess, true); } else if (aUpdateType == CacheUpdateType::Update) { fields->SetAttribute(nsGkAtoms::layout_guess, DeleteEntry()); } - } else if (TableCellAccessible* cell = AsTableCell()) { + } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) { // For HTML table cells, we must use the HTMLTableCellAccessible // GetRow/ColExtent methods rather than using the DOM attributes directly. // This is because of things like rowspan="0" which depend on knowing @@ -3836,6 +3852,19 @@ if (nsIFrame* frame = GetFrame()) { const ComputedStyle* newStyle = frame->Style(); + nsAutoCString oldOverflow, newOverflow; + mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_overflow, + oldOverflow); + newStyle->GetComputedPropertyValue(eCSSProperty_overflow, newOverflow); + + if (oldOverflow != newOverflow) { + RefPtr oldAtom = NS_Atomize(oldOverflow); + RefPtr newAtom = NS_Atomize(newOverflow); + if (oldAtom == nsGkAtoms::hidden || newAtom == nsGkAtoms::hidden) { + mDoc->QueueCacheUpdate(this, CacheDomain::Style); + } + } + nsAutoCString oldDisplay, newDisplay; mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display, oldDisplay); @@ -4011,18 +4040,18 @@ "LocalAccessible::mContextFlags was oversized by eLastContextFlag!"); } -TableAccessibleBase* LocalAccessible::AsTableBase() { +TableAccessible* LocalAccessible::AsTable() { if (IsTable() && !mContent->IsXULElement()) { return CachedTableAccessible::GetFrom(this); } - return AsTable(); + return nullptr; } -TableCellAccessibleBase* LocalAccessible::AsTableCellBase() { +TableCellAccessible* LocalAccessible::AsTableCell() { if (IsTableCell() && !mContent->IsXULElement()) { return CachedTableCellAccessible::GetFrom(this); } - return AsTableCell(); + return nullptr; } Maybe LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const { diff -Nru thunderbird-115.3.1+build1/accessible/generic/LocalAccessible.h thunderbird-115.4.1+build1/accessible/generic/LocalAccessible.h --- thunderbird-115.3.1+build1/accessible/generic/LocalAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/LocalAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -53,9 +53,7 @@ class Relation; class RootAccessible; class TableAccessible; -class TableAccessibleBase; class TableCellAccessible; -class TableCellAccessibleBase; class TextLeafAccessible; class XULLabelAccessible; class XULTreeAccessible; @@ -464,15 +462,8 @@ a11y::RootAccessible* AsRoot(); - virtual TableAccessible* AsTable() { return nullptr; } - - virtual TableCellAccessible* AsTableCell() { return nullptr; } - const TableCellAccessible* AsTableCell() const { - return const_cast(this)->AsTableCell(); - } - - virtual TableAccessibleBase* AsTableBase() override; - virtual TableCellAccessibleBase* AsTableCellBase() override; + virtual TableAccessible* AsTable() override; + virtual TableCellAccessible* AsTableCell() override; TextLeafAccessible* AsTextLeaf(); diff -Nru thunderbird-115.3.1+build1/accessible/generic/moz.build thunderbird-115.4.1+build1/accessible/generic/moz.build --- thunderbird-115.3.1+build1/accessible/generic/moz.build 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/moz.build 2023-10-24 21:13:26.000000000 +0000 @@ -22,8 +22,6 @@ "LocalAccessible.cpp", "OuterDocAccessible.cpp", "RootAccessible.cpp", - "TableAccessible.cpp", - "TableCellAccessible.cpp", "TextLeafAccessible.cpp", ] diff -Nru thunderbird-115.3.1+build1/accessible/generic/TableAccessible.cpp thunderbird-115.4.1+build1/accessible/generic/TableAccessible.cpp --- thunderbird-115.3.1+build1/accessible/generic/TableAccessible.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/TableAccessible.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,316 +0,0 @@ -/* -*- 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 "TableAccessible.h" - -#include "LocalAccessible-inl.h" -#include "AccIterator.h" - -#include "nsTableCellFrame.h" -#include "nsTableWrapperFrame.h" -#include "TableCellAccessible.h" - -using namespace mozilla; -using namespace mozilla::a11y; - -bool TableAccessible::IsProbablyLayoutTable() { - // Implement a heuristic to determine if table is most likely used for layout. - - // XXX do we want to look for rowspan or colspan, especialy that span all but - // a couple cells at the beginning or end of a row/col, and especially when - // they occur at the edge of a table? - - // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC - // This will allow release trunk builds to be used by testers to refine - // the algorithm. Integrate it into Logging. - // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release -#ifdef SHOW_LAYOUT_HEURISTIC -# define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ - { \ - mLayoutHeuristic = isLayout \ - ? nsLiteralString(u"layout table: " heuristic) \ - : nsLiteralString(u"data table: " heuristic); \ - return isLayout; \ - } -#else -# define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ - { return isLayout; } -#endif - - LocalAccessible* thisacc = AsAccessible(); - - MOZ_ASSERT(!thisacc->IsDefunct(), "Table accessible should not be defunct"); - - // Need to see all elements while document is being edited. - if (thisacc->Document()->State() & states::EDITABLE) { - RETURN_LAYOUT_ANSWER(false, "In editable document"); - } - - // Check to see if an ARIA role overrides the role from native markup, - // but for which we still expose table semantics (treegrid, for example). - if (thisacc->HasARIARole()) { - RETURN_LAYOUT_ANSWER(false, "Has role attribute"); - } - - dom::Element* el = thisacc->Elm(); - if (el->IsMathMLElement(nsGkAtoms::mtable_)) { - RETURN_LAYOUT_ANSWER(false, "MathML matrix"); - } - - MOZ_ASSERT(el->IsHTMLElement(nsGkAtoms::table), - "Table should not be built by CSS display:table style"); - - // Check if datatable attribute has "0" value. - if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable, u"0"_ns, - eCaseMatters)) { - RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout"); - } - - // Check for legitimate data table attributes. - if (el->Element::HasNonEmptyAttr(nsGkAtoms::summary)) { - RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures"); - } - - // Check for legitimate data table elements. - LocalAccessible* caption = thisacc->LocalFirstChild(); - if (caption && caption->IsHTMLCaption() && caption->HasChildren()) { - RETURN_LAYOUT_ANSWER(false, - "Not empty caption -- legitimate table structures"); - } - - for (nsIContent* childElm = el->GetFirstChild(); childElm; - childElm = childElm->GetNextSibling()) { - if (!childElm->IsHTMLElement()) continue; - - if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col, nsGkAtoms::colgroup, - nsGkAtoms::tfoot, nsGkAtoms::thead)) { - RETURN_LAYOUT_ANSWER( - false, - "Has col, colgroup, tfoot or thead -- legitimate table structures"); - } - - if (childElm->IsHTMLElement(nsGkAtoms::tbody)) { - for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm; - rowElm = rowElm->GetNextSibling()) { - if (rowElm->IsHTMLElement(nsGkAtoms::tr)) { - if (LocalAccessible* row = - thisacc->Document()->GetAccessible(rowElm)) { - if (const nsRoleMapEntry* roleMapEntry = row->ARIARoleMap()) { - if (roleMapEntry->role != roles::ROW) { - RETURN_LAYOUT_ANSWER(true, "Repurposed tr with different role"); - } - } - } - - for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm; - cellElm = cellElm->GetNextSibling()) { - if (cellElm->IsHTMLElement()) { - if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) { - RETURN_LAYOUT_ANSWER(false, - "Has th -- legitimate table structures"); - } - - if (cellElm->AsElement()->HasAttr(kNameSpaceID_None, - nsGkAtoms::headers) || - cellElm->AsElement()->HasAttr(kNameSpaceID_None, - nsGkAtoms::scope) || - cellElm->AsElement()->HasAttr(kNameSpaceID_None, - nsGkAtoms::abbr)) { - RETURN_LAYOUT_ANSWER(false, - "Has headers, scope, or abbr attribute -- " - "legitimate table structures"); - } - - if (LocalAccessible* cell = - thisacc->Document()->GetAccessible(cellElm)) { - if (const nsRoleMapEntry* roleMapEntry = cell->ARIARoleMap()) { - if (roleMapEntry->role != roles::CELL && - roleMapEntry->role != roles::COLUMNHEADER && - roleMapEntry->role != roles::ROWHEADER && - roleMapEntry->role != roles::GRID_CELL) { - RETURN_LAYOUT_ANSWER(true, - "Repurposed cell with different role"); - } - } - if (cell->ChildCount() == 1 && - cell->LocalFirstChild()->IsAbbreviation()) { - RETURN_LAYOUT_ANSWER( - false, "has abbr -- legitimate table structures"); - } - } - } - } - } - } - } - } - - // Check for nested tables. - nsCOMPtr nestedTables = - el->GetElementsByTagName(u"table"_ns); - if (nestedTables->Length() > 0) { - RETURN_LAYOUT_ANSWER(true, "Has a nested table within it"); - } - - // If only 1 column or only 1 row, it's for layout. - auto colCount = ColCount(); - if (colCount <= 1) { - RETURN_LAYOUT_ANSWER(true, "Has only 1 column"); - } - auto rowCount = RowCount(); - if (rowCount <= 1) { - RETURN_LAYOUT_ANSWER(true, "Has only 1 row"); - } - - // Check for many columns. - if (colCount >= 5) { - RETURN_LAYOUT_ANSWER(false, ">=5 columns"); - } - - // Now we know there are 2-4 columns and 2 or more rows. Check to see if - // there are visible borders on the cells. - // XXX currently, we just check the first cell -- do we really need to do - // more? - nsTableWrapperFrame* tableFrame = do_QueryFrame(el->GetPrimaryFrame()); - if (!tableFrame) { - RETURN_LAYOUT_ANSWER(false, "table with no frame!"); - } - - nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0); - if (!cellFrame) { - RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!"); - } - - nsMargin border = cellFrame->StyleBorder()->GetComputedBorder(); - if (border.top && border.bottom && border.left && border.right) { - RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell"); - } - - // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on - // forward. - - // Check for styled background color across rows (alternating background - // color is a common feature for data tables). - auto childCount = thisacc->ChildCount(); - nscolor rowColor = 0; - nscolor prevRowColor; - for (auto childIdx = 0U; childIdx < childCount; childIdx++) { - LocalAccessible* child = thisacc->LocalChildAt(childIdx); - if (child->IsHTMLTableRow()) { - prevRowColor = rowColor; - nsIFrame* rowFrame = child->GetFrame(); - MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up"); - if (!rowFrame) { - RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy"); - } - - rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame); - - if (childIdx > 0 && prevRowColor != rowColor) { - RETURN_LAYOUT_ANSWER(false, - "2 styles of row background color, non-bordered"); - } - } - } - - // Check for many rows. - const uint32_t kMaxLayoutRows = 20; - if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data - RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered"); - } - - // Check for very wide table. - nsIFrame* documentFrame = thisacc->Document()->GetFrame(); - nsSize documentSize = documentFrame->GetSize(); - if (documentSize.width > 0) { - nsSize tableSize = thisacc->GetFrame()->GetSize(); - int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width; - if (percentageOfDocWidth > 95) { - // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width - // Probably for layout - RETURN_LAYOUT_ANSWER( - true, "<= 4 columns, table width is 95% of document width"); - } - } - - // Two column rules. - if (rowCount * colCount <= 10) { - RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered"); - } - - static const nsLiteralString tags[] = {u"embed"_ns, u"object"_ns, - u"iframe"_ns}; - for (auto& tag : tags) { - nsCOMPtr descendants = el->GetElementsByTagName(tag); - if (descendants->Length() > 0) { - RETURN_LAYOUT_ANSWER(true, - "Has no borders, and has iframe, object or embed, " - "typical of advertisements"); - } - } - - RETURN_LAYOUT_ANSWER(false, - "No layout factor strong enough, so will guess data"); -} - -LocalAccessible* TableAccessible::RowAt(int32_t aRow) { - int32_t rowIdx = aRow; - - AccIterator rowIter(this->AsAccessible(), filters::GetRow); - - LocalAccessible* row = rowIter.Next(); - while (rowIdx != 0 && (row = rowIter.Next())) { - rowIdx--; - } - - return row; -} - -LocalAccessible* TableAccessible::CellInRowAt(LocalAccessible* aRow, - int32_t aColumn) { - int32_t colIdx = aColumn; - - AccIterator cellIter(aRow, filters::GetCell); - LocalAccessible* cell = nullptr; - - while (colIdx >= 0 && (cell = cellIter.Next())) { - MOZ_ASSERT(cell->IsTableCell(), "No table or grid cell!"); - colIdx -= cell->AsTableCell()->ColExtent(); - } - - return cell; -} - -int32_t TableAccessible::ColIndexAt(uint32_t aCellIdx) { - uint32_t colCount = ColCount(); - if (colCount < 1 || aCellIdx >= colCount * RowCount()) { - return -1; // Error: column count is 0 or index out of bounds. - } - - return aCellIdx % colCount; -} - -int32_t TableAccessible::RowIndexAt(uint32_t aCellIdx) { - uint32_t colCount = ColCount(); - if (colCount < 1 || aCellIdx >= colCount * RowCount()) { - return -1; // Error: column count is 0 or index out of bounds. - } - - return aCellIdx / colCount; -} - -void TableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, - int32_t* aColIdx) { - uint32_t colCount = ColCount(); - if (colCount < 1 || aCellIdx >= colCount * RowCount()) { - *aRowIdx = -1; - *aColIdx = -1; - return; // Error: column count is 0 or index out of bounds. - } - - *aRowIdx = aCellIdx / colCount; - *aColIdx = aCellIdx % colCount; -} diff -Nru thunderbird-115.3.1+build1/accessible/generic/TableAccessible.h thunderbird-115.4.1+build1/accessible/generic/TableAccessible.h --- thunderbird-115.3.1+build1/accessible/generic/TableAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/TableAccessible.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -/* -*- 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 "LocalAccessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" -#include "nsPointerHashKeys.h" -#include "nsRefPtrHashtable.h" - -namespace mozilla { -namespace a11y { - -/** - * Base class for LocalAccessible table implementations. - */ -class TableAccessible : public TableAccessibleBase { - public: - virtual LocalAccessible* Caption() const override { return nullptr; } - - virtual LocalAccessible* CellAt(uint32_t aRowIdx, uint32_t aColIdx) override { - return nullptr; - } - - virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override { - return ColCount() * aRowIdx + aColIdx; - } - - virtual int32_t ColIndexAt(uint32_t aCellIdx) override; - virtual int32_t RowIndexAt(uint32_t aCellIdx) override; - virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, - int32_t* aColIdx) override; - virtual bool IsProbablyLayoutTable() override; - virtual LocalAccessible* AsAccessible() override = 0; - - using HeaderCache = - nsRefPtrHashtable, - LocalAccessible>; - - /** - * Get the header cache, which maps a TableCellAccessible to its previous - * header. - * Although this data is only used in TableCellAccessible, it is stored on - * TableAccessible so the cache can be easily invalidated when the table - * is mutated. - */ - HeaderCache& GetHeaderCache() { return mHeaderCache; } - - protected: - /** - * Return row accessible at the given row index. - */ - LocalAccessible* RowAt(int32_t aRow); - - /** - * Return cell accessible at the given column index in the row. - */ - LocalAccessible* CellInRowAt(LocalAccessible* aRow, int32_t aColumn); - - private: - HeaderCache mHeaderCache; -}; - -} // namespace a11y -} // namespace mozilla - -#endif diff -Nru thunderbird-115.3.1+build1/accessible/generic/TableCellAccessible.cpp thunderbird-115.4.1+build1/accessible/generic/TableCellAccessible.cpp --- thunderbird-115.3.1+build1/accessible/generic/TableCellAccessible.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/TableCellAccessible.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,165 +0,0 @@ -/* -*- 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 "LocalAccessible-inl.h" -#include "TableAccessible.h" - -using namespace mozilla; -using namespace mozilla::a11y; - -void TableCellAccessible::RowHeaderCells(nsTArray* 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--) { - LocalAccessible* 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); - } - } -} - -LocalAccessible* TableCellAccessible::PrevColHeader() { - TableAccessible* table = Table(); - if (!table) { - return nullptr; - } - - TableAccessible::HeaderCache& cache = table->GetHeaderCache(); - bool inCache = false; - LocalAccessible* cachedHeader = cache.GetWeak(this, &inCache); - if (inCache) { - // Cached but null means we know there is no previous column header. - // if defunct, the cell was removed, so behave as if there is no cached - // value. - if (!cachedHeader || !cachedHeader->IsDefunct()) { - return cachedHeader; - } - } - - uint32_t rowIdx = RowIdx(), colIdx = ColIdx(); - for (uint32_t curRowIdx = rowIdx - 1; curRowIdx < rowIdx; curRowIdx--) { - LocalAccessible* cell = table->CellAt(curRowIdx, colIdx); - if (!cell) { - continue; - } - // CellAt should always return a TableCellAccessible (XXX Bug 587529) - TableCellAccessible* tableCell = cell->AsTableCell(); - MOZ_ASSERT(tableCell, "cell should be a table cell!"); - if (!tableCell) { - continue; - } - - // Check whether the previous table cell has a cached value. - cachedHeader = cache.GetWeak(tableCell, &inCache); - if ( - // We check the cache first because even though we might not use it, - // it's faster than the other conditions. - inCache && - // Only use the cached value if: - // 1. cell is a table cell which is not a column header. In that case, - // cell is the previous header and cachedHeader is the one before that. - // We will return cell later. - cell->Role() != roles::COLUMNHEADER && - // 2. cell starts in this column. If it starts in a previous column and - // extends into this one, its header will be for the starting column, - // which is wrong for this cell. - // ColExtent is faster than ColIdx, so check that first. - (tableCell->ColExtent() == 1 || tableCell->ColIdx() == colIdx)) { - if (!cachedHeader || !cachedHeader->IsDefunct()) { - // Cache it for this cell. - cache.InsertOrUpdate(this, RefPtr(cachedHeader)); - return cachedHeader; - } - } - - // Avoid addding cells multiple times, if this cell spans more rows - // we'll get it later. - if (cell->Role() != roles::COLUMNHEADER || - tableCell->RowIdx() != curRowIdx) { - continue; - } - - // Cache the header we found. - cache.InsertOrUpdate(this, RefPtr(cell)); - return cell; - } - - // There's no header, so cache that fact. - cache.InsertOrUpdate(this, RefPtr(nullptr)); - return nullptr; -} - -void TableCellAccessible::ColHeaderCells(nsTArray* aCells) { - for (LocalAccessible* cell = PrevColHeader(); cell; - cell = cell->AsTableCell()->PrevColHeader()) { - aCells->AppendElement(cell); - } -} - -a11y::role TableCellAccessible::GetHeaderCellRole( - const LocalAccessible* aAcc) const { - if (!aAcc || !aAcc->GetContent() || !aAcc->GetContent()->IsElement()) { - return roles::NOTHING; - } - - MOZ_ASSERT(aAcc->IsTableCell()); - - // Check value of @scope attribute. - static mozilla::dom::Element::AttrValuesArray scopeValues[] = { - nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::row, nsGkAtoms::rowgroup, - nullptr}; - int32_t valueIdx = aAcc->GetContent()->AsElement()->FindAttrValueIn( - kNameSpaceID_None, nsGkAtoms::scope, scopeValues, eCaseMatters); - - switch (valueIdx) { - case 0: - case 1: - return roles::COLUMNHEADER; - case 2: - case 3: - return roles::ROWHEADER; - } - - TableAccessible* table = Table(); - if (!table) { - return roles::NOTHING; - } - - // If the cell next to this one is not a header cell then assume this cell is - // a row header for it. - uint32_t rowIdx = RowIdx(), colIdx = ColIdx(); - LocalAccessible* cell = table->CellAt(rowIdx, colIdx + ColExtent()); - if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) { - return roles::ROWHEADER; - } - - // If the cell below this one is not a header cell then assume this cell is - // a column header for it. - uint32_t rowExtent = RowExtent(); - cell = table->CellAt(rowIdx + rowExtent, colIdx); - if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) { - return roles::COLUMNHEADER; - } - - // Otherwise if this cell is surrounded by header cells only then make a guess - // based on its cell spanning. In other words if it is row spanned then assume - // it's a row header, otherwise it's a column header. - return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER; -} diff -Nru thunderbird-115.3.1+build1/accessible/generic/TableCellAccessible.h thunderbird-115.4.1+build1/accessible/generic/TableCellAccessible.h --- thunderbird-115.3.1+build1/accessible/generic/TableCellAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/generic/TableCellAccessible.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ -/* -*- 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 "mozilla/a11y/TableCellAccessibleBase.h" -#include "TableAccessible.h" - -namespace mozilla { -namespace a11y { - -class LocalAccessible; - -/** - * Base class for LocalAccessible table cell implementations. - */ -class TableCellAccessible : public TableCellAccessibleBase { - public: - virtual TableAccessible* Table() const override = 0; - virtual void ColHeaderCells(nsTArray* aCells) override; - virtual void RowHeaderCells(nsTArray* aCells) override; - - protected: - // Get the proper role for the given header cell accessible. The given acc - // must be either an ARIA grid cell accessible for a th element or a true - // table header cell accessible for the result to be valid. - a11y::role GetHeaderCellRole(const LocalAccessible* aAcc) const; - - private: - LocalAccessible* PrevColHeader(); -}; - -} // namespace a11y -} // namespace mozilla - -#endif // mozilla_a11y_TableCellAccessible_h__ diff -Nru thunderbird-115.3.1+build1/accessible/html/HTMLTableAccessible.cpp thunderbird-115.4.1+build1/accessible/html/HTMLTableAccessible.cpp --- thunderbird-115.3.1+build1/accessible/html/HTMLTableAccessible.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/html/HTMLTableAccessible.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -5,11 +5,12 @@ #include "HTMLTableAccessible.h" +#include #include "mozilla/DebugOnly.h" #include "nsAccessibilityService.h" -#include "nsAccUtils.h" #include "AccAttributes.h" +#include "ARIAMap.h" #include "CacheConstants.h" #include "DocAccessible.h" #include "LocalAccessible-inl.h" @@ -17,18 +18,29 @@ #include "Relation.h" #include "Role.h" #include "States.h" -#include "TreeWalker.h" #include "mozilla/PresShell.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLTableElement.h" +#include "mozilla/dom/NameSpaceConstants.h" +#include "nsCaseTreatment.h" +#include "nsColor.h" +#include "nsCOMPtr.h" +#include "nsCoreUtils.h" +#include "nsDebug.h" #include "nsIHTMLCollection.h" -#include "mozilla/dom/Document.h" #include "nsITableCellLayout.h" #include "nsFrameSelection.h" #include "nsError.h" -#include "nsArrayUtils.h" -#include "nsComponentManagerUtils.h" -#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsLiteralString.h" +#include "nsMargin.h" +#include "nsQueryFrame.h" +#include "nsSize.h" +#include "nsStringFwd.h" #include "nsTableCellFrame.h" #include "nsTableWrapperFrame.h" @@ -51,6 +63,10 @@ // HTMLTableCellAccessible: LocalAccessible implementation role HTMLTableCellAccessible::NativeRole() const { + // We implement this rather than using the markup maps because we only want + // this role to be returned if this is a valid cell. An invalid cell (e.g. if + // the table has role="none") won't use this class, so it will get a generic + // role, since the markup map doesn't specify a role. if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) { return roles::MATHML_CELL; } @@ -78,21 +94,22 @@ RefPtr attributes = HyperTextAccessibleWrap::NativeAttributes(); - // table-cell-index attribute - TableAccessible* table = Table(); - if (!table) { - return attributes.forget(); - } - - int32_t rowIdx = -1, colIdx = -1; - nsresult rv = GetCellIndexes(rowIdx, colIdx); - if (NS_FAILED(rv)) { - return attributes.forget(); + // We only need to expose table-cell-index to clients. If we're in the content + // process, we don't need this, so building a CachedTableAccessible is very + // wasteful. This will be exposed by RemoteAccessible in the parent process + // instead. + if (!IPCAccessibilityActive()) { + if (const TableCellAccessible* cell = AsTableCell()) { + TableAccessible* table = cell->Table(); + const uint32_t row = cell->RowIdx(); + const uint32_t col = cell->ColIdx(); + const int32_t cellIdx = table->CellIndexAt(row, col); + if (cellIdx != -1) { + attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx); + } + } } - attributes->SetAttribute(nsGkAtoms::tableCellIndex, - table->CellIndexAt(rowIdx, colIdx)); - // abbr attribute // Pick up object attribute from abbr DOM element (a child of the cell) or @@ -124,11 +141,6 @@ attributes->SetAttribute(nsGkAtoms::axis, std::move(axisText)); } -#ifdef DEBUG - RefPtr cppClass = NS_Atomize(u"cppclass"_ns); - attributes->SetAttributeStringCopy(cppClass, u"HTMLTableCellAccessible"_ns); -#endif - return attributes.forget(); } @@ -144,61 +156,52 @@ aAttribute == nsGkAtoms::scope) { mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, this); - if (TableAccessible* table = Table()) { + if (HTMLTableAccessible* table = Table()) { // Modifying these attributes can also modify our table's classification // as either a layout or data table. Queue an update on the table itself // to re-compute our "layout guess" - mDoc->QueueCacheUpdate(table->AsAccessible(), CacheDomain::Table); + mDoc->QueueCacheUpdate(table, CacheDomain::Table); } mDoc->QueueCacheUpdate(this, CacheDomain::Table); } else if (aAttribute == nsGkAtoms::rowspan || aAttribute == nsGkAtoms::colspan) { - if (TableAccessible* table = Table()) { + if (HTMLTableAccessible* table = Table()) { // Modifying these attributes can also modify our table's classification // as either a layout or data table. Queue an update on the table itself // to re-compute our "layout guess" - mDoc->QueueCacheUpdate(table->AsAccessible(), CacheDomain::Table); + mDoc->QueueCacheUpdate(table, CacheDomain::Table); } mDoc->QueueCacheUpdate(this, CacheDomain::Table); } } //////////////////////////////////////////////////////////////////////////////// -// HTMLTableCellAccessible: TableCellAccessible implementation +// HTMLTableCellAccessible implementation -TableAccessible* HTMLTableCellAccessible::Table() const { +HTMLTableAccessible* HTMLTableCellAccessible::Table() const { LocalAccessible* parent = const_cast(this); while ((parent = parent->LocalParent())) { - if (parent->IsTable()) { - return parent->AsTable(); + if (parent->IsHTMLTable()) { + return HTMLTableAccessible::GetFrom(parent); } } return nullptr; } -uint32_t HTMLTableCellAccessible::ColIdx() const { - nsTableCellFrame* cellFrame = GetCellFrame(); - NS_ENSURE_TRUE(cellFrame, 0); - return cellFrame->ColIndex(); -} - -uint32_t HTMLTableCellAccessible::RowIdx() const { - nsTableCellFrame* cellFrame = GetCellFrame(); - NS_ENSURE_TRUE(cellFrame, 0); - return cellFrame->RowIndex(); -} - uint32_t HTMLTableCellAccessible::ColExtent() const { int32_t rowIdx = -1, colIdx = -1; if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { - return 0; + // This probably isn't a table according to the layout engine; e.g. it has + // display: block. + return 1; } - TableAccessible* table = Table(); - NS_ASSERTION(table, "cell not in a table!"); - if (!table) { - return 0; + HTMLTableAccessible* table = Table(); + if (NS_WARN_IF(!table)) { + // This can happen where there is a inside a
such as + // in Monorail. + return 1; } return table->ColExtentAt(rowIdx, colIdx); @@ -207,72 +210,21 @@ uint32_t HTMLTableCellAccessible::RowExtent() const { int32_t rowIdx = -1, colIdx = -1; if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { - return 0; + // This probably isn't a table according to the layout engine; e.g. it has + // display: block. + return 1; } - TableAccessible* table = Table(); - NS_ASSERTION(table, "cell not in atable!"); - if (!table) { - return 0; + HTMLTableAccessible* table = Table(); + if (NS_WARN_IF(!table)) { + // This can happen where there is a
inside a
such as + // in Monorail. + return 1; } return table->RowExtentAt(rowIdx, colIdx); } -void HTMLTableCellAccessible::ColHeaderCells(nsTArray* aCells) { - IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); - while (LocalAccessible* cell = itr.Next()) { - a11y::role cellRole = cell->Role(); - if (cellRole == roles::COLUMNHEADER) { - aCells->AppendElement(cell); - } else if (cellRole != roles::ROWHEADER) { - // If referred table cell is at the same column then treat it as a column - // header. - TableCellAccessible* tableCell = cell->AsTableCell(); - if (tableCell && tableCell->ColIdx() == ColIdx()) { - aCells->AppendElement(cell); - } - } - } - - if (aCells->IsEmpty()) { - TableCellAccessible::ColHeaderCells(aCells); - } -} - -void HTMLTableCellAccessible::RowHeaderCells(nsTArray* aCells) { - IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); - while (LocalAccessible* cell = itr.Next()) { - a11y::role cellRole = cell->Role(); - if (cellRole == roles::ROWHEADER) { - aCells->AppendElement(cell); - } else if (cellRole != roles::COLUMNHEADER) { - // If referred table cell is at the same row then treat it as a column - // header. - TableCellAccessible* tableCell = cell->AsTableCell(); - if (tableCell && tableCell->RowIdx() == RowIdx()) { - aCells->AppendElement(cell); - } - } - } - - if (aCells->IsEmpty()) { - TableCellAccessible::RowHeaderCells(aCells); - } -} - -bool HTMLTableCellAccessible::Selected() { - int32_t rowIdx = -1, colIdx = -1; - if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) { - return false; - } - - TableAccessible* table = Table(); - NS_ENSURE_TRUE(table, false); - - return table->IsCellSelected(rowIdx, colIdx); -} - //////////////////////////////////////////////////////////////////////////////// // HTMLTableCellAccessible: protected implementation @@ -280,10 +232,6 @@ return do_QueryFrame(mContent->GetPrimaryFrame()); } -nsTableCellFrame* HTMLTableCellAccessible::GetCellFrame() const { - return do_QueryFrame(mContent->GetPrimaryFrame()); -} - nsresult HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const { nsITableCellLayout* cellLayout = GetCellLayout(); @@ -304,22 +252,74 @@ // HTMLTableHeaderCellAccessible: LocalAccessible implementation role HTMLTableHeaderCellAccessible::NativeRole() const { - return GetHeaderCellRole(this); + dom::Element* el = Elm(); + if (!el) { + return roles::NOTHING; + } + + // Check value of @scope attribute. + static mozilla::dom::Element::AttrValuesArray scopeValues[] = { + nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::row, nsGkAtoms::rowgroup, + nullptr}; + int32_t valueIdx = el->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope, + scopeValues, eCaseMatters); + + switch (valueIdx) { + case 0: + case 1: + return roles::COLUMNHEADER; + case 2: + case 3: + return roles::ROWHEADER; + } + + dom::Element* nextEl = el->GetNextElementSibling(); + dom::Element* prevEl = el->GetPreviousElementSibling(); + // If this is the only cell in its row, it's a column header. + if (!nextEl && !prevEl) { + return roles::COLUMNHEADER; + } + const bool nextIsHeader = nextEl && nsCoreUtils::IsHTMLTableHeader(nextEl); + const bool prevIsHeader = prevEl && nsCoreUtils::IsHTMLTableHeader(prevEl); + // If this has a header on both sides, it is a column header. + if (prevIsHeader && nextIsHeader) { + return roles::COLUMNHEADER; + } + // If this has a header on one side and only a single normal cell on the + // other, it's a column header. + if (nextIsHeader && prevEl && !prevEl->GetPreviousElementSibling()) { + return roles::COLUMNHEADER; + } + if (prevIsHeader && nextEl && !nextEl->GetNextElementSibling()) { + return roles::COLUMNHEADER; + } + // If this has a normal cell next to it, it 's a row header. + if ((nextEl && !nextIsHeader) || (prevEl && !prevIsHeader)) { + return roles::ROWHEADER; + } + // If this has a row span, it could be a row header. + if (RowExtent() > 1) { + // It isn't a row header if it has 1 or more consecutive headers next to it. + if (prevIsHeader && + (!prevEl->GetPreviousElementSibling() || + nsCoreUtils::IsHTMLTableHeader(prevEl->GetPreviousElementSibling()))) { + return roles::COLUMNHEADER; + } + if (nextIsHeader && + (!nextEl->GetNextElementSibling() || + nsCoreUtils::IsHTMLTableHeader(nextEl->GetNextElementSibling()))) { + return roles::COLUMNHEADER; + } + return roles::ROWHEADER; + } + // Otherwise, assume it's a column header. + return roles::COLUMNHEADER; } //////////////////////////////////////////////////////////////////////////////// // HTMLTableRowAccessible //////////////////////////////////////////////////////////////////////////////// -role HTMLTableRowAccessible::NativeRole() const { - if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) { - return roles::MATHML_TABLE_ROW; - } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) { - return roles::MATHML_LABELED_ROW; - } - return roles::ROW; -} - // LocalAccessible protected ENameValueFlag HTMLTableRowAccessible::NativeName(nsString& aName) const { // For table row accessibles, we only want to calculate the name from the @@ -348,13 +348,6 @@ aChild->IsHTMLCaption() ? 0 : aIndex, aChild); } -role HTMLTableAccessible::NativeRole() const { - if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) { - return roles::MATHML_TABLE; - } - return roles::TABLE; -} - uint64_t HTMLTableAccessible::NativeState() const { return LocalAccessible::NativeState() | states::READONLY; } @@ -450,14 +443,6 @@ : nullptr; } -void HTMLTableAccessible::Summary(nsString& aSummary) { - dom::HTMLTableElement* table = dom::HTMLTableElement::FromNode(mContent); - - if (table) { - table->GetSummary(aSummary); - } -} - uint32_t HTMLTableAccessible::ColCount() const { nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); return tableFrame ? tableFrame->GetColCount() : 0; @@ -468,374 +453,262 @@ return tableFrame ? tableFrame->GetRowCount() : 0; } -uint32_t HTMLTableAccessible::SelectedCellCount() { +uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); if (!tableFrame) { - return 0; + return 1; } - uint32_t count = 0, rowCount = RowCount(), colCount = ColCount(); - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); - if (!cellFrame || !cellFrame->IsSelected()) { - continue; - } - - uint32_t startRow = cellFrame->RowIndex(); - uint32_t startCol = cellFrame->ColIndex(); - if (startRow == rowIdx && startCol == colIdx) { - count++; - } - } - } - - return count; + return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx); } -uint32_t HTMLTableAccessible::SelectedColCount() { - uint32_t count = 0, colCount = ColCount(); - - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - if (IsColSelected(colIdx)) { - count++; - } +uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { + nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + if (!tableFrame) { + return 1; } - return count; + return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx); } -uint32_t HTMLTableAccessible::SelectedRowCount() { - uint32_t count = 0, rowCount = RowCount(); +bool HTMLTableAccessible::IsProbablyLayoutTable() { + // Implement a heuristic to determine if table is most likely used for layout. - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - if (IsRowSelected(rowIdx)) { - count++; - } - } + // XXX do we want to look for rowspan or colspan, especialy that span all but + // a couple cells at the beginning or end of a row/col, and especially when + // they occur at the edge of a table? + + // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC + // This will allow release trunk builds to be used by testers to refine + // the algorithm. Integrate it into Logging. + // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release +#ifdef SHOW_LAYOUT_HEURISTIC +# define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ + { \ + mLayoutHeuristic = isLayout \ + ? nsLiteralString(u"layout table: " heuristic) \ + : nsLiteralString(u"data table: " heuristic); \ + return isLayout; \ + } +#else +# define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ + { return isLayout; } +#endif - return count; -} + MOZ_ASSERT(!IsDefunct(), "Table accessible should not be defunct"); -void HTMLTableAccessible::SelectedCells(nsTArray* aCells) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return; + // Need to see all elements while document is being edited. + if (Document()->State() & states::EDITABLE) { + RETURN_LAYOUT_ANSWER(false, "In editable document"); } - uint32_t rowCount = RowCount(), colCount = ColCount(); - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); - if (!cellFrame || !cellFrame->IsSelected()) { - continue; - } - - uint32_t startRow = cellFrame->RowIndex(); - uint32_t startCol = cellFrame->ColIndex(); - if (startRow != rowIdx || startCol != colIdx) { - continue; - } - - LocalAccessible* cell = mDoc->GetAccessible(cellFrame->GetContent()); - aCells->AppendElement(cell); - } + // Check to see if an ARIA role overrides the role from native markup, + // but for which we still expose table semantics (treegrid, for example). + if (HasARIARole()) { + RETURN_LAYOUT_ANSWER(false, "Has role attribute"); } -} -void HTMLTableAccessible::SelectedCellIndices(nsTArray* aCells) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return; + dom::Element* el = Elm(); + if (el->IsMathMLElement(nsGkAtoms::mtable_)) { + RETURN_LAYOUT_ANSWER(false, "MathML matrix"); } - uint32_t rowCount = RowCount(), colCount = ColCount(); - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); - if (!cellFrame || !cellFrame->IsSelected()) { - continue; - } + MOZ_ASSERT(el->IsHTMLElement(nsGkAtoms::table), + "Table should not be built by CSS display:table style"); - uint32_t startCol = cellFrame->ColIndex(); - uint32_t startRow = cellFrame->RowIndex(); - if (startRow == rowIdx && startCol == colIdx) { - aCells->AppendElement(CellIndexAt(rowIdx, colIdx)); - } - } + // Check if datatable attribute has "0" value. + if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable, u"0"_ns, + eCaseMatters)) { + RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout"); } -} -void HTMLTableAccessible::SelectedColIndices(nsTArray* aCols) { - uint32_t colCount = ColCount(); - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - if (IsColSelected(colIdx)) { - aCols->AppendElement(colIdx); - } + // Check for legitimate data table attributes. + if (el->Element::HasNonEmptyAttr(nsGkAtoms::summary)) { + RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures"); } -} -void HTMLTableAccessible::SelectedRowIndices(nsTArray* aRows) { - uint32_t rowCount = RowCount(); - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - if (IsRowSelected(rowIdx)) { - aRows->AppendElement(rowIdx); - } + // Check for legitimate data table elements. + LocalAccessible* caption = LocalFirstChild(); + if (caption && caption->IsHTMLCaption() && caption->HasChildren()) { + RETURN_LAYOUT_ANSWER(false, + "Not empty caption -- legitimate table structures"); } -} -LocalAccessible* HTMLTableAccessible::CellAt(uint32_t aRowIdx, - uint32_t aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return nullptr; - } + for (nsIContent* childElm = el->GetFirstChild(); childElm; + childElm = childElm->GetNextSibling()) { + if (!childElm->IsHTMLElement()) continue; - nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); - LocalAccessible* cell = mDoc->GetAccessible(cellContent); + if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col, nsGkAtoms::colgroup, + nsGkAtoms::tfoot, nsGkAtoms::thead)) { + RETURN_LAYOUT_ANSWER( + false, + "Has col, colgroup, tfoot or thead -- legitimate table structures"); + } - // Sometimes, the accessible returned here is a row accessible instead of - // a cell accessible, for example when a cell has CSS display:block; set. - // In such cases, iterate through the cells in this row differently to find - // it. - if (cell && cell->IsTableRow()) { - return CellInRowAt(cell, aColIdx); - } + if (childElm->IsHTMLElement(nsGkAtoms::tbody)) { + for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm; + rowElm = rowElm->GetNextSibling()) { + if (rowElm->IsHTMLElement(nsGkAtoms::tr)) { + if (LocalAccessible* row = Document()->GetAccessible(rowElm)) { + if (const nsRoleMapEntry* roleMapEntry = row->ARIARoleMap()) { + if (roleMapEntry->role != roles::ROW) { + RETURN_LAYOUT_ANSWER(true, "Repurposed tr with different role"); + } + } + } - // XXX bug 576838: bizarre tables (like table6 in tables/test_table2.html) may - // return itself as a cell what makes Orca hang. - return cell == this ? nullptr : cell; -} + for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm; + cellElm = cellElm->GetNextSibling()) { + if (cellElm->IsHTMLElement()) { + if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) { + RETURN_LAYOUT_ANSWER(false, + "Has th -- legitimate table structures"); + } -int32_t HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return -1; - } + if (cellElm->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::headers) || + cellElm->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::scope) || + cellElm->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::abbr)) { + RETURN_LAYOUT_ANSWER(false, + "Has headers, scope, or abbr attribute -- " + "legitimate table structures"); + } - int32_t cellIndex = tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx); - if (cellIndex == -1) { - // Sometimes, the accessible returned here is a row accessible instead of - // a cell accessible, for example when a cell has CSS display:block; set. - // In such cases, iterate through the cells in this row differently to find - // it. - nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); - LocalAccessible* cell = mDoc->GetAccessible(cellContent); - if (cell && cell->IsTableRow()) { - return TableAccessible::CellIndexAt(aRowIdx, aColIdx); + if (LocalAccessible* cell = Document()->GetAccessible(cellElm)) { + if (const nsRoleMapEntry* roleMapEntry = cell->ARIARoleMap()) { + if (roleMapEntry->role != roles::CELL && + roleMapEntry->role != roles::COLUMNHEADER && + roleMapEntry->role != roles::ROWHEADER && + roleMapEntry->role != roles::GRID_CELL) { + RETURN_LAYOUT_ANSWER(true, + "Repurposed cell with different role"); + } + } + if (cell->ChildCount() == 1 && + cell->LocalFirstChild()->IsAbbreviation()) { + RETURN_LAYOUT_ANSWER( + false, "has abbr -- legitimate table structures"); + } + } + } + } + } + } } } - return cellIndex; -} - -int32_t HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return -1; - } - - int32_t rowIdx = -1, colIdx = -1; - tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); - - if (colIdx == -1) { - // Sometimes, the index returned indicates that this is not a regular - // cell, for example when a cell has CSS display:block; set. - // In such cases, try the super class method to find it. - return TableAccessible::ColIndexAt(aCellIdx); + // Check for nested tables. + nsCOMPtr nestedTables = + el->GetElementsByTagName(u"table"_ns); + if (nestedTables->Length() > 0) { + RETURN_LAYOUT_ANSWER(true, "Has a nested table within it"); } - return colIdx; -} - -int32_t HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return -1; + // If only 1 column or only 1 row, it's for layout. + auto colCount = ColCount(); + if (colCount <= 1) { + RETURN_LAYOUT_ANSWER(true, "Has only 1 column"); } - - int32_t rowIdx = -1, colIdx = -1; - tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); - - if (rowIdx == -1) { - // Sometimes, the index returned indicates that this is not a regular - // cell, for example when a cell has CSS display:block; set. - // In such cases, try the super class method to find it. - return TableAccessible::RowIndexAt(aCellIdx); + auto rowCount = RowCount(); + if (rowCount <= 1) { + RETURN_LAYOUT_ANSWER(true, "Has only 1 row"); } - return rowIdx; -} - -void HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, - int32_t* aRowIdx, - int32_t* aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (tableFrame) { - tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx); - if (*aRowIdx == -1 || *aColIdx == -1) { - // Sometimes, the index returned indicates that this is not a regular - // cell, for example when a cell has CSS display:block; set. - // In such cases, try the super class method to find it. - TableAccessible::RowAndColIndicesAt(aCellIdx, aRowIdx, aColIdx); - } + // Check for many columns. + if (colCount >= 5) { + RETURN_LAYOUT_ANSWER(false, ">=5 columns"); } -} -uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); + // Now we know there are 2-4 columns and 2 or more rows. Check to see if + // there are visible borders on the cells. + // XXX currently, we just check the first cell -- do we really need to do + // more? + nsTableWrapperFrame* tableFrame = do_QueryFrame(el->GetPrimaryFrame()); if (!tableFrame) { - return 0; + RETURN_LAYOUT_ANSWER(false, "table with no frame!"); } - uint32_t colExtent = tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx); - if (colExtent == 0) { - nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); - LocalAccessible* cell = mDoc->GetAccessible(cellContent); - if (cell && cell->IsTableRow()) { - return TableAccessible::ColExtentAt(aRowIdx, aColIdx); - } + nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0); + if (!cellFrame) { + RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!"); } - return colExtent; -} - -uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return 0; + nsMargin border = cellFrame->StyleBorder()->GetComputedBorder(); + if (border.top && border.bottom && border.left && border.right) { + RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell"); } - return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx); -} + // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on + // forward. -bool HTMLTableAccessible::IsColSelected(uint32_t aColIdx) { - bool isSelected = false; - - uint32_t rowCount = RowCount(); - for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { - isSelected = IsCellSelected(rowIdx, aColIdx); - if (!isSelected) { - return false; - } - } - - return isSelected; -} + // Check for styled background color across rows (alternating background + // color is a common feature for data tables). + auto childCount = ChildCount(); + nscolor rowColor = 0; + nscolor prevRowColor; + for (auto childIdx = 0U; childIdx < childCount; childIdx++) { + LocalAccessible* child = LocalChildAt(childIdx); + if (child->IsHTMLTableRow()) { + prevRowColor = rowColor; + nsIFrame* rowFrame = child->GetFrame(); + MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up"); + if (!rowFrame) { + RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy"); + } -bool HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx) { - bool isSelected = false; + rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame); - uint32_t colCount = ColCount(); - for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { - isSelected = IsCellSelected(aRowIdx, colIdx); - if (!isSelected) { - return false; + if (childIdx > 0 && prevRowColor != rowColor) { + RETURN_LAYOUT_ANSWER(false, + "2 styles of row background color, non-bordered"); + } } } - return isSelected; -} - -bool HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return false; + // Check for many rows. + const uint32_t kMaxLayoutRows = 20; + if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data + RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered"); } - nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx); - return cellFrame ? cellFrame->IsSelected() : false; -} - -void HTMLTableAccessible::SelectRow(uint32_t aRowIdx) { - DebugOnly rv = - RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, true); - NS_ASSERTION(NS_SUCCEEDED(rv), - "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); - - AddRowOrColumnToSelection(aRowIdx, TableSelectionMode::Row); -} - -void HTMLTableAccessible::SelectCol(uint32_t aColIdx) { - DebugOnly rv = RemoveRowsOrColumnsFromSelection( - aColIdx, TableSelectionMode::Column, true); - NS_ASSERTION(NS_SUCCEEDED(rv), - "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); - - AddRowOrColumnToSelection(aColIdx, TableSelectionMode::Column); -} - -void HTMLTableAccessible::UnselectRow(uint32_t aRowIdx) { - RemoveRowsOrColumnsFromSelection(aRowIdx, TableSelectionMode::Row, false); -} - -void HTMLTableAccessible::UnselectCol(uint32_t aColIdx) { - RemoveRowsOrColumnsFromSelection(aColIdx, TableSelectionMode::Column, false); -} - -//////////////////////////////////////////////////////////////////////////////// -// HTMLTableAccessible: protected implementation - -nsresult HTMLTableAccessible::AddRowOrColumnToSelection( - int32_t aIndex, TableSelectionMode aTarget) { - bool doSelectRow = (aTarget == TableSelectionMode::Row); - - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return NS_OK; + // Check for very wide table. + nsIFrame* documentFrame = Document()->GetFrame(); + nsSize documentSize = documentFrame->GetSize(); + if (documentSize.width > 0) { + nsSize tableSize = GetFrame()->GetSize(); + int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width; + if (percentageOfDocWidth > 95) { + // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width + // Probably for layout + RETURN_LAYOUT_ANSWER( + true, "<= 4 columns, table width is 95% of document width"); + } } - uint32_t count = 0; - if (doSelectRow) { - count = ColCount(); - } else { - count = RowCount(); + // Two column rules. + if (rowCount * colCount <= 10) { + RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered"); } - PresShell* presShell = mDoc->PresShellPtr(); - RefPtr tableSelection = - const_cast(presShell->ConstFrameSelection()); - - for (uint32_t idx = 0; idx < count; idx++) { - int32_t rowIdx = doSelectRow ? aIndex : idx; - int32_t colIdx = doSelectRow ? idx : aIndex; - nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); - if (cellFrame && !cellFrame->IsSelected()) { - nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent()); - NS_ENSURE_SUCCESS(rv, rv); + static const nsLiteralString tags[] = {u"embed"_ns, u"object"_ns, + u"iframe"_ns}; + for (const auto& tag : tags) { + nsCOMPtr descendants = el->GetElementsByTagName(tag); + if (descendants->Length() > 0) { + RETURN_LAYOUT_ANSWER(true, + "Has no borders, and has iframe, object or embed, " + "typical of advertisements"); } } - return NS_OK; + RETURN_LAYOUT_ANSWER(false, + "No layout factor strong enough, so will guess data"); } -nsresult HTMLTableAccessible::RemoveRowsOrColumnsFromSelection( - int32_t aIndex, TableSelectionMode aTarget, bool aIsOuter) { - nsTableWrapperFrame* tableFrame = GetTableWrapperFrame(); - if (!tableFrame) { - return NS_OK; - } - - PresShell* presShell = mDoc->PresShellPtr(); - RefPtr tableSelection = - const_cast(presShell->ConstFrameSelection()); - - bool doUnselectRow = (aTarget == TableSelectionMode::Row); - uint32_t count = doUnselectRow ? ColCount() : RowCount(); - - int32_t startRowIdx = doUnselectRow ? aIndex : 0; - int32_t endRowIdx = doUnselectRow ? aIndex : count - 1; - int32_t startColIdx = doUnselectRow ? 0 : aIndex; - int32_t endColIdx = doUnselectRow ? count - 1 : aIndex; - - if (aIsOuter) { - return tableSelection->RestrictCellsToSelection( - mContent, startRowIdx, startColIdx, endRowIdx, endColIdx); - } - - return tableSelection->RemoveCellsFromSelection( - mContent, startRowIdx, startColIdx, endRowIdx, endColIdx); -} +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: protected implementation void HTMLTableAccessible::Description(nsString& aDescription) const { // Helpful for debugging layout vs. data tables diff -Nru thunderbird-115.3.1+build1/accessible/html/HTMLTableAccessible.h thunderbird-115.4.1+build1/accessible/html/HTMLTableAccessible.h --- thunderbird-115.3.1+build1/accessible/html/HTMLTableAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/html/HTMLTableAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -7,8 +7,6 @@ #define mozilla_a11y_HTMLTableAccessible_h__ #include "HyperTextAccessibleWrap.h" -#include "TableAccessible.h" -#include "TableCellAccessible.h" class nsITableCellLayout; class nsTableCellFrame; @@ -16,15 +14,14 @@ namespace mozilla { -enum class TableSelectionMode : uint32_t; - namespace a11y { +class HTMLTableAccessible; + /** * HTML table cell accessible (html:td). */ -class HTMLTableCellAccessible : public HyperTextAccessibleWrap, - public TableCellAccessible { +class HTMLTableCellAccessible : public HyperTextAccessibleWrap { public: HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc); @@ -33,7 +30,6 @@ HyperTextAccessibleWrap) // LocalAccessible - virtual TableCellAccessible* AsTableCell() override { return this; } virtual a11y::role NativeRole() const override; virtual uint64_t NativeState() const override; virtual uint64_t NativeInteractiveState() const override; @@ -44,16 +40,18 @@ int32_t aModType, const nsAttrValue* aOldValue, uint64_t aOldState) override; - // TableCellAccessible + // HTMLTableCellAccessible public: - virtual TableAccessible* Table() const override; - virtual uint32_t ColIdx() const override; - virtual uint32_t RowIdx() const override; - virtual uint32_t ColExtent() const override; - virtual uint32_t RowExtent() const override; - virtual void ColHeaderCells(nsTArray* aCells) override; - virtual void RowHeaderCells(nsTArray* aCells) override; - virtual bool Selected() override; + HTMLTableAccessible* Table() const; + uint32_t ColExtent() const; + uint32_t RowExtent() const; + + static HTMLTableCellAccessible* GetFrom(LocalAccessible* aAcc) { + if (aAcc->IsHTMLTableCell()) { + return static_cast(aAcc); + } + return nullptr; + } protected: virtual ~HTMLTableCellAccessible() {} @@ -64,11 +62,6 @@ nsITableCellLayout* GetCellLayout() const; /** - * Return the table cell frame. - */ - nsTableCellFrame* GetCellFrame() const; - - /** * Return row and column indices of the cell. */ nsresult GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const; @@ -99,9 +92,6 @@ NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTableRowAccessible, HyperTextAccessibleWrap) - // LocalAccessible - virtual a11y::role NativeRole() const override; - protected: virtual ~HTMLTableRowAccessible() {} @@ -118,8 +108,7 @@ // data vs. layout heuristic // #define SHOW_LAYOUT_HEURISTIC -class HTMLTableAccessible : public HyperTextAccessibleWrap, - public TableAccessible { +class HTMLTableAccessible : public HyperTextAccessibleWrap { public: HTMLTableAccessible(nsIContent* aContent, DocAccessible* aDoc) : HyperTextAccessibleWrap(aContent, aDoc) { @@ -130,40 +119,23 @@ NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLTableAccessible, HyperTextAccessibleWrap) - // TableAccessible - virtual LocalAccessible* Caption() const override; - virtual void Summary(nsString& aSummary) override; - virtual uint32_t ColCount() const override; - virtual uint32_t RowCount() override; - virtual LocalAccessible* CellAt(uint32_t aRowIndex, - uint32_t aColumnIndex) override; - virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override; - virtual int32_t ColIndexAt(uint32_t aCellIdx) override; - virtual int32_t RowIndexAt(uint32_t aCellIdx) override; - virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, - int32_t* aColIdx) override; - virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override; - virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override; - virtual bool IsColSelected(uint32_t aColIdx) override; - virtual bool IsRowSelected(uint32_t aRowIdx) override; - virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override; - virtual uint32_t SelectedCellCount() override; - virtual uint32_t SelectedColCount() override; - virtual uint32_t SelectedRowCount() override; - virtual void SelectedCells(nsTArray* aCells) override; - virtual void SelectedCellIndices(nsTArray* aCells) override; - virtual void SelectedColIndices(nsTArray* aCols) override; - virtual void SelectedRowIndices(nsTArray* aRows) override; - virtual void SelectCol(uint32_t aColIdx) override; - virtual void SelectRow(uint32_t aRowIdx) override; - virtual void UnselectCol(uint32_t aColIdx) override; - virtual void UnselectRow(uint32_t aRowIdx) override; - virtual LocalAccessible* AsAccessible() override { return this; } + // HTMLTableAccessible + LocalAccessible* Caption() const; + uint32_t ColCount() const; + uint32_t RowCount(); + uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx); + uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx); + bool IsProbablyLayoutTable(); + + static HTMLTableAccessible* GetFrom(LocalAccessible* aAcc) { + if (aAcc->IsHTMLTable()) { + return static_cast(aAcc); + } + return nullptr; + } // LocalAccessible - virtual TableAccessible* AsTable() override { return this; } virtual void Description(nsString& aDescription) const override; - virtual a11y::role NativeRole() const override; virtual uint64_t NativeState() const override; virtual already_AddRefed NativeAttributes() override; virtual Relation RelationByType(RelationType aRelationType) const override; @@ -183,29 +155,6 @@ // HTMLTableAccessible - /** - * Add row or column to selection. - * - * @param aIndex [in] index of row or column to be selected - * @param aTarget [in] indicates what should be selected, either row or - * column (see nsFrameSelection) - */ - nsresult AddRowOrColumnToSelection(int32_t aIndex, - TableSelectionMode aTarget); - - /** - * Removes rows or columns at the given index or outside it from selection. - * - * @param aIndex [in] row or column index - * @param aTarget [in] indicates whether row or column should unselected - * @param aIsOuter [in] indicates whether all rows or column excepting - * the given one should be unselected or the given one - * should be unselected only - */ - nsresult RemoveRowsOrColumnsFromSelection(int32_t aIndex, - TableSelectionMode aTarget, - bool aIsOuter); - #ifdef SHOW_LAYOUT_HEURISTIC nsString mLayoutHeuristic; #endif diff -Nru thunderbird-115.3.1+build1/accessible/interfaces/nsIAccessibleTable.idl thunderbird-115.4.1+build1/accessible/interfaces/nsIAccessibleTable.idl --- thunderbird-115.3.1+build1/accessible/interfaces/nsIAccessibleTable.idl 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/interfaces/nsIAccessibleTable.idl 2023-10-24 21:13:27.000000000 +0000 @@ -181,34 +181,6 @@ Array getSelectedRowIndices(); /** - * Select a row and unselects all previously selected rows. - * - * @param rowIndex [in] the row index to select - */ - void selectRow(in long rowIndex); - - /** - * Select a column and unselects all previously selected columns. - * - * @param columnIndex [in] the column index to select - */ - void selectColumn(in long columnIndex); - - /** - * Unselect the given row, leaving other selected rows selected (if any). - * - * @param rowIndex [in] the row index to select - */ - void unselectRow(in long rowIndex); - - /** - * Unselect the given column, leaving other selected columns selected (if any). - * - * @param columnIndex [in] the column index to select - */ - void unselectColumn(in long columnIndex); - - /** * Use heuristics to determine if table is most likely used for layout. */ boolean isProbablyForLayout(); diff -Nru thunderbird-115.3.1+build1/accessible/ipc/RemoteAccessibleBase.cpp thunderbird-115.4.1+build1/accessible/ipc/RemoteAccessibleBase.cpp --- thunderbird-115.3.1+build1/accessible/ipc/RemoteAccessibleBase.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/ipc/RemoteAccessibleBase.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -7,12 +7,15 @@ #include "ARIAMap.h" #include "CachedTableAccessible.h" #include "DocAccessible.h" +#include "RemoteAccessibleBase.h" #include "mozilla/a11y/DocAccessibleParent.h" #include "mozilla/a11y/DocManager.h" #include "mozilla/a11y/Platform.h" #include "mozilla/a11y/RemoteAccessibleBase.h" #include "mozilla/a11y/RemoteAccessible.h" #include "mozilla/a11y/Role.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "mozilla/BinarySearch.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/BrowserParent.h" @@ -412,6 +415,60 @@ } template +RemoteAccessible* RemoteAccessibleBase::DoFuzzyHittesting() { + uint32_t childCount = ChildCount(); + if (!childCount) { + return nullptr; + } + // Check if this match has a clipped child. + // This usually indicates invisible text, and we're + // interested in returning the inner text content + // even if it doesn't contain the point we're hittesting. + RemoteAccessible* clippedContainer = nullptr; + for (uint32_t i = 0; i < childCount; i++) { + RemoteAccessible* child = RemoteChildAt(i); + if (child->Role() == roles::TEXT_CONTAINER) { + if (child->IsClipped()) { + clippedContainer = child; + break; + } + } + } + // If we found a clipped container, descend it in search of + // meaningful text leaves. Ignore non-text-leaf/text-container + // siblings. + RemoteAccessible* container = clippedContainer; + while (container) { + RemoteAccessible* textLeaf = nullptr; + bool continueSearch = false; + childCount = container->ChildCount(); + for (uint32_t i = 0; i < childCount; i++) { + RemoteAccessible* child = container->RemoteChildAt(i); + if (child->Role() == roles::TEXT_CONTAINER) { + container = child; + continueSearch = true; + break; + } + if (child->IsTextLeaf()) { + textLeaf = child; + // Don't break here -- it's possible a text container + // exists as another sibling, and we should descend as + // deep as possible. + } + } + if (textLeaf) { + return textLeaf; + } + if (!continueSearch) { + // We didn't find anything useful in this set of siblings. + // Don't keep searching + break; + } + } + return nullptr; +} + +template Accessible* RemoteAccessibleBase::ChildAtPoint( int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) { // Elements that are partially on-screen should have their bounds masked by @@ -484,6 +541,13 @@ // this call shouldn't pass the boundary defined by // the acc this call originated on. If we hit `this`, // return our most recent match. + if (!lastMatch && + BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) { + // If we haven't found a match, but `this` contains the point we're + // looking for, set it as our temp last match so we can + // (potentially) do fuzzy hittesting on it below. + lastMatch = acc; + } break; } @@ -495,6 +559,10 @@ break; } } + if (lastMatch) { + RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting(); + lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch; + } } } @@ -653,6 +721,27 @@ } template +bool RemoteAccessibleBase::IsOverflowHidden() const { + MOZ_ASSERT(mCachedFields); + if (auto maybeOverflow = + mCachedFields->GetAttribute>(nsGkAtoms::overflow)) { + return *maybeOverflow == nsGkAtoms::hidden; + } + + return false; +} + +template +bool RemoteAccessibleBase::IsClipped() const { + MOZ_ASSERT(mCachedFields); + if (mCachedFields->GetAttribute(nsGkAtoms::clip_rule)) { + return true; + } + + return false; +} + +template LayoutDeviceIntRect RemoteAccessibleBase::BoundsWithOffset( Maybe aOffset, bool aBoundsAreForHittesting) const { Maybe maybeBounds = RetrieveCachedBounds(); @@ -728,11 +817,12 @@ // that the bounds we've calculated so far are constrained to the // bounds of the scroll area. Without this, we'll "hit" the off-screen // portions of accs that are are partially (but not fully) within the - // scroll area. - if (aBoundsAreForHittesting && hasScrollArea) { - nsRect selfRelativeScrollBounds(0, 0, remoteBounds.width, - remoteBounds.height); - bounds = bounds.SafeIntersect(selfRelativeScrollBounds); + // scroll area. This is also a problem for accs with overflow:hidden; + if (aBoundsAreForHittesting && + (hasScrollArea || remoteAcc->IsOverflowHidden())) { + nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width, + remoteBounds.height); + bounds = bounds.SafeIntersect(selfRelativeVisibleBounds); } } if (remoteAcc->IsDoc()) { @@ -1386,8 +1476,8 @@ attributes->SetAttribute(nsGkAtoms::display, display); } - if (TableCellAccessibleBase* cell = AsTableCellBase()) { - TableAccessibleBase* table = cell->Table(); + if (TableCellAccessible* cell = AsTableCell()) { + TableAccessible* table = cell->Table(); uint32_t row = cell->RowIdx(); uint32_t col = cell->ColIdx(); int32_t cellIdx = table->CellIndexAt(row, col); @@ -1899,7 +1989,7 @@ } template -TableAccessibleBase* RemoteAccessibleBase::AsTableBase() { +TableAccessible* RemoteAccessibleBase::AsTable() { if (IsTable()) { return CachedTableAccessible::GetFrom(this); } @@ -1907,7 +1997,7 @@ } template -TableCellAccessibleBase* RemoteAccessibleBase::AsTableCellBase() { +TableCellAccessible* RemoteAccessibleBase::AsTableCell() { if (IsTableCell()) { return CachedTableCellAccessible::GetFrom(this); } diff -Nru thunderbird-115.3.1+build1/accessible/ipc/RemoteAccessibleBase.h thunderbird-115.4.1+build1/accessible/ipc/RemoteAccessibleBase.h --- thunderbird-115.3.1+build1/accessible/ipc/RemoteAccessibleBase.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/ipc/RemoteAccessibleBase.h 2023-10-24 21:13:26.000000000 +0000 @@ -393,8 +393,8 @@ : nullptr; } - virtual TableAccessibleBase* AsTableBase() override; - virtual TableCellAccessibleBase* AsTableCellBase() override; + virtual TableAccessible* AsTable() override; + virtual TableCellAccessible* AsTableCell() override; virtual void DOMNodeID(nsString& aID) const override; @@ -452,6 +452,24 @@ LayoutDeviceIntRect BoundsWithOffset( Maybe aOffset, bool aBoundsAreForHittesting = false) const; bool IsFixedPos() const; + bool IsOverflowHidden() const; + + /** + * Returns true if an accessible's frame has no scrollable overflow, and + * false otherwise. + * Does not return true for partially clipped accessibles. + */ + bool IsClipped() const; + + /** + * Checks if our hittesting match has any clipped children and, if so + * descends it and subsequent TEXT_CONTAINERs in search of a text leaf. + * We do this because some sites use clipping to hide text that is only + * visible to a11y, while displaying a visual version of the same text on + * the web page. We want a hittest of the visible text to resolve to the + * hidden, a11y-only text node. + */ + RemoteAccessible* DoFuzzyHittesting(); // This function is used exclusively for hit testing. bool ContainsPoint(int32_t aX, int32_t aY); diff -Nru thunderbird-115.3.1+build1/accessible/mac/mozAccessible.mm thunderbird-115.4.1+build1/accessible/mac/mozAccessible.mm --- thunderbird-115.3.1+build1/accessible/mac/mozAccessible.mm 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/mac/mozAccessible.mm 2023-10-24 21:13:27.000000000 +0000 @@ -22,7 +22,6 @@ #include "Relation.h" #include "Role.h" #include "RootAccessible.h" -#include "TableAccessible.h" #include "mozilla/a11y/PDocAccessible.h" #include "mozilla/dom/BrowserParent.h" #include "OuterDocAccessible.h" diff -Nru thunderbird-115.3.1+build1/accessible/mac/mozTableAccessible.mm thunderbird-115.4.1+build1/accessible/mac/mozTableAccessible.mm --- thunderbird-115.3.1+build1/accessible/mac/mozTableAccessible.mm 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/mac/mozTableAccessible.mm 2023-10-24 21:13:27.000000000 +0000 @@ -11,8 +11,8 @@ #include "AccIterator.h" #include "LocalAccessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsAccessibilityService.h" #include "XULTreeAccessible.h" #include "Pivot.h" @@ -48,7 +48,7 @@ mChildren = [[NSMutableArray alloc] init]; - TableAccessibleBase* table = [mParent geckoAccessible]->AsTableBase(); + TableAccessible* table = [mParent geckoAccessible]->AsTable(); MOZ_ASSERT(table, "Got null table when fetching column children!"); uint32_t numRows = table->RowCount(); @@ -137,7 +137,7 @@ } // For LocalAccessible and cached RemoteAccessible, we could use - // AsTableBase()->IsProbablyLayoutTable(). However, if the cache is enabled, + // AsTable()->IsProbablyLayoutTable(). However, if the cache is enabled, // that would build the table cache, which is pointless for layout tables on // Mac because layout tables are AXGroups and do not expose table properties // like AXRows, AXColumns, etc. @@ -174,13 +174,13 @@ - (NSNumber*)moxRowCount { MOZ_ASSERT(mGeckoAccessible); - return @(mGeckoAccessible->AsTableBase()->RowCount()); + return @(mGeckoAccessible->AsTable()->RowCount()); } - (NSNumber*)moxColumnCount { MOZ_ASSERT(mGeckoAccessible); - return @(mGeckoAccessible->AsTableBase()->ColCount()); + return @(mGeckoAccessible->AsTable()->ColCount()); } - (NSArray*)moxRows { @@ -221,7 +221,7 @@ mColContainers = [[NSMutableArray alloc] init]; uint32_t numCols = 0; - numCols = mGeckoAccessible->AsTableBase()->ColCount(); + numCols = mGeckoAccessible->AsTable()->ColCount(); for (uint32_t i = 0; i < numCols; i++) { mozColumnContainer* container = [[mozColumnContainer alloc] initWithIndex:i andParent:self]; @@ -244,9 +244,9 @@ MOZ_ASSERT(mGeckoAccessible); uint32_t numCols = 0; - TableAccessibleBase* table = nullptr; + TableAccessible* table = nullptr; - table = mGeckoAccessible->AsTableBase(); + table = mGeckoAccessible->AsTable(); numCols = table->ColCount(); NSMutableArray* colHeaders = [[[NSMutableArray alloc] initWithCapacity:numCols] autorelease]; @@ -272,7 +272,7 @@ MOZ_ASSERT(mGeckoAccessible); - Accessible* cell = mGeckoAccessible->AsTableBase()->CellAt(row, col); + Accessible* cell = mGeckoAccessible->AsTable()->CellAt(row, col); if (!cell) { return nil; } @@ -338,7 +338,7 @@ - (NSValue*)moxRowIndexRange { MOZ_ASSERT(mGeckoAccessible); - TableCellAccessibleBase* cell = mGeckoAccessible->AsTableCellBase(); + TableCellAccessible* cell = mGeckoAccessible->AsTableCell(); return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(), cell->RowExtent())]; } @@ -346,7 +346,7 @@ - (NSValue*)moxColumnIndexRange { MOZ_ASSERT(mGeckoAccessible); - TableCellAccessibleBase* cell = mGeckoAccessible->AsTableCellBase(); + TableCellAccessible* cell = mGeckoAccessible->AsTableCell(); return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(), cell->ColExtent())]; } @@ -354,7 +354,7 @@ - (NSArray*)moxRowHeaderUIElements { MOZ_ASSERT(mGeckoAccessible); - TableCellAccessibleBase* cell = mGeckoAccessible->AsTableCellBase(); + TableCellAccessible* cell = mGeckoAccessible->AsTableCell(); AutoTArray headerCells; if (cell) { cell->RowHeaderCells(&headerCells); @@ -365,7 +365,7 @@ - (NSArray*)moxColumnHeaderUIElements { MOZ_ASSERT(mGeckoAccessible); - TableCellAccessibleBase* cell = mGeckoAccessible->AsTableCellBase(); + TableCellAccessible* cell = mGeckoAccessible->AsTableCell(); AutoTArray headerCells; if (cell) { cell->ColHeaderCells(&headerCells); diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/e10s/browser_caching_table.js thunderbird-115.4.1+build1/accessible/tests/browser/e10s/browser_caching_table.js --- thunderbird-115.3.1+build1/accessible/tests/browser/e10s/browser_caching_table.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/e10s/browser_caching_table.js 2023-10-24 21:13:27.000000000 +0000 @@ -427,15 +427,11 @@
`, async function (browser, docAcc) { - // XXX We don't create a TableAccessible in this case (bug 1494196). For - // now, just ensure we don't crash (bug 1793073). - const table = findAccessibleChildByID(docAcc, "table"); - let queryOk = false; - try { - table.QueryInterface(nsIAccessibleTable); - queryOk = true; - } catch (e) {} - todo(queryOk, "Got nsIAccessibleTable"); + const table = findAccessibleChildByID(docAcc, "table", [ + nsIAccessibleTable, + ]); + is(table.rowCount, 1, "table rowCount correct"); + is(table.columnCount, 1, "table columnCount correct"); }, { chrome: true, @@ -496,3 +492,15 @@ }, { topLevel: true } ); + +/** + * Verify that we don't crash for authoring error like
. + */ +addAccessibleTask( + `
`, + async function (browser, docAcc) { + const table = findAccessibleChildByID(docAcc, "table"); + ok(table, "Retrieved table Accessible"); + }, + { chrome: true, topLevel: true } +); diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/events/browser_test_panel.js thunderbird-115.4.1+build1/accessible/tests/browser/events/browser_test_panel.js --- thunderbird-115.3.1+build1/accessible/tests/browser/events/browser_test_panel.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/events/browser_test_panel.js 2023-10-24 21:13:27.000000000 +0000 @@ -31,9 +31,13 @@ ok(isAccessible(PopupNotifications.panel), "Popup panel is accessible"); testAccessibleTree(PopupNotifications.panel, { ALERT: [ - { LABEL: [{ TEXT_LEAF: [] }] }, - { PUSHBUTTON: [] }, - { PUSHBUTTON: [] }, + { + TEXT_CONTAINER: [ + { LABEL: [{ TEXT_LEAF: [] }] }, + { PUSHBUTTON: [] }, + { PUSHBUTTON: [] }, + ], + }, ], }); // Verify the popup panel is associated with the chrome window. diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/hittest/browser_test_general.js thunderbird-115.4.1+build1/accessible/tests/browser/hittest/browser_test_general.js --- thunderbird-115.3.1+build1/accessible/tests/browser/hittest/browser_test_general.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/hittest/browser_test_general.js 2023-10-24 21:13:27.000000000 +0000 @@ -232,3 +232,108 @@ iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" }, } ); + +/** + * Verify that hit testing returns the proper accessible when one acc content + * is partially hidden due to overflow:hidden; + */ +addAccessibleTask( + ` + +
+
abcde
fghij
+
`, + async function (browser, docAcc) { + const container = findAccessibleChildByID(docAcc, "container"); + const aNode = findAccessibleChildByID(docAcc, "aNode"); + const fNode = findAccessibleChildByID(docAcc, "fNode"); + const dpr = await getContentDPR(browser); + const [, , containerWidth] = Layout.getBounds(container, dpr); + const [, , aNodeWidth] = Layout.getBounds(aNode, dpr); + + await testChildAtPoint( + dpr, + containerWidth - 1, + 1, + container, + aNode, + aNode.firstChild + ); + await testChildAtPoint( + dpr, + containerWidth - aNodeWidth - 1, + 1, + container, + fNode, + fNode.firstChild + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); + +/** + * Verify that hit testing is appropriately fuzzy when working with generics. + * If we match on a generic which contains additional generics and a single text + * leaf, we should return the text leaf as the deepest match instead of the + * generic itself. + */ +addAccessibleTask( + ` + + I am some invisible text + `, + async function (browser, docAcc) { + const link = findAccessibleChildByID(docAcc, "link"); + const generic = findAccessibleChildByID(docAcc, "generic"); + const invisible = findAccessibleChildByID(docAcc, "invisible"); + const dpr = await getContentDPR(browser); + + await testChildAtPoint( + dpr, + 1, + 1, + link, + generic, // Direct Child + invisible.firstChild // Deepest Child + ); + + await testOffsetAtPoint( + findAccessibleChildByID(docAcc, "invisible", [Ci.nsIAccessibleText]), + 1, + 1, + COORDTYPE_PARENT_RELATIVE, + 0 + ); + }, + { chrome: false, iframe: true, remoteIframe: true } +); + +/** + * Verify that hit testing is appropriately fuzzy when working with generics with siblings. + * We should return the deepest text leaf as the deepest match instead of the generic itself. + */ +addAccessibleTask( + ` +
hello world
Mozilla

I am some other text
`, + async function (browser, docAcc) { + const generic = findAccessibleChildByID(docAcc, "generic"); + const invisible = findAccessibleChildByID(docAcc, "invisible"); + const dpr = await getContentDPR(browser); + + await testChildAtPoint( + dpr, + 1, + 1, + generic, + invisible, // Direct Child + invisible.firstChild // Deepest Child + ); + }, + { chrome: false, iframe: true, remoteIframe: true } +); diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/hittest/head.js thunderbird-115.4.1+build1/accessible/tests/browser/hittest/head.js --- thunderbird-115.3.1+build1/accessible/tests/browser/hittest/head.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/hittest/head.js 2023-10-24 21:13:26.000000000 +0000 @@ -47,6 +47,7 @@ await untilCacheIs( () => { actual = getChildAtPoint(container, x, y, false); + info(`Got direct child match of ${CommonUtils.prettyName(actual)}`); return actual; }, child, @@ -60,6 +61,7 @@ await untilCacheIs( () => { actual = getChildAtPoint(container, x, y, true); + info(`Got deepest child match of ${CommonUtils.prettyName(actual)}`); return actual; }, grandChild, diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/mac/browser_table.js thunderbird-115.4.1+build1/accessible/tests/browser/mac/browser_table.js --- thunderbird-115.3.1+build1/accessible/tests/browser/mac/browser_table.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/mac/browser_table.js 2023-10-24 21:13:27.000000000 +0000 @@ -363,12 +363,12 @@ // after abbr is set we should have a data table again await testIsLayout( table, - "cellOne", + "cellThree", EVENT_OBJECT_ATTRIBUTE_CHANGED, async () => { await SpecialPowers.spawn(browser, [], () => { content.document - .getElementById("cellOne") + .getElementById("cellThree") .setAttribute("abbr", "hello world"); }); }, @@ -379,11 +379,11 @@ // after abbr is removed we should have a layout table again await testIsLayout( table, - "cellOne", + "cellThree", EVENT_OBJECT_ATTRIBUTE_CHANGED, async () => { await SpecialPowers.spawn(browser, [], () => { - content.document.getElementById("cellOne").removeAttribute("abbr"); + content.document.getElementById("cellThree").removeAttribute("abbr"); }); }, true @@ -393,12 +393,12 @@ // after scope is set we should have a data table again await testIsLayout( table, - "cellOne", + "cellThree", EVENT_OBJECT_ATTRIBUTE_CHANGED, async () => { await SpecialPowers.spawn(browser, [], () => { content.document - .getElementById("cellOne") + .getElementById("cellThree") .setAttribute("scope", "col"); }); }, @@ -409,11 +409,11 @@ // remove scope should give layout await testIsLayout( table, - "cellOne", + "cellThree", EVENT_OBJECT_ATTRIBUTE_CHANGED, async () => { await SpecialPowers.spawn(browser, [], () => { - content.document.getElementById("cellOne").removeAttribute("scope"); + content.document.getElementById("cellThree").removeAttribute("scope"); }); }, true diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/tree/browser_general.js thunderbird-115.4.1+build1/accessible/tests/browser/tree/browser_general.js --- thunderbird-115.3.1+build1/accessible/tests/browser/tree/browser_general.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/tree/browser_general.js 2023-10-24 21:13:27.000000000 +0000 @@ -0,0 +1,128 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from ../../mochitest/role.js */ +loadScripts({ name: "role.js", dir: MOCHITESTS_DIR }); + +/** + * Verify adding `overflow:hidden;` styling to a div causes it to + * get an accessible. + */ +addAccessibleTask(`

hello world

`, async function (browser, docAcc) { + const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] }; + + testAccessibleTree(docAcc, originalTree); + info("Adding div element"); + await contentSpawnMutation( + browser, + { unexpected: [[EVENT_REORDER, docAcc]] }, + function () { + const d = content.document.createElement("div"); + content.document.body.appendChild(d); + } + ); + + testAccessibleTree(docAcc, originalTree); + info("Adding overflow:hidden styling to div"); + await contentSpawnMutation( + browser, + { expected: [[EVENT_REORDER, docAcc]] }, + function () { + content.document.body.lastElementChild.setAttribute( + "style", + "overflow:hidden;" + ); + } + ); + + testAccessibleTree(docAcc, { + DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }], + }); +}); + +/** + * Verify adding `overflow:scroll;` styling to a div causes + * it to get an accessible. + */ +addAccessibleTask(`

hello world

`, async function (browser, docAcc) { + const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] }; + + testAccessibleTree(docAcc, originalTree); + info("Adding div element"); + await contentSpawnMutation( + browser, + { unexpected: [[EVENT_REORDER, docAcc]] }, + function () { + const d = content.document.createElement("div"); + content.document.body.appendChild(d); + } + ); + + testAccessibleTree(docAcc, originalTree); + info("Adding overflow:scroll styling to div"); + await contentSpawnMutation( + browser, + { expected: [[EVENT_REORDER, docAcc]] }, + function () { + content.document.body.lastElementChild.setAttribute( + "style", + "overflow:scroll;" + ); + } + ); + + testAccessibleTree(docAcc, { + DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }], + }); +}); + +/** + * Verify adding `overflow:auto;` styling to a div causes + * it to get an accessible, but `overflow:visible` does not. + */ +addAccessibleTask(`

hello world

`, async function (browser, docAcc) { + const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] }; + + testAccessibleTree(docAcc, originalTree); + info("Adding div element"); + await contentSpawnMutation( + browser, + { unexpected: [[EVENT_REORDER, docAcc]] }, + function () { + const d = content.document.createElement("div"); + content.document.body.appendChild(d); + } + ); + + testAccessibleTree(docAcc, originalTree); + info("Adding overflow:visible styling to div"); + await contentSpawnMutation( + browser, + { unexpected: [[EVENT_REORDER, docAcc]] }, + function () { + content.document.body.lastElementChild.setAttribute( + "style", + "overflow:visible;" + ); + } + ); + + testAccessibleTree(docAcc, originalTree); + info("Adding overflow:auto styling to div"); + await contentSpawnMutation( + browser, + { expected: [[EVENT_REORDER, docAcc]] }, + function () { + content.document.body.lastElementChild.setAttribute( + "style", + "overflow:auto;" + ); + } + ); + + testAccessibleTree(docAcc, { + DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }], + }); +}); diff -Nru thunderbird-115.3.1+build1/accessible/tests/browser/tree/browser.ini thunderbird-115.4.1+build1/accessible/tests/browser/tree/browser.ini --- thunderbird-115.3.1+build1/accessible/tests/browser/tree/browser.ini 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/browser/tree/browser.ini 2023-10-24 21:13:26.000000000 +0000 @@ -12,6 +12,7 @@ skip-if = true || (verify && !debug && (os == 'linux')) #Bug 1445513 [browser_browser_element.js] [browser_css_content_visibility.js] +[browser_general.js] [browser_lazy_tabs.js] [browser_searchbar.js] [browser_shadowdom.js] diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/elm/test_shadowroot_subframe.html thunderbird-115.4.1+build1/accessible/tests/mochitest/elm/test_shadowroot_subframe.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/elm/test_shadowroot_subframe.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/elm/test_shadowroot_subframe.html 2023-10-24 21:13:26.000000000 +0000 @@ -61,8 +61,8 @@ var table = document.getElementById("table"); shadow = table.attachShadow({mode: "open"}); - shadow.innerHTML = "
" + - "
hi
" + + shadow.innerHTML = "
" + + "
hi
" + "
"; diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/events/test_attrchange.html thunderbird-115.4.1+build1/accessible/tests/mochitest/events/test_attrchange.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/events/test_attrchange.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/events/test_attrchange.html 2023-10-24 21:13:26.000000000 +0000 @@ -94,7 +94,7 @@
- + diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_ariagrid.html thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_ariagrid.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_ariagrid.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_ariagrid.html 2023-10-24 21:13:26.000000000 +0000 @@ -33,10 +33,6 @@ ]; testTableSelection("table", cellsArray); - testUnselectTableColumn("table", 3, cellsArray); - testUnselectTableRow("table", 3, cellsArray); - testSelectTableColumn("table", 0, cellsArray); - testSelectTableRow("table", 0, cellsArray); // //////////////////////////////////////////////////////////////////////// // a bit strange ARIA grid @@ -47,10 +43,6 @@ ]; testTableSelection("grid2", cellsArray); - testSelectTableColumn("grid2", 0, cellsArray); - testSelectTableRow("grid2", 0, cellsArray); - testUnselectTableColumn("grid2", 0, cellsArray); - testUnselectTableRow("grid2", 0, cellsArray); // //////////////////////////////////////////////////////////////////////// // ARIA grid (column and row headers) @@ -62,10 +54,6 @@ ]; testTableSelection("grid3", cellsArray); - testSelectTableColumn("grid3", 0, cellsArray); - testSelectTableRow("grid3", 0, cellsArray); - testUnselectTableColumn("grid3", 0, cellsArray); - testUnselectTableRow("grid3", 0, cellsArray); SimpleTest.finish(); } diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_table.html thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_table.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_table.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_table.html 2023-10-24 21:13:26.000000000 +0000 @@ -33,24 +33,6 @@ testTableSelection("table", cellsArray); - var rowCount = 4; - for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) - testSelectTableRow("table", rowIdx, cellsArray); - - for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { - testSelectTableRow("table", rowIdx, cellsArray); - testUnselectTableRow("table", rowIdx, cellsArray); - } - - var columsCount = 8; - for (let colIdx = 0; colIdx < columsCount; colIdx++) - testSelectTableColumn("table", colIdx, cellsArray); - - for (let colIdx = 0; colIdx < columsCount; colIdx++) { - testSelectTableColumn("table", colIdx, cellsArray); - testUnselectTableColumn("table", colIdx, cellsArray); - } - var accTable = getAccessible("table", [nsIAccessibleTable]); ok(!accTable.isProbablyForLayout(), "table is not for layout"); @@ -88,11 +70,6 @@ Mozilla Bug 501635 - Mozilla Bug 417929 - - Mozilla Bug 501659 diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_tree.xhtml thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_tree.xhtml --- thunderbird-115.3.1+build1/accessible/tests/mochitest/table/test_sels_tree.xhtml 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/table/test_sels_tree.xhtml 2023-10-24 21:13:26.000000000 +0000 @@ -39,8 +39,6 @@ ]; testTableSelection("tree", cellsArray); - testSelectTableRow("tree", 0, cellsArray); - testUnselectTableRow("tree", 0, cellsArray); SimpleTest.finish(); } diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/table.js thunderbird-115.4.1+build1/accessible/tests/mochitest/table.js --- thunderbird-115.3.1+build1/accessible/tests/mochitest/table.js 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/table.js 2023-10-24 21:13:26.000000000 +0000 @@ -745,189 +745,6 @@ } /** - * Test unselectColumn method of accessible table. - */ -function testUnselectTableColumn(aIdentifier, aColIdx, aCellsArray) { - var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); - if (!acc) { - return; - } - - var rowCount = aCellsArray.length; - for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { - // Unselect origin cell. - var [origRowIdx, origColIdx] = getOrigRowAndColumn( - aCellsArray, - rowIdx, - aColIdx - ); - aCellsArray[origRowIdx][origColIdx] = false; - } - - acc.unselectColumn(aColIdx); - testTableSelection( - aIdentifier, - aCellsArray, - "Unselect " + aColIdx + " column: " - ); -} - -/** - * Test selectColumn method of accessible table. - */ -function testSelectTableColumn(aIdentifier, aColIdx, aCellsArray) { - var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); - if (!acc) { - return; - } - - var rowCount = aCellsArray.length; - var colsCount = aCellsArray[0].length; - - for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { - for (var colIdx = 0; colIdx < colsCount; colIdx++) { - var cellState = aCellsArray[rowIdx][colIdx]; - - if (colIdx == aColIdx) { - // select target column - if (!(cellState & kSpanned)) { - // Select the cell if it is origin. - aCellsArray[rowIdx][colIdx] = true; - } else { - // If the cell is spanned then search origin cell and select it. - var [origRowIdx, origColIdx] = getOrigRowAndColumn( - aCellsArray, - rowIdx, - colIdx - ); - aCellsArray[origRowIdx][origColIdx] = true; - } - } else if (!(cellState & kSpanned)) { - // unselect other columns - if (colIdx > aColIdx) { - // Unselect the cell if traversed column index is greater than column - // index of target cell. - aCellsArray[rowIdx][colIdx] = false; - } else if (!(aCellsArray[rowIdx][aColIdx] & kColSpanned)) { - // Unselect the cell if the target cell is not row spanned. - aCellsArray[rowIdx][colIdx] = false; - } else { - // Unselect the cell if it is not spanned to the target cell. - for ( - var spannedColIdx = colIdx + 1; - spannedColIdx < aColIdx; - spannedColIdx++ - ) { - var spannedCellState = aCellsArray[rowIdx][spannedColIdx]; - if (!(spannedCellState & kRowSpanned)) { - aCellsArray[rowIdx][colIdx] = false; - break; - } - } - } - } - } - } - - acc.selectColumn(aColIdx); - testTableSelection( - aIdentifier, - aCellsArray, - "Select " + aColIdx + " column: " - ); -} - -/** - * Test unselectRow method of accessible table. - */ -function testUnselectTableRow(aIdentifier, aRowIdx, aCellsArray) { - var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); - if (!acc) { - return; - } - - var colsCount = aCellsArray[0].length; - for (var colIdx = 0; colIdx < colsCount; colIdx++) { - // Unselect origin cell. - var [origRowIdx, origColIdx] = getOrigRowAndColumn( - aCellsArray, - aRowIdx, - colIdx - ); - aCellsArray[origRowIdx][origColIdx] = false; - } - - acc.unselectRow(aRowIdx); - testTableSelection( - aIdentifier, - aCellsArray, - "Unselect " + aRowIdx + " row: " - ); -} - -/** - * Test selectRow method of accessible table. - */ -function testSelectTableRow(aIdentifier, aRowIdx, aCellsArray) { - var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); - if (!acc) { - return; - } - - var rowCount = aCellsArray.length; - var colsCount = aCellsArray[0].length; - - for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { - for (var colIdx = 0; colIdx < colsCount; colIdx++) { - var cellState = aCellsArray[rowIdx][colIdx]; - - if (rowIdx == aRowIdx) { - // select the given row - if (!(cellState & kSpanned)) { - // Select the cell if it is origin. - aCellsArray[rowIdx][colIdx] = true; - } else { - // If the cell is spanned then search origin cell and select it. - var [origRowIdx, origColIdx] = getOrigRowAndColumn( - aCellsArray, - rowIdx, - colIdx - ); - - aCellsArray[origRowIdx][origColIdx] = true; - } - } else if (!(cellState & kSpanned)) { - // unselect other rows - if (rowIdx > aRowIdx) { - // Unselect the cell if traversed row index is greater than row - // index of target cell. - aCellsArray[rowIdx][colIdx] = false; - } else if (!(aCellsArray[aRowIdx][colIdx] & kRowSpanned)) { - // Unselect the cell if the target cell is not row spanned. - aCellsArray[rowIdx][colIdx] = false; - } else { - // Unselect the cell if it is not spanned to the target cell. - for ( - var spannedRowIdx = rowIdx + 1; - spannedRowIdx < aRowIdx; - spannedRowIdx++ - ) { - var spannedCellState = aCellsArray[spannedRowIdx][colIdx]; - if (!(spannedCellState & kRowSpanned)) { - aCellsArray[rowIdx][colIdx] = false; - break; - } - } - } - } - } - } - - acc.selectRow(aRowIdx); - testTableSelection(aIdentifier, aCellsArray, "Select " + aRowIdx + " row: "); -} - -/** * Test columnHeaderCells and rowHeaderCells of accessible table. */ function testHeaderCells(aHeaderInfoMap) { diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table_2.html thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table_2.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table_2.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table_2.html 2023-10-24 21:13:27.000000000 +0000 @@ -150,14 +150,16 @@ name: "Top 10 Grossing Animated Films of All Time", }, ] }, - { ROW: [ - { role: COLHEADER, name: "Film Title" }, - { role: COLHEADER, name: "Released" }, - { role: COLHEADER, name: "Studio" }, - { role: COLHEADER, name: "Worldwide Gross" }, - { role: COLHEADER, name: "Domestic Gross" }, - { role: COLHEADER, name: "Foreign Gross" }, - { role: COLHEADER, name: "Budget" }, + { TEXT_CONTAINER: [ + { ROW: [ + { role: COLHEADER, name: "Film Title" }, + { role: COLHEADER, name: "Released" }, + { role: COLHEADER, name: "Studio" }, + { role: COLHEADER, name: "Worldwide Gross" }, + { role: COLHEADER, name: "Domestic Gross" }, + { role: COLHEADER, name: "Foreign Gross" }, + { role: COLHEADER, name: "Budget" }, + ] }, ] }, { ROW: [ { role: CELL }, diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table_3.html thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table_3.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table_3.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table_3.html 2023-10-24 21:13:27.000000000 +0000 @@ -116,14 +116,16 @@ name: "Top 10 Grossing Animated Films of All Time", }, ] }, - { ROW: [ - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Film Title" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Released" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Studio" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Worldwide Gross" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Domestic Gross" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Foreign Gross" } ] }, - { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Budget" } ] }, + { TEXT_CONTAINER: [ + { ROW: [ + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Film Title" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Released" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Studio" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Worldwide Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Domestic Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Foreign Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Budget" } ] }, + ] }, ] }, { ROW: [ { role: CELL }, diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table.html thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/tree/test_table.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/tree/test_table.html 2023-10-24 21:13:27.000000000 +0000 @@ -422,7 +422,7 @@
cell1 cell2
- + @@ -482,8 +482,8 @@
bla
- - +
+ diff -Nru thunderbird-115.3.1+build1/accessible/tests/mochitest/treeupdate/test_cssoverflow.html thunderbird-115.4.1+build1/accessible/tests/mochitest/treeupdate/test_cssoverflow.html --- thunderbird-115.3.1+build1/accessible/tests/mochitest/treeupdate/test_cssoverflow.html 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/tests/mochitest/treeupdate/test_cssoverflow.html 2023-10-24 21:13:27.000000000 +0000 @@ -61,7 +61,7 @@ } /** - * Change scrollbar styles from hidden to auto to make the scroll area focusable. + * Change scrollbar styles from visible to auto to make the scroll area focusable. * That causes us to create an accessible for it. * Make sure the tree stays intact. * The scroll area has no ID on purpose to make it inaccessible initially. @@ -120,7 +120,7 @@ gQueue = new eventQueue(); gQueue.push(new changeScrollRange("container", "scrollarea")); - gQueue.push(new makeFocusableByScrollbarStyles("container3")); + gQueue.push(new makeFocusableByScrollbarStyles("container2")); gQueue.invoke(); // Will call SimpleTest.finish(); } @@ -144,7 +144,6 @@
-
-

foo

+

foo

diff -Nru thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.cpp thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.cpp --- thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -12,16 +12,16 @@ #include "IUnknownImpl.h" #include "mozilla/a11y/Accessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsCOMPtr.h" #include "nsString.h" using namespace mozilla::a11y; -TableCellAccessibleBase* ia2AccessibleTableCell::CellAcc() { +TableCellAccessible* ia2AccessibleTableCell::CellAcc() { Accessible* acc = Acc(); - return acc ? acc->AsTableCellBase() : nullptr; + return acc ? acc->AsTableCell() : nullptr; } // IUnknown @@ -37,10 +37,10 @@ if (!aTable) return E_INVALIDARG; *aTable = nullptr; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; - TableAccessibleBase* table = tableCell->Table(); + TableAccessible* table = tableCell->Table(); if (!table) return E_FAIL; Accessible* tableAcc = table->AsAccessible(); @@ -54,7 +54,7 @@ if (!aSpan) return E_INVALIDARG; *aSpan = 0; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aSpan = tableCell->ColExtent(); @@ -69,7 +69,7 @@ *aCellAccessibles = nullptr; *aNColumnHeaderCells = 0; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; AutoTArray cells; @@ -94,7 +94,7 @@ if (!aColIdx) return E_INVALIDARG; *aColIdx = -1; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aColIdx = tableCell->ColIdx(); @@ -106,7 +106,7 @@ if (!aSpan) return E_INVALIDARG; *aSpan = 0; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aSpan = tableCell->RowExtent(); @@ -120,7 +120,7 @@ *aCellAccessibles = nullptr; *aNRowHeaderCells = 0; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; AutoTArray cells; @@ -144,7 +144,7 @@ if (!aRowIdx) return E_INVALIDARG; *aRowIdx = -1; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aRowIdx = tableCell->RowIdx(); @@ -161,7 +161,7 @@ *aRowIdx = *aColIdx = *aRowExtents = *aColExtents = 0; *aIsSelected = false; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aRowIdx = tableCell->RowIdx(); @@ -178,7 +178,7 @@ if (!aIsSelected) return E_INVALIDARG; *aIsSelected = false; - TableCellAccessibleBase* tableCell = CellAcc(); + TableCellAccessible* tableCell = CellAcc(); if (!tableCell) return CO_E_OBJNOTCONNECTED; *aIsSelected = tableCell->Selected(); diff -Nru thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.h thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.h --- thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTableCell.h 2023-10-24 21:13:26.000000000 +0000 @@ -14,7 +14,7 @@ namespace mozilla { namespace a11y { -class TableCellAccessibleBase; +class TableCellAccessible; class ia2AccessibleTableCell : public IAccessibleTableCell, public ia2AccessibleHypertext { @@ -62,7 +62,7 @@ using ia2AccessibleHypertext::ia2AccessibleHypertext; private: - TableCellAccessibleBase* CellAcc(); + TableCellAccessible* CellAcc(); }; } // namespace a11y diff -Nru thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTable.cpp thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTable.cpp --- thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTable.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTable.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -13,16 +13,16 @@ #include "IUnknownImpl.h" #include "mozilla/a11y/Accessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" #include "nsCOMPtr.h" #include "nsString.h" #include "Statistics.h" using namespace mozilla::a11y; -TableAccessibleBase* ia2AccessibleTable::TableAcc() { +TableAccessible* ia2AccessibleTable::TableAcc() { Accessible* acc = Acc(); - return acc ? acc->AsTableBase() : nullptr; + return acc ? acc->AsTable() : nullptr; } // IUnknown @@ -63,7 +63,7 @@ if (!aAccessible) return E_INVALIDARG; *aAccessible = nullptr; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; Accessible* caption = table->Caption(); @@ -80,7 +80,7 @@ if (!aChildIdx) return E_INVALIDARG; *aChildIdx = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || aColIdx < 0 || @@ -97,7 +97,7 @@ if (!aDescription) return E_INVALIDARG; *aDescription = nullptr; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aColIdx < 0 || static_cast(aColIdx) >= table->ColCount()) @@ -117,7 +117,7 @@ if (!aSpan) return E_INVALIDARG; *aSpan = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || aColIdx < 0 || @@ -144,7 +144,7 @@ if (!aColIdx) return E_INVALIDARG; *aColIdx = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aCellIdx < 0) { @@ -165,7 +165,7 @@ if (!aColCount) return E_INVALIDARG; *aColCount = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; *aColCount = table->ColCount(); @@ -177,7 +177,7 @@ if (!aRowCount) return E_INVALIDARG; *aRowCount = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; *aRowCount = table->RowCount(); @@ -194,7 +194,7 @@ if (!aColCount) return E_INVALIDARG; *aColCount = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; *aColCount = table->SelectedColCount(); @@ -206,7 +206,7 @@ if (!aRowCount) return E_INVALIDARG; *aRowCount = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; *aRowCount = table->SelectedRowCount(); @@ -219,7 +219,7 @@ if (!aDescription) return E_INVALIDARG; *aDescription = nullptr; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || static_cast(aRowIdx) >= table->RowCount()) @@ -238,7 +238,7 @@ if (!aSpan) return E_INVALIDARG; *aSpan = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || aColIdx < 0 || @@ -265,7 +265,7 @@ if (!aRowIdx) return E_INVALIDARG; *aRowIdx = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aCellIdx < 0) { @@ -288,7 +288,7 @@ *aChildren = nullptr; *aNChildren = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; AutoTArray cellIndices; @@ -334,7 +334,7 @@ if (!aIsSelected) return E_INVALIDARG; *aIsSelected = false; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aColIdx < 0 || static_cast(aColIdx) >= table->ColCount()) @@ -349,7 +349,7 @@ if (!aIsSelected) return E_INVALIDARG; *aIsSelected = false; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || static_cast(aRowIdx) >= table->RowCount()) @@ -365,7 +365,7 @@ if (!aIsSelected) return E_INVALIDARG; *aIsSelected = false; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aRowIdx < 0 || aColIdx < 0 || @@ -378,52 +378,16 @@ } STDMETHODIMP -ia2AccessibleTable::selectRow(long aRowIdx) { - TableAccessibleBase* table = TableAcc(); - if (!table) return CO_E_OBJNOTCONNECTED; - - if (aRowIdx < 0 || static_cast(aRowIdx) >= table->RowCount()) - return E_INVALIDARG; - - table->SelectRow(aRowIdx); - return S_OK; -} +ia2AccessibleTable::selectRow(long aRowIdx) { return E_NOTIMPL; } STDMETHODIMP -ia2AccessibleTable::selectColumn(long aColIdx) { - TableAccessibleBase* table = TableAcc(); - if (!table) return CO_E_OBJNOTCONNECTED; - - if (aColIdx < 0 || static_cast(aColIdx) >= table->ColCount()) - return E_INVALIDARG; - - table->SelectCol(aColIdx); - return S_OK; -} +ia2AccessibleTable::selectColumn(long aColIdx) { return E_NOTIMPL; } STDMETHODIMP -ia2AccessibleTable::unselectRow(long aRowIdx) { - TableAccessibleBase* table = TableAcc(); - if (!table) return CO_E_OBJNOTCONNECTED; - - if (aRowIdx < 0 || static_cast(aRowIdx) >= table->RowCount()) - return E_INVALIDARG; - - table->UnselectRow(aRowIdx); - return S_OK; -} +ia2AccessibleTable::unselectRow(long aRowIdx) { return E_NOTIMPL; } STDMETHODIMP -ia2AccessibleTable::unselectColumn(long aColIdx) { - TableAccessibleBase* table = TableAcc(); - if (!table) return CO_E_OBJNOTCONNECTED; - - if (aColIdx < 0 || static_cast(aColIdx) >= table->ColCount()) - return E_INVALIDARG; - - table->UnselectCol(aColIdx); - return S_OK; -} +ia2AccessibleTable::unselectColumn(long aColIdx) { return E_NOTIMPL; } STDMETHODIMP ia2AccessibleTable::get_rowColumnExtentsAtIndex(long aCellIdx, long* aRowIdx, @@ -439,7 +403,7 @@ *aRowExtents = 0; *aColExtents = 0; *aIsSelected = false; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; if (aCellIdx < 0) { @@ -475,7 +439,7 @@ *aCell = nullptr; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; Accessible* cell = table->CellAt(aRowIdx, aColIdx); @@ -491,7 +455,7 @@ if (!aCellCount) return E_INVALIDARG; *aCellCount = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; *aCellCount = table->SelectedCellCount(); @@ -505,7 +469,7 @@ *aCells = nullptr; *aNSelectedCells = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; AutoTArray cells; @@ -531,7 +495,7 @@ *aColumns = nullptr; *aNColumns = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; AutoTArray colIndices; @@ -553,7 +517,7 @@ *aRows = nullptr; *aNRows = 0; - TableAccessibleBase* table = TableAcc(); + TableAccessible* table = TableAcc(); if (!table) return CO_E_OBJNOTCONNECTED; AutoTArray rowIndices; diff -Nru thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTable.h thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTable.h --- thunderbird-115.3.1+build1/accessible/windows/ia2/ia2AccessibleTable.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/windows/ia2/ia2AccessibleTable.h 2023-10-24 21:13:26.000000000 +0000 @@ -16,7 +16,7 @@ namespace mozilla { namespace a11y { -class TableAccessibleBase; +class TableAccessible; class ia2AccessibleTable : public IAccessibleTable, public IAccessibleTable2, @@ -169,7 +169,7 @@ using ia2AccessibleHypertext::ia2AccessibleHypertext; private: - TableAccessibleBase* TableAcc(); + TableAccessible* TableAcc(); }; } // namespace a11y diff -Nru thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTableCell.cpp thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTableCell.cpp --- thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTableCell.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTableCell.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -7,8 +7,8 @@ #include "xpcAccessibleTableCell.h" #include "mozilla/a11y/Accessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" -#include "mozilla/a11y/TableCellAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "nsIAccessibleTable.h" #include "nsComponentManagerUtils.h" @@ -34,7 +34,7 @@ if (!Intl()) return NS_ERROR_FAILURE; - TableAccessibleBase* table = Intl()->Table(); + TableAccessible* table = Intl()->Table(); if (!table) return NS_ERROR_FAILURE; nsCOMPtr xpcTable = do_QueryInterface( diff -Nru thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTableCell.h thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTableCell.h --- thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTableCell.h 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTableCell.h 2023-10-24 21:13:26.000000000 +0000 @@ -13,7 +13,7 @@ namespace mozilla { namespace a11y { -class TableCellAccessibleBase; +class TableCellAccessible; /** * XPCOM wrapper around TableAccessibleCell class. @@ -40,7 +40,7 @@ virtual ~xpcAccessibleTableCell() {} private: - TableCellAccessibleBase* Intl() { return mIntl->AsTableCellBase(); } + TableCellAccessible* Intl() { return mIntl->AsTableCell(); } xpcAccessibleTableCell(const xpcAccessibleTableCell&) = delete; xpcAccessibleTableCell& operator=(const xpcAccessibleTableCell&) = delete; diff -Nru thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTable.cpp thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTable.cpp --- thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTable.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTable.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -7,7 +7,7 @@ #include "xpcAccessibleTable.h" #include "mozilla/a11y/Accessible.h" -#include "mozilla/a11y/TableAccessibleBase.h" +#include "mozilla/a11y/TableAccessible.h" #include "nsIMutableArray.h" #include "nsComponentManagerUtils.h" @@ -361,51 +361,3 @@ *aResult = Intl()->IsProbablyLayoutTable(); return NS_OK; } - -NS_IMETHODIMP -xpcAccessibleTable::SelectColumn(int32_t aColIdx) { - if (!Intl()) return NS_ERROR_FAILURE; - - if (aColIdx < 0 || static_cast(aColIdx) >= Intl()->ColCount()) { - return NS_ERROR_INVALID_ARG; - } - - Intl()->SelectCol(aColIdx); - return NS_OK; -} - -NS_IMETHODIMP -xpcAccessibleTable::SelectRow(int32_t aRowIdx) { - if (!Intl()) return NS_ERROR_FAILURE; - - if (aRowIdx < 0 || static_cast(aRowIdx) >= Intl()->RowCount()) { - return NS_ERROR_INVALID_ARG; - } - - Intl()->SelectRow(aRowIdx); - return NS_OK; -} - -NS_IMETHODIMP -xpcAccessibleTable::UnselectColumn(int32_t aColIdx) { - if (!Intl()) return NS_ERROR_FAILURE; - - if (aColIdx < 0 || static_cast(aColIdx) >= Intl()->ColCount()) { - return NS_ERROR_INVALID_ARG; - } - - Intl()->UnselectCol(aColIdx); - return NS_OK; -} - -NS_IMETHODIMP -xpcAccessibleTable::UnselectRow(int32_t aRowIdx) { - if (!Intl()) return NS_ERROR_FAILURE; - - if (aRowIdx < 0 || static_cast(aRowIdx) >= Intl()->RowCount()) { - return NS_ERROR_INVALID_ARG; - } - - Intl()->UnselectRow(aRowIdx); - return NS_OK; -} diff -Nru thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTable.h thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTable.h --- thunderbird-115.3.1+build1/accessible/xpcom/xpcAccessibleTable.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xpcom/xpcAccessibleTable.h 2023-10-24 21:13:26.000000000 +0000 @@ -12,7 +12,7 @@ namespace mozilla { namespace a11y { -class TableAccessibleBase; +class TableAccessible; /** * XPCOM wrapper around TableAccessible class. @@ -56,17 +56,13 @@ NS_IMETHOD GetSelectedCellIndices(nsTArray& aCellsArray) final; NS_IMETHOD GetSelectedColumnIndices(nsTArray& aColsArray) final; NS_IMETHOD GetSelectedRowIndices(nsTArray& aRowsArray) final; - NS_IMETHOD SelectColumn(int32_t aColIdx) final; - NS_IMETHOD SelectRow(int32_t aRowIdx) final; - NS_IMETHOD UnselectColumn(int32_t aColIdx) final; - NS_IMETHOD UnselectRow(int32_t aRowIdx) final; NS_IMETHOD IsProbablyForLayout(bool* aIsForLayout) final; protected: virtual ~xpcAccessibleTable() {} private: - TableAccessibleBase* Intl() { return mIntl->AsTableBase(); } + TableAccessible* Intl() { return mIntl->AsTable(); } xpcAccessibleTable(const xpcAccessibleTable&) = delete; xpcAccessibleTable& operator=(const xpcAccessibleTable&) = delete; diff -Nru thunderbird-115.3.1+build1/accessible/xul/XULListboxAccessible.cpp thunderbird-115.4.1+build1/accessible/xul/XULListboxAccessible.cpp --- thunderbird-115.3.1+build1/accessible/xul/XULListboxAccessible.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xul/XULListboxAccessible.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -309,40 +309,6 @@ } } -void XULListboxAccessible::SelectRow(uint32_t aRowIdx) { - nsCOMPtr control = - Elm()->AsXULMultiSelectControl(); - NS_ASSERTION(control, - "Doesn't implement nsIDOMXULMultiSelectControlElement."); - - RefPtr item; - control->GetItemAtIndex(aRowIdx, getter_AddRefs(item)); - if (!item) { - return; - } - - nsCOMPtr itemElm = - item->AsXULSelectControlItem(); - control->SelectItem(itemElm); -} - -void XULListboxAccessible::UnselectRow(uint32_t aRowIdx) { - nsCOMPtr control = - Elm()->AsXULMultiSelectControl(); - NS_ASSERTION(control, - "Doesn't implement nsIDOMXULMultiSelectControlElement."); - - RefPtr item; - control->GetItemAtIndex(aRowIdx, getter_AddRefs(item)); - if (!item) { - return; - } - - nsCOMPtr itemElm = - item->AsXULSelectControlItem(); - control->RemoveItemFromSelection(itemElm); -} - //////////////////////////////////////////////////////////////////////////////// // XULListboxAccessible: Widgets diff -Nru thunderbird-115.3.1+build1/accessible/xul/XULListboxAccessible.h thunderbird-115.4.1+build1/accessible/xul/XULListboxAccessible.h --- thunderbird-115.3.1+build1/accessible/xul/XULListboxAccessible.h 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xul/XULListboxAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -7,8 +7,7 @@ #define mozilla_a11y_XULListboxAccessible_h__ #include "BaseAccessibles.h" -#include "TableAccessible.h" -#include "TableCellAccessible.h" +#include "mozilla/a11y/TableAccessible.h" #include "XULMenuAccessible.h" #include "XULSelectControlAccessible.h" @@ -70,8 +69,6 @@ virtual void SelectedCellIndices(nsTArray* aCells) override; virtual void SelectedColIndices(nsTArray* aCols) override; virtual void SelectedRowIndices(nsTArray* aRows) override; - virtual void SelectRow(uint32_t aRowIdx) override; - virtual void UnselectRow(uint32_t aRowIdx) override; virtual LocalAccessible* AsAccessible() override { return this; } // LocalAccessible diff -Nru thunderbird-115.3.1+build1/accessible/xul/XULTreeGridAccessible.cpp thunderbird-115.4.1+build1/accessible/xul/XULTreeGridAccessible.cpp --- thunderbird-115.3.1+build1/accessible/xul/XULTreeGridAccessible.cpp 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xul/XULTreeGridAccessible.cpp 2023-10-24 21:13:26.000000000 +0000 @@ -5,6 +5,7 @@ #include "XULTreeGridAccessible.h" +#include #include "AccAttributes.h" #include "LocalAccessible-inl.h" #include "nsAccCache.h" @@ -21,6 +22,7 @@ #include "nsITreeSelection.h" #include "nsComponentManagerUtils.h" #include "mozilla/PresShell.h" +#include "mozilla/a11y/TableAccessible.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TreeColumnBinding.h" #include "mozilla/dom/XULTreeElementBinding.h" @@ -149,23 +151,36 @@ return IsRowSelected(aRowIdx); } -void XULTreeGridAccessible::SelectRow(uint32_t aRowIdx) { - if (!mTreeView) return; - - nsCOMPtr selection; - mTreeView->GetSelection(getter_AddRefs(selection)); - NS_ASSERTION(selection, "GetSelection() Shouldn't fail!"); +int32_t XULTreeGridAccessible::ColIndexAt(uint32_t aCellIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + return -1; // Error: column count is 0 or index out of bounds. + } - selection->Select(aRowIdx); + return static_cast(aCellIdx % colCount); } -void XULTreeGridAccessible::UnselectRow(uint32_t aRowIdx) { - if (!mTreeView) return; +int32_t XULTreeGridAccessible::RowIndexAt(uint32_t aCellIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + return -1; // Error: column count is 0 or index out of bounds. + } - nsCOMPtr selection; - mTreeView->GetSelection(getter_AddRefs(selection)); + return static_cast(aCellIdx / colCount); +} + +void XULTreeGridAccessible::RowAndColIndicesAt(uint32_t aCellIdx, + int32_t* aRowIdx, + int32_t* aColIdx) { + uint32_t colCount = ColCount(); + if (colCount < 1 || aCellIdx >= colCount * RowCount()) { + *aRowIdx = -1; + *aColIdx = -1; + return; // Error: column count is 0 or index out of bounds. + } - if (selection) selection->ClearRange(aRowIdx, aRowIdx); + *aRowIdx = static_cast(aCellIdx / colCount); + *aColIdx = static_cast(aCellIdx % colCount); } //////////////////////////////////////////////////////////////////////////////// diff -Nru thunderbird-115.3.1+build1/accessible/xul/XULTreeGridAccessible.h thunderbird-115.4.1+build1/accessible/xul/XULTreeGridAccessible.h --- thunderbird-115.3.1+build1/accessible/xul/XULTreeGridAccessible.h 2023-09-29 10:31:51.000000000 +0000 +++ thunderbird-115.4.1+build1/accessible/xul/XULTreeGridAccessible.h 2023-10-24 21:13:27.000000000 +0000 @@ -7,9 +7,9 @@ #ifndef mozilla_a11y_XULTreeGridAccessible_h__ #define mozilla_a11y_XULTreeGridAccessible_h__ +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" #include "XULTreeAccessible.h" -#include "TableAccessible.h" -#include "TableCellAccessible.h" namespace mozilla { namespace a11y { @@ -44,10 +44,17 @@ virtual void SelectedCellIndices(nsTArray* aCells) override; virtual void SelectedColIndices(nsTArray* aCols) override; virtual void SelectedRowIndices(nsTArray* aRows) override; - virtual void SelectRow(uint32_t aRowIdx) override; - virtual void UnselectRow(uint32_t aRowIdx) override; virtual LocalAccessible* AsAccessible() override { return this; } + virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override { + return static_cast(ColCount() * aRowIdx + aColIdx); + } + + virtual int32_t ColIndexAt(uint32_t aCellIdx) override; + virtual int32_t RowIndexAt(uint32_t aCellIdx) override; + virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) override; + // LocalAccessible virtual TableAccessible* AsTable() override { return this; } virtual a11y::role NativeRole() const override; diff -Nru thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/DllBlocklist.cpp thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/DllBlocklist.cpp --- thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/DllBlocklist.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/DllBlocklist.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -465,6 +465,7 @@ if (::RtlCompareUnicodeString(&k32Name, &leafOnStack, TRUE) == 0) { blockAction = BlockAction::Allow; } else { + auto noSharedSectionReset{SharedSection::AutoNoReset()}; k32Exports = gSharedSection.GetKernel32Exports(); // Small optimization: Since loading a dependent module does not involve // LdrLoadDll, we know isInjectedDependent is false if we hold a top frame. diff -Nru thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/SharedSection.cpp thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/SharedSection.cpp --- thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/SharedSection.cpp 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/SharedSection.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -113,8 +113,10 @@ HANDLE SharedSection::sSectionHandle = nullptr; SharedSection::Layout* SharedSection::sWriteCopyView = nullptr; RTL_RUN_ONCE SharedSection::sEnsureOnce = RTL_RUN_ONCE_INIT; +nt::SRWLock SharedSection::sLock; void SharedSection::Reset(HANDLE aNewSectionObject) { + nt::AutoExclusiveLock{sLock}; if (sWriteCopyView) { nt::AutoMappedView view(sWriteCopyView); sWriteCopyView = nullptr; diff -Nru thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/SharedSection.h thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/SharedSection.h --- thunderbird-115.3.1+build1/browser/app/winlauncher/freestanding/SharedSection.h 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/app/winlauncher/freestanding/SharedSection.h 2023-10-24 21:13:27.000000000 +0000 @@ -152,6 +152,15 @@ static Layout* sWriteCopyView; static RTL_RUN_ONCE sEnsureOnce; + // The sLock lock guarantees that while it is held, sSectionHandle will not + // change nor get closed, sEnsureOnce will not get reinitialized, and + // sWriteCopyView will not change nor get unmapped once initialized. We take + // sLock on paths that could run concurrently with ConvertToReadOnly(). This + // method is only called on the main process, and very early, so the only + // real risk here should be threads started by third-party products reaching + // our patched_NtMapViewOfSection (see bug 1850969). + static nt::SRWLock sLock; + static ULONG NTAPI EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID, PVOID*); static Layout* EnsureWriteCopyView(bool requireKernel32Exports = false); @@ -164,6 +173,10 @@ // Replace |sSectionHandle| with a given handle. static void Reset(HANDLE aNewSectionObject = sSectionHandle); + static inline nt::AutoSharedLock AutoNoReset() { + return nt::AutoSharedLock{sLock}; + } + // Replace |sSectionHandle| with a new readonly handle. static void ConvertToReadOnly(); diff -Nru thunderbird-115.3.1+build1/browser/base/content/tabbrowser.js thunderbird-115.4.1+build1/browser/base/content/tabbrowser.js --- thunderbird-115.3.1+build1/browser/base/content/tabbrowser.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/base/content/tabbrowser.js 2023-10-24 21:13:27.000000000 +0000 @@ -759,7 +759,10 @@ }, _notifyPinnedStatus(aTab) { - aTab.linkedBrowser.browsingContext.isAppTab = aTab.pinned; + // browsingContext is expected to not be defined on discarded tabs. + if (aTab.linkedBrowser.browsingContext) { + aTab.linkedBrowser.browsingContext.isAppTab = aTab.pinned; + } let event = document.createEvent("Events"); event.initEvent(aTab.pinned ? "TabPinned" : "TabUnpinned", true, false); diff -Nru thunderbird-115.3.1+build1/browser/base/content/test/tabs/browser_pinnedTabs.js thunderbird-115.4.1+build1/browser/base/content/test/tabs/browser_pinnedTabs.js --- thunderbird-115.3.1+build1/browser/base/content/test/tabs/browser_pinnedTabs.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/base/content/test/tabs/browser_pinnedTabs.js 2023-10-24 21:13:27.000000000 +0000 @@ -47,6 +47,10 @@ indexTest(2, 2); indexTest(3, 3); + // Discard one of the test tabs to verify that pinning/unpinning + // discarded tabs does not regress (regression test for Bug 1852391). + gBrowser.discardBrowser(tabs[1], true); + var eh = new PinUnpinHandler(tabs[3], "TabPinned"); gBrowser.pinTab(tabs[3]); is(eh.eventCount, 2, "TabPinned event should be fired"); diff -Nru thunderbird-115.3.1+build1/browser/components/extensions/parent/ext-windows.js thunderbird-115.4.1+build1/browser/components/extensions/parent/ext-windows.js --- thunderbird-115.3.1+build1/browser/components/extensions/parent/ext-windows.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/extensions/parent/ext-windows.js 2023-10-24 21:13:27.000000000 +0000 @@ -150,26 +150,6 @@ const { windowManager } = extension; - function getTriggeringPrincipalForUrl(url) { - if (context.checkLoadURL(url, { dontReportErrors: true })) { - return context.principal; - } - let window = context.currentWindow || windowTracker.topWindow; - // The extension principal cannot directly load about:-URLs except for about:blank, and - // possibly some other loads such as moz-extension. Ensure any page set as a home page - // will load by using a content principal. - return Services.scriptSecurityManager.createContentPrincipal( - Services.io.newURI(url), - { - privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate( - window.gBrowser - ) - ? 1 - : 0, - } - ); - } - return { windows: { onCreated: new EventManager({ @@ -265,11 +245,23 @@ Ci.nsIMutableArray ); + // Whether there is only one URL to load, and it is a moz-extension:-URL. + let isOnlyMozExtensionUrl = false; + // Creating a new window allows one single triggering principal for all tabs that // are created in the window. Due to that, if we need a browser principal to load // some urls, we fallback to using a content principal like we do in the tabs api. // Throws if url is an array and any url can't be loaded by the extension principal. - let { allowScriptsToClose, principal } = createData; + let principal = context.principal; + function setContentTriggeringPrincipal(url) { + principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(url), + { + // Note: privateBrowsingAllowed was already checked before. + privateBrowsingId: createData.incognito ? 1 : 0, + } + ); + } if (createData.tabId !== null) { if (createData.url !== null) { @@ -330,12 +322,22 @@ array.appendElement(mkstr(url)); } args.appendElement(array); + // TODO bug 1780583: support multiple triggeringPrincipals to + // avoid having to use the system principal here. + principal = Services.scriptSecurityManager.getSystemPrincipal(); } else { let url = context.uri.resolve(createData.url); args.appendElement(mkstr(url)); - principal = getTriggeringPrincipalForUrl(url); - if (allowScriptsToClose === null) { - allowScriptsToClose = url.startsWith("moz-extension://"); + isOnlyMozExtensionUrl = url.startsWith("moz-extension://"); + if (!context.checkLoadURL(url, { dontReportErrors: true })) { + if (isOnlyMozExtensionUrl) { + // For backwards-compatibility (also in tabs APIs), we allow + // extensions to open other moz-extension:-URLs even if that + // other resource is not listed in web_accessible_resources. + setContentTriggeringPrincipal(url); + } else { + throw new ExtensionError(`Illegal URL: ${url}`); + } } } } else { @@ -345,7 +347,15 @@ ? "about:privatebrowsing" : HomePage.get().split("|", 1)[0]; args.appendElement(mkstr(url)); - principal = getTriggeringPrincipalForUrl(url); + isOnlyMozExtensionUrl = url.startsWith("moz-extension://"); + + if (!context.checkLoadURL(url, { dontReportErrors: true })) { + // The extension principal cannot directly load about:-URLs, + // except for about:blank, or other moz-extension:-URLs that are + // not in web_accessible_resources. Ensure any page set as a home + // page will load by using a content principal. + setContentTriggeringPrincipal(url); + } } args.appendElement(null); // extraOptions @@ -432,6 +442,10 @@ window.addEventListener( "DOMContentLoaded", function () { + let { allowScriptsToClose } = createData; + if (allowScriptsToClose === null && isOnlyMozExtensionUrl) { + allowScriptsToClose = true; + } if (allowScriptsToClose) { window.gBrowserAllowScriptsToCloseInitialTabs = true; } diff -Nru thunderbird-115.3.1+build1/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js thunderbird-115.4.1+build1/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js --- thunderbird-115.3.1+build1/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js 2023-10-24 21:13:26.000000000 +0000 @@ -294,4 +294,46 @@ await extension.unload(); }); +// Regression test for Bug 1852391. +add_task(async function test_pin_discarded_tab() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["tabs"], + }, + async background() { + const url = "http://mochi.test:8888"; + const newTab = await browser.tabs.create({ + url, + active: false, + discarded: true, + }); + browser.tabs.onUpdated.addListener( + async (tabId, changeInfo) => { + browser.test.assertEq( + tabId, + newTab.id, + "Expect onUpdated to be fired for the expected tab" + ); + browser.test.assertEq( + changeInfo.pinned, + true, + "Expect pinned to be set to true" + ); + await browser.tabs.remove(newTab.id); + browser.test.notifyPass("onPinned"); + }, + { properties: ["pinned"] } + ); + await browser.tabs.update(newTab.id, { pinned: true }).catch(err => { + browser.test.fail(`Got unexpected rejection from tabs.update: ${err}`); + browser.test.notifyFail("onPinned"); + }); + }, + }); + + await extension.startup(); + await extension.awaitFinish("onPinned"); + await extension.unload(); +}); + add_task(forceGC); diff -Nru thunderbird-115.3.1+build1/browser/components/extensions/test/browser/browser_ext_windows_create_url.js thunderbird-115.4.1+build1/browser/components/extensions/test/browser/browser_ext_windows_create_url.js --- thunderbird-115.3.1+build1/browser/components/extensions/test/browser/browser_ext_windows_create_url.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/extensions/test/browser/browser_ext_windows_create_url.js 2023-10-24 21:13:27.000000000 +0000 @@ -60,6 +60,7 @@ async function create(options) { browser.test.log(`creating window for ${options.url}`); + // Note: may reject let window = await browser.windows.create(options); let win = windows.get(window.id); win.id = window.id; @@ -69,17 +70,6 @@ return win.promise; } - function createFail(options) { - return browser.windows - .create(options) - .then(() => { - browser.test.fail(`window opened with ${options.url}`); - }) - .catch(() => { - browser.test.succeed(`window could not open with ${options.url}`); - }); - } - let TEST_SETS = [ { name: "Single protocol URL in this extension", @@ -97,16 +87,35 @@ expect: [EXTENSION_URL], }, { + // This is primarily for backwards-compatibility, to allow extensions + // to open other home pages. This test case opens the home page + // explicitly; the implicit case (windows.create({}) without URL) is at + // browser_ext_chrome_settings_overrides_home.js. name: "Single, absolute, other extension URL", url: OTHER_PAGE, expect: [OTHER_PAGE], }, { + // This is oddly inconsistent with the non-array case, but here we are + // intentionally stricter because of lesser backwards-compatibility + // concerns. + name: "Array, absolute, other extension URL", + url: [OTHER_PAGE], + expectError: `Illegal URL: ${OTHER_PAGE}`, + }, + { name: "Single protocol URL in other extension", url: OTHER_PROTO, expect: [`${OTHER_PAGE}?val=ext%2Bfoo%3Abar`], }, { + name: "Single, about:blank", + // Added "?" after "about:blank" because the test's tab load detection + // ignores about:blank. + url: "about:blank?", + expect: ["about:blank?"], + }, + { name: "multiple urls", url: [EXT_PROTO, "test.html", EXTENSION_URL, OTHER_PROTO], expect: [ @@ -116,31 +125,54 @@ `${OTHER_PAGE}?val=ext%2Bfoo%3Abar`, ], }, + { + name: "Reject array of own allowed URLs and other moz-extension:-URL", + url: [EXTENSION_URL, EXT_PROTO, "about:blank?#", OTHER_PAGE], + expectError: `Illegal URL: ${OTHER_PAGE}`, + }, + { + name: "Single, about:robots", + url: "about:robots", + expectError: "Illegal URL: about:robots", + }, + { + name: "Array containing about:robots", + url: ["about:robots"], + expectError: "Illegal URL: about:robots", + }, ]; + async function checkCreateResult({ status, value, reason }, testCase) { + const window = status === "fulfilled" ? value : null; + try { + if (testCase.expectError) { + let error = reason?.message; + browser.test.assertEq(testCase.expectError, error, testCase.name); + } else { + let tabUrls = []; + for (let [tabIndex, tab] of window.tabs) { + tabUrls[tabIndex] = tab.url; + } + browser.test.assertDeepEq(testCase.expect, tabUrls, testCase.name); + } + } catch (e) { + browser.test.fail(`Unexpected failure in ${testCase.name} :: ${e}`); + } finally { + // Close opened windows, whether they were expected or not. + if (window) { + await browser.windows.remove(window.id); + } + } + } try { - let windows = await Promise.all( + // First collect all results, in parallel. + const results = await Promise.allSettled( TEST_SETS.map(t => create({ url: t.url })) ); - - TEST_SETS.forEach((test, i) => { - test.expect.forEach((expectUrl, x) => { - browser.test.assertEq( - expectUrl, - windows[i].tabs.get(x)?.url, - TEST_SETS[i].name - ); - }); - }); - - Promise.all(windows.map(({ id }) => browser.windows.remove(id))).then( - () => { - browser.test.notifyPass("window-create-url"); - } + // Then check the results sequentially + await Promise.all( + TEST_SETS.map((t, i) => checkCreateResult(results[i], t)) ); - - // Expecting to fail when opening windows with multiple urls that includes - // other extension urls. - await Promise.all([createFail({ url: [EXTENSION_URL, OTHER_PAGE] })]); + browser.test.notifyPass("window-create-url"); } catch (e) { browser.test.fail(`${e} :: ${e.stack}`); browser.test.notifyFail("window-create-url"); diff -Nru thunderbird-115.3.1+build1/browser/components/preferences/dialogs/sitePermissions.css thunderbird-115.4.1+build1/browser/components/preferences/dialogs/sitePermissions.css --- thunderbird-115.3.1+build1/browser/components/preferences/dialogs/sitePermissions.css 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/preferences/dialogs/sitePermissions.css 2023-10-24 21:13:27.000000000 +0000 @@ -12,6 +12,7 @@ #permissionsBox { flex: 1 auto; height: 18em; + min-height: 70px; /* 2 * 35px, which is the min row height specified below */ } #siteCol, diff -Nru thunderbird-115.3.1+build1/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js thunderbird-115.4.1+build1/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js --- thunderbird-115.3.1+build1/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js 2023-10-24 21:13:27.000000000 +0000 @@ -95,6 +95,9 @@ testVal("mozilla.org< >"); testVal("mozilla.org< >"); + // RTL characters in domain change order of domain and suffix. Domain should + // be highlighted correctly. + testVal("اختبار.اختبار"); testVal("mozilla.org"); testVal("mozilla.org"); diff -Nru thunderbird-115.3.1+build1/browser/components/urlbar/UrlbarInput.sys.mjs thunderbird-115.4.1+build1/browser/components/urlbar/UrlbarInput.sys.mjs --- thunderbird-115.3.1+build1/browser/components/urlbar/UrlbarInput.sys.mjs 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/components/urlbar/UrlbarInput.sys.mjs 2023-10-24 21:13:27.000000000 +0000 @@ -2579,9 +2579,14 @@ * The trimmed string */ _trimValue(val) { - return lazy.UrlbarPrefs.get("trimURLs") + let trimmedValue = lazy.UrlbarPrefs.get("trimURLs") ? lazy.BrowserUIUtils.trimURL(val) : val; + // Only trim value if the directionality doesn't change to RTL. + return this.window.windowUtils.getDirectionFromText(trimmedValue) == + this.window.windowUtils.DIRECTION_RTL + ? val + : trimmedValue; } /** diff -Nru thunderbird-115.3.1+build1/browser/config/version_display.txt thunderbird-115.4.1+build1/browser/config/version_display.txt --- thunderbird-115.3.1+build1/browser/config/version_display.txt 2023-09-29 10:32:44.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/config/version_display.txt 2023-10-24 21:13:48.000000000 +0000 @@ -1 +1 @@ -115.3.1esr +115.4.0esr diff -Nru thunderbird-115.3.1+build1/browser/config/version.txt thunderbird-115.4.1+build1/browser/config/version.txt --- thunderbird-115.3.1+build1/browser/config/version.txt 2023-09-29 10:32:44.000000000 +0000 +++ thunderbird-115.4.1+build1/browser/config/version.txt 2023-10-24 21:13:48.000000000 +0000 @@ -1 +1 @@ -115.3.1 +115.4.0 diff -Nru thunderbird-115.3.1+build1/BUILDID thunderbird-115.4.1+build1/BUILDID --- thunderbird-115.3.1+build1/BUILDID 2023-09-29 10:34:15.000000000 +0000 +++ thunderbird-115.4.1+build1/BUILDID 2023-10-24 21:14:29.000000000 +0000 @@ -1 +1 @@ -20230928194049 \ No newline at end of file +20231024181440 \ No newline at end of file diff -Nru thunderbird-115.3.1+build1/caps/nsScriptSecurityManager.cpp thunderbird-115.4.1+build1/caps/nsScriptSecurityManager.cpp --- thunderbird-115.3.1+build1/caps/nsScriptSecurityManager.cpp 2023-09-29 10:31:53.000000000 +0000 +++ thunderbird-115.4.1+build1/caps/nsScriptSecurityManager.cpp 2023-10-24 21:13:27.000000000 +0000 @@ -78,7 +78,7 @@ using namespace mozilla; using namespace mozilla::dom; -nsIIOService* nsScriptSecurityManager::sIOService = nullptr; +StaticRefPtr nsScriptSecurityManager::sIOService; std::atomic nsScriptSecurityManager::sStrictFileOriginPolicy = true; namespace { @@ -1548,9 +1548,12 @@ } nsresult nsScriptSecurityManager::Init() { - nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); - NS_ENSURE_SUCCESS(rv, rv); - + nsresult rv; + RefPtr io = mozilla::components::IO::Service(&rv); + if (NS_FAILED(rv)) { + return rv; + } + sIOService = std::move(io); InitPrefs(); // Create our system principal singleton @@ -1596,7 +1599,7 @@ } void nsScriptSecurityManager::Shutdown() { - NS_IF_RELEASE(sIOService); + sIOService = nullptr; BundleHelper::Shutdown(); SystemPrincipal::Shutdown(); } diff -Nru thunderbird-115.3.1+build1/caps/nsScriptSecurityManager.h thunderbird-115.4.1+build1/caps/nsScriptSecurityManager.h --- thunderbird-115.3.1+build1/caps/nsScriptSecurityManager.h 2023-09-29 10:31:52.000000000 +0000 +++ thunderbird-115.4.1+build1/caps/nsScriptSecurityManager.h 2023-10-24 21:13:27.000000000 +0000 @@ -135,7 +135,7 @@ static std::atomic sStrictFileOriginPolicy; - static nsIIOService* sIOService; + static mozilla::StaticRefPtr sIOService; static nsIStringBundle* sStrBundle; }; diff -Nru thunderbird-115.3.1+build1/CLOBBER thunderbird-115.4.1+build1/CLOBBER --- thunderbird-115.3.1+build1/CLOBBER 2023-09-29 10:32:44.000000000 +0000 +++ thunderbird-115.4.1+build1/CLOBBER 2023-10-24 21:13:48.000000000 +0000 @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Merge day clobber 2023-08-28 \ No newline at end of file +Merge day clobber 2023-09-25 \ No newline at end of file diff -Nru thunderbird-115.3.1+build1/comm/calendar/base/content/calendar-buttons-mail-toolbar.inc.xhtml thunderbird-115.4.1+build1/comm/calendar/base/content/calendar-buttons-mail-toolbar.inc.xhtml --- thunderbird-115.3.1+build1/comm/calendar/base/content/calendar-buttons-mail-toolbar.inc.xhtml 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/calendar/base/content/calendar-buttons-mail-toolbar.inc.xhtml 2023-10-24 21:13:57.000000000 +0000 @@ -18,7 +18,7 @@ class="toolbarbutton-1" label="&calendar.extract.event.button;" tooltiptext="&calendar.extract.event.button.tooltip;" - oncommand="calendarExtract.extractFromEmail(true);"> + oncommand="calendarExtract.extractFromEmail(null, true);"> @@ -28,7 +28,7 @@ class="toolbarbutton-1" label="&calendar.extract.task.button;" tooltiptext="&calendar.extract.task.button.tooltip;" - oncommand="calendarExtract.extractFromEmail(false);"> + oncommand="calendarExtract.extractFromEmail(null, false);"> diff -Nru thunderbird-115.3.1+build1/comm/calendar/base/content/calendar-extract.js thunderbird-115.4.1+build1/comm/calendar/base/content/calendar-extract.js --- thunderbird-115.3.1+build1/comm/calendar/base/content/calendar-extract.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/calendar/base/content/calendar-extract.js 2023-10-24 21:13:57.000000000 +0000 @@ -78,15 +78,18 @@ extractWithLocale(event, isEvent) { event.stopPropagation(); let locale = event.target.value; - this.extractFromEmail(isEvent, true, locale); + this.extractFromEmail(null, isEvent, true, locale); }, - async extractFromEmail(isEvent, fixedLang, fixedLocale) { - // Get the message from the 3pane, or from the standalone message window. - let aboutMessage = - document.getElementById("tabmail")?.currentAboutMessage || - document.getElementById("messageBrowser").contentWindow; - let message = aboutMessage.gMessage; + async extractFromEmail(message, isEvent, fixedLang, fixedLocale) { + let tabmail = document.getElementById("tabmail"); + if (!message) { + // Get the message from the 3pane, or from the standalone message window. + let aboutMessage = + tabmail?.currentAboutMessage || document.getElementById("messageBrowser").contentWindow; + message = aboutMessage.gMessage; + } + let folder = message.folder; let title = message.mime2DecodedSubject; @@ -131,17 +134,20 @@ item.setProperty("URL", `mid:${message.messageId}`); cal.dtz.setDefaultStartEndHour(item); cal.alarms.setDefaultValues(item); - let sel = aboutMessage.getMessagePaneBrowser().contentWindow.getSelection(); - // Thunderbird Conversations might be installed - if (sel === null) { + let messagePaneBrowser = + tabmail?.currentTabInfo.chromeBrowser.contentWindow.visibleMessagePaneBrowser?.() || + tabmail?.currentAboutMessage?.getMessagePaneBrowser() || + document.getElementById("messageBrowser").contentWindow?.getMessagePaneBrowser(); + let sel = messagePaneBrowser.contentWindow.getSelection(); + // Check if there's an iframe with a selection (e.g. Thunderbird Conversations) + if (sel.type !== "Range") { try { - sel = document - .getElementById("multimessage") - .contentDocument.querySelector(".iframe-container iframe") + sel = messagePaneBrowser?.contentDocument + .querySelector("iframe") .contentDocument.getSelection(); } catch (ex) { // If Thunderbird Conversations is not installed that is fine, - // we will just have a null selection. + // we will just have an empty or null selection. } } diff -Nru thunderbird-115.3.1+build1/comm/calendar/base/content/imip-bar.js thunderbird-115.4.1+build1/comm/calendar/base/content/imip-bar.js --- thunderbird-115.3.1+build1/comm/calendar/base/content/imip-bar.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/calendar/base/content/imip-bar.js 2023-10-24 21:13:57.000000000 +0000 @@ -273,8 +273,7 @@ let author = aMsgHdr.mime2DecodedAuthor; let isSentFolder = aMsgHdr.folder && aMsgHdr.folder.flags & Ci.nsMsgFolderFlags.SentMail; if (author && isSentFolder) { - let accounts = MailServices.accounts; - for (let identity of accounts.allIdentities) { + for (let identity of MailServices.accounts.allIdentities) { if (author.includes(identity.email) && !identity.fccReplyFollowsParent) { return true; } diff -Nru thunderbird-115.3.1+build1/comm/calendar/base/content/widgets/calendar-alarm-widget.js thunderbird-115.4.1+build1/comm/calendar/base/content/widgets/calendar-alarm-widget.js --- thunderbird-115.3.1+build1/comm/calendar/base/content/widgets/calendar-alarm-widget.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/calendar/base/content/widgets/calendar-alarm-widget.js 2023-10-24 21:13:57.000000000 +0000 @@ -29,7 +29,7 @@ src="chrome://calendar/skin/shared/icons/icon32.svg" alt="" /> - + diff -Nru thunderbird-115.3.1+build1/comm/mail/base/content/msgHdrView.js thunderbird-115.4.1+build1/comm/mail/base/content/msgHdrView.js --- thunderbird-115.3.1+build1/comm/mail/base/content/msgHdrView.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/content/msgHdrView.js 2023-10-24 21:13:57.000000000 +0000 @@ -3516,7 +3516,7 @@ return; } - archiveButton.disabled = false; + archiveButton.disabled = !MessageArchiver.canArchive([gMessage]); let junkScore = gMessage.getStringProperty("junkscore"); let hideJunk = junkScore == Ci.nsIJunkMailPlugin.IS_SPAM_SCORE; if (!commandController._getViewCommandStatus(Ci.nsMsgViewCommandType.junk)) { diff -Nru thunderbird-115.3.1+build1/comm/mail/base/content/tabmail.js thunderbird-115.4.1+build1/comm/mail/base/content/tabmail.js --- thunderbird-115.3.1+build1/comm/mail/base/content/tabmail.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/content/tabmail.js 2023-10-24 21:13:58.000000000 +0000 @@ -1459,7 +1459,9 @@ switch (this.currentTabInfo.mode.name) { case "mail3PaneTab": { let messageBrowser = this.currentAbout3Pane.messageBrowser; - return messageBrowser.hidden ? null : messageBrowser.contentWindow; + return messageBrowser && !messageBrowser.hidden + ? messageBrowser.contentWindow + : null; } case "mailMessageTab": return this.currentTabInfo.chromeBrowser.contentWindow; diff -Nru thunderbird-115.3.1+build1/comm/mail/base/content/threadPane.js thunderbird-115.4.1+build1/comm/mail/base/content/threadPane.js --- thunderbird-115.3.1+build1/comm/mail/base/content/threadPane.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/content/threadPane.js 2023-10-24 21:13:57.000000000 +0000 @@ -708,6 +708,7 @@ function ThreadPaneSelectionChanged() { GetThreadTree().view.selectionChanged(); UpdateSelectCol(); + UpdateMailSearch(); } function UpdateSelectCol() { diff -Nru thunderbird-115.3.1+build1/comm/mail/base/content/widgets/header-fields.js thunderbird-115.4.1+build1/comm/mail/base/content/widgets/header-fields.js --- thunderbird-115.3.1+build1/comm/mail/base/content/widgets/header-fields.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/content/widgets/header-fields.js 2023-10-24 21:13:57.000000000 +0000 @@ -716,9 +716,11 @@ this.heading = document.createElement("span"); this.heading.id = `${this.dataset.headerName}Heading`; this.heading.classList.add("row-heading"); + // message-header-newsgroups-list-name + // message-header-followup-to-list-name document.l10n.setAttributes( this.heading, - "message-header-newsgroups-list-name" + `message-header-${this.dataset.headerName}-list-name` ); this.appendChild(this.heading); diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_archive.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_archive.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_archive.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_archive.js 2023-10-24 21:13:58.000000000 +0000 @@ -13,6 +13,7 @@ const { threadTree } = about3Pane; add_setup(async function () { + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", false); // Create an account for the test. MailServices.accounts.createLocalMailAccount(); const account = MailServices.accounts.accounts[0]; @@ -34,6 +35,7 @@ // Clear the undo and redo stacks to avoid side-effects on // tests expecting them to start in a cleared state. messenger.transactionManager.clear(); + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", true); }); // Create a folder for the account to store test messages. diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_folderPaneContext.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_folderPaneContext.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_folderPaneContext.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_folderPaneContext.js 2023-10-24 21:13:57.000000000 +0000 @@ -32,7 +32,6 @@ "folderPaneContext-manageTags": ["tags"], "folderPaneContext-moveMenu": ["plain", "virtual", "rssFeed"], "folderPaneContext-copyMenu": ["plain", "rssFeed"], - "folderPaneContext-moveToFolderAgain": ["plain", "rssFeed"], }; let about3Pane = document.getElementById("tabmail").currentAbout3Pane; diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_folderTreeQuirks.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_folderTreeQuirks.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_folderTreeQuirks.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_folderTreeQuirks.js 2023-10-24 21:13:57.000000000 +0000 @@ -78,6 +78,7 @@ Services.xulStore.removeDocument( "chrome://messenger/content/messenger.xhtml" ); + about3Pane.paneLayout.messagePaneVisible = true; }); }); diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser.ini thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser.ini --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser.ini 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser.ini 2023-10-24 21:13:57.000000000 +0000 @@ -43,6 +43,7 @@ [browser_paneFocus.js] [browser_paneSplitter.js] [browser_preferDisplayName.js] +[browser_searchMessages.js] [browser_spacesToolbar.js] [browser_spacesToolbarCustomize.js] [browser_selectionWidgetController.js] diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_mailContext.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_mailContext.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_mailContext.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_mailContext.js 2023-10-24 21:13:57.000000000 +0000 @@ -24,16 +24,22 @@ let tabmail = document.getElementById("tabmail"); let testFolder, testMessages; let draftsFolder, draftsMessages; +let templatesFolder, templatesMessages; +let listFolder, listMessages; let singleSelectionMessagePane = [ "singleMessage", "draftsFolder", + "templatesFolder", + "listFolder", "syntheticFolderDraft", "syntheticFolder", ]; let singleSelectionThreadPane = [ "singleMessageTree", "draftsFolderTree", + "templatesFolderTree", + "listFolderTree", "syntheticFolderDraftTree", "syntheticFolderTree", ]; @@ -49,6 +55,8 @@ ...singleSelectionMessagePane, ...singleSelectionThreadPane, "multipleMessagesTree", + "multipleDraftsFolderTree", + "multipleTemplatesFolderTree", ]; let notExternal = [...allThreePane, ...onePane]; @@ -61,9 +69,20 @@ "mailContext-editDraftMsg": [ "draftsFolder", "draftsFolderTree", + "multipleDraftsFolderTree", "syntheticFolderDraft", "syntheticFolderDraftTree", ], + "mailContext-newMsgFromTemplate": [ + "templatesFolder", + "templatesFolderTree", + "multipleTemplatesFolderTree", + ], + "mailContext-editTemplateMsg": [ + "templatesFolder", + "templatesFolderTree", + "multipleTemplatesFolderTree", + ], "mailContext-openNewTab": singleSelectionThreadPane, "mailContext-openNewWindow": singleSelectionThreadPane, "mailContext-openConversation": notExternal, @@ -74,20 +93,28 @@ "syntheticFolderTree", ...onePane, ], - "mailContext-replySender": allSingleSelection, - "mailContext-replyAll": allSingleSelection, - "mailContext-replyList": allSingleSelection, + "mailContext-replySender": true, + "mailContext-replyAll": true, + "mailContext-replyList": ["listFolder", "listFolderTree"], "mailContext-forward": allSingleSelection, "mailContext-forwardAsMenu": allSingleSelection, - "mailContext-multiForwardAsAttachment": ["multipleMessagesTree"], - "mailContext-redirect": allSingleSelection, - "mailContext-editAsNew": allSingleSelection, + "mailContext-multiForwardAsAttachment": [ + "multipleMessagesTree", + "multipleDraftsFolderTree", + "multipleTemplatesFolderTree", + ], + "mailContext-redirect": true, + "mailContext-editAsNew": true, "mailContext-tags": true, // Should be notExternal really. "mailContext-mark": true, // Should be notExternal really. "mailContext-archive": notExternal, "mailContext-moveMenu": notExternal, "mailContext-copyMenu": true, - "mailContext-decryptToFolder": ["multipleMessagesTree"], + "mailContext-decryptToFolder": [ + "multipleMessagesTree", + "multipleDraftsFolderTree", + "multipleTemplatesFolderTree", + ], "mailContext-calendar-convert-menu": allSingleSelection, "mailContext-delete": notExternal, "mailContext-ignoreThread": allThreePane, @@ -95,7 +122,11 @@ "mailContext-watchThread": notExternal, "mailContext-saveAs": true, "mailContext-print": true, - "mailContext-downloadSelected": ["multipleMessagesTree"], + "mailContext-downloadSelected": [ + "multipleMessagesTree", + "multipleDraftsFolderTree", + "multipleTemplatesFolderTree", + ], }; function checkMenuitems(menu, mode) { @@ -176,6 +207,40 @@ generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) ); draftsMessages = [...draftsFolder.messages]; + rootFolder.createSubfolder("mailContextTemplates", null); + templatesFolder = rootFolder + .getChildNamed("mailContextTemplates") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + templatesFolder.setFlag(Ci.nsMsgFolderFlags.Templates); + templatesFolder.addMessageBatch( + generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) + ); + templatesMessages = [...templatesFolder.messages]; + rootFolder.createSubfolder("mailContextMailingList", null); + listFolder = rootFolder + .getChildNamed("mailContextMailingList") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + listFolder.addMessage( + "From - Mon Jan 01 00:00:00 2001\n" + + "To: Mailing List \n" + + "Date: Mon, 01 Jan 2001 00:00:00 +0100\n" + + "List-Help: \n" + + "List-Post: \n" + + "List-Software: Mailing List Software\n" + + "List-Subscribe: \n" + + "Precedence: list\n" + + "Subject: Mailing List Test Mail\n" + + `Message-ID: <${Date.now()}@example.com>\n` + + "From: Mailing List \n" + + "List-Unsubscribe: ,\n" + + " \n" + + "MIME-Version: 1.0\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "Content-Transfer-Encoding: quoted-printable\n" + + "\n" + + "Mailing List Message Body\n" + ); + listMessages = [...listFolder.messages]; tabmail.currentAbout3Pane.restoreState({ folderURI: testFolder.URI, @@ -398,7 +463,7 @@ about3Pane.restoreState({ folderURI: draftsFolder.URI }); await TestUtils.waitForCondition( - () => ConversationOpener.isMessageIndexed(draftsMessages[0]), + () => ConversationOpener.isMessageIndexed(draftsMessages[1]), "waiting for Gloda to finish indexing", 500 ); @@ -440,6 +505,136 @@ ); await shownPromise; checkMenuitems(mailContext, "draftsFolderTree"); + + threadTree.scrollToIndex(1, true); + threadTree.selectedIndices = [1, 2, 3]; + + shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + threadTree.getRowAtIndex(2), + { type: "contextmenu" }, + about3Pane + ); + await shownPromise; + checkMenuitems(mailContext, "multipleDraftsFolderTree"); +}); + +/** + * Tests the mailContext menu on the thread tree and message pane of a Templates + * folder. + */ +add_task(async function testTemplatesFolder() { + let about3Pane = tabmail.currentAbout3Pane; + about3Pane.restoreState({ folderURI: templatesFolder.URI }); + + await TestUtils.waitForCondition( + () => ConversationOpener.isMessageIndexed(templatesMessages[1]), + "waiting for Gloda to finish indexing", + 500 + ); + + let mailContext = about3Pane.document.getElementById("mailContext"); + let { gDBView, messageBrowser, threadTree } = about3Pane; + let messagePaneBrowser = messageBrowser.contentWindow.getMessagePaneBrowser(); + + let loadedPromise = BrowserTestUtils.browserLoaded( + messagePaneBrowser, + undefined, + url => url.endsWith(gDBView.getKeyAt(0)) + ); + threadTree.selectedIndex = 0; + await loadedPromise; + + // Open the menu from the message pane. + + Assert.ok( + BrowserTestUtils.is_visible(messageBrowser), + "message browser should be visible" + ); + let shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + await BrowserTestUtils.synthesizeMouseAtCenter( + ":root", + { type: "contextmenu" }, + messagePaneBrowser + ); + await shownPromise; + checkMenuitems(mailContext, "templatesFolder"); + + // Open the menu from the thread pane. + + shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + threadTree.getRowAtIndex(0), + { type: "contextmenu" }, + about3Pane + ); + await shownPromise; + checkMenuitems(mailContext, "templatesFolderTree"); + + threadTree.scrollToIndex(1, true); + threadTree.selectedIndices = [1, 2, 3]; + + shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + threadTree.getRowAtIndex(2), + { type: "contextmenu" }, + about3Pane + ); + await shownPromise; + checkMenuitems(mailContext, "multipleTemplatesFolderTree"); +}); + +/** + * Tests the mailContext menu on the thread tree and message pane of a + * mailing list message. + */ + +add_task(async function testListMessage() { + let about3Pane = tabmail.currentAbout3Pane; + about3Pane.restoreState({ folderURI: listFolder.URI }); + + await TestUtils.waitForCondition( + () => ConversationOpener.isMessageIndexed(listMessages[0]), + "waiting for Gloda to finish indexing", + 500 + ); + + let mailContext = about3Pane.document.getElementById("mailContext"); + let { gDBView, messageBrowser, threadTree } = about3Pane; + let messagePaneBrowser = messageBrowser.contentWindow.getMessagePaneBrowser(); + + let loadedPromise = BrowserTestUtils.browserLoaded( + messagePaneBrowser, + undefined, + url => url.endsWith(gDBView.getKeyAt(0)) + ); + threadTree.selectedIndex = 0; + await loadedPromise; + // Open the menu from the message pane. + + Assert.ok( + BrowserTestUtils.is_visible(messageBrowser), + "message browser should be visible" + ); + let shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + await BrowserTestUtils.synthesizeMouseAtCenter( + ":root", + { type: "contextmenu" }, + messagePaneBrowser + ); + await shownPromise; + checkMenuitems(mailContext, "listFolder"); + + // Open the menu from the thread pane. + + shownPromise = BrowserTestUtils.waitForEvent(mailContext, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + threadTree.getRowAtIndex(0), + { type: "contextmenu" }, + about3Pane + ); + await shownPromise; + checkMenuitems(mailContext, "listFolderTree"); }); /** diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_messageMenu.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_messageMenu.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_messageMenu.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_messageMenu.js 2023-10-24 21:13:57.000000000 +0000 @@ -15,17 +15,17 @@ /** @type MenuData */ const messageMenuData = { newMsgCmd: {}, - replyMainMenu: { disabled: nothingOrMultiSelected }, + replyMainMenu: { disabled: nothingSelected }, replyNewsgroupMainMenu: { hidden: true }, replySenderMainMenu: { hidden: true }, - menu_replyToAll: { disabled: nothingOrMultiSelected }, - menu_replyToList: { disabled: nothingOrMultiSelected }, + menu_replyToAll: { disabled: nothingSelected }, + menu_replyToList: { disabled: true }, menu_forwardMsg: { disabled: nothingOrMultiSelected }, forwardAsMenu: { disabled: nothingSelected }, menu_forwardAsInline: { disabled: nothingSelected }, menu_forwardAsAttachment: { disabled: nothingSelected }, - menu_redirectMsg: { disabled: nothingOrMultiSelected }, - menu_editMsgAsNew: { disabled: nothingOrMultiSelected }, + menu_redirectMsg: { disabled: nothingSelected }, + menu_editMsgAsNew: { disabled: nothingSelected }, menu_editDraftMsg: { hidden: true }, menu_newMsgFromTemplate: { hidden: true }, menu_editTemplate: { hidden: true }, @@ -74,6 +74,7 @@ let tabmail = document.getElementById("tabmail"); let rootFolder, testFolder, testMessages; +let draftsFolder, draftsMessages, templatesFolder, templatesMessages; add_setup(async function () { Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false); @@ -86,9 +87,9 @@ account.addIdentity(MailServices.accounts.createIdentity()); rootFolder = account.incomingServer.rootFolder; - rootFolder.createSubfolder("message menu", null); + rootFolder.createSubfolder("messageMenu", null); testFolder = rootFolder - .getChildNamed("message menu") + .getChildNamed("messageMenu") .QueryInterface(Ci.nsIMsgLocalMailFolder); testFolder.addMessageBatch( generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) @@ -106,8 +107,47 @@ }) .toMboxString() ); + testFolder.addMessage( + "From - Mon Jan 01 00:00:00 2001\n" + + "To: Mailing List \n" + + "Date: Mon, 01 Jan 2001 00:00:00 +0100\n" + + "List-Help: \n" + + "List-Post: \n" + + "List-Software: Mailing List Software\n" + + "List-Subscribe: \n" + + "Precedence: list\n" + + "Subject: Mailing List Test Mail\n" + + `Message-ID: <${Date.now()}@example.com>\n` + + "From: Mailing List \n" + + "List-Unsubscribe: ,\n" + + " \n" + + "MIME-Version: 1.0\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "Content-Transfer-Encoding: quoted-printable\n" + + "\n" + + "Mailing List Message Body\n" + ); testMessages = [...testFolder.messages]; + rootFolder.createSubfolder("messageMenuDrafts", null); + draftsFolder = rootFolder + .getChildNamed("messageMenuDrafts") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + draftsFolder.setFlag(Ci.nsMsgFolderFlags.Drafts); + draftsFolder.addMessageBatch( + generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) + ); + draftsMessages = [...draftsFolder.messages]; + rootFolder.createSubfolder("messageMenuTemplates", null); + templatesFolder = rootFolder + .getChildNamed("messageMenuTemplates") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + templatesFolder.setFlag(Ci.nsMsgFolderFlags.Templates); + templatesFolder.addMessageBatch( + generator.makeMessages({ count: 5 }).map(message => message.toMboxString()) + ); + templatesMessages = [...templatesFolder.messages]; + window.OpenMessageInNewTab(testMessages[0], { background: true }); await BrowserTestUtils.waitForEvent( tabmail.tabInfo[1].chromeBrowser, @@ -208,6 +248,15 @@ "menu-deleteAllAttachments": {}, }); + // This message is from a mailing list. + tabmail.currentAbout3Pane.threadTree.selectedIndex = 6; + await BrowserTestUtils.browserLoaded( + tabmail.currentAboutMessage.getMessagePaneBrowser() + ); + await helper.testItems({ + menu_replyToList: { disabled: false }, + }); + // FIXME: Select another message and wait for it load in order to properly // clear about:message. tabmail.currentAbout3Pane.threadTree.selectedIndex = 1; @@ -241,6 +290,44 @@ }); }); +add_task(async function testDraftsFolder() { + tabmail.currentAbout3Pane.restoreState({ + folderPaneVisible: true, + messagePaneVisible: true, + folderURI: draftsFolder, + }); + await new Promise(resolve => setTimeout(resolve)); + + tabmail.currentAbout3Pane.threadTree.selectedIndices = [1, 2, 4]; + await helper.testItems({ + menu_editDraftMsg: { hidden: false }, + }); + tabmail.currentAbout3Pane.threadTree.selectedIndices = [3]; + await helper.testItems({ + menu_editDraftMsg: { hidden: false }, + }); +}); + +add_task(async function testTemplatesFolder() { + tabmail.currentAbout3Pane.restoreState({ + folderPaneVisible: true, + messagePaneVisible: true, + folderURI: templatesFolder, + }); + await new Promise(resolve => setTimeout(resolve)); + + tabmail.currentAbout3Pane.threadTree.selectedIndices = [1, 2, 4]; + await helper.testItems({ + menu_newMsgFromTemplate: { hidden: false }, + menu_editTemplate: { hidden: false }, + }); + tabmail.currentAbout3Pane.threadTree.selectedIndices = [3]; + await helper.testItems({ + menu_newMsgFromTemplate: { hidden: false }, + menu_editTemplate: { hidden: false }, + }); +}); + add_task(async function testMessageTab() { tabmail.switchToTab(1); await helper.testAllItems("message"); diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_searchMessages.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_searchMessages.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_searchMessages.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_searchMessages.js 2023-10-24 21:13:58.000000000 +0000 @@ -0,0 +1,453 @@ +/* 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/. */ + +const { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +const { MessageGenerator } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +const tabmail = document.getElementById("tabmail"); +let rootFolder, testFolder, otherFolder; + +add_setup(async function () { + const generator = new MessageGenerator(); + + MailServices.accounts.createLocalMailAccount(); + const account = MailServices.accounts.accounts[0]; + account.addIdentity(MailServices.accounts.createIdentity()); + rootFolder = account.incomingServer.rootFolder; + rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + testFolder = rootFolder.createLocalSubfolder("searchMessagesFolder"); + testFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + const messageStrings = generator + .makeMessages({ count: 20 }) + .map(message => message.toMboxString()); + testFolder.addMessageBatch(messageStrings); + otherFolder = rootFolder.createLocalSubfolder("searchMessagesOtherFolder"); + + tabmail.currentAbout3Pane.paneLayout.messagePaneVisible = true; + Services.xulStore.removeDocument( + "chrome://messenger/content/SearchDialog.xhtml" + ); + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, false); + }); +}); + +add_task(async function () { + const windowOpenedPromise = BrowserTestUtils.domWindowOpenedAndLoaded( + null, + w => + w.document.documentURI == "chrome://messenger/content/SearchDialog.xhtml" + ); + goDoCommand("cmd_searchMessages"); + const win = await windowOpenedPromise; + const doc = win.document; + + await SimpleTest.promiseFocus(win); + + const searchButton = doc.getElementById("search-button"); + const clearButton = doc.querySelector( + "#searchTerms > vbox > hbox:nth-child(2) > button" + ); + const searchTermList = doc.getElementById("searchTermList"); + const threadTree = doc.getElementById("threadTree"); + const columns = threadTree.columns; + const picker = threadTree.querySelector("treecolpicker"); + const popup = picker.querySelector("menupopup"); + const openButton = doc.getElementById("openButton"); + const deleteButton = doc.getElementById("deleteButton"); + const fileMessageButton = doc.getElementById("fileMessageButton"); + const fileMessagePopup = fileMessageButton.querySelector("menupopup"); + const openInFolderButton = doc.getElementById("openInFolderButton"); + const saveAsVFButton = doc.getElementById("saveAsVFButton"); + const statusText = doc.getElementById("statusText"); + + const treeClick = mailTestUtils.treeClick.bind( + null, + EventUtils, + win, + threadTree + ); + + // Test search criteria. The search results are deterministic unless + // MessageGenerator is changed. + + await TestUtils.waitForCondition( + () => searchTermList.itemCount == 1, + "waiting for a search term to exist" + ); + const searchTerm0 = searchTermList.getItemAtIndex(0); + const input0 = searchTerm0.querySelector("search-value input"); + const button0 = searchTerm0.querySelector("button.small-button:first-child"); + + // Row 0 will look for subjects including "hovercraft". + Assert.equal(input0.value, ""); + input0.focus(); + EventUtils.sendString("hovercraft", win); + + // Add another row. + EventUtils.synthesizeMouseAtCenter(button0, {}, win); + await TestUtils.waitForCondition( + () => searchTermList.itemCount == 2, + "waiting for a second search term to exist" + ); + + const searchTerm1 = searchTermList.getItemAtIndex(1); + const menulist = searchTerm1.querySelector("search-attribute menulist"); + const menuitem = menulist.querySelector(`menuitem[value="1"]`); + const input1 = searchTerm1.querySelector("search-value input"); + + // Change row 1's search attribute. + EventUtils.synthesizeMouseAtCenter(menulist, {}, win); + await BrowserTestUtils.waitForPopupEvent(menulist, "shown"); + menulist.menupopup.activateItem(menuitem); + await BrowserTestUtils.waitForPopupEvent(menulist, "hidden"); + + // Row 1 will look for the sender Emily Ekberg. + Assert.equal(input1.value, ""); + EventUtils.synthesizeMouseAtCenter(input1, {}, win); + EventUtils.sendString("emily@ekberg.invalid", win); + + // Search. Emily didn't send a message about hovercraft, so no results. + EventUtils.synthesizeMouseAtCenter(searchButton, {}, win); + await TestUtils.waitForCondition( + () => statusText.value == "No matches found", + "waiting for status text to update" + ); + + // Change the search from AND to OR. + EventUtils.synthesizeMouseAtCenter( + doc.querySelector(`#booleanAndGroup > radio[value="or"]`), + {}, + win + ); + // Change the subject search to something more common. + input0.select(); + EventUtils.sendString("in", win); + + // Search. 10 messages should be found. + EventUtils.synthesizeMouseAtCenter(searchButton, {}, win); + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 10, + "waiting for tree view to be filled" + ); + Assert.equal(statusText.value, "10 matches found"); + + // Test tree sort column and direction. + + EventUtils.synthesizeMouseAtCenter(columns.subjectCol.element, {}, win); + Assert.equal( + columns.subjectCol.element.getAttribute("sortDirection"), + "ascending" + ); + EventUtils.synthesizeMouseAtCenter(columns.dateCol.element, {}, win); + Assert.equal( + columns.dateCol.element.getAttribute("sortDirection"), + "ascending" + ); + EventUtils.synthesizeMouseAtCenter(columns.dateCol.element, {}, win); + Assert.equal( + columns.dateCol.element.getAttribute("sortDirection"), + "descending" + ); + + // Test tree column visibility and order. + + checkTreeColumnsInOrder(threadTree, [ + "flaggedCol", + "attachmentCol", + "subjectCol", + "unreadButtonColHeader", + "correspondentCol", + "junkStatusCol", + "dateCol", + "locationCol", + ]); + EventUtils.synthesizeMouseAtCenter(picker, {}, win); + await BrowserTestUtils.waitForPopupEvent(popup, "shown"); + popup.activateItem( + popup.querySelector(`[colindex="${columns.selectCol.index}"]`) + ); + popup.activateItem( + popup.querySelector(`[colindex="${columns.deleteCol.index}"]`) + ); + popup.hidePopup(); + await BrowserTestUtils.waitForPopupEvent(popup, "hidden"); + // Wait for macOS to catch up. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + checkTreeColumnsInOrder(threadTree, [ + "selectCol", + "flaggedCol", + "attachmentCol", + "subjectCol", + "unreadButtonColHeader", + "correspondentCol", + "junkStatusCol", + "dateCol", + "locationCol", + "deleteCol", + ]); + + threadTree._reorderColumn( + columns.deleteCol.element, + columns.selectCol.element, + false + ); + threadTree.invalidate(); + // Wait for macOS to catch up. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + checkTreeColumnsInOrder(threadTree, [ + "selectCol", + "deleteCol", + "flaggedCol", + "attachmentCol", + "subjectCol", + "unreadButtonColHeader", + "correspondentCol", + "junkStatusCol", + "dateCol", + "locationCol", + ]); + + // Test message selection with the select column. + + treeClick(0, "subjectCol", {}); + await TestUtils.waitForCondition( + () => threadTree.view.selection.count == 1, + "waiting for first message to be selected" + ); + Assert.ok(!openButton.disabled); + Assert.ok(!deleteButton.disabled); + Assert.ok(!fileMessageButton.disabled); + Assert.ok(!openInFolderButton.disabled); + treeClick(1, "selectCol", {}); + await TestUtils.waitForCondition( + () => threadTree.view.selection.count == 2, + "waiting for second message to be selected" + ); + Assert.ok(!openButton.disabled); + Assert.ok(!deleteButton.disabled); + Assert.ok(!fileMessageButton.disabled); + Assert.ok(openInFolderButton.disabled); + treeClick(1, "selectCol", {}); + await TestUtils.waitForCondition( + () => threadTree.view.selection.count == 1, + "waiting for second message to be unselected" + ); + Assert.ok(!openButton.disabled); + Assert.ok(!deleteButton.disabled); + Assert.ok(!fileMessageButton.disabled); + Assert.ok(!openInFolderButton.disabled); + treeClick(0, "selectCol", {}); + await TestUtils.waitForCondition( + () => threadTree.view.selection.count == 0, + "waiting for first message to be selected" + ); + Assert.ok(openButton.disabled); + Assert.ok(deleteButton.disabled); + Assert.ok(fileMessageButton.disabled); + Assert.ok(openInFolderButton.disabled); + + // Opening messages. + + // Test opening a message with the "Open" button. + treeClick(0, "subjectCol", {}); + let tabOpenPromise = BrowserTestUtils.waitForEvent(window, "TabOpen"); + EventUtils.synthesizeMouseAtCenter(openButton, {}, win); + const { + detail: { tabInfo: tab1 }, + } = await tabOpenPromise; + await BrowserTestUtils.waitForEvent(tab1.chromeBrowser, "MsgLoaded"); + Assert.equal(tab1.mode.name, "mailMessageTab"); + + await SimpleTest.promiseFocus(win); + + // Test opening a message with a double click. + tabOpenPromise = BrowserTestUtils.waitForEvent(window, "TabOpen"); + treeClick(0, "subjectCol", { clickCount: 2 }); + const { + detail: { tabInfo: tab2 }, + } = await tabOpenPromise; + await BrowserTestUtils.waitForEvent(tab2.chromeBrowser, "MsgLoaded"); + Assert.equal(tab2.mode.name, "mailMessageTab"); + + await SimpleTest.promiseFocus(win); + + // Test opening a message with the keyboard. + tabOpenPromise = BrowserTestUtils.waitForEvent(window, "TabOpen"); + threadTree.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}, win); + const { + detail: { tabInfo: tab3 }, + } = await tabOpenPromise; + await BrowserTestUtils.waitForEvent(tab3.chromeBrowser, "MsgLoaded"); + Assert.equal(tab3.mode.name, "mailMessageTab"); + + await SimpleTest.promiseFocus(win); + + // Test opening a message with the "Open in Folder" button. + const tabSelectPromise = BrowserTestUtils.waitForEvent(window, "TabSelect"); + EventUtils.synthesizeMouseAtCenter(openInFolderButton, {}, win); + const { + detail: { tabInfo: tab0 }, + } = await tabSelectPromise; + await BrowserTestUtils.waitForEvent(tab0.chromeBrowser, "MsgLoaded"); + Assert.equal(tab0, tabmail.tabInfo[0]); + + tabmail.closeOtherTabs(tab0); + + await SimpleTest.promiseFocus(win); + + // Deleting messages. + + // Test deleting a message with the delete column. + let deletePromise = PromiseTestUtils.promiseFolderEvent( + testFolder, + "DeleteOrMoveMsgCompleted" + ); + treeClick(0, "deleteCol", {}); + await deletePromise; + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 9, + "waiting for row to be removed from tree view" + ); + + // Test deleting a message with the "Delete" button. + deletePromise = PromiseTestUtils.promiseFolderEvent( + testFolder, + "DeleteOrMoveMsgCompleted" + ); + EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win); + await deletePromise; + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 8, + "waiting for row to be removed from tree view" + ); + + // Test deleting a message with the keyboard. + treeClick(0, "subjectCol", {}); + deletePromise = PromiseTestUtils.promiseFolderEvent( + testFolder, + "DeleteOrMoveMsgCompleted" + ); + EventUtils.synthesizeKey("VK_DELETE", { shiftKey: true }, win); + await deletePromise; + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 7, + "waiting for row to be removed from tree view" + ); + + // Moving messages. + + // Test moving a message to another folder with the "Move To" button. + treeClick(0, "subjectCol", {}); + const movePromise = PromiseTestUtils.promiseFolderEvent( + testFolder, + "DeleteOrMoveMsgCompleted" + ); + + EventUtils.synthesizeMouseAtCenter(fileMessageButton, {}, win); + await BrowserTestUtils.waitForPopupEvent(fileMessagePopup, "shown"); + const rootFolderMenu = [...fileMessagePopup.children].find( + i => i._folder == rootFolder + ); + rootFolderMenu.openMenu(true); + await BrowserTestUtils.waitForPopupEvent(rootFolderMenu.menupopup, "shown"); + const otherFolderItem = [...rootFolderMenu.menupopup.children].find( + i => i._folder == otherFolder + ); + rootFolderMenu.menupopup.activateItem(otherFolderItem); + await BrowserTestUtils.waitForPopupEvent(fileMessagePopup, "hidden"); + + await movePromise; + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 6, + "waiting for row to be removed from tree view" + ); + + // TODO: Test dragging a message to another folder. + + // Test the "Save as Search Folder" button. + + const virtualFolderDialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/virtualFolderProperties.xhtml", + { + async callback(vfWin) { + await SimpleTest.promiseFocus(vfWin); + await BrowserTestUtils.closeWindow(vfWin); + }, + } + ); + EventUtils.synthesizeMouseAtCenter(saveAsVFButton, {}, win); + await virtualFolderDialogPromise; + + await SimpleTest.promiseFocus(win); + + // Test clearing the search. + + EventUtils.synthesizeMouseAtCenter(clearButton, {}, win); + await TestUtils.waitForCondition( + () => searchTermList.itemCount == 1, + "waiting for search term list to be cleared" + ); + await TestUtils.waitForCondition( + () => threadTree.view.rowCount == 0, + "waiting for tree view to be cleared" + ); + + const newSearchTerm0 = searchTermList.getItemAtIndex(0); + Assert.notEqual(newSearchTerm0, searchTerm0); + const newInput0 = newSearchTerm0.querySelector("search-value input"); + Assert.equal(newInput0.value, ""); + + await BrowserTestUtils.closeWindow(win); + + // Open the window again, and check the tree columns are as we left them. + + const window2OpenedPromise = BrowserTestUtils.domWindowOpenedAndLoaded( + null, + w => + w.document.documentURI == "chrome://messenger/content/SearchDialog.xhtml" + ); + goDoCommand("cmd_searchMessages"); + const win2 = await window2OpenedPromise; + const doc2 = win.document; + await SimpleTest.promiseFocus(win2); + + const threadTree2 = doc2.getElementById("threadTree"); + + checkTreeColumnsInOrder(threadTree2, [ + "selectCol", + "deleteCol", + "flaggedCol", + "attachmentCol", + "subjectCol", + "unreadButtonColHeader", + "correspondentCol", + "junkStatusCol", + "dateCol", + "locationCol", + ]); + + await BrowserTestUtils.closeWindow(win2); +}); + +function checkTreeColumnsInOrder(tree, expectedOrder) { + Assert.deepEqual( + Array.from(tree.querySelectorAll("treecol:not([hidden])")) + .sort((a, b) => a.ordinal - b.ordinal) + .map(c => c.id), + expectedOrder + ); +} diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_threadTreeSorting.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_threadTreeSorting.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_threadTreeSorting.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_threadTreeSorting.js 2023-10-24 21:13:57.000000000 +0000 @@ -13,6 +13,7 @@ let menuHelper = new MenuTestHelper("menu_View"); add_setup(async function () { + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", false); let generator = new MessageGenerator(); MailServices.accounts.createLocalMailAccount(); @@ -42,6 +43,7 @@ registerCleanupFunction(async () => { await ensure_cards_view(); MailServices.accounts.removeAccount(account, false); + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", true); }); }); diff -Nru thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_zoom.js thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_zoom.js --- thunderbird-115.3.1+build1/comm/mail/base/test/browser/browser_zoom.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/base/test/browser/browser_zoom.js 2023-10-24 21:13:57.000000000 +0000 @@ -11,6 +11,7 @@ const { threadTree } = about3Pane; add_setup(async function () { + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", false); // Create an account for the test. MailServices.accounts.createLocalMailAccount(); const account = MailServices.accounts.accounts[0]; @@ -38,6 +39,7 @@ // Remove test account on cleanup. registerCleanupFunction(() => { MailServices.accounts.removeAccount(account, false); + Services.prefs.setBoolPref("mailnews.scroll_to_new_message", true); }); }); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/accountcreation/GuessConfig.jsm thunderbird-115.4.1+build1/comm/mail/components/accountcreation/GuessConfig.jsm --- thunderbird-115.3.1+build1/comm/mail/components/accountcreation/GuessConfig.jsm 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/accountcreation/GuessConfig.jsm 2023-10-24 21:13:57.000000000 +0000 @@ -118,6 +118,7 @@ // If we're offline, we're going to pick the most common settings. // (Not the "best" settings, but common). if (Services.io.offline) { + // TODO: don't do this. Bug 599173. resultConfig.source = lazy.AccountConfig.kSourceUser; resultConfig.incoming.hostname = "mail." + domain; resultConfig.incoming.username = resultConfig.identity.emailAddress; @@ -152,22 +153,13 @@ ); }; - var updateConfig = function (config) { - resultConfig = config; - }; - - var errorInCallback = function (e) { - // The caller's errorCallback threw. Hopefully shouldn't happen for users. - console.error(e); - alertPrompt("Error in errorCallback for guessConfig()", e); - }; - var checkDone = function () { if (incomingEx) { try { errorCallback(incomingEx, resultConfig); } catch (e) { - errorInCallback(e); + console.error(e); + alertPrompt("Error in errorCallback for guessConfig()", e); } return; } @@ -175,7 +167,8 @@ try { errorCallback(outgoingEx, resultConfig); } catch (e) { - errorInCallback(e); + console.error(e); + alertPrompt("Error in errorCallback for guessConfig()", e); } return; } @@ -186,7 +179,8 @@ try { errorCallback(e); } catch (e) { - errorInCallback(e); + console.error(e); + alertPrompt("Error in errorCallback for guessConfig()", e); } } } @@ -198,7 +192,8 @@ server.hostname = thisTry.hostname; server.port = thisTry.port; server.socketType = thisTry.socketType; - server.auth = chooseBestAuthMethod(thisTry.authMethods); + server.auth = + thisTry.authMethod || chooseBestAuthMethod(thisTry.authMethods); server.authAlternatives = thisTry.authMethods; // TODO // cert is also bad when targetSite is set. (Same below for incoming.) @@ -306,7 +301,8 @@ !!resultConfig.incoming.hostname, resultConfig.incoming.type, resultConfig.incoming.port, - resultConfig.incoming.socketType + resultConfig.incoming.socketType, + resultConfig.incoming.auth ); } if (which == "outgoing" || which == "both") { @@ -315,26 +311,18 @@ !!resultConfig.outgoing.hostname, "smtp", resultConfig.outgoing.port, - resultConfig.outgoing.socketType + resultConfig.outgoing.socketType, + resultConfig.outgoing.auth ); } - return new GuessAbortable( - incomingHostDetector, - outgoingHostDetector, - updateConfig - ); + return new GuessAbortable(incomingHostDetector, outgoingHostDetector); } -function GuessAbortable( - incomingHostDetector, - outgoingHostDetector, - updateConfig -) { +function GuessAbortable(incomingHostDetector, outgoingHostDetector) { Abortable.call(this); this._incomingHostDetector = incomingHostDetector; this._outgoingHostDetector = outgoingHostDetector; - this._updateConfig = updateConfig; } GuessAbortable.prototype = Object.create(Abortable.prototype); GuessAbortable.prototype.constructor = GuessAbortable; @@ -445,20 +433,19 @@ }, /** - * Start the detection + * Start the detection. * - * @param domain {String} to be used as base for guessing. - * Should be a domain (e.g. yahoo.co.uk). - * If hostIsPrecise == true, it should be a full hostname - * @param hostIsPrecise {Boolean} (default false) use only this hostname, - * do not guess hostnames. - * @param type {String-enum}@see AccountConfig type - * (Optional. default, 0, undefined, null = guess it) - * @param port {Integer} (Optional. default, 0, undefined, null = guess it) - * @param socketType {Integer-enum}@see AccountConfig socketType - * (Optional. default, 0, undefined, null = guess it) + * @param {string} domain - Domain to be used as base for guessing. + * Should be a domain (e.g. yahoo.co.uk). + * If hostIsPrecise == true, it should be a full hostname. + * @param {boolean} hostIsPrecise - If true, use only this hostname, + * do not guess hostnames. + * @param {"pop3"|"imap"|"exchange"|"smtp"|""} - Account type. + * @param {integer} port - The port to use. 0 to autodetect + * @param {nsMsgSocketType|-1} socketType - Socket type. -1 to autodetect. + * @param {nsMsgAuthMethod|0} authMethod - Authentication method. 0 to autodetect. */ - start(domain, hostIsPrecise, type, port, socketType) { + start(domain, hostIsPrecise, type, port, socketType, authMethod) { domain = domain.replace(/\s*/g, ""); // Remove whitespace if (!hostIsPrecise) { hostIsPrecise = false; @@ -476,16 +463,9 @@ ); this._cancel = false; this._log.info( - "doing auto detect for protocol " + - protocol + - ", domain " + - domain + - ", (exactly: " + - hostIsPrecise + - "), port " + - port + - ", socketType " + - socketType + `Starting ${protocol} detection on ${ + !hostIsPrecise ? "~ " : "" + }${domain}:${port} with socketType=${socketType} and authMethod=${authMethod}` ); // fill this._hostsToTry @@ -517,6 +497,7 @@ hostTry.socketType + " " + protocolToString(hostTry.protocol); + hostTry.authMethod = authMethod; this._hostsToTry.push(hostTry); } } @@ -589,9 +570,8 @@ }, /** - * @param thisTry {HostTry} - * @param wiredata {Array of {String}} what the server returned - * in response to our protocol chat + * @param {HostTry} thisTry + * @param {string[]} wiredata - What the server returned in response to our protocol chat. */ _processResult(thisTry, wiredata) { if (thisTry._gotCertError) { @@ -698,25 +678,25 @@ /** * Which auth mechanism the server claims to support. - * (That doesn't necessarily reflect reality, it is more an upper bound.) + * That doesn't necessarily reflect reality, it is more an upper bound. * - * @param protocol {Integer-enum} IMAP, POP or SMTP - * @param capaResponse {Array of {String}} on the wire data - * that the server returned. May be the full exchange or just capa. - * @returns {Array of {Integer-enum} values for AccountConfig.incoming.auth - * (or outgoing), in decreasing order of preference. - * E.g. [ 5, 4 ] for a server that supports only Kerberos and - * encrypted passwords. + * @param {integer} protocol - IMAP, POP or SMTP + * @param {string[]} capaResponse - On the wire data that the server returned. + * May be the full exchange or just capa. + * @returns {nsMsgAuthMethod[]} Advertised authentication methods, + * in decreasing order of preference. + * E.g. [ nsMsgAuthMethod.GSSAPI, nsMsgAuthMethod.passwordEncrypted ] + * for a server that supports only Kerberos and encrypted passwords. */ _advertisesAuthMethods(protocol, capaResponse) { - // for imap, capabilities include e.g.: - // "AUTH=CRAM-MD5", "AUTH=NTLM", "AUTH=GSSAPI", "AUTH=MSN" - // for pop3, the auth mechanisms are returned in capa as the following: + // For IMAP, capabilities include e.g.: + // "AUTH=CRAM-MD5", "AUTH=NTLM", "AUTH=GSSAPI", "AUTH=MSN", "AUTH=PLAIN" + // for POP3, the auth mechanisms are returned in capa as the following: // "CRAM-MD5", "NTLM", "MSN", "GSSAPI" - // For smtp, EHLO will return AUTH and then a list of the + // For SMTP, EHLO will return AUTH and then a list of the // mechanism(s) supported, e.g., // AUTH LOGIN NTLM MSN CRAM-MD5 GSSAPI - var result = []; + var supported = new Set(); var line = capaResponse.join("\n").toUpperCase(); var prefix = ""; if (protocol == POP) { @@ -730,18 +710,25 @@ } // add in decreasing order of preference if (new RegExp(prefix + "GSSAPI").test(line)) { - result.push(Ci.nsMsgAuthMethod.GSSAPI); + supported.add(Ci.nsMsgAuthMethod.GSSAPI); } if (new RegExp(prefix + "CRAM-MD5").test(line)) { - result.push(Ci.nsMsgAuthMethod.passwordEncrypted); + supported.add(Ci.nsMsgAuthMethod.passwordEncrypted); } if (new RegExp(prefix + "(NTLM|MSN)").test(line)) { - result.push(Ci.nsMsgAuthMethod.NTLM); + supported.add(Ci.nsMsgAuthMethod.NTLM); + } + if (new RegExp(prefix + "LOGIN").test(line)) { + supported.add(Ci.nsMsgAuthMethod.passwordCleartext); + } + if (new RegExp(prefix + "PLAIN").test(line)) { + supported.add(Ci.nsMsgAuthMethod.passwordCleartext); } if (protocol != IMAP || !line.includes("LOGINDISABLED")) { - result.push(Ci.nsMsgAuthMethod.passwordCleartext); + supported.add(Ci.nsMsgAuthMethod.passwordCleartext); } - return result; + // The array elements will be in the Set's order of addition. + return Array.from(supported); }, _hasSTARTTLS(thisTry, wiredata) { @@ -754,9 +741,10 @@ }; /** - * @param authMethods @see return value of _advertisesAuthMethods() - * Note: the returned auth method will be removed from the array. - * @returns one of them, the preferred one + * @param {nsMsgAuthMethod[]} authMethods - Authentication methods to choose from. + * See return value of _advertisesAuthMethods() + * Note: the returned auth method will be removed from the array. + * @returns {nsMsgAuthMethod} one of them, the preferred one * Note: this might be Kerberos, which might not actually work, * so you might need to try the others, too. */ @@ -881,7 +869,7 @@ } /** - * @returns {Array of {HostTry}} + * @returns {HostTry[]} Hosts to try. */ function getIncomingTryOrder(host, protocol, socketType, port) { var lowerCaseHost = host.toLowerCase(); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/compose/content/addressingWidgetOverlay.js thunderbird-115.4.1+build1/comm/mail/components/compose/content/addressingWidgetOverlay.js --- thunderbird-115.3.1+build1/comm/mail/components/compose/content/addressingWidgetOverlay.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/compose/content/addressingWidgetOverlay.js 2023-10-24 21:13:58.000000000 +0000 @@ -47,7 +47,6 @@ } } - let headerParser = MailServices.headerParser; let getRecipientList = recipientType => Array.from( document.querySelectorAll( @@ -55,10 +54,10 @@ ), pill => { // Expect each pill to contain exactly one address. - let { name, email } = headerParser.makeFromDisplayAddress( + let { name, email } = MailServices.headerParser.makeFromDisplayAddress( pill.fullAddress )[0]; - return headerParser.makeMimeAddress(name, email); + return MailServices.headerParser.makeMimeAddress(name, email); } ).join(","); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/compose/content/dialogs/EdDictionary.xhtml thunderbird-115.4.1+build1/comm/mail/components/compose/content/dialogs/EdDictionary.xhtml --- thunderbird-115.3.1+build1/comm/mail/components/compose/content/dialogs/EdDictionary.xhtml 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/compose/content/dialogs/EdDictionary.xhtml 2023-10-24 21:13:58.000000000 +0000 @@ -71,8 +71,7 @@
diff -Nru thunderbird-115.3.1+build1/comm/mail/components/compose/content/messengercompose.xhtml thunderbird-115.4.1+build1/comm/mail/components/compose/content/messengercompose.xhtml --- thunderbird-115.3.1+build1/comm/mail/components/compose/content/messengercompose.xhtml 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/compose/content/messengercompose.xhtml 2023-10-24 21:13:58.000000000 +0000 @@ -1814,7 +1814,6 @@ #ifndef XP_MACOSX diff -Nru thunderbird-115.3.1+build1/comm/mail/components/extensions/parent/ext-messages.js thunderbird-115.4.1+build1/comm/mail/components/extensions/parent/ext-messages.js --- thunderbird-115.3.1+build1/comm/mail/components/extensions/parent/ext-messages.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/extensions/parent/ext-messages.js 2023-10-24 21:13:57.000000000 +0000 @@ -754,15 +754,34 @@ } return convertMessagePart(mimeMsg); }, - async getRaw(messageId) { + async getRaw(messageId, options) { + let data_format = options?.data_format; + if (!["File", "BinaryString"].includes(data_format)) { + data_format = + extension.manifestVersion < 3 ? "BinaryString" : "File"; + } + let msgHdr = messageTracker.getMessage(messageId); if (!msgHdr) { throw new ExtensionError(`Message not found: ${messageId}.`); } - return getRawMessage(msgHdr).catch(ex => { + try { + let raw = await getRawMessage(msgHdr); + if (data_format == "File") { + // Convert binary string to Uint8Array and return a File. + let bytes = new Uint8Array(raw.length); + for (let i = 0; i < raw.length; i++) { + bytes[i] = raw.charCodeAt(i) & 0xff; + } + return new File([bytes], `message-${messageId}.eml`, { + type: "message/rfc822", + }); + } + return raw; + } catch (ex) { console.error(ex); throw new ExtensionError(`Error reading message ${messageId}`); - }); + } }, async listAttachments(messageId) { let msgHdr = messageTracker.getMessage(messageId); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/extensions/schemas/messages.json thunderbird-115.4.1+build1/comm/mail/components/extensions/schemas/messages.json --- thunderbird-115.3.1+build1/comm/mail/components/extensions/schemas/messages.json 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/extensions/schemas/messages.json 2023-10-24 21:13:57.000000000 +0000 @@ -461,7 +461,7 @@ { "name": "getRaw", "type": "function", - "description": "Returns the unmodified source of a message as a `binary string <|link-binary-string|>`__, which is a simple series of 8-bit values. Throws if the message could not be read, for example due to network issues. If the message contains non-ASCII characters, the body parts in the binary string cannot be read directly and must be decoded according to their character sets. Use :ref:`messages.getFull` to get the correctly decoded parts. Manually decoding the raw message is probably too error-prone, especially if the message contains MIME parts with different character set encodings or attachments.\n\nTo get a readable version of the raw message as it appears in Thunderbird's message source view, it may be sufficient to decode the message according to the character set specified in its main `content-type <|link-content-type|>`__ header (example: text/html; charset=UTF-8) using the following function (see MDN for `supported input encodings <|link-input-encoding|>`__): includes/messages/decodeBinaryString.jsJavaScript", + "description": "Returns the unmodified source of a message. Throws if the message could not be read, for example due to network issues.", "async": "callback", "parameters": [ { @@ -469,12 +469,44 @@ "type": "integer" }, { + "name": "options", + "type": "object", + "properties": { + "data_format": { + "choices": [ + { + "max_manifest_version": 2, + "description": "The message can either be returned as a DOM File or as a `binary string <|link-binary-string|>`__. The historic default is to return a binary string (kept for backward compatibility). However, it is now recommended to use the ``File`` format, because the DOM File object can be used as-is with the downloads API and has useful methods to access the content, like `File.text() <|link-DOMFile-text|>`__ and `File.arrayBuffer() <|link-DOMFile-arrayBuffer|>`__. Working with binary strings is error prone and needs special handling: includes/messages/decodeBinaryString.jsJavaScript (see MDN for `supported input encodings <|link-input-encoding|>`__).", + "type": "string", + "enum": ["File", "BinaryString"] + }, + { + "min_manifest_version": 3, + "description": "The message can either be returned as a DOM File (default) or as a `binary string <|link-binary-string|>`__. It is recommended to use the ``File`` format, because the DOM File object can be used as-is with the downloads API and has useful methods to access the content, like `File.text() <|link-DOMFile-text|>`__ and `File.arrayBuffer() <|link-DOMFile-arrayBuffer|>`__. Working with binary strings is error prone and needs special handling: includes/messages/decodeBinaryString.jsJavaScript (see MDN for `supported input encodings <|link-input-encoding|>`__).", + "type": "string", + "enum": ["File", "BinaryString"] + } + ] + } + }, + "optional": true + }, + { "type": "function", "name": "callback", "optional": true, "parameters": [ { - "type": "string" + "choices": [ + { + "type": "string" + }, + { + "type": "object", + "isInstanceOf": "File", + "additionalProperties": true + } + ] } ] } diff -Nru thunderbird-115.3.1+build1/comm/mail/components/extensions/test/xpcshell/test_ext_messages_get.js thunderbird-115.4.1+build1/comm/mail/components/extensions/test/xpcshell/test_ext_messages_get.js --- thunderbird-115.3.1+build1/comm/mail/components/extensions/test/xpcshell/test_ext_messages_get.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/extensions/test/xpcshell/test_ext_messages_get.js 2023-10-24 21:13:58.000000000 +0000 @@ -27,7 +27,7 @@ * is unique and there are minor differences between the account * implementations, we don't compare exactly with a reference message. */ -add_task(async function test_plain() { +add_task(async function test_plain_mv2() { let _account = createAccount(); let _folder = await createSubfolder( _account.incomingServer.rootFolder, @@ -46,6 +46,20 @@ browser.test.assertEq(1, messages.length); let [message] = messages; + + // Expected message content: + // ------------------------- + // From andy@anway.invalid + // Content-Type: text/plain; charset=ISO-8859-1; format=flowed + // Subject: Big Meeting Today + // From: "Andy Anway" + // To: "Bob Bell" + // Message-Id: <0@made.up.invalid> + // Date: Wed, 06 Nov 2019 22:37:40 +1300 + // + // Hello Bob Bell! + // + browser.test.assertEq("Big Meeting Today", message.subject); browser.test.assertEq( '"Andy Anway" ', @@ -60,31 +74,35 @@ ); } - // From andy@anway.invalid - // Content-Type: text/plain; charset=ISO-8859-1; format=flowed - // Subject: Big Meeting Today - // From: "Andy Anway" - // To: "Bob Bell" - // Message-Id: <0@made.up.invalid> - // Date: Wed, 06 Nov 2019 22:37:40 +1300 - // - // Hello Bob Bell! - // - - let rawMessage = await browser.messages.getRaw(message.id); - // Fold Windows line-endings \r\n to \n. - rawMessage = rawMessage.replace(/\r/g, ""); - browser.test.assertEq("string", typeof rawMessage); - browser.test.assertTrue( - rawMessage.includes("Subject: Big Meeting Today\n") - ); - browser.test.assertTrue( - rawMessage.includes('From: "Andy Anway" \n') - ); - browser.test.assertTrue( - rawMessage.includes('To: "Bob Bell" \n') - ); - browser.test.assertTrue(rawMessage.includes("Hello Bob Bell!")); + let strMessage_1 = await browser.messages.getRaw(message.id); + browser.test.assertEq("string", typeof strMessage_1); + let strMessage_2 = await browser.messages.getRaw(message.id, { + data_format: "BinaryString", + }); + browser.test.assertEq("string", typeof strMessage_2); + let fileMessage_3 = await browser.messages.getRaw(message.id, { + data_format: "File", + }); + // eslint-disable-next-line mozilla/use-isInstance + browser.test.assertTrue(fileMessage_3 instanceof File); + // Since we do not have utf-8 chars in the test message, the returned BinaryString is + // identical to the return value of File.text(). + let strMessage_3 = await fileMessage_3.text(); + + for (let strMessage of [strMessage_1, strMessage_2, strMessage_3]) { + // Fold Windows line-endings \r\n to \n. + strMessage = strMessage.replace(/\r/g, ""); + browser.test.assertTrue( + strMessage.includes("Subject: Big Meeting Today\n") + ); + browser.test.assertTrue( + strMessage.includes('From: "Andy Anway" \n') + ); + browser.test.assertTrue( + strMessage.includes('To: "Bob Bell" \n') + ); + browser.test.assertTrue(strMessage.includes("Hello Bob Bell!")); + } // { // "contentType": "message/rfc822", @@ -156,6 +174,166 @@ }); await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + cleanUpAccount(_account); +}); + +add_task(async function test_plain_mv3() { + let _account = createAccount(); + let _folder = await createSubfolder( + _account.incomingServer.rootFolder, + "test1" + ); + await createMessages(_folder, 1); + + let extension = ExtensionTestUtils.loadExtension({ + background: async () => { + let accounts = await browser.accounts.list(); + browser.test.assertEq(1, accounts.length); + + for (let account of accounts) { + let folder = account.folders.find(f => f.name == "test1"); + let { messages } = await browser.messages.list(folder); + browser.test.assertEq(1, messages.length); + + let [message] = messages; + + // Expected message content: + // ------------------------- + // From chris@clarke.invalid + // Content-Type: text/plain; charset=ISO-8859-1; format=flowed + // Subject: Small Party Tomorrow + // From: "Chris Clarke" + // To: "David Davol" + // Message-Id: <1@made.up.invalid> + // Date: Tue, 01 Feb 2000 01:00:00 +0100 + // + // Hello David Davol! + // + + browser.test.assertEq("Small Party Tomorrow", message.subject); + browser.test.assertEq( + '"Chris Clarke" ', + message.author + ); + + // The msgHdr of NNTP messages have no recipients. + if (account.type != "nntp") { + browser.test.assertEq( + "David Davol ", + message.recipients[0] + ); + } + + let fileMessage_1 = await browser.messages.getRaw(message.id); + // eslint-disable-next-line mozilla/use-isInstance + browser.test.assertTrue(fileMessage_1 instanceof File); + // Since we do not have utf-8 chars in the test message, the returned + // BinaryString is identical to the return value of File.text(). + let strMessage_1 = await fileMessage_1.text(); + + let strMessage_2 = await browser.messages.getRaw(message.id, { + data_format: "BinaryString", + }); + browser.test.assertEq("string", typeof strMessage_2); + + let fileMessage_3 = await browser.messages.getRaw(message.id, { + data_format: "File", + }); + // eslint-disable-next-line mozilla/use-isInstance + browser.test.assertTrue(fileMessage_3 instanceof File); + let strMessage_3 = await fileMessage_3.text(); + + for (let strMessage of [strMessage_1, strMessage_2, strMessage_3]) { + // Fold Windows line-endings \r\n to \n. + strMessage = strMessage.replace(/\r/g, ""); + browser.test.assertTrue( + strMessage.includes("Subject: Small Party Tomorrow\n") + ); + browser.test.assertTrue( + strMessage.includes('From: "Chris Clarke" \n') + ); + browser.test.assertTrue( + strMessage.includes('To: "David Davol" \n') + ); + browser.test.assertTrue(strMessage.includes("Hello David Davol!")); + } + + // { + // "contentType": "message/rfc822", + // "headers": { + // "content-type": ["text/plain; charset=ISO-8859-1; format=flowed"], + // "subject": ["Small Party Tomorrow"], + // "from": ["\"Chris Clarke\" "], + // "to": ["\"David Davol\" "], + // "message-id": ["<1@made.up.invalid>"], + // "date": ["Tue, 01 Feb 2000 01:00:00 +0100"] + // }, + // "partName": "", + // "size": 20, + // "parts": [ + // { + // "body": "David Davol!\n\n", + // "contentType": "text/plain", + // "headers": { + // "content-type": ["text/plain; charset=ISO-8859-1; format=flowed"] + // }, + // "partName": "1", + // "size": 20 + // } + // ] + // } + + let fullMessage = await browser.messages.getFull(message.id); + browser.test.log(JSON.stringify(fullMessage)); + browser.test.assertEq("object", typeof fullMessage); + browser.test.assertEq("message/rfc822", fullMessage.contentType); + + browser.test.assertEq("object", typeof fullMessage.headers); + for (let header of [ + "content-type", + "date", + "from", + "message-id", + "subject", + "to", + ]) { + browser.test.assertTrue(Array.isArray(fullMessage.headers[header])); + browser.test.assertEq(1, fullMessage.headers[header].length); + } + browser.test.assertEq( + "Small Party Tomorrow", + fullMessage.headers.subject[0] + ); + browser.test.assertEq( + '"Chris Clarke" ', + fullMessage.headers.from[0] + ); + browser.test.assertEq( + '"David Davol" ', + fullMessage.headers.to[0] + ); + + browser.test.assertTrue(Array.isArray(fullMessage.parts)); + browser.test.assertEq(1, fullMessage.parts.length); + browser.test.assertEq("object", typeof fullMessage.parts[0]); + browser.test.assertEq( + "Hello David Davol!", + fullMessage.parts[0].body.trimRight() + ); + } + + browser.test.notifyPass("finished"); + }, + manifest: { + manifest_version: 3, + permissions: ["accountsRead", "messagesRead"], + }, + }); + + await extension.startup(); await extension.awaitFinish("finished"); await extension.unload(); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/im/content/chat-messenger.js thunderbird-115.4.1+build1/comm/mail/components/im/content/chat-messenger.js --- thunderbird-115.3.1+build1/comm/mail/components/im/content/chat-messenger.js 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/im/content/chat-messenger.js 2023-10-24 21:13:58.000000000 +0000 @@ -7,7 +7,7 @@ // This file is loaded in messenger.xhtml. /* globals MailToolboxCustomizeDone, openIMAccountMgr, - PROTO_TREE_VIEW, Status, statusSelector, ZoomManager, gSpacesToolbar */ + PROTO_TREE_VIEW, statusSelector, ZoomManager, gSpacesToolbar */ var { Notifications } = ChromeUtils.importESModule( "resource:///modules/chatNotifications.sys.mjs" @@ -18,8 +18,8 @@ var { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); -var { InlineSpellChecker } = ChromeUtils.importESModule( - "resource://gre/modules/InlineSpellChecker.sys.mjs" +var { Status } = ChromeUtils.importESModule( + "resource:///modules/imStatusUtils.sys.mjs" ); ChromeUtils.defineESModuleGetters(this, { @@ -1883,23 +1883,36 @@ } if (ChatEncryption.otrEnabled) { - new Promise(resolve => { - if (IMServices.core.initialized) { - resolve(); - return; - } + this._initOTR(); + } + + this._restoreWidth(document.getElementById("listPaneBox")); + this._restoreWidth(document.getElementById("contextPane")); + }, + + async _initOTR() { + if (!IMServices.core.initialized) { + await new Promise(resolve => { function initObserver() { Services.obs.removeObserver(initObserver, "prpl-init"); resolve(); } Services.obs.addObserver(initObserver, "prpl-init"); - }).then(() => { - OTRUI.init(); }); } - - this._restoreWidth(document.getElementById("listPaneBox")); - this._restoreWidth(document.getElementById("contextPane")); + // Avoid loading OTR until we have an im account set up. + if (IMServices.accounts.getAccounts().length === 0) { + await new Promise(resolve => { + function accountsObserver() { + if (IMServices.accounts.getAccounts().length > 0) { + Services.obs.removeObserver(accountsObserver, "account-added"); + resolve(); + } + } + Services.obs.addObserver(accountsObserver, "account-added"); + }); + } + await OTRUI.init(); }, }; @@ -2146,4 +2159,4 @@ ChatEncryption.verifyIdentity(window, buddy); } -window.addEventListener("load", chatHandler.init.bind(chatHandler)); +window.addEventListener("load", () => chatHandler.init()); diff -Nru thunderbird-115.3.1+build1/comm/mail/components/im/modules/chatHandler.sys.mjs thunderbird-115.4.1+build1/comm/mail/components/im/modules/chatHandler.sys.mjs --- thunderbird-115.3.1+build1/comm/mail/components/im/modules/chatHandler.sys.mjs 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/im/modules/chatHandler.sys.mjs 2023-10-24 21:13:58.000000000 +0000 @@ -37,8 +37,7 @@ for (let account of IMServices.accounts.getAccounts()) { accountsById[account.numericId] = account; } - let mgr = MailServices.accounts; - for (let account of mgr.accounts) { + for (let account of MailServices.accounts.accounts) { let incomingServer = account.incomingServer; if (!incomingServer || incomingServer.type != "im") { continue; @@ -48,18 +47,18 @@ // Let's recreate each of them... for (let id in accountsById) { let account = accountsById[id]; - let inServer = mgr.createIncomingServer( + let inServer = MailServices.accounts.createIncomingServer( account.name, account.protocol.id, // hostname "im" ); inServer.wrappedJSObject.imAccount = account; - let acc = mgr.createAccount(); + let acc = MailServices.accounts.createAccount(); // Avoid new folder notifications. inServer.valid = false; acc.incomingServer = inServer; inServer.valid = true; - mgr.notifyServerLoaded(inServer); + MailServices.accounts.notifyServerLoaded(inServer); } IMServices.tags.getTags().forEach(function (aTag) { diff -Nru thunderbird-115.3.1+build1/comm/mail/components/preferences/general.inc.xhtml thunderbird-115.4.1+build1/comm/mail/components/preferences/general.inc.xhtml --- thunderbird-115.3.1+build1/comm/mail/components/preferences/general.inc.xhtml 2023-09-29 10:32:58.000000000 +0000 +++ thunderbird-115.4.1+build1/comm/mail/components/preferences/general.inc.xhtml 2023-10-24 21:13:58.000000000 +0000 @@ -45,7 +45,7 @@
static colheader relative colheader absolute colheader