nsresult ARIAGridCellAccessible::GetAttributesInternal(nsIPersistentProperties* aAttributes) { if (IsDefunct()) return NS_ERROR_FAILURE; nsresult rv = HyperTextAccessibleWrap::GetAttributesInternal(aAttributes); NS_ENSURE_SUCCESS(rv, rv); // Expose "table-cell-index" attribute. Accessible* thisRow = Parent(); if (!thisRow || thisRow->Role() != roles::ROW) return NS_OK; int32_t colIdx = 0, colCount = 0; uint32_t childCount = thisRow->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = thisRow->GetChildAt(childIdx); if (child == this) colIdx = colCount; roles::Role role = child->Role(); if (role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) colCount++; } Accessible* table = thisRow->Parent(); if (!table) return NS_OK; roles::Role tableRole = table->Role(); if (tableRole != roles::TABLE && tableRole != roles::TREE_TABLE) return NS_OK; int32_t rowIdx = 0; childCount = table->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = table->GetChildAt(childIdx); if (child == thisRow) break; if (child->Role() == roles::ROW) rowIdx++; } int32_t idx = rowIdx * colCount + colIdx; nsAutoString stringIdx; stringIdx.AppendInt(idx); nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::tableCellIndex, stringIdx); return NS_OK; }
void XULListCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) { TableAccessible* table = Table(); NS_ASSERTION(table, "cell not in a table!"); if (!table) return; // Get column header cell from XUL listhead. Accessible* list = nullptr; Accessible* tableAcc = table->AsAccessible(); uint32_t tableChildCount = tableAcc->ChildCount(); for (uint32_t childIdx = 0; childIdx < tableChildCount; childIdx++) { Accessible* child = tableAcc->GetChildAt(childIdx); if (child->Role() == roles::LIST) { list = child; break; } } if (list) { Accessible* headerCell = list->GetChildAt(ColIdx()); if (headerCell) { aCells->AppendElement(headerCell); return; } } // No column header cell from XUL markup, try to get it from ARIA markup. TableCellAccessible::ColHeaderCells(aCells); }
void XULListboxAccessible::SelectedCells(nsTArray<Accessible*>* aCells) { nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = do_QueryInterface(mContent); NS_ASSERTION(control, "Doesn't implement nsIDOMXULMultiSelectControlElement."); nsCOMPtr<nsIDOMNodeList> selectedItems; control->GetSelectedItems(getter_AddRefs(selectedItems)); if (!selectedItems) return; uint32_t selectedItemsCount = 0; DebugOnly<nsresult> rv = selectedItems->GetLength(&selectedItemsCount); NS_ASSERTION(NS_SUCCEEDED(rv), "GetLength() Shouldn't fail!"); for (uint32_t index = 0; index < selectedItemsCount; index++) { nsCOMPtr<nsIDOMNode> itemNode; selectedItems->Item(index, getter_AddRefs(itemNode)); nsCOMPtr<nsIContent> itemContent(do_QueryInterface(itemNode)); Accessible* item = mDoc->GetAccessible(itemContent); if (item) { uint32_t cellCount = item->ChildCount(); for (uint32_t cellIdx = 0; cellIdx < cellCount; cellIdx++) { Accessible* cell = mChildren[cellIdx]; if (cell->Role() == roles::CELL) aCells->AppendElement(cell); } } } }
Accessible* AccGroupInfo::NextItemTo(Accessible* aItem) { AccGroupInfo* groupInfo = aItem->GetGroupInfo(); if (!groupInfo) return nullptr; // If the item in middle of the group then search next item in siblings. if (groupInfo->PosInSet() >= groupInfo->SetSize()) return nullptr; Accessible* parent = aItem->Parent(); uint32_t childCount = parent->ChildCount(); for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) { Accessible* nextItem = parent->GetChildAt(idx); AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo(); if (nextGroupInfo && nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { return nextItem; } } NS_NOTREACHED("Item in the middle of the group but there's no next item!"); return nullptr; }
void XULToolbarButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet, int32_t* aSetSize) { int32_t setSize = 0; int32_t posInSet = 0; Accessible* parent = Parent(); if (!parent) return; uint32_t childCount = parent->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = parent->GetChildAt(childIdx); if (IsSeparator(child)) { // end of a group of buttons if (posInSet) break; // we've found our group, so we're done setSize = 0; // not our group, so start a new group } else { setSize++; // another button in the group if (child == this) posInSet = setSize; // we've found our button } } *aPosInSet = posInSet; *aSetSize = setSize; }
nsIContent* nsXFormsSelectableAccessible::GetItemByIndex(PRUint32* aIndex, Accessible* aAccessible) { Accessible* accessible = aAccessible ? aAccessible : this; PRUint32 childCount = accessible->ChildCount(); for (PRUint32 childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = accessible->GetChildAt(childIdx); nsIContent* childContent = child->GetContent(); nsINodeInfo *nodeInfo = childContent->NodeInfo(); if (nodeInfo->NamespaceEquals(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS))) { if (nodeInfo->Equals(nsGkAtoms::item)) { if (!*aIndex) return childContent; --*aIndex; } else if (nodeInfo->Equals(nsGkAtoms::choices)) { nsIContent* itemContent = GetItemByIndex(aIndex, child); if (itemContent) return itemContent; } } } return nsnull; }
void TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const { if (mStartContainer == mEndContainer) { int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset); int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset); for (int32_t idx = startIdx; idx <= endIdx; idx++) { Accessible* child = mStartContainer->GetChildAt(idx); if (nsAccUtils::IsEmbeddedObject(child)) aChildren->AppendElement(child); } return; } Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset); Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset); uint32_t pos1 = 0, pos2 = 0; AutoTArray<Accessible*, 30> parents1, parents2; Accessible* container = CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2); // Traverse the tree up to the container and collect embedded objects. for (uint32_t idx = 0; idx < pos1 - 1; idx++) { Accessible* parent = parents1[idx + 1]; Accessible* child = parents1[idx]; uint32_t childCount = parent->ChildCount(); for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount; childIdx++) { Accessible* next = parent->GetChildAt(childIdx); if (nsAccUtils::IsEmbeddedObject(next)) aChildren->AppendElement(next); } } // Traverse through direct children in the container. int32_t endIdx = parents2[pos2 - 1]->IndexInParent(); int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1; for (; childIdx < endIdx; childIdx++) { Accessible* next = container->GetChildAt(childIdx); if (nsAccUtils::IsEmbeddedObject(next)) aChildren->AppendElement(next); } // Traverse down from the container to end point. for (int32_t idx = pos2 - 2; idx > 0; idx--) { Accessible* parent = parents2[idx]; Accessible* child = parents2[idx - 1]; int32_t endIdx = child->IndexInParent(); for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) { Accessible* next = parent->GetChildAt(childIdx); if (nsAccUtils::IsEmbeddedObject(next)) aChildren->AppendElement(next); } } }
NS_IMETHODIMP nsXULListboxAccessible::GetSelectedCells(nsIArray **aCells) { NS_ENSURE_ARG_POINTER(aCells); *aCells = nsnull; if (IsDefunct()) return NS_ERROR_FAILURE; nsresult rv = NS_OK; nsCOMPtr<nsIMutableArray> selCells = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = do_QueryInterface(mContent); NS_ASSERTION(control, "Doesn't implement nsIDOMXULMultiSelectControlElement."); nsCOMPtr<nsIDOMNodeList> selectedItems; control->GetSelectedItems(getter_AddRefs(selectedItems)); if (!selectedItems) return NS_OK; PRUint32 selectedItemsCount = 0; rv = selectedItems->GetLength(&selectedItemsCount); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); PRUint32 index = 0; for (; index < selectedItemsCount; index++) { nsCOMPtr<nsIDOMNode> itemNode; selectedItems->Item(index, getter_AddRefs(itemNode)); nsCOMPtr<nsIContent> itemContent(do_QueryInterface(itemNode)); Accessible* item = mDoc->GetAccessible(itemContent); if (item) { PRUint32 cellCount = item->ChildCount(); for (PRUint32 cellIdx = 0; cellIdx < cellCount; cellIdx++) { Accessible* cell = mChildren[cellIdx]; if (cell->Role() == roles::CELL) selCells->AppendElement(static_cast<nsIAccessible*>(cell), false); } } } NS_ADDREF(*aCells = selCells); return NS_OK; }
already_AddRefed<nsIPersistentProperties> ARIAGridCellAccessible::NativeAttributes() { nsCOMPtr<nsIPersistentProperties> attributes = HyperTextAccessibleWrap::NativeAttributes(); // Expose "table-cell-index" attribute. Accessible* thisRow = Row(); if (!thisRow) return attributes.forget(); int32_t colIdx = 0, colCount = 0; uint32_t childCount = thisRow->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = thisRow->GetChildAt(childIdx); if (child == this) colIdx = colCount; roles::Role role = child->Role(); if (role == roles::CELL || role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) colCount++; } int32_t rowIdx = RowIndexFor(thisRow); nsAutoString stringIdx; stringIdx.AppendInt(rowIdx * colCount + colIdx); nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx); #ifdef DEBUG nsAutoString unused; attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"), NS_LITERAL_STRING("ARIAGridCellAccessible"), unused); #endif return attributes.forget(); }
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 expose this info via object attributes to AT-SPI // 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 // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release #ifdef SHOW_LAYOUT_HEURISTIC #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ { \ mLayoutHeuristic = isLayout ? \ NS_LITERAL_STRING("layout table: " heuristic) : \ NS_LITERAL_STRING("data table: " heuristic); \ return isLayout; \ } #else #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; } #endif DocAccessible* docAccessible = Document(); if (docAccessible) { uint64_t docState = docAccessible->State(); if (docState & states::EDITABLE) { // Need to see all elements while document is being edited 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 (Role() != roles::TABLE) RETURN_LAYOUT_ANSWER(false, "Has role attribute"); if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) { // Role attribute is present, but overridden roles have already been dealt with. // Only landmarks and other roles that don't override the role from native // markup are left to deal with here. RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table"); } NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table), "table should not be built by CSS display:table style"); // Check if datatable attribute has "0" value. if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable, NS_LITERAL_STRING("0"), eCaseMatters)) { RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout"); } // Check for legitimate data table attributes. nsAutoString summary; if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) && !summary.IsEmpty()) RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures"); // Check for legitimate data table elements. Accessible* caption = FirstChild(); if (caption && caption->Role() == roles::CAPTION && caption->HasChildren()) RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures"); for (nsIContent* childElm = mContent->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)) { 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(kNameSpaceID_None, nsGkAtoms::headers) || cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) || cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) { RETURN_LAYOUT_ANSWER(false, "Has headers, scope, or abbr attribute -- legitimate table structures"); } Accessible* cell = mDoc->GetAccessible(cellElm); if (cell && cell->ChildCount() == 1 && cell->FirstChild()->IsAbbreviation()) { RETURN_LAYOUT_ANSWER(false, "has abbr -- legitimate table structures"); } } } } } } } if (HasDescendant(NS_LITERAL_STRING("table"))) { RETURN_LAYOUT_ANSWER(true, "Has a nested table within it"); } // If only 1 column or only 1 row, it's for layout uint32_t colCount = ColCount(); if (colCount <=1) { RETURN_LAYOUT_ANSWER(true, "Has only 1 column"); } uint32_t 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(mContent->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!"); nsMargin border; cellFrame->GetXULBorder(border); if (border.top && border.bottom && border.left && border.right) { RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell"); } /** * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward */ // Check for styled background color across rows (alternating background // color is a common feature for data tables). uint32_t childCount = ChildCount(); nscolor rowColor = 0; nscolor prevRowColor; for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { Accessible* child = GetChildAt(childIdx); if (child->Role() == roles::ROW) { prevRowColor = rowColor; nsIFrame* rowFrame = child->GetFrame(); rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame); 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"); } if (HasDescendant(NS_LITERAL_STRING("embed")) || HasDescendant(NS_LITERAL_STRING("object")) || HasDescendant(NS_LITERAL_STRING("iframe"))) { RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, or iframe, typical of advertisements"); } RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data"); }
void AccGroupInfo::Update() { Accessible* parent = mItem->Parent(); if (!parent) return; int32_t indexInParent = mItem->IndexInParent(); uint32_t siblingCount = parent->ChildCount(); if (indexInParent == -1 || indexInParent >= static_cast<int32_t>(siblingCount)) { NS_ERROR("Wrong index in parent! Tree invalidation problem."); return; } int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem); // Compute position in set. mPosInSet = 1; for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) { Accessible* sibling = parent->GetChildAt(idx); roles::Role siblingRole = sibling->Role(); // If the sibling is separator then the group is ended. if (siblingRole == roles::SEPARATOR) break; // If sibling is not visible and hasn't the same base role. if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) continue; // Check if it's hierarchical flatten structure, i.e. if the sibling // level is lesser than this one then group is ended, if the sibling level // is greater than this one then the group is split by some child elements // (group will be continued). int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); if (siblingLevel < level) { mParent = sibling; break; } // Skip subset. if (siblingLevel > level) continue; // If the previous item in the group has calculated group information then // build group information for this item based on found one. if (sibling->mBits.groupInfo) { mPosInSet += sibling->mBits.groupInfo->mPosInSet; mParent = sibling->mBits.groupInfo->mParent; mSetSize = sibling->mBits.groupInfo->mSetSize; return; } mPosInSet++; } // Compute set size. mSetSize = mPosInSet; for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) { Accessible* sibling = parent->GetChildAt(idx); roles::Role siblingRole = sibling->Role(); // If the sibling is separator then the group is ended. if (siblingRole == roles::SEPARATOR) break; // If sibling is visible and has the same base role if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) continue; // and check if it's hierarchical flatten structure. int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); if (siblingLevel < level) break; // Skip subset. if (siblingLevel > level) continue; // If the next item in the group has calculated group information then // build group information for this item based on found one. if (sibling->mBits.groupInfo) { mParent = sibling->mBits.groupInfo->mParent; mSetSize = sibling->mBits.groupInfo->mSetSize; return; } mSetSize++; } if (mParent) return; roles::Role parentRole = parent->Role(); if (ShouldReportRelations(mRole, parentRole)) mParent = parent; // ARIA tree and list can be arranged by using ARIA groups to organize levels. if (parentRole != roles::GROUPING) return; // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a // parent. In other words the parent of the tree item will be a group and // the previous tree item of the group is a conceptual parent of the tree // item. if (mRole == roles::OUTLINEITEM) { Accessible* parentPrevSibling = parent->PrevSibling(); if (parentPrevSibling && parentPrevSibling->Role() == mRole) { mParent = parentPrevSibling; return; } } // Way #2 for ARIA list and tree: group is a child of an item. In other words // the parent of the item will be a group and containing item of the group is // a conceptual parent of the item. if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { Accessible* grandParent = parent->Parent(); if (grandParent && grandParent->Role() == mRole) mParent = grandParent; } }
void RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) { // Get popup accessible. There are cases when popup element isn't accessible // but an underlying widget is and behaves like popup, an example is // autocomplete popups. DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); if (!document) return; Accessible* popup = document->GetAccessible(aPopupNode); if (!popup) { Accessible* popupContainer = document->GetContainerAccessible(aPopupNode); if (!popupContainer) return; uint32_t childCount = popupContainer->ChildCount(); for (uint32_t idx = 0; idx < childCount; idx++) { Accessible* child = popupContainer->GetChildAt(idx); if (child->IsAutoCompletePopup()) { popup = child; break; } } // No popup no events. Focus is managed by DOM. This is a case for // menupopups of menus on Linux since there are no accessible for popups. if (!popup) return; } // In case of autocompletes and comboboxes fire state change event for // expanded state. Note, HTML form autocomplete isn't a subject of state // change event because they aren't autocompletes strictly speaking. // When popup closes (except nested popups and menus) then fire focus event to // where it was. The focus event is expected even if popup didn't take a focus. static const uint32_t kNotifyOfFocus = 1; static const uint32_t kNotifyOfState = 2; uint32_t notifyOf = 0; // HTML select is target of popuphidding event. Otherwise get container // widget. No container widget means this is either tooltip or menupopup. // No events in the former case. Accessible* widget = nullptr; if (popup->IsCombobox()) { widget = popup; } else { widget = popup->ContainerWidget(); if (!widget) { if (!popup->IsMenuPopup()) return; widget = popup; } } if (popup->IsAutoCompletePopup()) { // No focus event for autocomplete because it's managed by // DOMMenuItemInactive events. if (widget->IsAutoComplete()) notifyOf = kNotifyOfState; } else if (widget->IsCombobox()) { // Fire focus for active combobox, otherwise the focus is managed by DOM // focus notifications. Always fire state change event. if (widget->IsActiveWidget()) notifyOf = kNotifyOfFocus; notifyOf |= kNotifyOfState; } else if (widget->IsMenuButton()) { // Can be a part of autocomplete. Accessible* compositeWidget = widget->ContainerWidget(); if (compositeWidget && compositeWidget->IsAutoComplete()) { widget = compositeWidget; notifyOf = kNotifyOfState; } // Autocomplete (like searchbar) can be inactive when popup hiddens notifyOf |= kNotifyOfFocus; } else if (widget == popup) { // Top level context menus and alerts. // Ignore submenus and menubar. When submenu is closed then sumbenu // container menuitem takes a focus via DOMMenuItemActive notification. // For menubars processing we listen DOMMenubarActive/Inactive // notifications. notifyOf = kNotifyOfFocus; } // Restore focus to where it was. if (notifyOf & kNotifyOfFocus) { FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("popuphiding", popup); #endif } // Fire expanded state change event. if (notifyOf & kNotifyOfState) { nsRefPtr<AccEvent> event = new AccStateChangeEvent(widget, states::EXPANDED, false); document->FireDelayedEvent(event); } }
bool TableAccessible::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 ? \ NS_LITERAL_STRING("layout table: " heuristic) : \ NS_LITERAL_STRING("data table: " heuristic); \ return isLayout; \ } #else #define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; } #endif Accessible* thisacc = AsAccessible(); // Need to see all elements while document is being edited. if (thisacc->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 (thisacc->HasARIARole()) { RETURN_LAYOUT_ANSWER(false, "Has role attribute"); } dom::Element* el = thisacc->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, NS_LITERAL_STRING("0"), eCaseMatters)) { RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout"); } // Check for legitimate data table attributes. nsAutoString summary; if (el->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) && !summary.IsEmpty()) { RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures"); } // Check for legitimate data table elements. Accessible* caption = thisacc->FirstChild(); 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)) { 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(kNameSpaceID_None, nsGkAtoms::headers) || cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) || cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) { RETURN_LAYOUT_ANSWER(false, "Has headers, scope, or abbr attribute -- legitimate table structures"); } Accessible* cell = thisacc->Document()->GetAccessible(cellElm); if (cell && cell->ChildCount() == 1 && cell->FirstChild()->IsAbbreviation()) { RETURN_LAYOUT_ANSWER(false, "has abbr -- legitimate table structures"); } } } } } } } // Check for nested tables. nsCOMPtr<nsIHTMLCollection> nestedTables = el->GetElementsByTagName(NS_LITERAL_STRING("table")); if (nestedTables->Length() > 0) { RETURN_LAYOUT_ANSWER(true, "Has a nested table within it"); } // 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!"); } nsMargin border; cellFrame->GetXULBorder(border); if (border.top && border.bottom && border.left && border.right) { RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell"); } // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on // forward. // Check for styled background color across rows (alternating background // color is a common feature for data tables). auto childCount = thisacc->ChildCount(); nscolor rowColor = 0; nscolor prevRowColor; for (auto childIdx = 0U; childIdx < childCount; childIdx++) { Accessible* child = thisacc->GetChildAt(childIdx); if (child->IsHTMLTableRow()) { prevRowColor = rowColor; nsIFrame* rowFrame = child->GetFrame(); MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up"); if (!rowFrame) { RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy"); } rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame); 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 = thisacc->Document()->GetFrame(); nsSize documentSize = documentFrame->GetSize(); if (documentSize.width > 0) { nsSize tableSize = thisacc->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"); } static const nsLiteralString tags[] = { NS_LITERAL_STRING("embed"), NS_LITERAL_STRING("object"), NS_LITERAL_STRING("iframe") }; for (auto& 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" ); }