void NotificationController::WillRefresh(mozilla::TimeStamp aTime) { // If the document accessible that notification collector was created for is // now shut down, don't process notifications anymore. NS_ASSERTION(mDocument, "The document was shut down while refresh observer is attached!"); if (!mDocument) return; // Any generic notifications should be queued if we're processing content // insertions or generic notifications. mObservingState = eRefreshProcessingForUpdate; // Initial accessible tree construction. if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) { // If document is not bound to parent at this point then the document is not // ready yet (process notifications later). if (!mDocument->IsBoundToParent()) { mObservingState = eRefreshObserving; return; } #ifdef DEBUG_NOTIFICATIONS printf("\ninitial tree created, document: %p, document node: %p\n", mDocument.get(), mDocument->GetDocumentNode()); #endif mDocument->DoInitialUpdate(); NS_ASSERTION(mContentInsertions.Length() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Process content inserted notifications to update the tree. Process other // notifications like DOM events and then flush event queue. If any new // notifications are queued during this processing then they will be processed // on next refresh. If notification processing queues up new events then they // are processed in this refresh. If events processing queues up new events // then new events are processed on next refresh. // Note: notification processing or event handling may shut down the owning // document accessible. // Process only currently queued content inserted notifications. nsTArray<nsRefPtr<ContentInsertion> > contentInsertions; contentInsertions.SwapElements(mContentInsertions); PRUint32 insertionCount = contentInsertions.Length(); for (PRUint32 idx = 0; idx < insertionCount; idx++) { contentInsertions[idx]->Process(); if (!mDocument) return; } // Process rendered text change notifications. mTextHash.EnumerateEntries(TextEnumerator, mDocument); mTextHash.Clear(); // Bind hanging child documents. PRUint32 hangingDocCnt = mHangingChildDocuments.Length(); for (PRUint32 idx = 0; idx < hangingDocCnt; idx++) { DocAccessible* childDoc = mHangingChildDocuments[idx]; if (childDoc->IsDefunct()) continue; nsIContent* ownerContent = mDocument->GetDocumentNode()-> FindContentForSubDocument(childDoc->GetDocumentNode()); if (ownerContent) { Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { if (mDocument->AppendChildDocument(childDoc)) continue; outerDocAcc->RemoveChild(childDoc); } // Failed to bind the child document, destroy it. childDoc->Shutdown(); } } mHangingChildDocuments.Clear(); // If the document is ready and all its subdocuments are completely loaded // then process the document load. if (mDocument->HasLoadState(DocAccessible::eReady) && !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && hangingDocCnt == 0) { PRUint32 childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0; for (; childDocIdx < childDocCnt; childDocIdx++) { DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx); if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) break; } if (childDocIdx == childDocCnt) { mDocument->ProcessLoad(); if (!mDocument) return; } } // Process only currently queued generic notifications. nsTArray < nsRefPtr<Notification> > notifications; notifications.SwapElements(mNotifications); PRUint32 notificationCount = notifications.Length(); for (PRUint32 idx = 0; idx < notificationCount; idx++) { notifications[idx]->Process(); if (!mDocument) return; } // Process invalidation list of the document after all accessible tree // modification are done. mDocument->ProcessInvalidationList(); // If a generic notification occurs after this point then we may be allowed to // process it synchronously. mObservingState = eRefreshObserving; // Process only currently queued events. nsTArray<nsRefPtr<AccEvent> > events; events.SwapElements(mEvents); PRUint32 eventCount = events.Length(); for (PRUint32 idx = 0; idx < eventCount; idx++) { AccEvent* accEvent = events[idx]; if (accEvent->mEventRule != AccEvent::eDoNotEmit) { Accessible* target = accEvent->GetAccessible(); if (!target || target->IsDefunct()) continue; // Dispatch the focus event if target is still focused. if (accEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) { FocusMgr()->ProcessFocusEvent(accEvent); continue; } mDocument->ProcessPendingEvent(accEvent); // Fire text change event caused by tree mutation. AccMutationEvent* showOrHideEvent = downcast_accEvent(accEvent); if (showOrHideEvent) { if (showOrHideEvent->mTextChangeEvent) mDocument->ProcessPendingEvent(showOrHideEvent->mTextChangeEvent); } } if (!mDocument) return; } // Stop further processing if there are no new notifications of any kind or // events and document load is processed. if (mContentInsertions.Length() == 0 && mNotifications.Length() == 0 && mEvents.Length() == 0 && mTextHash.Count() == 0 && mHangingChildDocuments.Length() == 0 && mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && mPresShell->RemoveRefreshObserver(this, Flush_Display)) { mObservingState = eNotObservingRefresh; } }
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); } } }
HyperTextAccessible* nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward) { Accessible* root = GetActiveRoot(); Accessible* accessible = aAccessible; while (true) { Accessible* child = nullptr; while ((child = (aBackward ? accessible->LastChild() : accessible->FirstChild()))) { accessible = child; if (child->IsHyperText()) return child->AsHyperText(); } Accessible* sibling = nullptr; Accessible* temp = accessible; do { if (temp == root) break; if (temp != aAccessible && temp->IsHyperText()) return temp->AsHyperText(); sibling = aBackward ? temp->PrevSibling() : temp->NextSibling(); if (sibling) break; } while ((temp = temp->Parent())); if (!sibling) break; accessible = sibling; if (accessible->IsHyperText()) return accessible->AsHyperText(); } return nullptr; }
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 EventQueue::ProcessEventQueue() { // Process only currently queued events. nsTArray<nsRefPtr<AccEvent> > events; events.SwapElements(mEvents); uint32_t eventCount = events.Length(); #ifdef A11Y_LOG if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) { logging::MsgBegin("EVENTS", "events processing"); logging::Address("document", mDocument); logging::MsgEnd(); } #endif for (uint32_t idx = 0; idx < eventCount; idx++) { AccEvent* event = events[idx]; if (event->mEventRule != AccEvent::eDoNotEmit) { Accessible* target = event->GetAccessible(); if (!target || target->IsDefunct()) continue; // Dispatch the focus event if target is still focused. if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) { FocusMgr()->ProcessFocusEvent(event); continue; } // Dispatch caret moved and text selection change events. if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) { AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(event); HyperTextAccessible* hyperText = target->AsHyperText(); if (hyperText && NS_SUCCEEDED(hyperText->GetCaretOffset(&caretMoveEvent->mCaretOffset))) { nsEventShell::FireEvent(caretMoveEvent); // There's a selection so fire selection change as well. int32_t selectionCount; hyperText->GetSelectionCount(&selectionCount); if (selectionCount) nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, hyperText); } continue; } // Fire selected state change events in support to selection events. if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) { nsEventShell::FireEvent(event->mAccessible, states::SELECTED, true, event->mIsFromUserInput); } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) { nsEventShell::FireEvent(event->mAccessible, states::SELECTED, false, event->mIsFromUserInput); } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) { AccSelChangeEvent* selChangeEvent = downcast_accEvent(event); nsEventShell::FireEvent(event->mAccessible, states::SELECTED, (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), event->mIsFromUserInput); if (selChangeEvent->mPackedEvent) { nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible, states::SELECTED, (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), selChangeEvent->mPackedEvent->mIsFromUserInput); } } nsEventShell::FireEvent(event); // Fire text change events. AccMutationEvent* mutationEvent = downcast_accEvent(event); if (mutationEvent) { if (mutationEvent->mTextChangeEvent) nsEventShell::FireEvent(mutationEvent->mTextChangeEvent); } } if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE) mDocument->ShutdownChildrenInSubtree(event->mAccessible); if (!mDocument) return; } }
void NotificationController::WillRefresh(mozilla::TimeStamp aTime) { Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer; // If the document accessible that notification collector was created for is // now shut down, don't process notifications anymore. NS_ASSERTION(mDocument, "The document was shut down while refresh observer is attached!"); if (!mDocument) return; // Any generic notifications should be queued if we're processing content // insertions or generic notifications. mObservingState = eRefreshProcessingForUpdate; // Initial accessible tree construction. if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) { // If document is not bound to parent at this point then the document is not // ready yet (process notifications later). if (!mDocument->IsBoundToParent()) { mObservingState = eRefreshObserving; return; } #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree)) { logging::MsgBegin("TREE", "initial tree created"); logging::Address("document", mDocument); logging::MsgEnd(); } #endif mDocument->DoInitialUpdate(); NS_ASSERTION(mContentInsertions.Length() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Process content inserted notifications to update the tree. Process other // notifications like DOM events and then flush event queue. If any new // notifications are queued during this processing then they will be processed // on next refresh. If notification processing queues up new events then they // are processed in this refresh. If events processing queues up new events // then new events are processed on next refresh. // Note: notification processing or event handling may shut down the owning // document accessible. // Process only currently queued content inserted notifications. nsTArray<nsRefPtr<ContentInsertion> > contentInsertions; contentInsertions.SwapElements(mContentInsertions); uint32_t insertionCount = contentInsertions.Length(); for (uint32_t idx = 0; idx < insertionCount; idx++) { contentInsertions[idx]->Process(); if (!mDocument) return; } // Process rendered text change notifications. mTextHash.EnumerateEntries(TextEnumerator, mDocument); mTextHash.Clear(); // Bind hanging child documents. uint32_t hangingDocCnt = mHangingChildDocuments.Length(); for (uint32_t idx = 0; idx < hangingDocCnt; idx++) { DocAccessible* childDoc = mHangingChildDocuments[idx]; if (childDoc->IsDefunct()) continue; nsIContent* ownerContent = mDocument->DocumentNode()-> FindContentForSubDocument(childDoc->DocumentNode()); if (ownerContent) { Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { if (mDocument->AppendChildDocument(childDoc)) continue; outerDocAcc->RemoveChild(childDoc); } // Failed to bind the child document, destroy it. childDoc->Shutdown(); } } mHangingChildDocuments.Clear(); // If the document is ready and all its subdocuments are completely loaded // then process the document load. if (mDocument->HasLoadState(DocAccessible::eReady) && !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && hangingDocCnt == 0) { uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0; for (; childDocIdx < childDocCnt; childDocIdx++) { DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx); if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) break; } if (childDocIdx == childDocCnt) { mDocument->ProcessLoad(); if (!mDocument) return; } } // Process only currently queued generic notifications. nsTArray < nsRefPtr<Notification> > notifications; notifications.SwapElements(mNotifications); uint32_t notificationCount = notifications.Length(); for (uint32_t idx = 0; idx < notificationCount; idx++) { notifications[idx]->Process(); if (!mDocument) return; } // Process invalidation list of the document after all accessible tree // modification are done. mDocument->ProcessInvalidationList(); // If a generic notification occurs after this point then we may be allowed to // process it synchronously. mObservingState = eRefreshObserving; ProcessEventQueue(); if (!mDocument) return; // Stop further processing if there are no new notifications of any kind or // events and document load is processed. if (mContentInsertions.Length() == 0 && mNotifications.Length() == 0 && mEvents.Length() == 0 && mTextHash.Count() == 0 && mHangingChildDocuments.Length() == 0 && mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && mPresShell->RemoveRefreshObserver(this, Flush_Display)) { mObservingState = eNotObservingRefresh; } }
TableAccessible* ARIAGridCellAccessible::Table() const { Accessible* table = nsAccUtils::TableFor(Row()); return table ? table->AsTable() : nullptr; }
// RootAccessible protected void RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent) { nsCOMPtr<nsIDOMNSEvent> DOMNSEvent(do_QueryInterface(aDOMEvent)); nsCOMPtr<nsIDOMEventTarget> DOMEventTarget; DOMNSEvent->GetOriginalTarget(getter_AddRefs(DOMEventTarget)); nsCOMPtr<nsINode> origTargetNode(do_QueryInterface(DOMEventTarget)); nsAutoString eventType; aDOMEvent->GetType(eventType); if (eventType.EqualsLiteral("popuphiding")) { HandlePopupHidingEvent(origTargetNode); return; } DocAccessible* targetDocument = GetAccService()-> GetDocAccessible(origTargetNode->OwnerDoc()); NS_ASSERTION(targetDocument, "No document while accessible is in document?!"); Accessible* accessible = targetDocument->GetAccessibleOrContainer(origTargetNode); if (!accessible) return; nsINode* targetNode = accessible->GetNode(); #ifdef MOZ_XUL XULTreeAccessible* treeAcc = accessible->AsXULTree(); if (treeAcc) { if (eventType.EqualsLiteral("TreeRowCountChanged")) { HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); return; } if (eventType.EqualsLiteral("TreeInvalidated")) { HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); return; } } #endif if (eventType.EqualsLiteral("RadioStateChange")) { PRUint64 state = accessible->State(); // radiogroup in prefWindow is exposed as a list, // and panebutton is exposed as XULListitem in A11y. // XULListitemAccessible::GetStateInternal uses STATE_SELECTED in this case, // so we need to check states::SELECTED also. bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); if (isEnabled) { FocusMgr()->ActiveItemChanged(accessible); A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("RadioStateChange", accessible) } return; }
void EventQueue::ProcessEventQueue() { // Process only currently queued events. nsTArray<RefPtr<AccEvent> > events; events.SwapElements(mEvents); uint32_t eventCount = events.Length(); #ifdef A11Y_LOG if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) { logging::MsgBegin("EVENTS", "events processing"); logging::Address("document", mDocument); logging::MsgEnd(); } #endif for (uint32_t idx = 0; idx < eventCount; idx++) { AccEvent* event = events[idx]; if (event->mEventRule != AccEvent::eDoNotEmit) { Accessible* target = event->GetAccessible(); if (!target || target->IsDefunct()) continue; // Dispatch the focus event if target is still focused. if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) { FocusMgr()->ProcessFocusEvent(event); continue; } // Dispatch caret moved and text selection change events. if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) { SelectionMgr()->ProcessTextSelChangeEvent(event); continue; } // Fire selected state change events in support to selection events. if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) { nsEventShell::FireEvent(event->mAccessible, states::SELECTED, true, event->mIsFromUserInput); } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) { nsEventShell::FireEvent(event->mAccessible, states::SELECTED, false, event->mIsFromUserInput); } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) { AccSelChangeEvent* selChangeEvent = downcast_accEvent(event); nsEventShell::FireEvent(event->mAccessible, states::SELECTED, (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), event->mIsFromUserInput); if (selChangeEvent->mPackedEvent) { nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible, states::SELECTED, (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), selChangeEvent->mPackedEvent->mIsFromUserInput); } } nsEventShell::FireEvent(event); } if (!mDocument) return; } }
void NotificationController::WillRefresh(mozilla::TimeStamp aTime) { Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer; // If the document accessible that notification collector was created for is // now shut down, don't process notifications anymore. NS_ASSERTION(mDocument, "The document was shut down while refresh observer is attached!"); if (!mDocument) return; if (mObservingState == eRefreshProcessing || mObservingState == eRefreshProcessingForUpdate) return; // Any generic notifications should be queued if we're processing content // insertions or generic notifications. mObservingState = eRefreshProcessingForUpdate; // Initial accessible tree construction. if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) { // If document is not bound to parent at this point then the document is not // ready yet (process notifications later). if (!mDocument->IsBoundToParent()) { mObservingState = eRefreshObserving; return; } #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree)) { logging::MsgBegin("TREE", "initial tree created"); logging::Address("document", mDocument); logging::MsgEnd(); } #endif mDocument->DoInitialUpdate(); NS_ASSERTION(mContentInsertions.Length() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Initialize scroll support if needed. if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized)) mDocument->AddScrollListener(); // Process content inserted notifications to update the tree. Process other // notifications like DOM events and then flush event queue. If any new // notifications are queued during this processing then they will be processed // on next refresh. If notification processing queues up new events then they // are processed in this refresh. If events processing queues up new events // then new events are processed on next refresh. // Note: notification processing or event handling may shut down the owning // document accessible. // Process only currently queued content inserted notifications. nsTArray<RefPtr<ContentInsertion> > contentInsertions; contentInsertions.SwapElements(mContentInsertions); uint32_t insertionCount = contentInsertions.Length(); for (uint32_t idx = 0; idx < insertionCount; idx++) { contentInsertions[idx]->Process(); if (!mDocument) return; } // Process rendered text change notifications. for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) { nsCOMPtrHashKey<nsIContent>* entry = iter.Get(); nsIContent* textNode = entry->GetKey(); Accessible* textAcc = mDocument->GetAccessible(textNode); // If the text node is not in tree or doesn't have frame then this case should // have been handled already by content removal notifications. nsINode* containerNode = textNode->GetParentNode(); if (!containerNode) { NS_ASSERTION(!textAcc, "Text node was removed but accessible is kept alive!"); continue; } nsIFrame* textFrame = textNode->GetPrimaryFrame(); if (!textFrame) { NS_ASSERTION(!textAcc, "Text node isn't rendered but accessible is kept alive!"); continue; } nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nullptr; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node lost its content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif mDocument->ContentRemoved(containerElm, textNode); continue; } // Update text of the accessible and fire text change events. #ifdef A11Y_LOG if (logging::IsEnabled(logging::eText)) { logging::MsgBegin("TEXT", "text may be changed"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEntry("old text '%s'", NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get()); logging::MsgEntry("new text: '%s'", NS_ConvertUTF16toUTF8(text).get()); logging::MsgEnd(); } #endif TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text); continue; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node gains new content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif // Make sure the text node is in accessible document still. Accessible* container = mDocument->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); mDocument->ProcessContentInserted(container, &insertedContents); } } } mTextHash.Clear(); // Bind hanging child documents. uint32_t hangingDocCnt = mHangingChildDocuments.Length(); nsTArray<RefPtr<DocAccessible>> newChildDocs; for (uint32_t idx = 0; idx < hangingDocCnt; idx++) { DocAccessible* childDoc = mHangingChildDocuments[idx]; if (childDoc->IsDefunct()) continue; nsIContent* ownerContent = mDocument->DocumentNode()-> FindContentForSubDocument(childDoc->DocumentNode()); if (ownerContent) { Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { if (mDocument->AppendChildDocument(childDoc)) { newChildDocs.AppendElement(Move(mHangingChildDocuments[idx])); continue; } outerDocAcc->RemoveChild(childDoc); } // Failed to bind the child document, destroy it. childDoc->Shutdown(); } } mHangingChildDocuments.Clear(); // If the document is ready and all its subdocuments are completely loaded // then process the document load. if (mDocument->HasLoadState(DocAccessible::eReady) && !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && hangingDocCnt == 0) { uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0; for (; childDocIdx < childDocCnt; childDocIdx++) { DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx); if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) break; } if (childDocIdx == childDocCnt) { mDocument->ProcessLoad(); if (!mDocument) return; } } // Process only currently queued generic notifications. nsTArray < RefPtr<Notification> > notifications; notifications.SwapElements(mNotifications); uint32_t notificationCount = notifications.Length(); for (uint32_t idx = 0; idx < notificationCount; idx++) { notifications[idx]->Process(); if (!mDocument) return; } // Process invalidation list of the document after all accessible tree // modification are done. mDocument->ProcessInvalidationList(); // If a generic notification occurs after this point then we may be allowed to // process it synchronously. However we do not want to reenter if fireing // events causes script to run. mObservingState = eRefreshProcessing; ProcessEventQueue(); if (IPCAccessibilityActive()) { size_t newDocCount = newChildDocs.Length(); for (size_t i = 0; i < newDocCount; i++) { DocAccessible* childDoc = newChildDocs[i]; Accessible* parent = childDoc->Parent(); DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc(); uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID()); MOZ_ASSERT(id); DocAccessibleChild* ipcDoc = childDoc->IPCDoc(); if (ipcDoc) { parentIPCDoc->SendBindChildDoc(ipcDoc, id); continue; } ipcDoc = new DocAccessibleChild(childDoc); childDoc->SetIPCDoc(ipcDoc); nsCOMPtr<nsITabChild> tabChild = do_GetInterface(mDocument->DocumentNode()->GetDocShell()); static_cast<TabChild*>(tabChild.get())-> SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id); } } mObservingState = eRefreshObserving; if (!mDocument) return; // Stop further processing if there are no new notifications of any kind or // events and document load is processed. if (mContentInsertions.IsEmpty() && mNotifications.IsEmpty() && mEvents.IsEmpty() && mTextHash.Count() == 0 && mHangingChildDocuments.IsEmpty() && mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) && mPresShell->RemoveRefreshObserver(this, Flush_Display)) { mObservingState = eNotObservingRefresh; } }
EventTree* EventTree::FindOrInsert(Accessible* aContainer) { if (!mFirst) { mFirst.reset(new EventTree(aContainer, true)); return mFirst.get(); } EventTree* prevNode = nullptr; EventTree* node = mFirst.get(); do { MOZ_ASSERT(!node->mContainer->IsApplication(), "No event for application accessible is expected here"); MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive"); // Case of same target. if (node->mContainer == aContainer) { return node; } // Check if the given container is contained by a current node Accessible* top = mContainer ? mContainer : aContainer->Document(); Accessible* parent = aContainer; while (parent) { // Reached a top, no match for a current event. if (parent == top) { break; } // We got a match. if (parent->Parent() == node->mContainer) { return node->FindOrInsert(aContainer); } parent = parent->Parent(); MOZ_ASSERT(parent, "Wrong tree"); } // If the given container contains a current node // then // if show or hide of the given node contains a grand parent of the current node // then ignore the current node and its show and hide events // otherwise ignore the current node, but not its show and hide events Accessible* curParent = node->mContainer; while (curParent && !curParent->IsDoc()) { if (curParent->Parent() != aContainer) { curParent = curParent->Parent(); continue; } // Insert the tail node into the hierarchy between the current node and // its parent. node->mFireReorder = false; UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst; UniquePtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty())); newNode->mFirst = Move(nodeOwnerRef); nodeOwnerRef = Move(newNode); nodeOwnerRef->mNext = Move(node->mNext); // Check if a next node is contained by the given node too, and move them // under the given node if so. prevNode = nodeOwnerRef.get(); node = nodeOwnerRef->mNext.get(); UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext; EventTree* insNode = nodeOwnerRef->mFirst.get(); while (node) { Accessible* curParent = node->mContainer; while (curParent && !curParent->IsDoc()) { if (curParent->Parent() != aContainer) { curParent = curParent->Parent(); continue; } MOZ_ASSERT(!insNode->mNext); node->mFireReorder = false; insNode->mNext = Move(*nodeRef); insNode = insNode->mNext.get(); prevNode->mNext = Move(node->mNext); node = prevNode; break; } prevNode = node; nodeRef = &node->mNext; node = node->mNext.get(); } return nodeOwnerRef.get(); } prevNode = node; } while ((node = node->mNext.get())); MOZ_ASSERT(prevNode, "Nowhere to insert"); MOZ_ASSERT(!prevNode->mNext, "Taken by another node"); // If 'this' node contains the given container accessible, then // do not emit a reorder event for the container // if a dependent show event target contains the given container then do not // emit show / hide events (see Process() method) prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty())); return prevNode->mNext.get(); }
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" ); }
PLDHashOperator NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry, void* aUserArg) { DocAccessible* document = static_cast<DocAccessible*>(aUserArg); nsIContent* textNode = aEntry->GetKey(); Accessible* textAcc = document->GetAccessible(textNode); // If the text node is not in tree or doesn't have frame then this case should // have been handled already by content removal notifications. nsINode* containerNode = textNode->GetNodeParent(); if (!containerNode) { NS_ASSERTION(!textAcc, "Text node was removed but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIFrame* textFrame = textNode->GetPrimaryFrame(); if (!textFrame) { NS_ASSERTION(!textAcc, "Text node isn't rendered but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nsnull; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef DEBUG_NOTIFICATIONS PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\npending text node removal: container: %s@id='%s', index in container: %d\n\n", tag.get(), id.get(), index); #endif document->ContentRemoved(containerElm, textNode); return PL_DHASH_NEXT; } // Update text of the accessible and fire text change events. #ifdef DEBUG_TEXTCHANGE PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\ntext may be changed: container: %s@id='%s', index in container: %d, old text '%s', new text: '%s'\n\n", tag.get(), id.get(), index, NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get(), NS_ConvertUTF16toUTF8(text).get()); #endif TextUpdater::Run(document, textAcc->AsTextLeaf(), text); return PL_DHASH_NEXT; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef DEBUG_NOTIFICATIONS PRUint32 index = containerNode->IndexOf(textNode); nsCAutoString tag; nsCAutoString id; if (containerElm) { containerElm->Tag()->ToUTF8String(tag); nsIAtom* atomid = containerElm->GetID(); if (atomid) atomid->ToUTF8String(id); } printf("\npending text node insertion: container: %s@id='%s', index in container: %d\n\n", tag.get(), id.get(), index); #endif // Make sure the text node is in accessible document still. Accessible* container = document->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); document->ProcessContentInserted(container, &insertedContents); } } return PL_DHASH_NEXT; }
Accessible* HTMLTableAccessible::Caption() { Accessible* child = mChildren.SafeElementAt(0, nullptr); return child && child->Role() == roles::CAPTION ? child : nullptr; }
uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer, bool* aIsHierarchical) { uint32_t itemCount = 0; switch (aContainer->Role()) { case roles::TABLE: if (nsCoreUtils::GetUIntAttr(aContainer->GetContent(), nsGkAtoms::aria_rowcount, (int32_t*)&itemCount)) { break; } if (TableAccessible* tableAcc = aContainer->AsTable()) { return tableAcc->RowCount(); } break; case roles::ROW: if (Accessible* table = nsAccUtils::TableFor(aContainer)) { if (nsCoreUtils::GetUIntAttr(table->GetContent(), nsGkAtoms::aria_colcount, (int32_t*)&itemCount)) { break; } if (TableAccessible* tableAcc = table->AsTable()) { return tableAcc->ColCount(); } } break; case roles::OUTLINE: case roles::LIST: case roles::MENUBAR: case roles::MENUPOPUP: case roles::COMBOBOX: case roles::GROUPING: case roles::TREE_TABLE: case roles::COMBOBOX_LIST: case roles::LISTBOX: case roles::DEFINITION_LIST: case roles::EDITCOMBOBOX: case roles::RADIO_GROUP: case roles::PAGETABLIST: { Accessible* childItem = AccGroupInfo::FirstItemOf(aContainer); if (!childItem) { childItem = aContainer->FirstChild(); if (childItem && childItem->IsTextLeaf()) { // First child can be a text leaf, check its sibling for an item. childItem = childItem->NextSibling(); } } if (childItem) { GroupPos groupPos = childItem->GroupPosition(); itemCount = groupPos.setSize; if (groupPos.level && aIsHierarchical) { *aIsHierarchical = true; } } break; } default: break; } return itemCount; }
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->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"); } if (mContent->Tag() != nsGkAtoms::table) RETURN_LAYOUT_ANSWER(true, "table built by CSS display:table style"); // Check if datatable attribute has "0" value. if (mContent->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->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->IsHTML()) continue; if (childElm->Tag() == nsGkAtoms::col || childElm->Tag() == nsGkAtoms::colgroup || childElm->Tag() == nsGkAtoms::tfoot || childElm->Tag() == nsGkAtoms::thead) { RETURN_LAYOUT_ANSWER(false, "Has col, colgroup, tfoot or thead -- legitimate table structures"); } if (childElm->Tag() == nsGkAtoms::tbody) { for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm; rowElm = rowElm->GetNextSibling()) { if (rowElm->IsHTML() && rowElm->Tag() == nsGkAtoms::tr) { for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm; cellElm = cellElm->GetNextSibling()) { if (cellElm->IsHTML()) { if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) { RETURN_LAYOUT_ANSWER(false, "Has th -- legitimate table structures"); } if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) || cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) || cellElm->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 int32_t columns, rows; GetColumnCount(&columns); if (columns <=1) { RETURN_LAYOUT_ANSWER(true, "Has only 1 column"); } GetRowCount(&rows); if (rows <=1) { RETURN_LAYOUT_ANSWER(true, "Has only 1 row"); } // Check for many columns if (columns >= 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? nsTableOuterFrame* 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->GetBorder(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()->mBackgroundColor; if (childIdx > 0 && prevRowColor != rowColor) RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered"); } } // Check for many rows const int32_t kMaxLayoutRows = 20; if (rows > 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 (rows * columns <= 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("applet")) || HasDescendant(NS_LITERAL_STRING("iframe"))) { RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements"); } RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data"); }
void TextAttrsMgr::GetAttributes(nsIPersistentProperties* aAttributes, uint32_t* aStartOffset, uint32_t* aEndOffset) { // 1. Hyper text accessible must be specified always. // 2. Offset accessible and result hyper text offsets must be specified in // the case of text attributes. // 3. Offset accessible and result hyper text offsets must not be specified // but include default text attributes flag and attributes list must be // specified in the case of default text attributes. NS_PRECONDITION(mHyperTextAcc && ((mOffsetAcc && mOffsetAccIdx != -1 && aStartOffset && aEndOffset) || (!mOffsetAcc && mOffsetAccIdx == -1 && !aStartOffset && !aEndOffset && mIncludeDefAttrs && aAttributes)), "Wrong usage of TextAttrsMgr!"); // Embedded objects are combined into own range with empty attributes set. if (mOffsetAcc && !mOffsetAcc->IsText()) { for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) { Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx); if (currAcc->IsText()) break; (*aStartOffset)--; } uint32_t childCount = mHyperTextAcc->ChildCount(); for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childCount; childIdx++) { Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx); if (currAcc->IsText()) break; (*aEndOffset)++; } return; } // Get the content and frame of the accessible. In the case of document // accessible it's role content and root frame. nsIContent* hyperTextElm = mHyperTextAcc->GetContent(); if (!hyperTextElm) return; // XXX: we don't support text attrs on document with no body nsIFrame* rootFrame = mHyperTextAcc->GetFrame(); MOZ_ASSERT(rootFrame, "No frame for accessible!"); if (!rootFrame) return; nsIContent *offsetNode = nullptr, *offsetElm = nullptr; nsIFrame *frame = nullptr; if (mOffsetAcc) { offsetNode = mOffsetAcc->GetContent(); offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode); MOZ_ASSERT(offsetElm, "No element for offset accessible!"); if (!offsetElm) return; frame = offsetElm->GetPrimaryFrame(); } // "language" text attribute LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode); // "aria-invalid" text attribute InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode); // "background-color" text attribute BGColorTextAttr bgColorTextAttr(rootFrame, frame); // "color" text attribute ColorTextAttr colorTextAttr(rootFrame, frame); // "font-family" text attribute FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame); // "font-size" text attribute FontSizeTextAttr fontSizeTextAttr(rootFrame, frame); // "font-style" text attribute FontStyleTextAttr fontStyleTextAttr(rootFrame, frame); // "font-weight" text attribute FontWeightTextAttr fontWeightTextAttr(rootFrame, frame); // "auto-generated" text attribute AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc); // "text-underline(line-through)-style(color)" text attributes TextDecorTextAttr textDecorTextAttr(rootFrame, frame); // "text-position" text attribute TextPosTextAttr textPosTextAttr(rootFrame, frame); TextAttr* attrArray[] = { &langTextAttr, &invalidTextAttr, &bgColorTextAttr, &colorTextAttr, &fontFamilyTextAttr, &fontSizeTextAttr, &fontStyleTextAttr, &fontWeightTextAttr, &autoGenTextAttr, &textDecorTextAttr, &textPosTextAttr }; // Expose text attributes if applicable. if (aAttributes) { for (uint32_t idx = 0; idx < ArrayLength(attrArray); idx++) attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs); } // Expose text attributes range where they are applied if applicable. if (mOffsetAcc) GetRange(attrArray, ArrayLength(attrArray), aStartOffset, aEndOffset); }
PLDHashOperator NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry, void* aUserArg) { DocAccessible* document = static_cast<DocAccessible*>(aUserArg); nsIContent* textNode = aEntry->GetKey(); Accessible* textAcc = document->GetAccessible(textNode); // If the text node is not in tree or doesn't have frame then this case should // have been handled already by content removal notifications. nsINode* containerNode = textNode->GetParentNode(); if (!containerNode) { NS_ASSERTION(!textAcc, "Text node was removed but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIFrame* textFrame = textNode->GetPrimaryFrame(); if (!textFrame) { NS_ASSERTION(!textAcc, "Text node isn't rendered but accessible is kept alive!"); return PL_DHASH_NEXT; } nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nullptr; nsAutoString text; textFrame->GetRenderedText(&text); // Remove text accessible if rendered text is empty. if (textAcc) { if (text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node lost its content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif document->ContentRemoved(containerElm, textNode); return PL_DHASH_NEXT; } // Update text of the accessible and fire text change events. #ifdef A11Y_LOG if (logging::IsEnabled(logging::eText)) { logging::MsgBegin("TEXT", "text may be changed"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEntry("old text '%s'", NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get()); logging::MsgEntry("new text: '%s'", NS_ConvertUTF16toUTF8(text).get()); logging::MsgEnd(); } #endif TextUpdater::Run(document, textAcc->AsTextLeaf(), text); return PL_DHASH_NEXT; } // Append an accessible if rendered text is not empty. if (!text.IsEmpty()) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node gains new content"); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif // Make sure the text node is in accessible document still. Accessible* container = document->GetAccessibleOrContainer(containerNode); NS_ASSERTION(container, "Text node having rendered text hasn't accessible document!"); if (container) { nsTArray<nsCOMPtr<nsIContent> > insertedContents; insertedContents.AppendElement(textNode); document->ProcessContentInserted(container, &insertedContents); } } return PL_DHASH_NEXT; }
PRUint64 nsXULMenuitemAccessible::NativeState() { PRUint64 state = Accessible::NativeState(); // Has Popup? if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) { state |= states::HASPOPUP; if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::open)) state |= states::EXPANDED; else state |= states::COLLAPSED; } // Checkable/checked? static nsIContent::AttrValuesArray strings[] = { &nsGkAtoms::radio, &nsGkAtoms::checkbox, nsnull }; if (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings, eCaseMatters) >= 0) { // Checkable? state |= states::CHECKABLE; // Checked? if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true, eCaseMatters)) state |= states::CHECKED; } // Combo box listitem bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION); if (isComboboxOption) { // Is selected? bool isSelected = false; nsCOMPtr<nsIDOMXULSelectControlItemElement> item(do_QueryInterface(mContent)); NS_ENSURE_TRUE(item, state); item->GetSelected(&isSelected); // Is collapsed? bool isCollapsed = false; Accessible* parent = Parent(); if (parent && parent->State() & states::INVISIBLE) isCollapsed = true; if (isSelected) { state |= states::SELECTED; // Selected and collapsed? if (isCollapsed) { // Set selected option offscreen/invisible according to combobox state Accessible* grandParent = parent->Parent(); if (!grandParent) return state; NS_ASSERTION(grandParent->Role() == roles::COMBOBOX, "grandparent of combobox listitem is not combobox"); PRUint64 grandParentState = grandParent->State(); state &= ~(states::OFFSCREEN | states::INVISIBLE); state |= (grandParentState & states::OFFSCREEN) | (grandParentState & states::INVISIBLE) | (grandParentState & states::OPAQUE1); } // isCollapsed } // isSelected } // ROLE_COMBOBOX_OPTION return state; }
nsresult ARIAGridAccessible::SetARIASelected(Accessible* aAccessible, bool aIsSelected, bool aNotify) { nsIContent *content = aAccessible->GetContent(); NS_ENSURE_STATE(content); nsresult rv = NS_OK; if (aIsSelected) rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, NS_LITERAL_STRING("true"), aNotify); else rv = content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, aNotify); NS_ENSURE_SUCCESS(rv, rv); // No "smart" select/unselect for internal call. if (!aNotify) return NS_OK; // If row or cell accessible was selected then we're able to not bother about // selection of its cells or its row because our algorithm is row oriented, // i.e. we check selection on row firstly and then on cells. if (aIsSelected) return NS_OK; roles::Role role = aAccessible->Role(); // If the given accessible is row that was unselected then remove // aria-selected from cell accessible. if (role == roles::ROW) { AccIterator cellIter(aAccessible, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { rv = SetARIASelected(cell, false, false); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // If the given accessible is cell that was unselected and its row is selected // then remove aria-selected from row and put aria-selected on // siblings cells. if (role == roles::GRID_CELL || role == roles::ROWHEADER || role == roles::COLUMNHEADER) { Accessible* row = aAccessible->Parent(); if (row && row->Role() == roles::ROW && nsAccUtils::IsARIASelected(row)) { rv = SetARIASelected(row, false, false); NS_ENSURE_SUCCESS(rv, rv); AccIterator cellIter(row, filters::GetCell); Accessible* cell = nullptr; while ((cell = cellIter.Next())) { if (cell != aAccessible) { rv = SetARIASelected(cell, true, false); NS_ENSURE_SUCCESS(rv, rv); } } } } return NS_OK; }
// RootAccessible protected void RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent) { MOZ_ASSERT(aDOMEvent); Event* event = aDOMEvent->InternalDOMEvent(); nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget()); nsAutoString eventType; aDOMEvent->GetType(eventType); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eDOMEvents)) logging::DOMEvent("processed", origTargetNode, eventType); #endif if (eventType.EqualsLiteral("popuphiding")) { HandlePopupHidingEvent(origTargetNode); return; } DocAccessible* targetDocument = GetAccService()-> GetDocAccessible(origTargetNode->OwnerDoc()); NS_ASSERTION(targetDocument, "No document while accessible is in document?!"); Accessible* accessible = targetDocument->GetAccessibleOrContainer(origTargetNode); if (!accessible) return; #ifdef MOZ_XUL XULTreeAccessible* treeAcc = accessible->AsXULTree(); if (treeAcc) { if (eventType.EqualsLiteral("TreeRowCountChanged")) { HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); return; } if (eventType.EqualsLiteral("TreeInvalidated")) { HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); return; } } #endif if (eventType.EqualsLiteral("RadioStateChange")) { uint64_t state = accessible->State(); bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; if (accessible->NeedsDOMUIEvent()) { nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); } if (isEnabled) { FocusMgr()->ActiveItemChanged(accessible); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("RadioStateChange", accessible); #endif } return; } if (eventType.EqualsLiteral("CheckboxStateChange")) { if (accessible->NeedsDOMUIEvent()) { uint64_t state = accessible->State(); bool isEnabled = !!(state & states::CHECKED); nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); } return; } Accessible* treeItemAcc = nullptr; #ifdef MOZ_XUL // If it's a tree element, need the currently selected item. if (treeAcc) { treeItemAcc = accessible->CurrentItem(); if (treeItemAcc) accessible = treeItemAcc; } if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) { uint64_t state = accessible->State(); bool isEnabled = (state & states::EXPANDED) != 0; nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); nsEventShell::FireEvent(accEvent); return; } nsINode* targetNode = accessible->GetNode(); if (treeItemAcc && eventType.EqualsLiteral("select")) { // XXX: We shouldn't be based on DOM select event which doesn't provide us // any context info. We should integrate into nsTreeSelection instead. // If multiselect tree, we should fire selectionadd or selection removed if (FocusMgr()->HasDOMFocus(targetNode)) { nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel = do_QueryInterface(targetNode); nsAutoString selType; multiSel->GetSelType(selType); if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE // for each tree item. Perhaps each tree item will need to cache its // selection state and fire an event after a DOM "select" event when // that state changes. XULTreeAccessible::UpdateTreeSelection(); nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, accessible); return; } nsRefPtr<AccSelChangeEvent> selChangeEvent = new AccSelChangeEvent(treeAcc, treeItemAcc, AccSelChangeEvent::eSelectionAdd); nsEventShell::FireEvent(selChangeEvent); return; } } else #endif if (eventType.EqualsLiteral("AlertActive")) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); } else if (eventType.EqualsLiteral("popupshown")) { HandlePopupShownEvent(accessible); } else if (eventType.EqualsLiteral("DOMMenuInactive")) { if (accessible->Role() == roles::MENUPOPUP) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, accessible); } } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { FocusMgr()->ActiveItemChanged(accessible); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); #endif } else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { // Process DOMMenuItemInactive event for autocomplete only because this is // unique widget that may acquire focus from autocomplete popup while popup // stays open and has no active item. In case of XUL tree autocomplete // popup this event is fired for tree accessible. Accessible* widget = accessible->IsWidget() ? accessible : accessible->ContainerWidget(); if (widget && widget->IsAutoCompletePopup()) { FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible); #endif } } else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, accessible, eFromUserInput); // Notify of active item change when menubar gets active and if it has // current item. This is a case of mouseover (set current menuitem) and // mouse click (activate the menubar). If menubar doesn't have current item // (can be a case of menubar activation from keyboard) then ignore this // notification because later we'll receive DOMMenuItemActive event after // current menuitem is set. Accessible* activeItem = accessible->CurrentItem(); if (activeItem) { FocusMgr()->ActiveItemChanged(activeItem); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible); #endif } } else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, accessible, eFromUserInput); FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible); #endif } else if (accessible->NeedsDOMUIEvent() && eventType.EqualsLiteral("ValueChange")) { targetDocument->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, accessible); } #ifdef DEBUG_DRAGDROPSTART else if (eventType.EqualsLiteral("mouseover")) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, accessible); } #endif }
void EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent) { uint32_t count = mEvents.Length(); for (uint32_t index = count - 2; index < count; index--) { AccEvent* thisEvent = mEvents[index]; // Skip events of different types and targeted to application accessible. if (thisEvent->mEventType != aTailEvent->mEventType || thisEvent->mAccessible->IsApplication()) continue; // If thisEvent target is not in document longer, i.e. if it was // removed from the tree then do not emit the event. if (!thisEvent->mAccessible->IsDoc() && !thisEvent->mAccessible->IsInDocument()) { thisEvent->mEventRule = AccEvent::eDoNotEmit; continue; } // Coalesce earlier event of the same target. if (thisEvent->mAccessible == aTailEvent->mAccessible) { if (thisEvent->mEventRule == AccEvent::eDoNotEmit) { AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); tailReorder->DoNotEmitAll(); } else { thisEvent->mEventRule = AccEvent::eDoNotEmit; } return; } // If tailEvent contains thisEvent // then // if show of tailEvent contains a grand parent of thisEvent // then assert // else if hide of tailEvent contains a grand parent of thisEvent // then ignore thisEvent and its show and hide events // otherwise ignore thisEvent but not its show and hide events Accessible* thisParent = thisEvent->mAccessible; while (thisParent && thisParent != mDocument) { if (thisParent->Parent() == aTailEvent->mAccessible) { AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); uint32_t eventType = tailReorder->IsShowHideEventTarget(thisParent); if (eventType == nsIAccessibleEvent::EVENT_SHOW) { NS_ERROR("Accessible tree was created after it was modified! Huh?"); } else if (eventType == nsIAccessibleEvent::EVENT_HIDE) { AccReorderEvent* thisReorder = downcast_accEvent(thisEvent); thisReorder->DoNotEmitAll(); } else { thisEvent->mEventRule = AccEvent::eDoNotEmit; } return; } thisParent = thisParent->Parent(); } // If tailEvent is contained by thisEvent // then // if show of thisEvent contains the tailEvent // then ignore tailEvent // if hide of thisEvent contains the tailEvent // then assert // otherwise ignore tailEvent but not its show and hide events Accessible* tailParent = aTailEvent->mAccessible; while (tailParent && tailParent != mDocument) { if (tailParent->Parent() == thisEvent->mAccessible) { AccReorderEvent* thisReorder = downcast_accEvent(thisEvent); AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent); if (eventType == nsIAccessibleEvent::EVENT_SHOW) tailReorder->DoNotEmitAll(); else if (eventType == nsIAccessibleEvent::EVENT_HIDE) NS_ERROR("Accessible tree was modified after it was removed! Huh?"); else aTailEvent->mEventRule = AccEvent::eDoNotEmit; return; } tailParent = tailParent->Parent(); } } // for (index) }
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 TextRange::Crop(Accessible* aContainer) { uint32_t boundaryPos = 0, containerPos = 0; AutoTArray<Accessible*, 30> boundaryParents, containerParents; // Crop the start boundary. Accessible* container = nullptr; Accessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset); if (boundary != aContainer) { CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos, &containerParents, &containerPos); if (boundaryPos == 0) { if (containerPos != 0) { // The container is contained by the start boundary, reduce the range to // the point starting at the container. aContainer->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset); static_cast<Accessible*>(mStartContainer)->AddRef(); } else { // The start boundary and the container are siblings. container = aContainer; } } else if (containerPos != 0) { // The container does not contain the start boundary. boundary = boundaryParents[boundaryPos]; container = containerParents[containerPos]; } if (container) { // If the range start is after the container, then make the range invalid. if (boundary->IndexInParent() > container->IndexInParent()) { return !!(mRoot = nullptr); } // If the range starts before the container, then reduce the range to // the point starting at the container. if (boundary->IndexInParent() < container->IndexInParent()) { container->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset); mStartContainer.get()->AddRef(); } } boundaryParents.SetLengthAndRetainStorage(0); containerParents.SetLengthAndRetainStorage(0); } boundary = mEndContainer->GetChildAtOffset(mEndOffset); if (boundary == aContainer) { return true; } // Crop the end boundary. container = nullptr; CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos, &containerParents, &containerPos); if (boundaryPos == 0) { if (containerPos != 0) { aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false); static_cast<Accessible*>(mEndContainer)->AddRef(); } else { container = aContainer; } } else if (containerPos != 0) { boundary = boundaryParents[boundaryPos]; container = containerParents[containerPos]; } if (!container) { return true; } if (boundary->IndexInParent() < container->IndexInParent()) { return !!(mRoot = nullptr); } if (boundary->IndexInParent() > container->IndexInParent()) { container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false); static_cast<Accessible*>(mEndContainer)->AddRef(); } return true; }
void HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) { nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame()); // If image map is not initialized yet then we trigger one time more later. nsImageMap* imageMapObj = imageFrame->GetExistingImageMap(); if (!imageMapObj) return; bool treeChanged = false; AutoTreeMutation mut(this); nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this); // Remove areas that are not a valid part of the image map anymore. for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) { Accessible* area = mChildren.ElementAt(childIdx); if (area->GetContent()->GetPrimaryFrame()) continue; if (aDoFireEvents) { nsRefPtr<AccHideEvent> event = new AccHideEvent(area, area->GetContent()); mDoc->FireDelayedEvent(event); reorderEvent->AddSubMutationEvent(event); } RemoveChild(area); treeChanged = true; } // Insert new areas into the tree. uint32_t areaElmCount = imageMapObj->AreaCount(); for (uint32_t idx = 0; idx < areaElmCount; idx++) { nsIContent* areaContent = imageMapObj->GetAreaAt(idx); Accessible* area = mChildren.SafeElementAt(idx); if (!area || area->GetContent() != areaContent) { nsRefPtr<Accessible> area = new HTMLAreaAccessible(areaContent, mDoc); mDoc->BindToDocument(area, aria::GetRoleMap(areaContent)); if (!InsertChildAt(idx, area)) { mDoc->UnbindFromDocument(area); break; } if (aDoFireEvents) { nsRefPtr<AccShowEvent> event = new AccShowEvent(area, areaContent); mDoc->FireDelayedEvent(event); reorderEvent->AddSubMutationEvent(event); } treeChanged = true; } } // Fire reorder event if needed. if (treeChanged && aDoFireEvents) mDoc->FireDelayedEvent(reorderEvent); if (!treeChanged) mut.mInvalidationRequired = false; }
NS_IMETHODIMP nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) { NS_ENSURE_ARG(aResult); *aResult = false; int32_t tempStart = mStartOffset, tempEnd = mEndOffset; Accessible* tempPosition = mPosition; Accessible* root = GetActiveRoot(); while (true) { Accessible* curPosition = tempPosition; HyperTextAccessible* text; // Find the nearest text node using a reverse preorder traversal starting // from the current node. if (!(text = tempPosition->AsHyperText())) { text = SearchForText(tempPosition, true); if (!text) return NS_OK; if (text != curPosition) tempStart = tempEnd = -1; tempPosition = text; } // If the search led to the parent of the node we started on (e.g. when // starting on a text leaf), start the text movement from the end of that // node, otherwise we just default to 0. if (tempStart == -1) { if (tempPosition != curPosition) tempStart = text == curPosition->Parent() ? text->GetChildOffset(curPosition) : text->CharacterCount(); else tempStart = 0; } // If there's no more text on the current node, try to find the previous // text node; if there isn't one, bail out. if (tempStart == 0) { if (tempPosition == root) return NS_OK; // If we're currently sitting on a link, try move to either the previous // sibling or the parent, whichever is closer to the current end // offset. Otherwise, do a forward search for the next node to land on // (we don't do this in the first case because we don't want to go to the // subtree). Accessible* sibling = tempPosition->PrevSibling(); if (tempPosition->IsLink()) { if (sibling && sibling->IsLink()) { HyperTextAccessible* siblingText = sibling->AsHyperText(); tempStart = tempEnd = siblingText ? siblingText->CharacterCount() : -1; tempPosition = sibling; } else { tempStart = tempPosition->StartOffset(); tempEnd = tempPosition->EndOffset(); tempPosition = tempPosition->Parent(); } } else { HyperTextAccessible* tempText = SearchForText(tempPosition, true); if (!tempText) return NS_OK; tempPosition = tempText; tempStart = tempEnd = tempText->CharacterCount(); } continue; } AccessibleTextBoundary startBoundary, endBoundary; switch (aBoundary) { case CHAR_BOUNDARY: startBoundary = nsIAccessibleText::BOUNDARY_CHAR; endBoundary = nsIAccessibleText::BOUNDARY_CHAR; break; case WORD_BOUNDARY: startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; break; default: return NS_ERROR_INVALID_ARG; } nsAutoString unusedText; int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0; text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, unusedText); if (newStart < tempStart) tempStart = newEnd >= currentStart ? newStart : newEnd; else // XXX: In certain odd cases newStart is equal to tempStart text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart, &tempStart, unusedText); text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd, unusedText); tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart; // The offset range we've obtained might have embedded characters in it, // limit the range to the start of the last occurrence of an embedded // character. Accessible* childAtOffset = nullptr; for (int32_t i = tempEnd - 1; i >= tempStart; i--) { childAtOffset = text->GetChildAtOffset(i); if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) { tempStart = childAtOffset->EndOffset(); break; } } // If there's an embedded character at the very end of the range, we // instead want to traverse into it. So restart the movement with // the child as the starting point. if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) && tempEnd == static_cast<int32_t>(childAtOffset->EndOffset())) { tempPosition = childAtOffset; tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount(); continue; } *aResult = true; Accessible* startPosition = mPosition; int32_t oldStart = mStartOffset, oldEnd = mEndOffset; mPosition = tempPosition; mStartOffset = tempStart; mEndOffset = tempEnd; NotifyOfPivotChange(startPosition, oldStart, oldEnd, nsIAccessiblePivot::REASON_TEXT); return NS_OK; } }
nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { nsresult rv = Accessible::HandleAccEvent(aEvent); NS_ENSURE_SUCCESS(rv, rv); Accessible* accessible = aEvent->GetAccessible(); NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE); // The accessible can become defunct if we have an xpcom event listener // which decides it would be fun to change the DOM and flush layout. if (accessible->IsDefunct()) return NS_OK; uint32_t type = aEvent->GetEventType(); AtkObject* atkObj = AccessibleWrap::GetAtkObject(accessible); // We don't create ATK objects for plain text leaves, just return NS_OK in // such case. if (!atkObj) { NS_ASSERTION(type == nsIAccessibleEvent::EVENT_SHOW || type == nsIAccessibleEvent::EVENT_HIDE, "Event other than SHOW and HIDE fired for plain text leaves"); return NS_OK; } AccessibleWrap* accWrap = GetAccessibleWrap(atkObj); if (!accWrap) { return NS_OK; // Node is shut down } switch (type) { case nsIAccessibleEvent::EVENT_STATE_CHANGE: { AccStateChangeEvent* event = downcast_accEvent(aEvent); MAI_ATK_OBJECT(atkObj)->FireStateChangeEvent(event->GetState(), event->IsStateEnabled()); break; } case nsIAccessibleEvent::EVENT_TEXT_REMOVED: case nsIAccessibleEvent::EVENT_TEXT_INSERTED: return FireAtkTextChangedEvent(aEvent, atkObj); case nsIAccessibleEvent::EVENT_FOCUS: { a11y::RootAccessible* rootAccWrap = accWrap->RootAccessible(); if (rootAccWrap && rootAccWrap->mActivated) { atk_focus_tracker_notify(atkObj); // Fire state change event for focus atk_object_notify_state_change(atkObj, ATK_STATE_FOCUSED, true); return NS_OK; } } break; case nsIAccessibleEvent::EVENT_NAME_CHANGE: { nsAutoString newName; accessible->Name(newName); MaybeFireNameChange(atkObj, newName); break; } case nsIAccessibleEvent::EVENT_VALUE_CHANGE: if (accessible->HasNumericValue()) { // Make sure this is a numeric value. Don't fire for string value changes // (e.g. text editing) ATK values are always numeric. g_object_notify((GObject*)atkObj, "accessible-value"); } break; case nsIAccessibleEvent::EVENT_SELECTION: case nsIAccessibleEvent::EVENT_SELECTION_ADD: case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { // XXX: dupe events may be fired AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent); g_signal_emit_by_name(AccessibleWrap::GetAtkObject(selChangeEvent->Widget()), "selection_changed"); break; } case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: { g_signal_emit_by_name(atkObj, "selection_changed"); break; } case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: g_signal_emit_by_name(atkObj, "text_selection_changed"); break; case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(aEvent); NS_ASSERTION(caretMoveEvent, "Event needs event data"); if (!caretMoveEvent) break; int32_t caretOffset = caretMoveEvent->GetCaretOffset(); g_signal_emit_by_name(atkObj, "text_caret_moved", caretOffset); } break; case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED: g_signal_emit_by_name(atkObj, "text-attributes-changed"); break; case nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED: g_signal_emit_by_name(atkObj, "model_changed"); break; case nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT: { AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); int32_t rowIndex = tableEvent->GetIndex(); int32_t numRows = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "row_inserted", rowIndex, numRows); } break; case nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE: { AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); int32_t rowIndex = tableEvent->GetIndex(); int32_t numRows = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "row_deleted", rowIndex, numRows); } break; case nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER: { g_signal_emit_by_name(atkObj, "row_reordered"); break; } case nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT: { AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); int32_t colIndex = tableEvent->GetIndex(); int32_t numCols = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "column_inserted", colIndex, numCols); } break; case nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE: { AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); int32_t colIndex = tableEvent->GetIndex(); int32_t numCols = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "column_deleted", colIndex, numCols); } break; case nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER: g_signal_emit_by_name(atkObj, "column_reordered"); break; case nsIAccessibleEvent::EVENT_SECTION_CHANGED: g_signal_emit_by_name(atkObj, "visible_data_changed"); break; case nsIAccessibleEvent::EVENT_SHOW: return FireAtkShowHideEvent(aEvent, atkObj, true); case nsIAccessibleEvent::EVENT_HIDE: // XXX - Handle native dialog accessibles. if (!accessible->IsRoot() && accessible->HasARIARole() && accessible->ARIARole() == roles::DIALOG) { guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } return FireAtkShowHideEvent(aEvent, atkObj, false); /* * Because dealing with menu is very different between nsIAccessible * and ATK, and the menu activity is important, specially transfer the * following two event. * Need more verification by AT test. */ case nsIAccessibleEvent::EVENT_MENU_START: case nsIAccessibleEvent::EVENT_MENU_END: break; case nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE: { accessible->AsRoot()->mActivated = true; guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); // Always fire a current focus event after activation. FocusMgr()->ForceFocusEvent(); } break; case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE: { accessible->AsRoot()->mActivated = false; guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE: { guint id = g_signal_lookup("maximize", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE: { guint id = g_signal_lookup("minimize", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_WINDOW_RESTORE: { guint id = g_signal_lookup("restore", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: g_signal_emit_by_name (atkObj, "load_complete"); // XXX - Handle native dialog accessibles. if (!accessible->IsRoot() && accessible->HasARIARole() && accessible->ARIARole() == roles::DIALOG) { guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD: g_signal_emit_by_name (atkObj, "reload"); break; case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED: g_signal_emit_by_name (atkObj, "load_stopped"); break; case nsIAccessibleEvent::EVENT_MENUPOPUP_START: atk_focus_tracker_notify(atkObj); // fire extra focus event atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, true); atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, true); break; case nsIAccessibleEvent::EVENT_MENUPOPUP_END: atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, false); atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, false); break; } return NS_OK; }
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; } }
PRUint64 nsHTMLSelectOptionAccessible::NativeState() { // As a nsHTMLSelectOptionAccessible we can have the following states: // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN // Upcall to Accessible, but skip HyperTextAccessible impl // because we don't want EDITABLE or SELECTABLE_TEXT PRUint64 state = Accessible::NativeState(); Accessible* select = GetSelect(); if (!select) return state; PRUint64 selectState = select->State(); if (selectState & states::INVISIBLE) return state; // Focusable and selectable if (!(state & states::UNAVAILABLE)) state |= (states::FOCUSABLE | states::SELECTABLE); // Are we selected? bool isSelected = false; nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent)); if (option) { option->GetSelected(&isSelected); if (isSelected) state |= states::SELECTED; } if (selectState & states::OFFSCREEN) { state |= states::OFFSCREEN; } else if (selectState & states::COLLAPSED) { // <select> is COLLAPSED: add OFFSCREEN, if not the currently // visible option if (!isSelected) { state |= states::OFFSCREEN; } else { // Clear offscreen and invisible for currently showing option state &= ~(states::OFFSCREEN | states::INVISIBLE); state |= selectState & states::OPAQUE1; } } else { // XXX list frames are weird, don't rely on Accessible's general // visibility implementation unless they get reimplemented in layout state &= ~states::OFFSCREEN; // <select> is not collapsed: compare bounds to calculate OFFSCREEN Accessible* listAcc = Parent(); if (listAcc) { PRInt32 optionX, optionY, optionWidth, optionHeight; PRInt32 listX, listY, listWidth, listHeight; GetBounds(&optionX, &optionY, &optionWidth, &optionHeight); listAcc->GetBounds(&listX, &listY, &listWidth, &listHeight); if (optionY < listY || optionY + optionHeight > listY + listHeight) { state |= states::OFFSCREEN; } } } return state; }