summaryrefslogtreecommitdiffstats
path: root/accessible/android/DocAccessibleWrap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/android/DocAccessibleWrap.cpp')
-rw-r--r--accessible/android/DocAccessibleWrap.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/accessible/android/DocAccessibleWrap.cpp b/accessible/android/DocAccessibleWrap.cpp
new file mode 100644
index 0000000000..269ab85cb9
--- /dev/null
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -0,0 +1,325 @@
+/* -*- 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 "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"
+#include "mozilla/StaticPrefs_accessibility.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) {
+ // We need an nsINode associated with this accessible to register it with the
+ // right SessionAccessibility instance. When the base AccessibleWrap
+ // constructor is called we don't have one yet because null is passed as the
+ // content node. So we do it here after a Document is associated with the
+ // accessible.
+ if (!IPCAccessibilityActive()) {
+ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
+ SessionAccessibility::RegisterAccessible(this);
+ }
+}
+
+DocAccessibleWrap::~DocAccessibleWrap() {}
+
+void DocAccessibleWrap::Shutdown() {
+ // Unregister here before disconnecting from PresShell.
+ if (!IPCAccessibilityActive()) {
+ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
+ if (IsRoot()) {
+ SessionAccessibility::UnregisterAll(PresShellPtr());
+ } else {
+ SessionAccessibility::UnregisterAccessible(this);
+ }
+ }
+ DocAccessible::Shutdown();
+}
+
+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() ||
+ (IPCAccessibilityActive() && !docAcc->IPCDoc())) {
+ 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;
+ accessible->DOMNodeID(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<Accessible*> accessibles(inViewAccs.Count());
+ for (const auto& entry : inViewAccs) {
+ accessibles.AppendElement(entry.GetWeak());
+ }
+
+ sessionAcc->ReplaceViewportCache(accessibles);
+ }
+
+ if (docAcc->mCachePivotBoundaries) {
+ a11y::Pivot pivot(docAcc);
+ TraversalRule rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT,
+ true);
+ Accessible* maybeFirst = pivot.First(rule);
+ Accessible* maybeLast = pivot.Last(rule);
+ LocalAccessible* first = maybeFirst ? maybeFirst->AsLocal() : nullptr;
+ LocalAccessible* last = maybeLast ? maybeLast->AsLocal() : nullptr;
+
+ // 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) {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ return;
+ }
+ mCachePivotBoundaries |= aCachePivotBoundaries;
+ if (IsTopLevelContentDoc() && !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->IsTopLevelContentDoc()) {
+ doc = static_cast<DocAccessibleWrap*>(doc->ParentDocument());
+ }
+
+ return doc;
+}
+
+bool DocAccessibleWrap::IsTopLevelContentDoc() {
+ DocAccessible* parentDoc = ParentDocument();
+ return DocumentNode()->IsContentDocument() &&
+ (!parentDoc || !parentDoc->DocumentNode()->IsContentDocument());
+}
+
+void DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ return;
+ }
+
+ 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->DOMNodeID(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<Accessible*> accessibles;
+ for (LocalAccessible* acc = aAccessible; acc && acc != this->LocalParent();
+ acc = acc->LocalParent()) {
+ accessibles.AppendElement(acc);
+ mFocusPath.InsertOrUpdate(acc->UniqueID(), RefPtr{acc});
+ }
+
+ sessionAcc->ReplaceFocusPathCache(accessibles);
+ }
+}
+
+void DocAccessibleWrap::UpdateFocusPathBounds() {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ return;
+ }
+
+ 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<Accessible*> 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(accessible);
+ }
+
+ sessionAcc->UpdateCachedBounds(accessibles);
+ }
+}
+
+#undef UNIQUE_ID