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; }