// The following is a heuristic used to determine if a // should be with ax::mojom::blink::Role::kTable or // ax::mojom::blink::Role::kLayoutTable. bool AXLayoutObject::IsDataTable() const { if (!layout_object_ || !GetNode()) return false; // If it has an ARIA role, it's definitely a data table. AtomicString role; if (HasAOMPropertyOrARIAAttribute(AOMStringProperty::kRole, role)) return true; // When a section of the document is contentEditable, all tables should be // treated as data tables, otherwise users may not be able to work with rich // text editors that allow creating and editing tables. if (GetNode() && blink::IsEditable(*GetNode())) return true; // This employs a heuristic to determine if this table should appear. // Only "data" tables should be exposed as tables. // Unfortunately, there is no good way to determine the difference // between a "layout" table and a "data" table. auto* table_element = DynamicTo(GetNode()); if (!table_element) return false; // If there is a caption element, summary, THEAD, or TFOOT section, it's most // certainly a data table if (!table_element->Summary().empty() || table_element->tHead() || table_element->tFoot() || table_element->caption()) return true; // if someone used "rules" attribute than the table should appear if (!table_element->Rules().empty()) return true; // if there's a colgroup or col element, it's probably a data table. if (Traversal::FirstChild(*table_element)) return true; // If there are at least 20 rows, we'll call it a data table. HTMLTableRowsCollection* rows = table_element->rows(); int num_rows = rows->length(); if (num_rows >= AXObjectCacheImpl::kDataTableHeuristicMinRows) return true; if (num_rows <= 0) return false; int num_cols_in_first_body = rows->Item(0)->cells()->length(); // If there's only one cell, it's not a good AXTable candidate. if (num_rows == 1 && num_cols_in_first_body == 1) return false; // Store the background color of the table to check against cell's background // colors. const ComputedStyle* table_style = layout_object_->Style(); if (!table_style) return false; Color table_bg_color = table_style->VisitedDependentColor(GetCSSPropertyBackgroundColor()); bool has_cell_spacing = table_style->HorizontalBorderSpacing() && table_style->VerticalBorderSpacing(); // check enough of the cells to find if the table matches our criteria // Criteria: // 1) must have at least one valid cell (and) // 2) at least half of cells have borders (or) // 3) at least half of cells have different bg colors than the table, and // there is cell spacing unsigned valid_cell_count = 0; unsigned bordered_cell_count = 0; unsigned background_difference_cell_count = 0; unsigned cells_with_top_border = 0; unsigned cells_with_bottom_border = 0; unsigned cells_with_left_border = 0; unsigned cells_with_right_border = 0; Color alternating_row_colors[5]; int alternating_row_color_count = 0; for (int row = 0; row < num_rows; ++row) { HTMLTableRowElement* row_element = rows->Item(row); int n_cols = row_element->cells()->length(); for (int col = 0; col < n_cols; ++col) { const Element* cell = row_element->cells()->item(col); if (!cell) continue; // Any
tag -> treat as data table. if (cell->HasTagName(html_names::kThTag)) return true; // Check for an explicitly assigned a "data" table attribute. auto* cell_elem = DynamicTo(*cell); if (cell_elem) { if (!cell_elem->Headers().empty() || !cell_elem->Abbr().empty() || !cell_elem->Axis().empty() || !cell_elem->FastGetAttribute(html_names::kScopeAttr).empty()) return true; } LayoutObject* cell_layout_object = cell->GetLayoutObject(); if (!cell_layout_object || !cell_layout_object->IsLayoutBlock()) continue; const LayoutBlock* cell_layout_block = To(cell_layout_object); if (cell_layout_block->Size().Width() < 1 || cell_layout_block->Size().Height() < 1) continue; valid_cell_count++; const ComputedStyle* computed_style = cell_layout_block->Style(); if (!computed_style) continue; // If the empty-cells style is set, we'll call it a data table. if (computed_style->EmptyCells() == EEmptyCells::kHide) return true; // If a cell has matching bordered sides, call it a (fully) bordered cell. if ((cell_layout_block->BorderTop() > 0 && cell_layout_block->BorderBottom() > 0) || (cell_layout_block->BorderLeft() > 0 && cell_layout_block->BorderRight() > 0)) bordered_cell_count++; // Also keep track of each individual border, so we can catch tables where // most cells have a bottom border, for example. if (cell_layout_block->BorderTop() > 0) cells_with_top_border++; if (cell_layout_block->BorderBottom() > 0) cells_with_bottom_border++; if (cell_layout_block->BorderLeft() > 0) cells_with_left_border++; if (cell_layout_block->BorderRight() > 0) cells_with_right_border++; // If the cell has a different color from the table and there is cell // spacing, then it is probably a data table cell (spacing and colors take // the place of borders). Color cell_color = computed_style->VisitedDependentColor( GetCSSPropertyBackgroundColor()); if (has_cell_spacing && table_bg_color != cell_color && !cell_color.IsFullyTransparent()) { background_difference_cell_count++; } // If we've found 10 "good" cells, we don't need to keep searching. if (bordered_cell_count >= 10 || background_difference_cell_count >= 10) return true; // For the first 5 rows, cache the background color so we can check if // this table has zebra-striped rows. if (row < 5 && row == alternating_row_color_count) { LayoutObject* layout_row = cell_layout_block->Parent(); if (!layout_row || !layout_row->IsBoxModelObject() || !layout_row->IsTableRow()) continue; const ComputedStyle* row_computed_style = layout_row->Style(); if (!row_computed_style) continue; Color row_color = row_computed_style->VisitedDependentColor( GetCSSPropertyBackgroundColor()); alternating_row_colors[alternating_row_color_count] = row_color; alternating_row_color_count++; } } } // if there is less than two valid cells, it's not a data table if (valid_cell_count <= 1) return false; // half of the cells had borders, it's a data table unsigned needed_cell_count = valid_cell_count / 2; if (bordered_cell_count >= needed_cell_count || cells_with_top_border >= needed_cell_count || cells_with_bottom_border >= needed_cell_count || cells_with_left_border >= needed_cell_count || cells_with_right_border >= needed_cell_count) return true; // half had different background colors, it's a data table if (background_difference_cell_count >= needed_cell_count) return true; // Check if there is an alternating row background color indicating a zebra // striped style pattern. if (alternating_row_color_count > 2) { Color first_color = alternating_row_colors[0]; for (int k = 1; k < alternating_row_color_count; k++) { // If an odd row was the same color as the first row, its not alternating. if (k % 2 == 1 && alternating_row_colors[k] == first_color) return false; // If an even row is not the same as the first row, its not alternating. if (!(k % 2) && alternating_row_colors[k] != first_color) return false; } return true; } return false; }