Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/generic/TableAccessible.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "TableAccessible.h"
8
9
#include "Accessible-inl.h"
10
11
#include "nsTableCellFrame.h"
12
#include "nsTableWrapperFrame.h"
13
14
using namespace mozilla;
15
using namespace mozilla::a11y;
16
17
bool
18
TableAccessible::IsProbablyLayoutTable()
19
0
{
20
0
  // Implement a heuristic to determine if table is most likely used for layout.
21
0
22
0
  // XXX do we want to look for rowspan or colspan, especialy that span all but
23
0
  // a couple cells  at the beginning or end of a row/col, and especially when
24
0
  // they occur at the edge of a table?
25
0
26
0
  // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
27
0
  // This will allow release trunk builds to be used by testers to refine
28
0
  // the algorithm. Integrate it into Logging.
29
0
  // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
30
#ifdef SHOW_LAYOUT_HEURISTIC
31
#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
32
  { \
33
    mLayoutHeuristic = isLayout ? \
34
      NS_LITERAL_STRING("layout table: " heuristic) : \
35
      NS_LITERAL_STRING("data table: " heuristic); \
36
    return isLayout; \
37
  }
38
#else
39
0
#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
40
0
#endif
41
0
42
0
  Accessible* thisacc = AsAccessible();
43
0
44
0
  // Need to see all elements while document is being edited.
45
0
  if (thisacc->Document()->State() & states::EDITABLE) {
46
0
    RETURN_LAYOUT_ANSWER(false, "In editable document");
47
0
  }
48
0
49
0
  // Check to see if an ARIA role overrides the role from native markup,
50
0
  // but for which we still expose table semantics (treegrid, for example).
51
0
  if (thisacc->HasARIARole()) {
52
0
    RETURN_LAYOUT_ANSWER(false, "Has role attribute");
53
0
  }
54
0
55
0
  dom::Element* el = thisacc->Elm();
56
0
  if (el->IsMathMLElement(nsGkAtoms::mtable_)) {
57
0
    RETURN_LAYOUT_ANSWER(false, "MathML matrix");
58
0
  }
59
0
60
0
  MOZ_ASSERT(el->IsHTMLElement(nsGkAtoms::table),
61
0
             "Table should not be built by CSS display:table style");
62
0
63
0
  // Check if datatable attribute has "0" value.
64
0
  if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
65
0
                      NS_LITERAL_STRING("0"), eCaseMatters)) {
66
0
    RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
67
0
  }
68
0
69
0
  // Check for legitimate data table attributes.
70
0
  nsAutoString summary;
71
0
  if (el->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
72
0
      !summary.IsEmpty()) {
73
0
    RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
74
0
  }
75
0
76
0
  // Check for legitimate data table elements.
77
0
  Accessible* caption = thisacc->FirstChild();
78
0
  if (caption && caption->IsHTMLCaption() && caption->HasChildren()) {
79
0
    RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
80
0
  }
81
0
82
0
  for (nsIContent* childElm = el->GetFirstChild(); childElm;
83
0
       childElm = childElm->GetNextSibling()) {
84
0
    if (!childElm->IsHTMLElement())
85
0
      continue;
86
0
87
0
    if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col,
88
0
                                      nsGkAtoms::colgroup,
89
0
                                      nsGkAtoms::tfoot,
90
0
                                      nsGkAtoms::thead)) {
91
0
      RETURN_LAYOUT_ANSWER(false,
92
0
                           "Has col, colgroup, tfoot or thead -- legitimate table structures");
93
0
    }
94
0
95
0
    if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
96
0
      for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
97
0
           rowElm = rowElm->GetNextSibling()) {
98
0
        if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
99
0
          for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
100
0
               cellElm = cellElm->GetNextSibling()) {
101
0
            if (cellElm->IsHTMLElement()) {
102
0
103
0
              if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
104
0
                RETURN_LAYOUT_ANSWER(false,
105
0
                                     "Has th -- legitimate table structures");
106
0
              }
107
0
108
0
              if (cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
109
0
                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
110
0
                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
111
0
                RETURN_LAYOUT_ANSWER(false,
112
0
                                     "Has headers, scope, or abbr attribute -- legitimate table structures");
113
0
              }
114
0
115
0
              Accessible* cell = thisacc->Document()->GetAccessible(cellElm);
116
0
              if (cell && cell->ChildCount() == 1 &&
117
0
                  cell->FirstChild()->IsAbbreviation()) {
118
0
                RETURN_LAYOUT_ANSWER(false,
119
0
                                     "has abbr -- legitimate table structures");
120
0
              }
121
0
            }
