forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			309 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			309 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 "AccessibleOrProxy.h"
 | 
						|
#include "DocAccessibleChild.h"
 | 
						|
#include "DocAccessibleWrap.h"
 | 
						|
#include "nsIDocShell.h"
 | 
						|
#include "nsIScrollableFrame.h"
 | 
						|
#include "nsLayoutUtils.h"
 | 
						|
#include "nsAccessibilityService.h"
 | 
						|
#include "nsAccUtils.h"
 | 
						|
#include "nsIPersistentProperties2.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>(), nsTArray<Attribute>()));
 | 
						|
    }
 | 
						|
 | 
						|
    ipcDoc->SendBatch(eBatch_Viewport, cacheData);
 | 
						|
  } else if (RefPtr<SessionAccessibility> sessionAcc =
 | 
						|
                 SessionAccessibility::GetInstanceFor(docAcc)) {
 | 
						|
    nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
 | 
						|
    for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
 | 
						|
      accessibles.AppendElement(
 | 
						|
          static_cast<AccessibleWrap*>(iter.Data().get()));
 | 
						|
    }
 | 
						|
 | 
						|
    sessionAcc->ReplaceViewportCache(accessibles);
 | 
						|
  }
 | 
						|
 | 
						|
  if (docAcc->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);
 | 
						|
      nsCOMPtr<nsIPersistentProperties> props = acc->Attributes();
 | 
						|
      nsTArray<Attribute> attributes;
 | 
						|
      nsAccUtils::PersistentPropertiesToArray(props, &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>(), nsTArray<Attribute>()));
 | 
						|
    }
 | 
						|
 | 
						|
    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
 |