Bug 1351783 part 7 - Create FocusState and FocusTarget types. r=kats,botond draft
authorRyan Hunt <rhunt@eqrion.net>
Mon, 05 Jun 2017 19:12:22 -0500
changeset 599271 c5e9ea2359feb4da940ea74a337054a10769c2da
parent 599270 bd8770f701277340d8fd012007d86dc7aabe47f8
child 599272 5b4db2f72cefe05cedaf9f632ad8898fc4ccdce2
push id65466
push userbmo:rhunt@eqrion.net
push dateThu, 22 Jun 2017 22:16:51 +0000
reviewerskats, botond
bugs1351783
milestone56.0a1
Bug 1351783 part 7 - Create FocusState and FocusTarget types. r=kats,botond This commit begins the work needed for tracking focus by creating two new classes, FocusTarget and FocusState. FocusState is created and used by APZCTreeManager to track the global focus information, while FocusTarget is created per layer tree and sent to APZ with local focus information. Between the two we are able to figure out what the correct scrollable layer is to use in response to a keyboard scroll. See the comment in `FocusState.h` for more details on the architecture and things needed in future patches to complete this. MozReview-Commit-ID: F75VZv3i9U2
dom/events/EventStateManager.cpp
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/FocusState.cpp
gfx/layers/apz/src/FocusState.h
gfx/layers/apz/src/FocusTarget.cpp
gfx/layers/apz/src/FocusTarget.h
gfx/layers/moz.build
layout/base/PresShell.cpp
layout/base/PresShell.h
layout/base/nsIPresShell.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -5285,17 +5285,17 @@ EventStateManager::DoContentCommandScrol
       break;
     default:
       return NS_ERROR_INVALID_ARG;
   }
 
   aEvent->mSucceeded = true;
 
   nsIScrollableFrame* sf =
-    ps->GetFrameToScrollAsScrollable(nsIPresShell::eEither);
+    ps->GetScrollableFrameToScroll(nsIPresShell::eEither);
   aEvent->mIsEnabled = sf ?
     (aEvent->mScroll.mIsHorizontal ?
       WheelHandlingUtils::CanScrollOn(sf, aEvent->mScroll.mAmount, 0) :
       WheelHandlingUtils::CanScrollOn(sf, 0, aEvent->mScroll.mAmount)) : false;
 
   if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
     return NS_OK;
   }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 <stack>
+#include <unordered_set>
 #include "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "Compositor.h"                 // for Compositor
 #include "DragTracker.h"                // for DragTracker
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
 #include "InputBlockState.h"            // for InputBlockState
 #include "InputData.h"                  // for InputData, etc
@@ -16,16 +17,17 @@
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/GPUParent.h"      // for GPUParent
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Point.h"          // for Point
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnCompositorThread, etc
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
+#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
 #include "mozilla/webrender/WebRenderAPI.h"
@@ -77,16 +79,21 @@ struct APZCTreeManager::TreeBuildingStat
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
 
   // A list of nodes that need to be destroyed at the end of the tree building.
   // This is initialized with all nodes in the old tree, and nodes are removed
   // from it as we reuse them in the new tree.
   nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
+  // A set of layer trees that are no longer in the hit testing tree. This is
+  // used to destroy unneeded focus targets at the end of tree building. This
+  // is needed in addition to mNodesToDestroy because a hit testing node for a
+  // layer tree can be removed without the whole layer tree being removed.
+  std::unordered_set<uint64_t> mLayersIdsToDestroy;
 
   // This map is populated as we place APZCs into the new tree. Its purpose is
   // to facilitate re-using the same APZC for different layers that scroll
   // together (and thus have the same ScrollableLayerGuid).
   std::unordered_map<ScrollableLayerGuid, AsyncPanZoomController*, ScrollableLayerGuidHash> mApzcMap;
 };
 
 class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
@@ -257,16 +264,17 @@ APZCTreeManager::UpdateHitTestingTreeImp
   // we are sure that the layer was removed and not just transplanted elsewhere. Doing that
   // as part of a recursive tree walk is hard and so maintaining a list and removing
   // APZCs that are still alive is much simpler.
   ForEachNode<ReverseIterator>(mRootNode.get(),
       [&state] (HitTestingTreeNode* aNode)
       {
         state.mNodesToDestroy.AppendElement(aNode);
       });
