tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLTableAccessible.cpp (25092B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "HTMLTableAccessible.h"
      7 
      8 #include <stdint.h>
      9 
     10 #include "nsAccessibilityService.h"
     11 #include "AccAttributes.h"
     12 #include "ARIAMap.h"
     13 #include "CacheConstants.h"
     14 #include "LocalAccessible-inl.h"
     15 #include "DocAccessible-inl.h"
     16 #include "nsTextEquivUtils.h"
     17 #include "Relation.h"
     18 #include "mozilla/a11y/Role.h"
     19 #include "States.h"
     20 
     21 #include "mozilla/a11y/TableAccessible.h"
     22 #include "mozilla/a11y/TableCellAccessible.h"
     23 #include "mozilla/Assertions.h"
     24 #include "mozilla/dom/Element.h"
     25 #include "mozilla/dom/NameSpaceConstants.h"
     26 #include "nsCaseTreatment.h"
     27 #include "nsColor.h"
     28 #include "nsCOMPtr.h"
     29 #include "nsCoreUtils.h"
     30 #include "nsDebug.h"
     31 #include "nsIHTMLCollection.h"
     32 #include "nsError.h"
     33 #include "nsGkAtoms.h"
     34 #include "nsLiteralString.h"
     35 #include "nsMargin.h"
     36 #include "nsQueryFrame.h"
     37 #include "nsSize.h"
     38 #include "nsStringFwd.h"
     39 #include "nsTableCellFrame.h"
     40 #include "nsTableWrapperFrame.h"
     41 
     42 using namespace mozilla;
     43 using namespace mozilla::dom;
     44 using namespace mozilla::a11y;
     45 
     46 ////////////////////////////////////////////////////////////////////////////////
     47 // HTMLTableCellAccessible
     48 ////////////////////////////////////////////////////////////////////////////////
     49 
     50 HTMLTableCellAccessible::HTMLTableCellAccessible(nsIContent* aContent,
     51                                                 DocAccessible* aDoc)
     52    : HyperTextAccessible(aContent, aDoc) {
     53  mType = eHTMLTableCellType;
     54  mGenericTypes |= eTableCell;
     55 }
     56 
     57 ////////////////////////////////////////////////////////////////////////////////
     58 // HTMLTableCellAccessible: LocalAccessible implementation
     59 
     60 role HTMLTableCellAccessible::NativeRole() const {
     61  // We implement this rather than using the markup maps because we only want
     62  // this role to be returned if this is a valid cell. An invalid cell (e.g. if
     63  // the table has role="none") won't use this class, so it will get a generic
     64  // role, since the markup map doesn't specify a role.
     65  if (mContent->IsMathMLElement(nsGkAtoms::mtd)) {
     66    return roles::MATHML_CELL;
     67  }
     68  return roles::CELL;
     69 }
     70 
     71 uint64_t HTMLTableCellAccessible::NativeState() const {
     72  uint64_t state = HyperTextAccessible::NativeState();
     73 
     74  nsIFrame* frame = mContent->GetPrimaryFrame();
     75  NS_ASSERTION(frame, "No frame for valid cell accessible!");
     76 
     77  if (frame && frame->IsSelected()) {
     78    state |= states::SELECTED;
     79  }
     80 
     81  return state;
     82 }
     83 
     84 already_AddRefed<AccAttributes> HTMLTableCellAccessible::NativeAttributes() {
     85  RefPtr<AccAttributes> attributes = HyperTextAccessible::NativeAttributes();
     86 
     87  // We only need to expose table-cell-index to clients. If we're in the content
     88  // process, we don't need this, so building a CachedTableAccessible is very
     89  // wasteful. This will be exposed by RemoteAccessible in the parent process
     90  // instead.
     91  if (!IPCAccessibilityActive()) {
     92    if (const TableCellAccessible* cell = AsTableCell()) {
     93      TableAccessible* table = cell->Table();
     94      const uint32_t row = cell->RowIdx();
     95      const uint32_t col = cell->ColIdx();
     96      const int32_t cellIdx = table->CellIndexAt(row, col);
     97      if (cellIdx != -1) {
     98        attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
     99      }
    100    }
    101  }
    102 
    103  // abbr attribute
    104 
    105  // Pick up object attribute from abbr DOM element (a child of the cell) or
    106  // from abbr DOM attribute.
    107  nsString abbrText;
    108  if (ChildCount() == 1) {
    109    LocalAccessible* abbr = LocalFirstChild();
    110    if (abbr->IsAbbreviation()) {
    111      nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
    112      if (firstChildNode) {
    113        nsTextEquivUtils::AppendTextEquivFromTextContent(firstChildNode,
    114                                                         &abbrText);
    115      }
    116    }
    117  }
    118  if (abbrText.IsEmpty()) {
    119    mContent->AsElement()->GetAttr(nsGkAtoms::abbr, abbrText);
    120  }
    121 
    122  if (!abbrText.IsEmpty()) {
    123    attributes->SetAttribute(nsGkAtoms::abbr, std::move(abbrText));
    124  }
    125 
    126  // axis attribute
    127  nsString axisText;
    128  mContent->AsElement()->GetAttr(nsGkAtoms::axis, axisText);
    129  if (!axisText.IsEmpty()) {
    130    attributes->SetAttribute(nsGkAtoms::axis, std::move(axisText));
    131  }
    132 
    133  return attributes.forget();
    134 }
    135 
    136 void HTMLTableCellAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
    137                                                  nsAtom* aAttribute,
    138                                                  AttrModType aModType,
    139                                                  const nsAttrValue* aOldValue,
    140                                                  uint64_t aOldState) {
    141  HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
    142                                           aOldValue, aOldState);
    143 
    144  if (aAttribute == nsGkAtoms::headers || aAttribute == nsGkAtoms::abbr ||
    145      aAttribute == nsGkAtoms::scope) {
    146    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
    147                           this);
    148    if (HTMLTableAccessible* table = Table()) {
    149      // Modifying these attributes can also modify our table's classification
    150      // as either a layout or data table. Queue an update on the table itself
    151      // to re-compute our "layout guess"
    152      mDoc->QueueCacheUpdate(table, CacheDomain::Table);
    153    }
    154    mDoc->QueueCacheUpdate(this, CacheDomain::Table);
    155  } else if (aAttribute == nsGkAtoms::rowspan ||
    156             aAttribute == nsGkAtoms::colspan) {
    157    if (HTMLTableAccessible* table = Table()) {
    158      // Modifying these attributes can also modify our table's classification
    159      // as either a layout or data table. Queue an update on the table itself
    160      // to re-compute our "layout guess"
    161      mDoc->QueueCacheUpdate(table, CacheDomain::Table);
    162    }
    163    mDoc->QueueCacheUpdate(this, CacheDomain::Table);
    164  }
    165 }
    166 
    167 ////////////////////////////////////////////////////////////////////////////////
    168 // HTMLTableCellAccessible implementation
    169 
    170 HTMLTableAccessible* HTMLTableCellAccessible::Table() const {
    171  LocalAccessible* parent = const_cast<HTMLTableCellAccessible*>(this);
    172  while ((parent = parent->LocalParent())) {
    173    if (parent->IsHTMLTable()) {
    174      return HTMLTableAccessible::GetFrom(parent);
    175    }
    176  }
    177 
    178  return nullptr;
    179 }
    180 
    181 uint32_t HTMLTableCellAccessible::ColExtent() const {
    182  nsTableCellFrame* cell = do_QueryFrame(GetFrame());
    183  if (!cell) {
    184    // This probably isn't a table according to the layout engine; e.g. it has
    185    // display: block.
    186    return 1;
    187  }
    188  nsTableFrame* table = cell->GetTableFrame();
    189  MOZ_ASSERT(table);
    190  return table->GetEffectiveColSpan(*cell);
    191 }
    192 
    193 uint32_t HTMLTableCellAccessible::RowExtent() const {
    194  nsTableCellFrame* cell = do_QueryFrame(GetFrame());
    195  if (!cell) {
    196    // This probably isn't a table according to the layout engine; e.g. it has
    197    // display: block.
    198    return 1;
    199  }
    200  nsTableFrame* table = cell->GetTableFrame();
    201  MOZ_ASSERT(table);
    202  return table->GetEffectiveRowSpan(*cell);
    203 }
    204 
    205 ////////////////////////////////////////////////////////////////////////////////
    206 // HTMLTableHeaderCellAccessible
    207 ////////////////////////////////////////////////////////////////////////////////
    208 
    209 HTMLTableHeaderCellAccessible::HTMLTableHeaderCellAccessible(
    210    nsIContent* aContent, DocAccessible* aDoc)
    211    : HTMLTableCellAccessible(aContent, aDoc) {}
    212 
    213 ////////////////////////////////////////////////////////////////////////////////
    214 // HTMLTableHeaderCellAccessible: LocalAccessible implementation
    215 
    216 role HTMLTableHeaderCellAccessible::NativeRole() const {
    217  dom::Element* el = Elm();
    218  if (!el) {
    219    return roles::NOTHING;
    220  }
    221 
    222  // Check value of @scope attribute.
    223  static mozilla::dom::Element::AttrValuesArray scopeValues[] = {
    224      nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::row, nsGkAtoms::rowgroup,
    225      nullptr};
    226  int32_t valueIdx = el->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope,
    227                                         scopeValues, eCaseMatters);
    228 
    229  switch (valueIdx) {
    230    case 0:
    231    case 1:
    232      return roles::COLUMNHEADER;
    233    case 2:
    234    case 3:
    235      return roles::ROWHEADER;
    236  }
    237 
    238  dom::Element* nextEl = el->GetNextElementSibling();
    239  dom::Element* prevEl = el->GetPreviousElementSibling();
    240  // If this is the only cell in its row, it's a column header.
    241  if (!nextEl && !prevEl) {
    242    return roles::COLUMNHEADER;
    243  }
    244  const bool nextIsHeader = nextEl && nsCoreUtils::IsHTMLTableHeader(nextEl);
    245  const bool prevIsHeader = prevEl && nsCoreUtils::IsHTMLTableHeader(prevEl);
    246  // If this has a header on both sides, it is a column header.
    247  if (prevIsHeader && nextIsHeader) {
    248    return roles::COLUMNHEADER;
    249  }
    250  // If this has a header on one side and only a single normal cell on the
    251  // other, it's a column header.
    252  if (nextIsHeader && prevEl && !prevEl->GetPreviousElementSibling()) {
    253    return roles::COLUMNHEADER;
    254  }
    255  if (prevIsHeader && nextEl && !nextEl->GetNextElementSibling()) {
    256    return roles::COLUMNHEADER;
    257  }
    258  // If this has a normal cell next to it, it 's a row header.
    259  if ((nextEl && !nextIsHeader) || (prevEl && !prevIsHeader)) {
    260    return roles::ROWHEADER;
    261  }
    262  // If this has a row span, it could be a row header.
    263  if (RowExtent() > 1) {
    264    // It isn't a row header if it has 1 or more consecutive headers next to it.
    265    if (prevIsHeader &&
    266        (!prevEl->GetPreviousElementSibling() ||
    267         nsCoreUtils::IsHTMLTableHeader(prevEl->GetPreviousElementSibling()))) {
    268      return roles::COLUMNHEADER;
    269    }
    270    if (nextIsHeader &&
    271        (!nextEl->GetNextElementSibling() ||
    272         nsCoreUtils::IsHTMLTableHeader(nextEl->GetNextElementSibling()))) {
    273      return roles::COLUMNHEADER;
    274    }
    275    return roles::ROWHEADER;
    276  }
    277  // Otherwise, assume it's a column header.
    278  return roles::COLUMNHEADER;
    279 }
    280 
    281 ////////////////////////////////////////////////////////////////////////////////
    282 // HTMLTableAccessible
    283 ////////////////////////////////////////////////////////////////////////////////
    284 
    285 ////////////////////////////////////////////////////////////////////////////////
    286 // HTMLTableAccessible: LocalAccessible
    287 
    288 bool HTMLTableAccessible::InsertChildAt(uint32_t aIndex,
    289                                        LocalAccessible* aChild) {
    290  // Move caption accessible so that it's the first child. Check for the first
    291  // caption only, because nsAccessibilityService ensures we don't create
    292  // accessibles for the other captions, since only the first is actually
    293  // visible.
    294  return HyperTextAccessible::InsertChildAt(
    295      aChild->IsHTMLCaption() ? 0 : aIndex, aChild);
    296 }
    297 
    298 uint64_t HTMLTableAccessible::NativeState() const {
    299  return LocalAccessible::NativeState() | states::READONLY;
    300 }
    301 
    302 ENameValueFlag HTMLTableAccessible::NativeName(nsString& aName) const {
    303  ENameValueFlag nameFlag = LocalAccessible::NativeName(aName);
    304  if (!aName.IsEmpty()) {
    305    return nameFlag;
    306  }
    307 
    308  // Use table caption as a name.
    309  LocalAccessible* caption = Caption();
    310  if (caption) {
    311    nsIContent* captionContent = caption->GetContent();
    312    if (captionContent) {
    313      bool usedHiddenContent = nsTextEquivUtils::AppendTextEquivFromContent(
    314          this, captionContent, &aName);
    315      aName.CompressWhitespace();
    316      if (!aName.IsEmpty()) {
    317        return usedHiddenContent ? eNameOK : eNameFromRelations;
    318      }
    319    }
    320  }
    321 
    322  // If no caption then use summary as a name.
    323  mContent->AsElement()->GetAttr(nsGkAtoms::summary, aName);
    324  return eNameOK;
    325 }
    326 
    327 void HTMLTableAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
    328                                              nsAtom* aAttribute,
    329                                              AttrModType aModType,
    330                                              const nsAttrValue* aOldValue,
    331                                              uint64_t aOldState) {
    332  HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
    333                                           aOldValue, aOldState);
    334 
    335  if (aAttribute == nsGkAtoms::summary) {
    336    nsAutoString name;
    337    ARIAName(name);
    338    if (name.IsEmpty()) {
    339      if (!Caption()) {
    340        // XXX: Should really be checking if caption provides a name.
    341        mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
    342      }
    343    }
    344 
    345    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
    346                           this);
    347    mDoc->QueueCacheUpdate(this, CacheDomain::Table);
    348  }
    349 }
    350 
    351 already_AddRefed<AccAttributes> HTMLTableAccessible::NativeAttributes() {
    352  RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
    353 
    354  if (mContent->IsMathMLElement(nsGkAtoms::mtable)) {
    355    GetAccService()->MarkupAttributes(this, attributes);
    356  }
    357 
    358  if (IsProbablyLayoutTable()) {
    359    attributes->SetAttribute(nsGkAtoms::layout_guess, true);
    360  }
    361 
    362  return attributes.forget();
    363 }
    364 
    365 ////////////////////////////////////////////////////////////////////////////////
    366 // HTMLTableAccessible: LocalAccessible
    367 
    368 Relation HTMLTableAccessible::RelationByType(RelationType aType) const {
    369  Relation rel = AccessibleWrap::RelationByType(aType);
    370  if (aType == RelationType::LABELLED_BY) {
    371    rel.AppendTarget(Caption());
    372  }
    373 
    374  return rel;
    375 }
    376 
    377 ////////////////////////////////////////////////////////////////////////////////
    378 // HTMLTableAccessible: Table
    379 
    380 LocalAccessible* HTMLTableAccessible::Caption() const {
    381  LocalAccessible* child = mChildren.SafeElementAt(0, nullptr);
    382  return child && child->IsHTMLCaption() ? child : nullptr;
    383 }
    384 
    385 uint32_t HTMLTableAccessible::ColCount() const {
    386  nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
    387  return tableFrame ? tableFrame->GetColCount() : 0;
    388 }
    389 
    390 uint32_t HTMLTableAccessible::RowCount() {
    391  nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
    392  return tableFrame ? tableFrame->GetRowCount() : 0;
    393 }
    394 
    395 bool HTMLTableAccessible::IsProbablyLayoutTable() {
    396  // Implement a heuristic to determine if table is most likely used for layout.
    397 
    398  // XXX do we want to look for rowspan or colspan, especialy that span all but
    399  // a couple cells  at the beginning or end of a row/col, and especially when
    400  // they occur at the edge of a table?
    401 
    402  // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
    403  // This will allow release trunk builds to be used by testers to refine
    404  // the algorithm. Integrate it into Logging.
    405  // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
    406 #ifdef SHOW_LAYOUT_HEURISTIC
    407 #  define RETURN_LAYOUT_ANSWER(isLayout, heuristic)                         \
    408    {                                                                       \
    409      mLayoutHeuristic = isLayout                                           \
    410                             ? nsLiteralString(u"layout table: " heuristic) \
    411                             : nsLiteralString(u"data table: " heuristic);  \
    412      return isLayout;                                                      \
    413    }
    414 #else
    415 #  define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
    416    {                                               \
    417      return isLayout;                              \
    418    }
    419 #endif
    420 
    421  MOZ_ASSERT(!IsDefunct(), "Table accessible should not be defunct");
    422 
    423  // Need to see all elements while document is being edited.
    424  if (Document()->State() & states::EDITABLE) {
    425    RETURN_LAYOUT_ANSWER(false, "In editable document");
    426  }
    427 
    428  // Check to see if an ARIA role overrides the role from native markup,
    429  // but for which we still expose table semantics (treegrid, for example).
    430  if (HasARIARole()) {
    431    RETURN_LAYOUT_ANSWER(false, "Has role attribute");
    432  }
    433 
    434  dom::Element* el = Elm();
    435  if (el->IsMathMLElement(nsGkAtoms::mtable)) {
    436    RETURN_LAYOUT_ANSWER(false, "MathML matrix");
    437  }
    438 
    439  MOZ_ASSERT(el->IsHTMLElement(nsGkAtoms::table),
    440             "Table should not be built by CSS display:table style");
    441 
    442  // Check if datatable attribute has "0" value.
    443  if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable, u"0"_ns,
    444                      eCaseMatters)) {
    445    RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
    446  }
    447 
    448  // Check for legitimate data table attributes.
    449  if (el->Element::HasNonEmptyAttr(nsGkAtoms::summary)) {
    450    RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
    451  }
    452 
    453  // Check for legitimate data table elements.
    454  LocalAccessible* caption = LocalFirstChild();
    455  if (caption && caption->IsHTMLCaption() && caption->HasChildren()) {
    456    RETURN_LAYOUT_ANSWER(false,
    457                         "Not empty caption -- legitimate table structures");
    458  }
    459 
    460  for (nsIContent* childElm = el->GetFirstChild(); childElm;
    461       childElm = childElm->GetNextSibling()) {
    462    if (!childElm->IsHTMLElement()) continue;
    463 
    464    if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col, nsGkAtoms::colgroup,
    465                                      nsGkAtoms::tfoot, nsGkAtoms::thead)) {
    466      RETURN_LAYOUT_ANSWER(
    467          false,
    468          "Has col, colgroup, tfoot or thead -- legitimate table structures");
    469    }
    470 
    471    if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
    472      for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
    473           rowElm = rowElm->GetNextSibling()) {
    474        if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
    475          if (LocalAccessible* row = Document()->GetAccessible(rowElm)) {
    476            if (const nsRoleMapEntry* roleMapEntry = row->ARIARoleMap()) {
    477              if (roleMapEntry->role != roles::ROW) {
    478                RETURN_LAYOUT_ANSWER(true, "Repurposed tr with different role");
    479              }
    480            }
    481          }
    482 
    483          for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
    484               cellElm = cellElm->GetNextSibling()) {
    485            if (cellElm->IsHTMLElement()) {
    486              if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
    487                RETURN_LAYOUT_ANSWER(false,
    488                                     "Has th -- legitimate table structures");
    489              }
    490 
    491              if (cellElm->AsElement()->HasAttr(nsGkAtoms::headers) ||
    492                  cellElm->AsElement()->HasAttr(nsGkAtoms::scope) ||
    493                  cellElm->AsElement()->HasAttr(nsGkAtoms::abbr)) {
    494                RETURN_LAYOUT_ANSWER(false,
    495                                     "Has headers, scope, or abbr attribute -- "
    496                                     "legitimate table structures");
    497              }
    498 
    499              if (LocalAccessible* cell = Document()->GetAccessible(cellElm)) {
    500                if (const nsRoleMapEntry* roleMapEntry = cell->ARIARoleMap()) {
    501                  if (roleMapEntry->role != roles::CELL &&
    502                      roleMapEntry->role != roles::COLUMNHEADER &&
    503                      roleMapEntry->role != roles::ROWHEADER &&
    504                      roleMapEntry->role != roles::GRID_CELL) {
    505                    RETURN_LAYOUT_ANSWER(true,
    506                                         "Repurposed cell with different role");
    507                  }
    508                }
    509                if (cell->ChildCount() == 1 &&
    510                    cell->LocalFirstChild()->IsAbbreviation()) {
    511                  RETURN_LAYOUT_ANSWER(
    512                      false, "has abbr -- legitimate table structures");
    513                }
    514              }
    515            }
    516          }
    517        }
    518      }
    519    }
    520  }
    521 
    522  // If only 1 column or only 1 row, it's for layout.
    523  auto colCount = ColCount();
    524  if (colCount <= 1) {
    525    RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
    526  }
    527  auto rowCount = RowCount();
    528  if (rowCount <= 1) {
    529    RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
    530  }
    531 
    532  // Check for many columns.
    533  if (colCount >= 5) {
    534    RETURN_LAYOUT_ANSWER(false, ">=5 columns");
    535  }
    536 
    537  // Now we know there are 2-4 columns and 2 or more rows. Check to see if
    538  // there are visible borders on the cells.
    539  // XXX currently, we just check the first cell -- do we really need to do
    540  // more?
    541  nsTableWrapperFrame* tableFrame = do_QueryFrame(el->GetPrimaryFrame());
    542  if (!tableFrame) {
    543    RETURN_LAYOUT_ANSWER(false, "table with no frame!");
    544  }
    545 
    546  nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
    547  if (!cellFrame) {
    548    RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
    549  }
    550 
    551  nsMargin border = cellFrame->StyleBorder()->GetComputedBorder();
    552  if (border.top || border.bottom || border.left || border.right) {
    553    RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
    554  }
    555 
    556  // Check for nested tables.
    557  nsCOMPtr<nsIHTMLCollection> nestedTables =
    558      el->GetElementsByTagName(u"table"_ns);
    559  if (nestedTables->Length() > 0) {
    560    RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
    561  }
    562 
    563  // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on
    564  // forward.
    565 
    566  // Check for styled background color across rows (alternating background
    567  // color is a common feature for data tables).
    568  auto childCount = ChildCount();
    569  nscolor rowColor = 0;
    570  nscolor prevRowColor;
    571  for (auto childIdx = 0U; childIdx < childCount; childIdx++) {
    572    LocalAccessible* child = LocalChildAt(childIdx);
    573    if (child->IsHTMLTableRow()) {
    574      prevRowColor = rowColor;
    575      nsIFrame* rowFrame = child->GetFrame();
    576      MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
    577      if (!rowFrame) {
    578        RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
    579      }
    580 
    581      rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
    582 
    583      if (childIdx > 0 && prevRowColor != rowColor) {
    584        RETURN_LAYOUT_ANSWER(false,
    585                             "2 styles of row background color, non-bordered");
    586      }
    587    }
    588  }
    589 
    590  // Check for many rows.
    591  const uint32_t kMaxLayoutRows = 20;
    592  if (rowCount > kMaxLayoutRows) {  // A ton of rows, this is probably for data
    593    RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
    594  }
    595 
    596  // Check for very wide table.
    597  nsIFrame* documentFrame = Document()->GetFrame();
    598  nsSize documentSize = documentFrame->GetSize();
    599  if (documentSize.width > 0) {
    600    nsSize tableSize = GetFrame()->GetSize();
    601    int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
    602    if (percentageOfDocWidth > 95) {
    603      // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
    604      // Probably for layout
    605      RETURN_LAYOUT_ANSWER(
    606          true, "<= 4 columns, table width is 95% of document width");
    607    }
    608  }
    609 
    610  // Two column rules.
    611  if (rowCount * colCount <= 10) {
    612    RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
    613  }
    614 
    615  static const nsLiteralString tags[] = {u"embed"_ns, u"object"_ns,
    616                                         u"iframe"_ns};
    617  for (const auto& tag : tags) {
    618    nsCOMPtr<nsIHTMLCollection> descendants = el->GetElementsByTagName(tag);
    619    if (descendants->Length() > 0) {
    620      RETURN_LAYOUT_ANSWER(true,
    621                           "Has no borders, and has iframe, object or embed, "
    622                           "typical of advertisements");
    623    }
    624  }
    625 
    626  RETURN_LAYOUT_ANSWER(false,
    627                       "No layout factor strong enough, so will guess data");
    628 }
    629 
    630 ////////////////////////////////////////////////////////////////////////////////
    631 // HTMLTableAccessible: protected implementation
    632 
    633 EDescriptionValueFlag HTMLTableAccessible::Description(
    634    nsString& aDescription) const {
    635  // Helpful for debugging layout vs. data tables
    636  aDescription.Truncate();
    637  EDescriptionValueFlag descFlag = LocalAccessible::Description(aDescription);
    638  if (!aDescription.IsEmpty()) {
    639    return descFlag;
    640  }
    641 
    642  // Use summary as description if it weren't used as a name.
    643  // XXX: get rid code duplication with NameInternal().
    644  LocalAccessible* caption = Caption();
    645  if (caption) {
    646    nsIContent* captionContent = caption->GetContent();
    647    if (captionContent) {
    648      nsAutoString captionText;
    649      nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
    650                                                   &captionText);
    651 
    652      if (!captionText.IsEmpty()) {  // summary isn't used as a name.
    653        mContent->AsElement()->GetAttr(nsGkAtoms::summary, aDescription);
    654      }
    655    }
    656  }
    657 
    658 #ifdef SHOW_LAYOUT_HEURISTIC
    659  if (aDescription.IsEmpty()) {
    660    bool isProbablyForLayout = IsProbablyLayoutTable();
    661    aDescription = mLayoutHeuristic;
    662  }
    663  printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
    664 #endif
    665 
    666  return eDescriptionOK;
    667 }
    668 
    669 nsTableWrapperFrame* HTMLTableAccessible::GetTableWrapperFrame() const {
    670  nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
    671  if (tableFrame && tableFrame->InnerTableFrame()) {
    672    return tableFrame;
    673  }
    674 
    675  return nullptr;
    676 }
    677 
    678 ////////////////////////////////////////////////////////////////////////////////
    679 // HTMLCaptionAccessible
    680 ////////////////////////////////////////////////////////////////////////////////
    681 
    682 Relation HTMLCaptionAccessible::RelationByType(RelationType aType) const {
    683  Relation rel = HyperTextAccessible::RelationByType(aType);
    684  if (aType == RelationType::LABEL_FOR) {
    685    rel.AppendTarget(LocalParent());
    686  }
    687 
    688  return rel;
    689 }
    690 
    691 role HTMLCaptionAccessible::NativeRole() const { return roles::CAPTION; }