Bug 1502187 - Implement native part of viewport caching. r=Jamie a=jcristau
authorEitan Isaacson <eitan@monotonous.org>
Tue, 06 Nov 2018 04:35:51 +0000
changeset 501344 b3869ac5fad25da3c0d9913eabaf2f0679a1d6cf
parent 501343 f75f849333f5d4f2616e483cae97a3956083fb27
child 501345 26a874969a1e6efe27fbdc617f791bfdf141730d
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJamie, jcristau
bugs1502187
milestone64.0
Bug 1502187 - Implement native part of viewport caching. r=Jamie a=jcristau Depends on D9865 Differential Revision: https://phabricator.services.mozilla.com/D9866
accessible/android/AccessibleWrap.cpp
accessible/android/AccessibleWrap.h
accessible/android/DocAccessibleWrap.cpp
accessible/android/DocAccessibleWrap.h
accessible/android/Platform.cpp
accessible/android/SessionAccessibility.cpp
accessible/android/SessionAccessibility.h
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -201,20 +201,20 @@ bool
 AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) {
   if (IsHyperText()) {
     return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
   }
 
   return false;
 }
 
-uint64_t
+uint32_t
 AccessibleWrap::GetFlags(role aRole, uint64_t aState)
 {
-  uint64_t flags = 0;
+  uint32_t flags = 0;
   if (aState & states::CHECKABLE) {
     flags |= java::SessionAccessibility::FLAG_CHECKABLE;
   }
 
   if (aState & states::CHECKED) {
     flags |= java::SessionAccessibility::FLAG_CHECKED;
   }
 
@@ -383,17 +383,17 @@ AccessibleWrap::ToBundle()
   GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
 
   AccessibleWrap* parent = WrapperParent();
   GECKOBUNDLE_PUT(nodeInfo, "parentId",
     java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
 
   role role = WrapperRole();
   uint64_t state = State();
-  uint64_t flags = GetFlags(role, state);
+  uint32_t flags = GetFlags(role, state);
   GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
   GECKOBUNDLE_PUT(nodeInfo, "className", java::sdk::Integer::ValueOf(AndroidClass()));
 
   nsAutoString text;
   if (state & states::EDITABLE) {
     Value(text);
   }
 
@@ -534,8 +534,49 @@ AccessibleWrap::ToBundle()
 
   GECKOBUNDLE_PUT(nodeInfo,
                   "children",
                   jni::IntArray::New(children.Elements(), children.Length()));
   GECKOBUNDLE_FINISH(nodeInfo);
 
   return nodeInfo;
 }
+
+mozilla::java::GeckoBundle::LocalRef
+AccessibleWrap::ToSmallBundle()
+{
+  return ToSmallBundle(State(), Bounds());
+}
+
+mozilla::java::GeckoBundle::LocalRef
+AccessibleWrap::ToSmallBundle(const uint64_t aState, const nsIntRect& aBounds)
+{
+  GECKOBUNDLE_START(nodeInfo);
+  GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
+
+  AccessibleWrap* parent = WrapperParent();
+  GECKOBUNDLE_PUT(nodeInfo, "parentId",
+    java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
+
+  uint32_t flags = GetFlags(WrapperRole(), aState);
+  GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
+  GECKOBUNDLE_PUT(nodeInfo, "className", java::sdk::Integer::ValueOf(AndroidClass()));
+
+  const int32_t data[4] = {
+    aBounds.x, aBounds.y, aBounds.x + aBounds.width, aBounds.y + aBounds.height
+  };
+  GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
+
+  auto childCount = ChildCount();
+  nsTArray<int32_t> children(childCount);
+  for (uint32_t i = 0; i < childCount; ++i) {
+    auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
+    children.AppendElement(child->VirtualViewID());
+  }
+
+  GECKOBUNDLE_PUT(nodeInfo,
+                  "children",
+                  jni::IntArray::New(children.Elements(), children.Length()));
+
+  GECKOBUNDLE_FINISH(nodeInfo);
+
+  return nodeInfo;
+}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -28,16 +28,20 @@ public:
   virtual void SetTextContents(const nsAString& aText);
 
   virtual void GetTextContents(nsAString& aText);
 
   virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
 
   mozilla::java::GeckoBundle::LocalRef ToBundle();
 
+  mozilla::java::GeckoBundle::LocalRef ToSmallBundle(const uint64_t aState, const nsIntRect& aBounds);
+
+  mozilla::java::GeckoBundle::LocalRef ToSmallBundle();
+
   int32_t AndroidClass()
   {
     return mID == kNoID ? java::SessionAccessibility::CLASSNAME_WEBVIEW
                         : GetAndroidClass(WrapperRole());
   }
 
   static const int32_t kNoID = -1;
 
@@ -60,18 +64,17 @@ private:
 
   virtual role WrapperRole() { return Role(); }
 
   virtual void WrapperDOMNodeID(nsString& aDOMNodeID);
 
   static void GetRoleDescription(role aRole,
                                  nsAString& aGeckoRole,
                                  nsAString& aRoleDescription);
-
-  static uint64_t GetFlags(role aRole, uint64_t aState);
+  static uint32_t GetFlags(role aRole, uint64_t aState);
 };
 
 static inline AccessibleWrap*
 WrapperFor(const ProxyAccessible* aProxy)
 {
   return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
 }
 
--- a/accessible/android/DocAccessibleWrap.cpp
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -1,19 +1,25 @@
 /* -*- 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 "DocAccessibleWrap.h"
 #include "nsIDocShell.h"
+#include "nsLayoutUtils.h"
+#include "DocAccessibleChild.h"
+#include "nsAccessibilityService.h"
+#include "SessionAccessibility.h"
 
 using namespace mozilla::a11y;
 
+const uint32_t kCacheRefreshInterval = 500;
+
 ////////////////////////////////////////////////////////////////////////////////
 // DocAccessibleWrap
 ////////////////////////////////////////////////////////////////////////////////
 
 DocAccessibleWrap::DocAccessibleWrap(nsIDocument* aDocument,
                                      nsIPresShell* aPresShell)
   : DocAccessible(aDocument, aPresShell)
 {
@@ -46,8 +52,122 @@ DocAccessibleWrap::GetAccessibleByID(int
     auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
     if (childDoc->VirtualViewID() == aID) {
       return childDoc;
     }
   }
 
   return nullptr;
 }
+
+void
+DocAccessibleWrap::DoInitialUpdate()
+{
+  DocAccessible::DoInitialUpdate();
+  CacheViewport();
+}
+
+nsresult
+DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+  switch(aEvent->GetEventType()) {
+    case nsIAccessibleEvent::EVENT_SHOW:
+    case nsIAccessibleEvent::EVENT_HIDE:
+    case nsIAccessibleEvent::EVENT_SCROLLING_END:
+      CacheViewport();
+      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) {
+    return;
+  }
+
+  nsIPresShell *presShell = docAcc->PresShell();
+  if (!presShell) {
+    return;
+  }
+  nsIFrame* rootFrame = presShell->GetRootFrame();
+  if (!rootFrame) {
+    return;
+  }
+
+  nsTArray<nsIFrame*> frames;
+  nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+  nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
+
+  nsLayoutUtils::GetFramesForArea(
+    presShell->GetRootFrame(),
+    scrollPort,
+    frames,
+    nsLayoutUtils::FrameForPointFlags::ONLY_VISIBLE);
+  AccessibleHashtable inViewAccs;
+  for (size_t i = 0; i < frames.Length(); i++) {
+    nsIContent* content = frames.ElementAt(i)->GetContent();
+    if (!content) {
+      continue;
+    }
+
+    Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content);
+    if (!visibleAcc) {
+      continue;
+    }
+
+    for (Accessible* acc = visibleAcc; acc && acc != docAcc->Parent(); acc = acc->Parent()) {
+      if (inViewAccs.Contains(acc->UniqueID())) {
+        break;
+      }
+      inViewAccs.Put(acc->UniqueID(), acc);
+    }
+  }
+
+  if (IPCAccessibilityActive()) {
+    DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
+    nsTArray<BatchData> cacheData(inViewAccs.Count());
+    for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
+      Accessible* accessible = iter.Data();
+      auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc() ? 0
+        : reinterpret_cast<uint64_t>(accessible->UniqueID());
+      cacheData.AppendElement(BatchData(accessible->Document()->IPCDoc(),
+                                        uid,
+                                        accessible->State(),
+                                        accessible->Bounds()));
+    }
+
+    ipcDoc->SendBatch(eBatch_Viewport, cacheData);
+  } else if (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->mCacheRefreshTimer) {
+    docAcc->mCacheRefreshTimer = nullptr;
+  }
+}
+
+void
+DocAccessibleWrap::CacheViewport()
+{
+  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
+    }
+  }
+}
--- a/accessible/android/DocAccessibleWrap.h
+++ b/accessible/android/DocAccessibleWrap.h
@@ -2,39 +2,55 @@
 /* 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/. */
 
 #ifndef mozilla_a11y_DocAccessibleWrap_h__
 #define mozilla_a11y_DocAccessibleWrap_h__
 
 #include "DocAccessible.h"
+#include "nsITimer.h"
 
 namespace mozilla {
 namespace a11y {
 
 class DocAccessibleWrap : public DocAccessible
 {
 public:
   DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
   virtual ~DocAccessibleWrap();
 
+  virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
   /**
    * Manage the mapping from id to Accessible.
    */
   void AddID(uint32_t aID, AccessibleWrap* aAcc)
   {
     mIDToAccessibleMap.Put(aID, aAcc);
   }
   void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
   AccessibleWrap* GetAccessibleByID(int32_t aID) const;
 
+  enum {
+    eBatch_Viewport = 0
+  };
+
 protected:
   /*
    * This provides a mapping from 32 bit id to accessible objects.
    */
   nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+
+  virtual void DoInitialUpdate() override;
+
+private:
+  void CacheViewport();
+
+  static void CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam);
+
+  nsCOMPtr<nsITimer> mCacheRefreshTimer;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/android/Platform.cpp
+++ b/accessible/android/Platform.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "Platform.h"
 #include "ProxyAccessibleWrap.h"
+#include "DocAccessibleWrap.h"
 #include "SessionAccessibility.h"
 #include "mozilla/a11y/ProxyAccessible.h"
 #include "nsIAccessibleEvent.h"
 #include "nsIAccessiblePivot.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
@@ -200,9 +201,28 @@ a11y::ProxyScrollingEvent(ProxyAccessibl
 }
 
 void
 a11y::ProxyBatch(ProxyAccessible* aDocument,
                  const uint64_t aBatchType,
                  const nsTArray<ProxyAccessible*>& aAccessibles,
                  const nsTArray<BatchData>& aData)
 {
+  SessionAccessibility* sessionAcc =
+    SessionAccessibility::GetInstanceFor(aDocument);
+  if (!sessionAcc) {
+    return;
+  }
+
+  nsTArray<AccessibleWrap*> accWraps(aAccessibles.Length());
+  for (size_t i = 0; i < aAccessibles.Length(); i++) {
+    accWraps.AppendElement(WrapperFor(aAccessibles.ElementAt(i)));
+  }
+
+  switch (aBatchType) {
+    case DocAccessibleWrap::eBatch_Viewport:
+      sessionAcc->ReplaceViewportCache(accWraps, aData);
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown batch type.");
+      break;
+  }
 }
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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 "SessionAccessibility.h"
 #include "AndroidUiThread.h"
+#include "DocAccessibleParent.h"
 #include "nsThreadUtils.h"
 #include "AccessibilityEvent.h"
 #include "HyperTextAccessible.h"
 #include "JavaBuiltins.h"
 #include "RootAccessibleWrap.h"
 #include "nsAccessibilityService.h"
 #include "nsViewManager.h"
 
@@ -317,8 +318,26 @@ SessionAccessibility::SendClickedEvent(A
 
 void
 SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible)
 {
   mSessionAccessibility->SendEvent(
     java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
     aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
 }
+
+void
+SessionAccessibility::ReplaceViewportCache(const nsTArray<AccessibleWrap*>& aAccessibles,
+                                          const nsTArray<BatchData>& aData)
+{
+  auto infos = jni::ObjectArray::New<java::GeckoBundle>(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);
+      infos->SetElement(i, acc->ToSmallBundle(data.State(), data.Bounds()));
+    } else {
+      infos->SetElement(i, acc->ToSmallBundle());
+    }
+  }
+
+  mSessionAccessibility->ReplaceViewportCache(infos);
+}
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -36,16 +36,17 @@
     mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
 
 namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap;
 class ProxyAccessible;
 class RootAccessibleWrap;
+class BatchData;
 
 class SessionAccessibility final
   : public java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility>
 {
 public:
   typedef java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility> Base;
 
   SessionAccessibility(
@@ -99,16 +100,20 @@ public:
                             uint32_t aLen,
                             bool aIsInsert,
                             bool aFromUser);
   void SendSelectedEvent(AccessibleWrap* aAccessible);
   void SendClickedEvent(AccessibleWrap* aAccessible, bool aChecked);
   void SendWindowContentChangedEvent(AccessibleWrap* aAccessible);
   void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
 
+  // Cache methods
+  void ReplaceViewportCache(const nsTArray<AccessibleWrap*>& aAccessibles,
+                           const nsTArray<BatchData>& aData = nsTArray<BatchData>());
+
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
 
 private:
   ~SessionAccessibility() {}
 
   void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
   RootAccessibleWrap* GetRoot();
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -635,10 +635,14 @@ public class SessionAccessibility {
         private void sendEventNative(final int eventType, final int sourceId, final int className, final GeckoBundle eventData) {
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     sendEvent(eventType, sourceId, className, eventData);
                 }
             });
         }
+
+        @WrapForJNI(calledFrom = "gecko")
+        private void replaceViewportCache(final GeckoBundle[] bundles) {
+        }
     }
 }