+  state.mLayersIdsToDestroy = mFocusState.GetFocusTargetLayerIds();
   mRootNode = nullptr;
 
   if (aRoot) {
     std::stack<gfx::TreeAutoIndent> indents;
     std::stack<gfx::Matrix4x4> ancestorTransforms;
     HitTestingTreeNode* parent = nullptr;
     HitTestingTreeNode* next = nullptr;
     uint64_t layersId = aRootLayerTreeId;
@@ -284,16 +292,19 @@ APZCTreeManager::UpdateHitTestingTreeImp
                 aLayerMetrics.Metrics(), layersId, ancestorTransforms.top(),
                 parent, next, state);
           MOZ_ASSERT(node);
           AsyncPanZoomController* apzc = node->GetApzc();
           aLayerMetrics.SetApzc(apzc);
 
           mApzcTreeLog << '\n';
 
+          // Mark that this layer tree is being used
+          state.mLayersIdsToDestroy.erase(layersId);
+
           // Accumulate the CSS transform between layers that have an APZC.
           // In the terminology of the big comment above APZCTreeManager::GetScreenToApzcTransform, if
           // we are at layer M, then aAncestorTransform is NC * OC * PC, and we left-multiply MC and
           // compute ancestorTransform to be MC * NC * OC * PC. This gets passed down as the ancestor
           // transform to layer L when we recurse into the children below. If we are at a layer
           // with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
           // the new accumulation as we go down.
           // If a transform is a perspective transform, it's ignored for this purpose
@@ -329,24 +340,39 @@ APZCTreeManager::UpdateHitTestingTreeImp
 
   for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
     APZCTM_LOG("Destroying node at %p with APZC %p\n",
         state.mNodesToDestroy[i].get(),
         state.mNodesToDestroy[i]->GetApzc());
     state.mNodesToDestroy[i]->Destroy();
   }
 
+  // Clear out any focus targets that are no longer needed
+  for (auto layersId : state.mLayersIdsToDestroy) {
+    mFocusState.RemoveFocusTarget(layersId);
+  }
+
 #if ENABLE_APZCTM_LOGGING
   // Make the hit-test tree line up with the layer dump
   printf_stderr("APZCTreeManager (%p)\n", this);
   mRootNode->Dump("  ");
 #endif
 }
 
 void
