diff --git a/accessible/android/DocAccessibleWrap.cpp b/accessible/android/DocAccessibleWrap.cpp index a74456c11f30..712a9a4066a9 100644 --- a/accessible/android/DocAccessibleWrap.cpp +++ b/accessible/android/DocAccessibleWrap.cpp @@ -76,6 +76,9 @@ DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent) case nsIAccessibleEvent::EVENT_SCROLLING_END: CacheViewport(); break; + case nsIAccessibleEvent::EVENT_SCROLLING: + UpdateFocusPathBounds(); + break; default: break; } @@ -195,6 +198,7 @@ DocAccessibleWrap::GetTopLevelContentDoc(AccessibleWrap* aAccessible) { void DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) { + mFocusPath.Clear(); if (IPCAccessibilityActive()) { DocAccessibleChild* ipcDoc = IPCDoc(); nsTArray cacheData; @@ -223,6 +227,7 @@ DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) acc->MaxValue(), acc->Step(), attributes)); + mFocusPath.Put(acc->UniqueID(), acc); } ipcDoc->SendBatch(eBatch_FocusPath, cacheData); @@ -235,4 +240,43 @@ DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) sessionAcc->ReplaceFocusPathCache(accessibles); } -} \ No newline at end of file +} + +void +DocAccessibleWrap::UpdateFocusPathBounds() +{ + if (!mFocusPath.Count()) { + return; + } + + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = IPCDoc(); + nsTArray boundsData(mFocusPath.Count()); + for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { + Accessible* accessible = iter.Data(); + auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc() ? 0 + : reinterpret_cast(accessible->UniqueID()); + boundsData.AppendElement(BatchData(accessible->Document()->IPCDoc(), + uid, + 0, + accessible->Bounds(), + nsString(), + nsString(), + nsString(), + UnspecifiedNaN(), + UnspecifiedNaN(), + UnspecifiedNaN(), + UnspecifiedNaN(), + nsTArray())); + } + + ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData); + } else if (SessionAccessibility* sessionAcc = SessionAccessibility::GetInstanceFor(this)) { + nsTArray accessibles(mFocusPath.Count()); + for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { + accessibles.AppendElement(static_cast(iter.Data().get())); + } + + sessionAcc->UpdateCachedBounds(accessibles); + } +} diff --git a/accessible/android/DocAccessibleWrap.h b/accessible/android/DocAccessibleWrap.h index cbba0f61478d..0b2a04f10ef2 100644 --- a/accessible/android/DocAccessibleWrap.h +++ b/accessible/android/DocAccessibleWrap.h @@ -37,6 +37,7 @@ public: enum { eBatch_Viewport = 0, eBatch_FocusPath = 1, + eBatch_BoundsUpdate = 2, }; protected: @@ -50,9 +51,13 @@ protected: private: void CacheViewport(); + void UpdateFocusPathBounds(); + static void CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam); nsCOMPtr mCacheRefreshTimer; + + AccessibleHashtable mFocusPath; }; } // namespace a11y diff --git a/accessible/android/Platform.cpp b/accessible/android/Platform.cpp index e1fe6ea7b040..deceef0cbd29 100644 --- a/accessible/android/Platform.cpp +++ b/accessible/android/Platform.cpp @@ -224,6 +224,9 @@ a11y::ProxyBatch(ProxyAccessible* aDocument, case DocAccessibleWrap::eBatch_FocusPath: sessionAcc->ReplaceFocusPathCache(accWraps, aData); break; + case DocAccessibleWrap::eBatch_BoundsUpdate: + sessionAcc->UpdateCachedBounds(accWraps, aData); + break; default: MOZ_ASSERT_UNREACHABLE("Unknown batch type."); break; diff --git a/accessible/android/SessionAccessibility.cpp b/accessible/android/SessionAccessibility.cpp index f7251ada5c65..04cbda2ac754 100644 --- a/accessible/android/SessionAccessibility.cpp +++ b/accessible/android/SessionAccessibility.cpp @@ -373,3 +373,22 @@ SessionAccessibility::ReplaceFocusPathCache(const nsTArray& aAc mSessionAccessibility->ReplaceFocusPathCache(infos); } + +void +SessionAccessibility::UpdateCachedBounds(const nsTArray& aAccessibles, + const nsTArray& aData) +{ + auto infos = jni::ObjectArray::New(aAccessibles.Length()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + AccessibleWrap* acc = aAccessibles.ElementAt(i); + if (aData.Length() == aAccessibles.Length()) { + const BatchData& data = aData.ElementAt(i); + auto bundle = acc->ToSmallBundle(data.State(), data.Bounds()); + infos->SetElement(i, bundle); + } else { + infos->SetElement(i, acc->ToSmallBundle()); + } + } + + mSessionAccessibility->UpdateCachedBounds(infos); +} diff --git a/accessible/android/SessionAccessibility.h b/accessible/android/SessionAccessibility.h index ecd4e5378e63..556f7685cf96 100644 --- a/accessible/android/SessionAccessibility.h +++ b/accessible/android/SessionAccessibility.h @@ -112,6 +112,9 @@ public: void ReplaceFocusPathCache(const nsTArray& aAccessibles, const nsTArray& aData = nsTArray()); + void UpdateCachedBounds(const nsTArray& aAccessibles, + const nsTArray& aData = nsTArray()); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility) private: diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java index 08cb6ab6b4e3..1bc21e67eee5 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java @@ -33,6 +33,7 @@ import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeProvider; +import java.util.Iterator; import java.util.LinkedList; public class SessionAccessibility { @@ -250,10 +251,6 @@ public class SessionAccessibility { return node; } - private boolean isNodeCached(final int virtualViewId) { - return mViewportCache.get(virtualViewId) != null || mFocusPathCache.get(virtualViewId) != null; - } - private synchronized AccessibilityNodeInfo getNodeFromCache(final int virtualViewId) { AccessibilityNodeInfo node = null; for (SparseArray cache : mCaches) { @@ -352,7 +349,7 @@ public class SessionAccessibility { int[] children = nodeInfo.getIntArray("children"); if (children != null) { for (int childId : children) { - if (!fromCache || isNodeCached(childId)) { + if (!fromCache || getMostRecentBundle(childId) != null) { // If this node is from cache, only populate with children that are cached as well. node.addChild(mView, childId); } @@ -664,6 +661,18 @@ public class SessionAccessibility { ((ViewParent) mView).requestSendAccessibilityEvent(mView, event); } + private synchronized GeckoBundle getMostRecentBundle(final int virtualViewId) { + Iterator> iter = mCaches.descendingIterator(); + while (iter.hasNext()) { + GeckoBundle bundle = iter.next().get(virtualViewId); + if (bundle != null) { + return bundle; + } + } + + return null; + } + /* package */ final class NativeProvider extends JNIObject { @WrapForJNI(calledFrom = "ui") private void setAttached(final boolean attached) { @@ -713,5 +722,17 @@ public class SessionAccessibility { mCaches.remove(mFocusPathCache); mCaches.add(mFocusPathCache); } + + @WrapForJNI(calledFrom = "gecko") + private synchronized void updateCachedBounds(final GeckoBundle[] bundles) { + for (GeckoBundle bundle : bundles) { + GeckoBundle cachedBundle = getMostRecentBundle(bundle.getInt("id")); + if (cachedBundle == null) { + Log.e(LOGTAG, "Can't update bounds of uncached node " + bundle.getInt("id")); + continue; + } + cachedBundle.putIntArray("bounds", bundle.getIntArray("bounds")); + } + } } }