STDMETHODIMP ia2Accessible::get_accessibleWithCaret(IUnknown** aAccessible, long* aCaretOffset) { if (!aAccessible || !aCaretOffset) return E_INVALIDARG; *aAccessible = nullptr; *aCaretOffset = -1; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; int32_t caretOffset = -1; Accessible* accWithCaret = SelectionMgr()->AccessibleWithCaret(&caretOffset); if (!accWithCaret || acc->Document() != accWithCaret->Document()) return S_FALSE; Accessible* child = accWithCaret; while (!child->IsDoc() && child != acc) child = child->Parent(); if (child != acc) return S_FALSE; *aAccessible = static_cast<IAccessible2*>( static_cast<AccessibleWrap*>(accWithCaret)); (*aAccessible)->AddRef(); *aCaretOffset = caretOffset; return S_OK; }
void FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::FocusNotificationTarget("active item changed", "Item", aItem); #endif // Nothing changed, happens for XUL trees and HTML selects. if (aItem && aItem == mActiveItem) return; mActiveItem = nullptr; if (aItem && aCheckIfActive) { Accessible* widget = aItem->ContainerWidget(); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveWidget(widget); #endif if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable()) return; } mActiveItem = aItem; // If active item is changed then fire accessible focus event on it, otherwise // if there's no an active item then fire focus event to accessible having // DOM focus. Accessible* target = FocusedAccessible(); if (target) DispatchFocusEvent(target->Document(), target); }
xpcAccessibleGeneric::~xpcAccessibleGeneric() { if (mIntl.IsNull()) { return; } xpcAccessibleDocument* xpcDoc = nullptr; if (mIntl.IsAccessible()) { Accessible* acc = mIntl.AsAccessible(); if (!acc->IsDoc() && !acc->IsApplication()) { xpcDoc = GetAccService()->GetXPCDocument(acc->Document()); xpcDoc->NotifyOfShutdown(acc); } } else { ProxyAccessible* proxy = mIntl.AsProxy(); if (!proxy->IsDoc()) { xpcDoc = GetAccService()->GetXPCDocument(proxy->Document()); xpcDoc->NotifyOfShutdown(proxy); } } }
void DocAccessibleWrap::UpdateFocusPathBounds() { if (!mFocusPath.Count()) { return; } if (IPCAccessibilityActive()) { DocAccessibleChild* ipcDoc = IPCDoc(); nsTArray<BatchData> boundsData(mFocusPath.Count()); for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { Accessible* accessible = iter.Data(); if (!accessible || accessible->IsDefunct()) { MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone."); continue; } auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc() ? 0 : reinterpret_cast<uint64_t>(accessible->UniqueID()); boundsData.AppendElement(BatchData( accessible->Document()->IPCDoc(), uid, 0, accessible->Bounds(), 0, nsString(), nsString(), nsString(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), nsTArray<Attribute>())); } ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData); } else if (SessionAccessibility* sessionAcc = SessionAccessibility::GetInstanceFor(this)) { nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count()); for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { accessibles.AppendElement( static_cast<AccessibleWrap*>(iter.Data().get())); } sessionAcc->UpdateCachedBounds(accessibles); } }
void FocusManager::ProcessFocusEvent(AccEvent* aEvent) { NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS, "Focus event is expected!"); // Emit focus event if event target is the active item. Otherwise then check // if it's still focused and then update active item and emit focus event. Accessible* target = aEvent->GetAccessible(); if (target != mActiveItem) { // Check if still focused. Otherwise we can end up with storing the active // item for control that isn't focused anymore. DocAccessible* document = aEvent->GetDocAccessible(); nsINode* focusedNode = FocusedDOMNode(); if (!focusedNode) return; Accessible* DOMFocus = document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode); if (target != DOMFocus) return; Accessible* activeItem = target->CurrentItem(); if (activeItem) { mActiveItem = activeItem; target = activeItem; } } // Fire menu start/end events for ARIA menus. if (target->IsARIARole(nsGkAtoms::menuitem)) { // The focus was moved into menu. Accessible* ARIAMenubar = nullptr; for (Accessible* parent = target->Parent(); parent; parent = parent->Parent()) { if (parent->IsARIARole(nsGkAtoms::menubar)) { ARIAMenubar = parent; break; } // Go up in the parent chain of the menu hierarchy. if (!parent->IsARIARole(nsGkAtoms::menuitem) && !parent->IsARIARole(nsGkAtoms::menu)) { break; } } if (ARIAMenubar != mActiveARIAMenubar) { // Leaving ARIA menu. Fire menu_end event on current menubar. if (mActiveARIAMenubar) { RefPtr<AccEvent> menuEndEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuEndEvent); } mActiveARIAMenubar = ARIAMenubar; // Entering ARIA menu. Fire menu_start event. if (mActiveARIAMenubar) { RefPtr<AccEvent> menuStartEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_START, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuStartEvent); } } } else if (mActiveARIAMenubar) { // Focus left a menu. Fire menu_end event. RefPtr<AccEvent> menuEndEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuEndEvent); mActiveARIAMenubar = nullptr; } #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::FocusNotificationTarget("fire focus event", "Target", target); #endif // Reset cached caret value. The cache will be updated upon processing the // next caret move event. This ensures that we will return the correct caret // offset before the caret move event is handled. SelectionMgr()->ResetCaretOffset(); RefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput()); nsEventShell::FireEvent(focusEvent); // Fire scrolling_start event when the document receives the focus if it has // an anchor jump. If an accessible within the document receive the focus // then null out the anchor jump because it no longer applies. DocAccessible* targetDocument = target->Document(); Accessible* anchorJump = targetDocument->AnchorJump(); if (anchorJump) { if (target == targetDocument) { // XXX: bug 625699, note in some cases the node could go away before we // we receive focus event, for example if the node is removed from DOM. nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START, anchorJump, aEvent->FromUserInput()); } targetDocument->SetAnchorJump(nullptr); } }
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" ); }
void FocusManager::ProcessFocusEvent(AccEvent* aEvent) { NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS, "Focus event is expected!"); // Emit focus event if event target is the active item. Otherwise then check // if it's still focused and then update active item and emit focus event. Accessible* target = aEvent->GetAccessible(); if (target != mActiveItem) { // Check if still focused. Otherwise we can end up with storing the active // item for control that isn't focused anymore. DocAccessible* document = aEvent->GetDocAccessible(); nsINode* focusedNode = FocusedDOMNode(); if (!focusedNode) return; Accessible* DOMFocus = document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode); if (target != DOMFocus) return; Accessible* activeItem = target->CurrentItem(); if (activeItem) { mActiveItem = activeItem; target = activeItem; } } // Fire menu start/end events for ARIA menus. if (target->IsARIARole(nsGkAtoms::menuitem)) { // The focus was moved into menu. bool tryOwnsParent = true; Accessible* ARIAMenubar = nullptr; Accessible* child = target; Accessible* parent = child->Parent(); while (parent) { nsRoleMapEntry* roleMap = parent->ARIARoleMap(); if (roleMap) { if (roleMap->Is(nsGkAtoms::menubar)) { ARIAMenubar = parent; break; } // Go up in the parent chain of the menu hierarchy. if (roleMap->Is(nsGkAtoms::menuitem) || roleMap->Is(nsGkAtoms::menu)) { child = parent; parent = child->Parent(); tryOwnsParent = true; continue; } } // If no required context role then check aria-owns relation. if (!tryOwnsParent) break; RelatedAccIterator iter(child->Document(), child->GetContent(), nsGkAtoms::aria_owns); parent = iter.Next(); tryOwnsParent = false; } if (ARIAMenubar != mActiveARIAMenubar) { // Leaving ARIA menu. Fire menu_end event on current menubar. if (mActiveARIAMenubar) { nsRefPtr<AccEvent> menuEndEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuEndEvent); } mActiveARIAMenubar = ARIAMenubar; // Entering ARIA menu. Fire menu_start event. if (mActiveARIAMenubar) { nsRefPtr<AccEvent> menuStartEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_START, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuStartEvent); } } } else if (mActiveARIAMenubar) { // Focus left a menu. Fire menu_end event. nsRefPtr<AccEvent> menuEndEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, aEvent->FromUserInput()); nsEventShell::FireEvent(menuEndEvent); mActiveARIAMenubar = nullptr; } #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::FocusNotificationTarget("fire focus event", "Target", target); #endif nsRefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput()); nsEventShell::FireEvent(focusEvent); // Fire scrolling_start event when the document receives the focus if it has // an anchor jump. If an accessible within the document receive the focus // then null out the anchor jump because it no longer applies. DocAccessible* targetDocument = target->Document(); Accessible* anchorJump = targetDocument->AnchorJump(); if (anchorJump) { if (target == targetDocument) { // XXX: bug 625699, note in some cases the node could go away before we // we receive focus event, for example if the node is removed from DOM. nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START, anchorJump, aEvent->FromUserInput()); } targetDocument->SetAnchorJump(nullptr); } }
void DocAccessibleWrap::CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam) { RefPtr<DocAccessibleWrap> docAcc( dont_AddRef(reinterpret_cast<DocAccessibleWrap*>(aDocAccParam))); if (!docAcc) { return; } nsIPresShell* presShell = docAcc->PresShell(); if (!presShell) { return; } nsIFrame* rootFrame = presShell->GetRootFrame(); if (!rootFrame) { return; } nsTArray<nsIFrame*> frames; nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect(); nsLayoutUtils::GetFramesForArea( presShell->GetRootFrame(), scrollPort, frames, nsLayoutUtils::FrameForPointFlags::ONLY_VISIBLE); AccessibleHashtable inViewAccs; for (size_t i = 0; i < frames.Length(); i++) { nsIContent* content = frames.ElementAt(i)->GetContent(); if (!content) { continue; } Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content); if (!visibleAcc) { continue; } for (Accessible* acc = visibleAcc; acc && acc != docAcc->Parent(); acc = acc->Parent()) { if (inViewAccs.Contains(acc->UniqueID())) { break; } inViewAccs.Put(acc->UniqueID(), acc); } } if (IPCAccessibilityActive()) { DocAccessibleChild* ipcDoc = docAcc->IPCDoc(); nsTArray<BatchData> cacheData(inViewAccs.Count()); for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) { Accessible* accessible = iter.Data(); auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc() ? 0 : reinterpret_cast<uint64_t>(accessible->UniqueID()); cacheData.AppendElement( BatchData(accessible->Document()->IPCDoc(), uid, accessible->State(), accessible->Bounds(), accessible->ActionCount(), nsString(), nsString(), nsString(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), nsTArray<Attribute>())); } ipcDoc->SendBatch(eBatch_Viewport, cacheData); } else if (SessionAccessibility* sessionAcc = SessionAccessibility::GetInstanceFor(docAcc)) { nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count()); for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) { accessibles.AppendElement( static_cast<AccessibleWrap*>(iter.Data().get())); } sessionAcc->ReplaceViewportCache(accessibles); } if (docAcc->mCacheRefreshTimer) { docAcc->mCacheRefreshTimer = nullptr; } }