+APZCTreeManager::UpdateFocusState(uint64_t aRootLayerTreeId,
+                                  uint64_t aOriginatingLayersId,
+                                  const FocusTarget& aFocusTarget)
+{
+  mFocusState.Update(aRootLayerTreeId,
+                     aOriginatingLayersId,
+                     aFocusTarget);
+}
+
+void
 APZCTreeManager::UpdateHitTestingTree(uint64_t aRootLayerTreeId,
                                       Layer* aRoot,
                                       bool aIsFirstPaint,
                                       uint64_t aOriginatingLayersId,
                                       uint32_t aPaintSequenceNumber)
 {
   LayerMetricsWrapper root(aRoot);
   UpdateHitTestingTreeImpl(aRootLayerTreeId, root, aIsFirstPaint,
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -10,16 +10,17 @@
 
 #include "gfxPoint.h"                   // for gfxPoint
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/TouchCounter.h"// for TouchCounter
 #include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager
 #include "mozilla/layers/Keyboard.h"    // for KeyboardMap
+#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/Mutex.h"              // for Mutex
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "nsCOMPtr.h"                   // for already_AddRefed
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
@@ -36,16 +37,17 @@ class WebRenderAPI;
 namespace layers {
 
 class Layer;
 class AsyncPanZoomController;
 class APZCTreeManagerParent;
 class CompositorBridgeParent;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
+class FocusTarget;
 struct FlingHandoffState;
 struct ScrollableLayerGuidHash;
 class LayerMetricsWrapper;
 class InputQueue;
 class GeckoContentController;
 class HitTestingTreeNode;
 class WebRenderScrollData;
 
@@ -108,16 +110,29 @@ public:
    * Initializes the global state used in AsyncPanZoomController.
    * This is normally called when it is first needed in the constructor
    * of APZCTreeManager, but can be called manually to force it to be
    * initialized earlier.
    */
   static void InitializeGlobalState();
 
   /**
+   * Rebuild the focus state based on the focus target from the layer tree update
+   * that just occurred.
+   *
+   * @param aRootLayerTreeId The layer tree ID of the root layer corresponding
+   *                         to this APZCTreeManager
+   * @param aOriginatingLayersId The layer tree ID of the layer corresponding to
+   *                             this layer tree update.
+   */
+  void UpdateFocusState(uint64_t aRootLayerTreeId,
+                        uint64_t aOriginatingLayersId,
+                        const FocusTarget& aFocusTarget);
+
+  /**
    * Rebuild the hit-testing tree based on the layer update that just came up.
    * Preserve nodes and APZC instances where possible, but retire those whose
    * layers are no longer in the layer tree.
    *
    * This must be called on the compositor thread as it walks the layer tree.
    *
    * @param aRootLayerTreeId The layer tree ID of the root layer corresponding
    *                         to this APZCTreeManager
@@ -555,16 +570,20 @@ private:
   RefPtr<HitTestingTreeNode> mRootNode;
   /* Holds the zoom constraints for scrollable layers, as determined by the
    * the main-thread gecko code. */
   std::unordered_map<ScrollableLayerGuid, ZoomConstraints, ScrollableLayerGuidHash> mZoomConstraints;
   /* A list of keyboard shortcuts to use for translating keyboard inputs into
    * keyboard actions. This is gathered on the main thread from XBL bindings.
    */
   KeyboardMap mKeyboardMap;
+  /* This tracks the focus targets of chrome and content and whether we have
+   * a current focus target or whether we are waiting for a new confirmation.
+   */
+  FocusState mFocusState;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   RefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/layers/FocusState.h"
+
+namespace mozilla {
+namespace layers {
+
+FocusState::FocusState()
+  : mFocusLayersId(0)
+  , mFocusHorizontalTarget(FrameMetrics::NULL_SCROLL_ID)
+  , mFocusVerticalTarget(FrameMetrics::NULL_SCROLL_ID)
+{
+}
+
+void
+FocusState::Update(uint64_t aRootLayerTreeId,
+                   uint64_t aOriginatingLayersId,
+                   const FocusTarget& aState)
+{
+  // Update the focus tree with the latest target
+  mFocusTree[aOriginatingLayersId] = aState;
+
+  // Reset our internal state so we can recalculate it
+  mFocusLayersId = aRootLayerTreeId;
+  mFocusHorizontalTarget = FrameMetrics::NULL_SCROLL_ID;
+  mFocusVerticalTarget = FrameMetrics::NULL_SCROLL_ID;
+
+  // To update the focus state for the entire APZCTreeManager, we need
+  // to traverse the focus tree to find the current leaf which is the global
+  // focus target we can use for async keyboard scrolling
+  while (true) {
+    auto currentNode = mFocusTree.find(mFocusLayersId);
+    if (currentNode == mFocusTree.end()) {
+      return;
+    }
+
+    const FocusTarget& target = currentNode->second;
+
+    switch (target.mType) {
+      case FocusTarget::eRefLayer: {
+        // Guard against infinite loops
+        MOZ_ASSERT(mFocusLayersId != target.mData.mRefLayerId);
+        if (mFocusLayersId == target.mData.mRefLayerId) {
+          return;
+        }
+
+        // The focus target is in a child layer tree
+        mFocusLayersId = target.mData.mRefLayerId;
+        break;
+      }
+      case FocusTarget::eScrollLayer: {
+        // This is the global focus target
+        mFocusHorizontalTarget = target.mData.mScrollTargets.mHorizontal;
+        mFocusVerticalTarget = target.mData.mScrollTargets.mVertical;
+        return;
+      }
+      case FocusTarget::eNone: {
+        return;
+      }
+      case FocusTarget::eSentinel: {
+        MOZ_ASSERT_UNREACHABLE("Invalid FocusTargetType");
+      }
+    }
+  }
+}
+
+std::unordered_set<uint64_t>
+FocusState::GetFocusTargetLayerIds() const
+{
+  std::unordered_set<uint64_t> layersIds;
+  layersIds.reserve(mFocusTree.size());
+
+  for (auto focusNode : mFocusTree) {
+    layersIds.insert(focusNode.first);
+  }
+
+  return layersIds;
+}
+
+void
+FocusState::RemoveFocusTarget(uint64_t aLayersId)
+{
+  mFocusTree.erase(aLayersId);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusState.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_layers_FocusState_h
+#define mozilla_layers_FocusState_h
+
+#include <unordered_map>    // for std::unordered_map
+#include <unordered_set>    // for std::unordered_set
+
+#include "FrameMetrics.h"   // for FrameMetrics::ViewID
+#include "mozilla/layers/FocusTarget.h" // for FocusTarget
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class is used for tracking chrome and content focus targets and calculating
+ * global focus information from them for use by APZCTreeManager for async keyboard
+ * scrolling.
+ *
+ * # Calculating the element to scroll
+ *
+ * Chrome and content processes have independently focused elements. This makes it
+ * difficult to calculate the global focused element and its scrollable frame from
+ * the chrome or content side. So instead we send the local focus information from
+ * each process to here and then calculate the global focus information. This
+ * local information resides in a `focus target`.
+ *
+ * A focus target indicates that either:
+ *    1. The focused element is a remote browser along with its layer tree ID
+ *    2. The focused element is not scrollable
+ *    3. The focused element is scrollable along with the ViewID's of its
+         scrollable layers
+ *
+ * Using this information we can determine the global focus information by
+ * starting at the focus target of the root layer tree ID and following remote
+ * browsers until we reach a scrollable or non-scrollable focus target.
+ *
+ * # Determinism and sequence numbers
+ *
+ * The focused element in content can be changed within any javascript code. And
+ * javascript can run in response to an event or at any moment from `setTimeout`
+ * and others. This makes it impossible to always have the current focus
+ * information in APZ as it can be changed asynchronously at any moment. If we
+ * don't have the latest focus information, we may incorrectly scroll a target
+ * when we shouldn't.
+ *
+ * A tradeoff is designed here whereby we will maintain deterministic focus
+ * changes for user input, but not for other javascript code. The reasoning
+ * here is that `setTimeout` and others are already non-deterministic and so it
+ * might not be as breaking to web content.
+ *
+ * To maintain deterministic focus changes for a given stream of user inputs, we
+ * invalidate our focus state whenever we receive a user input that may trigger
+ * event listeners. We then attach a new sequence number to these events and
+ * dispatch them to content. Content will then include the latest sequence number
+ * it has processed to every focus update. Using this we can determine whether
+ * any potentially focus changing events have yet to be handled by content.
+ *
+ * Once we have received the latest focus sequence number from content, we know
+ * that all event listeners triggered by user inputs, and their resulting focus
+ * changes, have been processed and so we have a current target that we can use
+ * again.
+ */
+class FocusState final
+{
+public:
+  FocusState();
+
+  /**
+   * Update the internal focus tree and recalculate the global focus target for
+   * a focus target update received from chrome or content.
+   *
+   * @param aRootLayerTreeId the layer tree ID of the root layer for the
+                             parent APZCTreeManager
+   * @param aOriginatingLayersId the layer tree ID that this focus target
+                                 belongs to
+   */
+  void Update(uint64_t aRootLayerTreeId,
+              uint64_t aOriginatingLayersId,
+              const FocusTarget& aTarget);
+
+  /**
+   * Collects a set of the layer tree IDs that we have a focus target for.
+   */
+  std::unordered_set<uint64_t> GetFocusTargetLayerIds() const;
+
+  /**
+   * Removes a focus target by its layer tree ID.
+   */
+  void RemoveFocusTarget(uint64_t aLayersId);
+
+private:
+  // The set of focus targets received indexed by their layer tree ID
+  std::unordered_map<uint64_t, FocusTarget> mFocusTree;
+
+  // The layer tree ID which contains the scrollable frame of the focused element
+  uint64_t mFocusLayersId;
+  // The scrollable layer corresponding to the scrollable frame that is used to
+  // scroll the focused element. This depends on the direction the user is
+  // scrolling.
+  FrameMetrics::ViewID mFocusHorizontalTarget;
+  FrameMetrics::ViewID mFocusVerticalTarget;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_FocusState_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/layers/FocusTarget.h"
+
+#include "mozilla/dom/TabParent.h"  // for TabParent
+#include "mozilla/layout/RenderFrameParent.h" // For RenderFrameParent
+#include "nsIPresShell.h"  // for nsIPresShell
+#include "nsLayoutUtils.h" // for nsLayoutUtils
+
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+namespace mozilla {
+namespace layers {
+
+static already_AddRefed<nsIPresShell>
+GetRetargetEventPresShell(nsIPresShell* aRootPresShell)
+{
+  MOZ_ASSERT(aRootPresShell);
+
+  // Use the last focused window in this PresShell and its
+  // associated PresShell
+  nsCOMPtr<nsPIDOMWindowOuter> window =
+    aRootPresShell->GetFocusedDOMWindowInOurWindow();
+  if (!window) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocument> retargetEventDoc = window->GetExtantDoc();
+  if (!retargetEventDoc) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIPresShell> presShell = retargetEventDoc->GetShell();
+  return presShell.forget();
+}
+
+FocusTarget::FocusTarget() : mType(FocusTarget::eNone)
+{
+}
+
+FocusTarget::FocusTarget(nsIPresShell* aRootPresShell)
+{
+  MOZ_ASSERT(aRootPresShell);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Key events can be retargeted to a child PresShell when there is an iframe
+  nsCOMPtr<nsIPresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
+
+  // Get the content that should be scrolled for this PresShell, which is
+  // the current focused element or the current DOM selection
+  nsCOMPtr<nsIContent> scrollTarget = presShell->GetContentForScrolling();
+
+  // Check if the scroll target is a remote browser
+  if (TabParent* browserParent = TabParent::GetFrom(scrollTarget)) {
+    RenderFrameParent* rfp = browserParent->GetRenderFrame();
+
+    // The globally focused element for scrolling is in a remote layer tree
+    if (rfp) {
+      mType = FocusTarget::eRefLayer;
+      mData.mRefLayerId = rfp->GetLayersId();
+      return;
+    }
+
+    mType = FocusTarget::eNone;
+    return;
+  }
+
+  // Gather the scrollable frames that would be scrolled in each direction
+  // for this scroll target
+  nsIScrollableFrame* horizontal =
+    presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
+                                                    nsIPresShell::eHorizontal);
+  nsIScrollableFrame* vertical =
+    presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
+                                                    nsIPresShell::eVertical);
+
+  // We might have the globally focused element for scrolling. Gather a ViewID for
+  // the horizontal and vertical scroll targets of this element.
+  mType = FocusTarget::eScrollLayer;
+  mData.mScrollTargets.mHorizontal =
+    nsLayoutUtils::FindIDForScrollableFrame(horizontal);
+  mData.mScrollTargets.mVertical =
+    nsLayoutUtils::FindIDForScrollableFrame(vertical);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusTarget.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_layers_FocusTarget_h
+#define mozilla_layers_FocusTarget_h
+
+#include <stdint.h> // for int32_t, uint32_t
+
+#include "FrameMetrics.h" // for FrameMetrics::ViewID
+
+class nsIPresShell;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class is used for communicating information about the currently focused
+ * element of a document and the scrollable frames to use when keyboard scrolling
+ * it. It is created on the main thread at paint-time, but is then passed over
+ * IPC to the compositor/APZ code.
+ */
+class FocusTarget final
+{
+public:
+  struct ScrollTargets
+  {
+    FrameMetrics::ViewID mHorizontal;
+    FrameMetrics::ViewID mVertical;
+  };
+
+  enum FocusTargetType
+  {
+    eNone,
+    eRefLayer,
+    eScrollLayer,
+
+    // Used as an upper bound for ContiguousEnumSerializer
+    eSentinel,
+  };
+  union FocusTargetData
+  {
+    uint64_t      mRefLayerId;
+    ScrollTargets mScrollTargets;
+  };
+
+  FocusTarget();
+
+  /**
+   * Construct a focus target for the specified top level PresShell
+   */
+  FocusTarget(nsIPresShell* aRootPresShell);
+
+public:
+  FocusTargetType mType;
+  FocusTargetData mData;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_FocusTarget_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -95,16 +95,18 @@ EXPORTS.mozilla.layers += [
     'apz/public/IAPZCTreeManager.h',
     'apz/public/MetricsSharingController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
     'apz/src/AsyncDragMetrics.h',
     'apz/src/AsyncPanZoomAnimation.h',
+    'apz/src/FocusState.h',
+    'apz/src/FocusTarget.h',
     'apz/src/Keyboard.h',
     'apz/src/TouchCounter.h',
     'apz/testutil/APZTestData.h',
     'apz/util/ActiveElementManager.h',
     'apz/util/APZCCallbackHelper.h',
     'apz/util/APZEventState.h',
     'apz/util/APZThreadUtils.h',
     'apz/util/ChromeProcessController.h',
@@ -273,16 +275,18 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
     'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/CheckerboardEvent.cpp',
     'apz/src/DragTracker.cpp',
+    'apz/src/FocusState.cpp',
+    'apz/src/FocusTarget.cpp',
     'apz/src/GestureEventListener.cpp',
     'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
     'apz/src/Keyboard.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/PotentialCheckerboardDurationTracker.cpp',
     'apz/src/QueuedInput.cpp',
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2272,17 +2272,17 @@ PresShell::IntraLineMove(bool aForward, 
 }
 
 
 
 NS_IMETHODIMP
 PresShell::PageMove(bool aForward, bool aExtend)
 {
   nsIScrollableFrame *scrollableFrame =
-    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+    GetScrollableFrameToScroll(nsIPresShell::eVertical);
   if (!scrollableFrame)
     return NS_OK;
 
   RefPtr<nsFrameSelection> frameSelection = mSelection;
   frameSelection->CommonPageMove(aForward, aExtend, scrollableFrame);
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
@@ -2292,17 +2292,17 @@ PresShell::PageMove(bool aForward, bool 
 }
 
 
 
 NS_IMETHODIMP
 PresShell::ScrollPage(bool aForward)
 {
   nsIScrollableFrame* scrollFrame =
-    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+    GetScrollableFrameToScroll(nsIPresShell::eVertical);
   if (scrollFrame) {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
         (uint32_t) ScrollInputMethod::MainThreadScrollPage);
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                           nsIScrollableFrame::PAGES,
                           nsIScrollableFrame::SMOOTH,
                           nullptr, nullptr,
                           nsIScrollableFrame::NOT_MOMENTUM,
@@ -2310,17 +2310,17 @@ PresShell::ScrollPage(bool aForward)
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::ScrollLine(bool aForward)
 {
   nsIScrollableFrame* scrollFrame =
-    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+    GetScrollableFrameToScroll(nsIPresShell::eVertical);
   if (scrollFrame) {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
         (uint32_t) ScrollInputMethod::MainThreadScrollLine);
 
     int32_t lineCount = Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
                                             NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? lineCount : -lineCount),
                           nsIScrollableFrame::LINES,
@@ -2331,17 +2331,17 @@ PresShell::ScrollLine(bool aForward)
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::ScrollCharacter(bool aRight)
 {
   nsIScrollableFrame* scrollFrame =
-    GetFrameToScrollAsScrollable(nsIPresShell::eHorizontal);
+    GetScrollableFrameToScroll(nsIPresShell::eHorizontal);
   if (scrollFrame) {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
         (uint32_t) ScrollInputMethod::MainThreadScrollCharacter);
     int32_t h = Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
                                     NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
     scrollFrame->ScrollBy(nsIntPoint(aRight ? h : -h, 0),
                           nsIScrollableFrame::LINES,
                           nsIScrollableFrame::SMOOTH,
@@ -2351,17 +2351,17 @@ PresShell::ScrollCharacter(bool aRight)
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::CompleteScroll(bool aForward)
 {
   nsIScrollableFrame* scrollFrame =
-    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+    GetScrollableFrameToScroll(nsIPresShell::eVertical);
   if (scrollFrame) {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
         (uint32_t) ScrollInputMethod::MainThreadCompleteScroll);
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                           nsIScrollableFrame::WHOLE,
                           nsIScrollableFrame::SMOOTH,
                           nullptr, nullptr,
                           nsIScrollableFrame::NOT_MOMENTUM,
@@ -2825,22 +2825,19 @@ PresShell::FrameNeedsToContinueReflow(ns
                nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
                "Frame passed in is not the descendant of mCurrentReflowRoot");
   NS_ASSERTION(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW,
                "Frame passed in not in reflow?");
 
   mFramesToDirty.PutEntry(aFrame);
 }
 
-nsIScrollableFrame*
-nsIPresShell::GetFrameToScrollAsScrollable(
-                nsIPresShell::ScrollDirection aDirection)
-{
-  nsIScrollableFrame* scrollFrame = nullptr;
-
+already_AddRefed<nsIContent>
+nsIPresShell::GetContentForScrolling() const
+{
   nsCOMPtr<nsIContent> focusedContent;
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm && mDocument) {
     nsCOMPtr<nsIDOMElement> focusedElement;
     fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
                                    getter_AddRefs(focusedElement));
     focusedContent = do_QueryInterface(focusedElement);
   }
@@ -2848,18 +2845,27 @@ nsIPresShell::GetFrameToScrollAsScrollab
     nsISelection* domSelection =
       mSelection->GetSelection(SelectionType::eNormal);
     if (domSelection) {
       nsCOMPtr<nsIDOMNode> focusedNode;
       domSelection->GetFocusNode(getter_AddRefs(focusedNode));
       focusedContent = do_QueryInterface(focusedNode);
     }
   }
-  if (focusedContent) {
-    nsIFrame* startFrame = focusedContent->GetPrimaryFrame();
+  return focusedContent.forget();
+}
+
+nsIScrollableFrame*
+nsIPresShell::GetScrollableFrameToScrollForContent(
+                nsIContent* aContent,
+                nsIPresShell::ScrollDirection aDirection)
+{
+  nsIScrollableFrame* scrollFrame = nullptr;
+  if (aContent) {
+    nsIFrame* startFrame = aContent->GetPrimaryFrame();
     if (startFrame) {
       scrollFrame = startFrame->GetScrollTargetFrame();
       if (scrollFrame) {
         startFrame = scrollFrame->GetScrolledFrame();
       }
       if (aDirection == nsIPresShell::eEither) {
         scrollFrame =
           nsLayoutUtils::GetNearestScrollableFrame(startFrame);
@@ -2872,16 +2878,23 @@ nsIPresShell::GetFrameToScrollAsScrollab
     }
   }
   if (!scrollFrame) {
     scrollFrame = GetRootScrollFrameAsScrollable();
   }
   return scrollFrame;
 }
 
+nsIScrollableFrame*
+nsIPresShell::GetScrollableFrameToScroll(nsIPresShell::ScrollDirection aDirection)
+{
+  nsCOMPtr<nsIContent> content = GetContentForScrolling();
+  return GetScrollableFrameToScrollForContent(content.get(), aDirection);
+}
+
 void
 PresShell::CancelAllPendingReflows()
 {
   mDirtyRoots.Clear();
 
   if (mObservingLayoutFlushes) {
     GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
     mObservingLayoutFlushes = false;
@@ -6738,16 +6751,27 @@ PresShell::GetRootWindow()
 
   // If we don't have DOM window, we're zombie, we should find the root window
   // with our parent shell.
   nsCOMPtr<nsIPresShell> parent = GetParentPresShellForEventHandling();
   NS_ENSURE_TRUE(parent, nullptr);
   return parent->GetRootWindow();
 }
 
+already_AddRefed<nsPIDOMWindowOuter>
+PresShell::GetFocusedDOMWindowInOurWindow()
+{
+  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
+  NS_ENSURE_TRUE(rootWindow, nullptr);
+  nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+  nsFocusManager::GetFocusedDescendant(rootWindow, true,
+                                       getter_AddRefs(focusedWindow));
+  return focusedWindow.forget();
+}
+
 already_AddRefed<nsIPresShell>
 PresShell::GetParentPresShellForEventHandling()
 {
   NS_ENSURE_TRUE(mPresContext, nullptr);
 
   // Now, find the parent pres shell and send the event there
   nsCOMPtr<nsIDocShellTreeItem> treeItem = mPresContext->GetDocShell();
   if (!treeItem) {
@@ -6784,27 +6808,16 @@ PresShell::RetargetEventToParent(WidgetG
 }
 
 void
 PresShell::DisableNonTestMouseEvents(bool aDisable)
 {
   sDisableNonTestMouseEvents = aDisable;
 }
 
-already_AddRefed<nsPIDOMWindowOuter>
-PresShell::GetFocusedDOMWindowInOurWindow()
-{
-  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
-  NS_ENSURE_TRUE(rootWindow, nullptr);
-  nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
-  nsFocusManager::GetFocusedDescendant(rootWindow, true,
-                                       getter_AddRefs(focusedWindow));
-  return focusedWindow.forget();
-}
-
 void
 PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent)
 {
   if (!mPresContext)
     return;
 
   if (!mPresContext->IsRoot()) {
     PresShell* rootPresShell = GetRootPresShell();
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -205,16 +205,18 @@ public:
   virtual already_AddRefed<SourceSurface>
   RenderSelection(nsISelection* aSelection,
                   const mozilla::LayoutDeviceIntPoint aPoint,
                   mozilla::LayoutDeviceIntRect* aScreenRect,
                   uint32_t aFlags) override;
 
   virtual already_AddRefed<nsPIDOMWindowOuter> GetRootWindow() override;
 
+  virtual already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow() override;
+
   virtual LayerManager* GetLayerManager() override;
 
   virtual bool AsyncPanZoomEnabled() override;
 
   virtual void SetIgnoreViewportScrolling(bool aIgnore) override;
 
   virtual nsresult SetResolution(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
@@ -683,19 +685,16 @@ protected:
    *                                    event.  Otherwise, false.
    */
   nsresult HandleEventInternal(mozilla::WidgetEvent* aEvent,
                                nsEventStatus* aStatus,
                                bool aIsHandlingNativeEvent);
   nsresult HandlePositionedEvent(nsIFrame* aTargetFrame,
                                  mozilla::WidgetGUIEvent* aEvent,
                                  nsEventStatus* aEventStatus);
-  // This returns the focused DOM window under our top level window.
-  //  I.e., when we are deactive, this returns the *last* focused DOM window.
-  already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow();
 
   /*
    * This and the next two helper methods are used to target and position the
    * context menu when the keyboard shortcut is used to open it.
    *
    * If another menu is open, the context menu is opened relative to the
    * active menuitem within the menu, or the menu itself if no item is active.
    * Otherwise, if the caret is visible, the menu is opened near the caret.
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -423,25 +423,41 @@ public:
   nsIScrollableFrame* GetRootScrollFrameAsScrollable() const;
 
   /*
    * The same as GetRootScrollFrame, but returns an nsIScrollableFrame.
    * Can be called by code not linked into gklayout.
    */
   virtual nsIScrollableFrame* GetRootScrollFrameAsScrollableExternal() const;
 
-  /*
+  /**
+   * Get the current focused content or DOM selection that should be the
+   * target for scrolling.
+   */
+  already_AddRefed<nsIContent> GetContentForScrolling() const;
+
+  /**
+   * Gets nearest scrollable frame from the specified content node. The frame
+   * is scrollable with overflow:scroll or overflow:auto in some direction when
+   * aDirection is eEither.  Otherwise, this returns a nearest frame that is
+   * scrollable in the specified direction.
+   */
+  enum ScrollDirection { eHorizontal, eVertical, eEither };
+  nsIScrollableFrame* GetScrollableFrameToScrollForContent(
+                         nsIContent* aContent,
+                         ScrollDirection aDirection);
+
+  /**
    * Gets nearest scrollable frame from current focused content or DOM
    * selection if there is no focused content. The frame is scrollable with
    * overflow:scroll or overflow:auto in some direction when aDirection is
    * eEither.  Otherwise, this returns a nearest frame that is scrollable in
    * the specified direction.
    */
-  enum ScrollDirection { eHorizontal, eVertical, eEither };
-  nsIScrollableFrame* GetFrameToScrollAsScrollable(ScrollDirection aDirection);
+  nsIScrollableFrame* GetScrollableFrameToScroll(ScrollDirection aDirection);
 
   /**
    * Returns the page sequence frame associated with the frame hierarchy.
    * Returns nullptr if not a paginated view.
    */
   virtual nsIPageSequenceFrame* GetPageSequenceFrame() const = 0;
 
   /**
@@ -1416,16 +1432,22 @@ public:
   void IncrementPaintCount() { ++mPaintCount; }
 
   /**
    * Get the root DOM window of this presShell.
    */
   virtual already_AddRefed<nsPIDOMWindowOuter> GetRootWindow() = 0;
 
   /**
+   * This returns the focused DOM window under our top level window.
+   * I.e., when we are deactive, this returns the *last* focused DOM window.
+   */
+  virtual already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow() = 0;
+
+  /**
    * Get the layer manager for the widget of the root view, if it has
    * one.
    */
   virtual LayerManager* GetLayerManager() = 0;
 
   /**
    * Return true iff there is a widget rendering this presShell and that
    * widget is APZ-enabled.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -860,16 +860,35 @@ nsLayoutUtils::FindScrollableFrameFor(Vi
   if (!content) {
     return nullptr;
   }
 
   nsIFrame* scrollFrame = GetScrollFrameFromContent(content);
   return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
 }
 
+ViewID
+nsLayoutUtils::FindIDForScrollableFrame(nsIScrollableFrame* aScrollable)
+{
+  if (!aScrollable) {
+    return FrameMetrics::NULL_SCROLL_ID;
+  }
+
+  nsIFrame* scrollFrame = do_QueryFrame(aScrollable);
+  nsIContent* scrollContent = scrollFrame->GetContent();
+
+  FrameMetrics::ViewID scrollId;
+  if (scrollContent &&
+      nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) {
+    return scrollId;
+  }
+
+  return FrameMetrics::NULL_SCROLL_ID;
+}
+
 static nsRect
 ApplyRectMultiplier(nsRect aRect, float aMultiplier)
 {
   if (aMultiplier == 1.0f) {
     return aRect;
   }
   float newWidth = aRect.width * aMultiplier;
   float newHeight = aRect.height * aMultiplier;
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -184,16 +184,21 @@ public:
   static nsIContent* FindContentFor(ViewID aId);
 
   /**
    * Find the scrollable frame for a given ID.
    */
   static nsIScrollableFrame* FindScrollableFrameFor(ViewID aId);
 
   /**
+   * Find the ID for a given scrollable frame.
+   */
+  static ViewID FindIDForScrollableFrame(nsIScrollableFrame* aScrollable);
+
+  /**
    * Get display port for the given element, relative to the specified entity,
    * defaulting to the scrollport.
    */
   static bool GetDisplayPort(nsIContent* aContent, nsRect *aResult,
     RelativeTo aRelativeTo = RelativeTo::ScrollPort);
 
   /**
    * Check whether the given element has a displayport.