forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			305 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "LocalAccessible-inl.h"
 | |
| #include "AccAttributes.h"
 | |
| #include "AccessibleOrProxy.h"
 | |
| #include "DocAccessibleChild.h"
 | |
| #include "DocAccessibleWrap.h"
 | |
| #include "nsIDocShell.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsAccessibilityService.h"
 | |
| #include "nsAccUtils.h"
 | |
| #include "Pivot.h"
 | |
| #include "SessionAccessibility.h"
 | |
| #include "TraversalRule.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/a11y/DocAccessiblePlatformExtChild.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::a11y;
 | |
| 
 | |
| const uint32_t kCacheRefreshInterval = 500;
 | |
| 
 | |
| #define UNIQUE_ID(acc)                             \
 | |
|   !acc || (acc->IsDoc() && acc->AsDoc()->IPCDoc()) \
 | |
|       ? 0                                          \
 | |
|       : reinterpret_cast<uint64_t>(acc->UniqueID())
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // DocAccessibleWrap
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| DocAccessibleWrap::DocAccessibleWrap(Document* aDocument, PresShell* aPresShell)
 | |
|     : DocAccessible(aDocument, aPresShell) {
 | |
|   if (aDocument->GetBrowsingContext()->IsTopContent()) {
 | |
|     // The top-level content document gets this special ID.
 | |
|     mID = kNoID;
 | |
|   } else {
 | |
|     mID = AcquireID();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DocAccessibleWrap::~DocAccessibleWrap() {}
 | |
| 
 | |
| AccessibleWrap* DocAccessibleWrap::GetAccessibleByID(int32_t aID) const {
 | |
|   if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
 | |
|     return acc;
 | |
|   }
 | |
| 
 | |
|   // If the ID is not in the hash table, check the IDs of the child docs.
 | |
|   for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
 | |
|     auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
 | |
|     if (childDoc->VirtualViewID() == aID) {
 | |
|       return childDoc;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void DocAccessibleWrap::DoInitialUpdate() {
 | |
|   DocAccessible::DoInitialUpdate();
 | |
|   CacheViewport(true);
 | |
| }
 | |
| 
 | |
| nsresult DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
 | |
|   switch (aEvent->GetEventType()) {
 | |
|     case nsIAccessibleEvent::EVENT_SCROLLING_END:
 | |
|       CacheViewport(false);
 | |
|       break;
 | |
|     case nsIAccessibleEvent::EVENT_SCROLLING:
 | |
|       UpdateFocusPathBounds();
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return DocAccessible::HandleAccEvent(aEvent);
 | |
| }
 | |
| 
 | |
| void DocAccessibleWrap::CacheViewportCallback(nsITimer* aTimer,
 | |
|                                               void* aDocAccParam) {
 | |
|   RefPtr<DocAccessibleWrap> docAcc(
 | |
|       dont_AddRef(reinterpret_cast<DocAccessibleWrap*>(aDocAccParam)));
 | |
|   if (!docAcc || docAcc->HasShutdown()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   PresShell* presShell = docAcc->PresShellPtr();
 | |
|   nsIFrame* rootFrame = presShell->GetRootFrame();
 | |
|   if (!rootFrame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsTArray<nsIFrame*> frames;
 | |
|   nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
 | |
|   nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
 | |
| 
 | |
|   nsLayoutUtils::GetFramesForArea(
 | |
|       RelativeTo{presShell->GetRootFrame()}, scrollPort, frames,
 | |
|       {nsLayoutUtils::FrameForPointOption::OnlyVisible});
 | |
|   AccessibleHashtable inViewAccs;
 | |
|   for (size_t i = 0; i < frames.Length(); i++) {
 | |
|     nsIContent* content = frames.ElementAt(i)->GetContent();
 | |
|     if (!content) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     LocalAccessible* visibleAcc = docAcc->GetAccessibleOrContainer(content);
 | |
|     if (!visibleAcc) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     for (LocalAccessible* acc = visibleAcc; acc && acc != docAcc->LocalParent();
 | |
|          acc = acc->LocalParent()) {
 | |
|       const bool alreadyPresent =
 | |
|           inViewAccs.WithEntryHandle(acc->UniqueID(), [&](auto&& entry) {
 | |
|             if (entry) {
 | |
|               return true;
 | |
|             }
 | |
| 
 | |
|             entry.Insert(RefPtr{acc});
 | |
|             return false;
 | |
|           });
 | |
|       if (alreadyPresent) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (IPCAccessibilityActive()) {
 | |
|     DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
 | |
|     nsTArray<BatchData> cacheData(inViewAccs.Count());
 | |
|     for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
 | |
|       LocalAccessible* accessible = iter.Data();
 | |
|       nsAutoString name;
 | |
|       accessible->Name(name);
 | |
|       nsAutoString textValue;
 | |
|       accessible->Value(textValue);
 | |
|       nsAutoString nodeID;
 | |
|       static_cast<AccessibleWrap*>(accessible)->WrapperDOMNodeID(nodeID);
 | |
|       nsAutoString description;
 | |
|       accessible->Description(description);
 | |
| 
 | |
|       cacheData.AppendElement(BatchData(
 | |
|           accessible->Document()->IPCDoc(), UNIQUE_ID(accessible),
 | |
|           accessible->State(), accessible->Bounds(), accessible->ActionCount(),
 | |
|           name, textValue, nodeID, description, UnspecifiedNaN<double>(),
 | |
|           UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
 | |
|           UnspecifiedNaN<double>(), nullptr));
 | |
|     }
 | |
| 
 | |
|     ipcDoc->SendBatch(eBatch_Viewport, cacheData);
 | |
|   } else if (RefPtr<SessionAccessibility> sessionAcc =
 | |
|                  SessionAccessibility::GetInstanceFor(docAcc)) {
 | |
|     nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
 | |
|     for (const auto& entry : inViewAccs) {
 | |
|       accessibles.AppendElement(static_cast<AccessibleWrap*>(entry.GetWeak()));
 | |
|     }
 | |
| 
 | |
|     sessionAcc->ReplaceViewportCache(accessibles);
 | |
|   }
 | |
| 
 | |
|   if (docAcc->mCachePivotBoundaries) {
 | |
|     AccessibleOrProxy accOrProxy = AccessibleOrProxy(docAcc);
 | |
|     a11y::Pivot pivot(accOrProxy);
 | |
|     TraversalRule rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT);
 | |
|     LocalAccessible* first = pivot.First(rule).AsAccessible();
 | |
|     LocalAccessible* last = pivot.Last(rule).AsAccessible();
 | |
| 
 | |
|     // If first/last are null, pass the root document as pivot boundary.
 | |
|     if (IPCAccessibilityActive()) {
 | |
|       DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
 | |
|       DocAccessibleChild* firstDoc =
 | |
|           first ? first->Document()->IPCDoc() : ipcDoc;
 | |
|       DocAccessibleChild* lastDoc = last ? last->Document()->IPCDoc() : ipcDoc;
 | |
|       if (ipcDoc && firstDoc && lastDoc) {
 | |
|         // One or more of the documents may not have recieved an IPC doc yet.
 | |
|         // In that case, just throw away this update. We will get a new one soon
 | |
|         // enough.
 | |
|         ipcDoc->GetPlatformExtension()->SendSetPivotBoundaries(
 | |
|             firstDoc, UNIQUE_ID(first), lastDoc, UNIQUE_ID(last));
 | |
|       }
 | |
|     } else if (RefPtr<SessionAccessibility> sessionAcc =
 | |
|                    SessionAccessibility::GetInstanceFor(docAcc)) {
 | |
|       sessionAcc->UpdateAccessibleFocusBoundaries(
 | |
|           first ? static_cast<AccessibleWrap*>(first) : docAcc,
 | |
|           last ? static_cast<AccessibleWrap*>(last) : docAcc);
 | |
|     }
 | |
| 
 | |
|     docAcc->mCachePivotBoundaries = false;
 | |
|   }
 | |
| 
 | |
|   if (docAcc->mCacheRefreshTimer) {
 | |
|     docAcc->mCacheRefreshTimer = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DocAccessibleWrap::CacheViewport(bool aCachePivotBoundaries) {
 | |
|   mCachePivotBoundaries |= aCachePivotBoundaries;
 | |
|   if (VirtualViewID() == kNoID && !mCacheRefreshTimer) {
 | |
|     NS_NewTimerWithFuncCallback(getter_AddRefs(mCacheRefreshTimer),
 | |
|                                 CacheViewportCallback, this,
 | |
|                                 kCacheRefreshInterval, nsITimer::TYPE_ONE_SHOT,
 | |
|                                 "a11y::DocAccessibleWrap::CacheViewport");
 | |
|     if (mCacheRefreshTimer) {
 | |
|       NS_ADDREF_THIS();  // Kung fu death grip
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| DocAccessibleWrap* DocAccessibleWrap::GetTopLevelContentDoc(
 | |
|     AccessibleWrap* aAccessible) {
 | |
|   DocAccessibleWrap* doc =
 | |
|       static_cast<DocAccessibleWrap*>(aAccessible->Document());
 | |
|   while (doc && doc->VirtualViewID() != kNoID) {
 | |
|     doc = static_cast<DocAccessibleWrap*>(doc->ParentDocument());
 | |
|   }
 | |
| 
 | |
|   return doc;
 | |
| }
 | |
| 
 | |
| void DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) {
 | |
|   mFocusPath.Clear();
 | |
|   if (IPCAccessibilityActive()) {
 | |
|     DocAccessibleChild* ipcDoc = IPCDoc();
 | |
|     nsTArray<BatchData> cacheData;
 | |
|     for (AccessibleWrap* acc = aAccessible; acc && acc != this->LocalParent();
 | |
|          acc = static_cast<AccessibleWrap*>(acc->LocalParent())) {
 | |
|       nsAutoString name;
 | |
|       acc->Name(name);
 | |
|       nsAutoString textValue;
 | |
|       acc->Value(textValue);
 | |
|       nsAutoString nodeID;
 | |
|       acc->WrapperDOMNodeID(nodeID);
 | |
|       nsAutoString description;
 | |
|       acc->Description(description);
 | |
|       RefPtr<AccAttributes> attributes = acc->Attributes();
 | |
|       cacheData.AppendElement(
 | |
|           BatchData(acc->Document()->IPCDoc(), UNIQUE_ID(acc), acc->State(),
 | |
|                     acc->Bounds(), acc->ActionCount(), name, textValue, nodeID,
 | |
|                     description, acc->CurValue(), acc->MinValue(),
 | |
|                     acc->MaxValue(), acc->Step(), attributes));
 | |
|       mFocusPath.InsertOrUpdate(acc->UniqueID(), RefPtr{acc});
 | |
|     }
 | |
| 
 | |
|     ipcDoc->SendBatch(eBatch_FocusPath, cacheData);
 | |
|   } else if (RefPtr<SessionAccessibility> sessionAcc =
 | |
|                  SessionAccessibility::GetInstanceFor(this)) {
 | |
|     nsTArray<AccessibleWrap*> accessibles;
 | |
|     for (AccessibleWrap* acc = aAccessible; acc && acc != this->LocalParent();
 | |
|          acc = static_cast<AccessibleWrap*>(acc->LocalParent())) {
 | |
|       accessibles.AppendElement(acc);
 | |
|       mFocusPath.InsertOrUpdate(acc->UniqueID(), RefPtr{acc});
 | |
|     }
 | |
| 
 | |
|     sessionAcc->ReplaceFocusPathCache(accessibles);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DocAccessibleWrap::UpdateFocusPathBounds() {
 | |
|   if (!mFocusPath.Count()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (IPCAccessibilityActive()) {
 | |
|     DocAccessibleChild* ipcDoc = IPCDoc();
 | |
|     nsTArray<BatchData> boundsData(mFocusPath.Count());
 | |
|     for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
 | |
|       LocalAccessible* accessible = iter.Data();
 | |
|       if (!accessible || accessible->IsDefunct()) {
 | |
|         MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       boundsData.AppendElement(BatchData(
 | |
|           accessible->Document()->IPCDoc(), UNIQUE_ID(accessible), 0,
 | |
|           accessible->Bounds(), 0, nsString(), nsString(), nsString(),
 | |
|           nsString(), UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
 | |
|           UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), nullptr));
 | |
|     }
 | |
| 
 | |
|     ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData);
 | |
|   } else if (RefPtr<SessionAccessibility> sessionAcc =
 | |
|                  SessionAccessibility::GetInstanceFor(this)) {
 | |
|     nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count());
 | |
|     for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
 | |
|       LocalAccessible* accessible = iter.Data();
 | |
|       if (!accessible || accessible->IsDefunct()) {
 | |
|         MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       accessibles.AppendElement(static_cast<AccessibleWrap*>(accessible));
 | |
|     }
 | |
| 
 | |
|     sessionAcc->UpdateCachedBounds(accessibles);
 | |
|   }
 | |
| }
 | |
| 
 | |
| #undef UNIQUE_ID
 | 
