/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */
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;
} return roles::CELL;
}
uint64_t HTMLTableCellAccessible::NativeState() const {
uint64_t state = HyperTextAccessible::NativeState();
nsIFrame* frame = mContent->GetPrimaryFrame();
NS_ASSERTION(frame, "No frame for valid cell accessible!");
if (frame && frame->IsSelected()) {
state |= states::SELECTED;
}
// 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);
}
}
}
// abbr attribute
// Pick up object attribute from abbr DOM element (a child of the cell) or // from abbr DOM attribute.
nsString abbrText; if (ChildCount() == 1) {
LocalAccessible* abbr = LocalFirstChild(); if (abbr->IsAbbreviation()) {
nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild(); if (firstChildNode) {
nsTextEquivUtils::AppendTextEquivFromTextContent(firstChildNode,
&abbrText);
}
}
} if (abbrText.IsEmpty()) {
mContent->AsElement()->GetAttr(nsGkAtoms::abbr, abbrText);
}
if (!abbrText.IsEmpty()) {
attributes->SetAttribute(nsGkAtoms::abbr, std::move(abbrText));
}
if (aAttribute == nsGkAtoms::headers || aAttribute == nsGkAtoms::abbr ||
aAttribute == nsGkAtoms::scope) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, this); 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, CacheDomain::Table);
}
mDoc->QueueCacheUpdate(this, CacheDomain::Table);
} elseif (aAttribute == nsGkAtoms::rowspan ||
aAttribute == nsGkAtoms::colspan) { 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, CacheDomain::Table);
}
mDoc->QueueCacheUpdate(this, CacheDomain::Table);
}
}
HTMLTableAccessible* HTMLTableCellAccessible::Table() const {
LocalAccessible* parent = const_cast<HTMLTableCellAccessible*>(this); while ((parent = parent->LocalParent())) { if (parent->IsHTMLTable()) { return HTMLTableAccessible::GetFrom(parent);
}
}
return nullptr;
}
uint32_t HTMLTableCellAccessible::ColExtent() const {
nsTableCellFrame* cell = do_QueryFrame(GetFrame()); if (!cell) { // This probably isn't a table according to the layout engine; e.g. it has // display: block. return 1;
}
nsTableFrame* table = cell->GetTableFrame();
MOZ_ASSERT(table); return table->GetEffectiveColSpan(*cell);
}
uint32_t HTMLTableCellAccessible::RowExtent() const {
nsTableCellFrame* cell = do_QueryFrame(GetFrame()); if (!cell) { // This probably isn't a table according to the layout engine; e.g. it has // display: block. return 1;
}
nsTableFrame* table = cell->GetTableFrame();
MOZ_ASSERT(table); return table->GetEffectiveRowSpan(*cell);
}
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;
} constbool nextIsHeader = nextEl && nsCoreUtils::IsHTMLTableHeader(nextEl); constbool 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;
}
// LocalAccessible protected
ENameValueFlag HTMLTableRowAccessible::NativeName(nsString& aName) const { // For table row accessibles, we only want to calculate the name from the // sub tree if an ARIA role is present. if (HasStrongARIARole()) { return AccessibleWrap::NativeName(aName);
}
bool HTMLTableAccessible::InsertChildAt(uint32_t aIndex,
LocalAccessible* aChild) { // Move caption accessible so that it's the first child. Check for the first // caption only, because nsAccessibilityService ensures we don't create // accessibles for the other captions, since only the first is actually // visible. return HyperTextAccessible::InsertChildAt(
aChild->IsHTMLCaption() ? 0 : aIndex, aChild);
}
if (aAttribute == nsGkAtoms::summary) {
nsAutoString name;
ARIAName(name); if (name.IsEmpty()) { if (!Caption()) { // XXX: Should really be checking if caption provides a name.
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
}
}
LocalAccessible* HTMLTableAccessible::Caption() const {
LocalAccessible* child = mChildren.SafeElementAt(0, nullptr); // Since this is an HTML table the caption needs to be a caption // element with no ARIA role (except for a reduntant role='caption'). // If we did a full Role() calculation here we risk getting into an infinite // loop where the parent role would depend on its name which would need to be // calculated by retrieving the caption (bug 1420773.) return child && child->NativeRole() == roles::CAPTION &&
(!child->HasStrongARIARole() ||
child->IsARIARole(nsGkAtoms::caption))
? child
: nullptr;
}
bool HTMLTableAccessible::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
MOZ_ASSERT(!IsDefunct(), "Table accessible should not be defunct");
// Need to see all elements while document is being edited. if (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 (HasARIARole()) {
RETURN_LAYOUT_ANSWER(false, "Has role attribute");
}
dom::Element* el = 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 = 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 = 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(nsGkAtoms::headers) ||
cellElm->AsElement()->HasAttr(nsGkAtoms::scope) ||
cellElm->AsElement()->HasAttr(nsGkAtoms::abbr)) {
RETURN_LAYOUT_ANSWER(false, "Has headers, scope, or abbr attribute -- " "legitimate table structures");
}
// 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!");
}
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 = 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");
}
}
// Two column rules. if (rowCount * colCount <= 10) {
RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
}
staticconst nsLiteralString tags[] = {u"embed"_ns, u"object"_ns,
u"iframe"_ns}; for (constauto& tag : tags) {
nsCOMPtr<nsIHTMLCollection> 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");
}
void HTMLTableAccessible::Description(nsString& aDescription) const { // Helpful for debugging layout vs. data tables
aDescription.Truncate();
LocalAccessible::Description(aDescription); if (!aDescription.IsEmpty()) { return;
}
// Use summary as description if it weren't used as a name. // XXX: get rid code duplication with NameInternal().
LocalAccessible* caption = Caption(); if (caption) {
nsIContent* captionContent = caption->GetContent(); if (captionContent) {
nsAutoString captionText;
nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
&captionText);
if (!captionText.IsEmpty()) { // summary isn't used as a name.
mContent->AsElement()->GetAttr(nsGkAtoms::summary, aDescription);
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.