122
0
          }
123
0
        }
124
0
      }
125
0
    }
126
0
  }
127
0
128
0
  // Check for nested tables.
129
0
  nsCOMPtr<nsIHTMLCollection> nestedTables =
130
0
    el->GetElementsByTagName(NS_LITERAL_STRING("table"));
131
0
  if (nestedTables->Length() > 0) {
132
0
    RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
133
0
  }
134
0
135
0
  // If only 1 column or only 1 row, it's for layout.
136
0
  auto colCount = ColCount();
137
0
  if (colCount <= 1) {
138
0
    RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
139
0
  }
140
0
  auto rowCount = RowCount();
141
0
  if (rowCount <=1) {
142
0
    RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
143
0
  }
144
0
145
0
  // Check for many columns.
146
0
  if (colCount >= 5) {
147
0
    RETURN_LAYOUT_ANSWER(false, ">=5 columns");
148
0
  }
149
0
150
0
  // Now we know there are 2-4 columns and 2 or more rows. Check to see if
151
0
  // there are visible borders on the cells.
152
0
  // XXX currently, we just check the first cell -- do we really need to do more?
153
0
  nsTableWrapperFrame* tableFrame = do_QueryFrame(el->GetPrimaryFrame());
154
0
  if (!tableFrame) {
155
0
    RETURN_LAYOUT_ANSWER(false, "table with no frame!");
156
0
  }
157
0
158
0
  nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
159
0
  if (!cellFrame) {
160
0
    RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
161
0
  }
162
0
163
0
  nsMargin border;
164
0
  cellFrame->GetXULBorder(border);
165
0
  if (border.top && border.bottom && border.left && border.right) {
166
0
    RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
167
0
  }
168
0
169
0
  // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on
170
0
  // forward.
171
0
172
0
  // Check for styled background color across rows (alternating background
173
0
  // color is a common feature for data tables).
174
0
  auto childCount = thisacc->ChildCount();
175
0
  nscolor rowColor = 0;
176
0
  nscolor prevRowColor;
177
0
  for (auto childIdx = 0U; childIdx < childCount; childIdx++) {
178
0
    Accessible* child = thisacc->GetChildAt(childIdx);
179
0
    if (child->IsHTMLTableRow()) {
180
0
      prevRowColor = rowColor;
181
0
      nsIFrame* rowFrame = child->GetFrame();
182
0
      MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
183
0
      if (!rowFrame) {
184
0
        RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
185
0
      }
186
0
187
0
      rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
188
0
189
0
      if (childIdx > 0 && prevRowColor != rowColor) {
190
0
        RETURN_LAYOUT_ANSWER(
191
0
          false, "2 styles of row background color, non-bordered"
192
0
        );
193
0
      }
194
0
    }
195
0
  }
196
0
197
0
  // Check for many rows.
198
0
  const uint32_t kMaxLayoutRows = 20;
199
0
  if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
200
0
    RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
201
0
  }
202
0
203
0
  // Check for very wide table.
204
0
  nsIFrame* documentFrame = thisacc->Document()->GetFrame();
205
0
  nsSize documentSize = documentFrame->GetSize();
206
0
  if (documentSize.width > 0) {
207
0
    nsSize tableSize = thisacc->GetFrame()->GetSize();
208
0
    int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
209
0
    if (percentageOfDocWidth > 95) {
210
0
      // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
211
0
      // Probably for layout
212
0
      RETURN_LAYOUT_ANSWER(
213
0
        true, "<= 4 columns, table width is 95% of document width"
214
0
      );
215
0
    }
216
0
  }
217
0
218
0
  // Two column rules.
219
0
  if (rowCount * colCount <= 10) {
220
0
    RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
221
0
  }
222
0
223
0
  static const nsLiteralString tags[] = {
224
0
    NS_LITERAL_STRING("embed"),
225
0
    NS_LITERAL_STRING("object"),
226
0
    NS_LITERAL_STRING("iframe")
227
0
  };
228
0
  for (auto& tag : tags) {
229
0
    nsCOMPtr<nsIHTMLCollection> descendants = el->GetElementsByTagName(tag);
230
0
    if (descendants->Length() > 0) {
231
0
      RETURN_LAYOUT_ANSWER(
232
0
        true, "Has no borders, and has iframe, object or embed, typical of advertisements"
233
0
      );
234
0
    }
235
0
  }
236
0
237
0
  RETURN_LAYOUT_ANSWER(
238
0
    false, "No layout factor strong enough, so will guess data"
239
0
  );
240
0
}