Bug 1385071 - Allow keyboard APZ with passive listeners, behind a pref. r=kats draft
authorRyan Hunt <rhunt@eqrion.net>
Thu, 27 Jul 2017 19:53:57 -0400
changeset 620712 77543ef3f28bdbb8ef77e984097ce75cdf333c82
parent 620711 724b110fd55c5935df589f1cbcc0b3380028a1ed
child 640788 cce4a6b1102d2d0bb5337e196d1dfe3c1c01a5c1
push id72133
push userbmo:rhunt@eqrion.net
push dateThu, 03 Aug 2017 20:23:49 +0000
reviewerskats
bugs1385071
milestone57.0a1
Bug 1385071 - Allow keyboard APZ with passive listeners, behind a pref. r=kats This commit adds the pref, 'apz.keyboard.passive-listeners', to allow web content to have passive key event listeners and use keyboard APZ. When we are allowing passive listeners, we need to dispatch the input to content and can no longer consume the event. So we use mHandledByAPZ in nsXBLWindowKeyHandler to determine whether we still need to do the default action, or whether it has been done by APZ. MozReview-Commit-ID: 2HAC6DjDyPZ
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/FocusTarget.cpp
gfx/layers/apz/src/InputQueue.cpp
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1354,17 +1354,18 @@ APZCTreeManager::ReceiveInputEvent(Input
       // Dispatch the event to the input queue.
       result = mInputQueue->ReceiveInputEvent(
           targetApzc,
           /* aTargetConfirmed = */ true,
           keyInput, aOutInputBlockId);
 
       // Any keyboard event that is dispatched to the input queue at this point
       // should have been consumed
-      MOZ_ASSERT(result == nsEventStatus_eConsumeNoDefault);
+      MOZ_ASSERT(result == nsEventStatus_eConsumeDoDefault ||
+                 result == nsEventStatus_eConsumeNoDefault);
 
       keyInput.mHandledByAPZ = true;
       focusSetter.MarkAsNonFocusChanging();
 
       break;
     }
   }
   return result;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -280,16 +280,22 @@ typedef GenericFlingAnimation FlingAnima
  *
  * \li\b apz.frame_delay.enabled
  * If this is set to true, changes to the async scroll offset and async zoom
  * will not be immediately reflected in GetCurrentAsyncTransform() when called
  * with |AsyncTransformConsumer::eForCompositing|. Rather, the transform will
  * reflect the value of the async scroll offset and async zoom at the last time
  * SampleCompositedAsyncTransform() was called.
  *
+ * \li\b apz.keyboard.passive-listeners
+ * When enabled, APZ will interpret the passive event listener flag to mean
+ * that the event listener won't change the focused element or selection of
+ * the page. With this, web content can use passive key listeners and not have
+ * keyboard APZ disabled.
+ *
  * \li\b apz.max_velocity_inches_per_ms
  * Maximum velocity.  Velocity will be capped at this value if a faster fling
  * occurs.  Negative values indicate unlimited velocity.\n
  * Units: (real-world, i.e. screen) inches per millisecond
  *
  * \li\b apz.max_velocity_queue_size
  * Maximum size of velocity queue. The queue contains last N velocity records.
  * On touch end we calculate the average velocity in order to compensate
@@ -1745,17 +1751,17 @@ AsyncPanZoomController::OnKeyboard(const
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, scrollUnit)) {
     // If we're scroll snapping, use a smooth scroll animation to get
     // the desired physics. Note that SmoothScrollTo() will re-use an
     // existing smooth scroll animation if there is one.
     APZC_LOG("%p keyboard scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str());
     SmoothScrollTo(*snapPoint);
-    return nsEventStatus_eConsumeNoDefault;
+    return nsEventStatus_eConsumeDoDefault;
   }
 
   // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
   if (mState != KEYBOARD_SCROLL) {
     CancelAnimation();
     SetState(KEYBOARD_SCROLL);
 
     nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
@@ -1770,17 +1776,17 @@ AsyncPanZoomController::OnKeyboard(const
 
   KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
   MOZ_ASSERT(animation);
 
   animation->UpdateDestination(aEvent.mTimeStamp,
                                CSSPixel::ToAppUnits(destination),
                                nsSize(velocity.x, velocity.y));
 
-  return nsEventStatus_eConsumeNoDefault;
+  return nsEventStatus_eConsumeDoDefault;
 }
 
 CSSPoint
 AsyncPanZoomController::GetKeyboardDestination(const KeyboardScrollAction& aAction) const
 {
   CSSSize lineScrollSize;
   CSSSize pageScrollSize;
   CSSPoint scrollOffset;
--- a/gfx/layers/apz/src/FocusTarget.cpp
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -67,16 +67,36 @@ HasListenersForKeyEvents(nsIContent* aCo
     if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) {
       return true;
     }
   }
   return false;
 }
 
 static bool
+HasListenersForNonPassiveKeyEvents(nsIContent* aContent)
+{
+  if (!aContent) {
+    return false;
+  }
+
+  WidgetEvent event(true, eVoidEvent);
+  nsTArray<EventTarget*> targets;
+  nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
+      nullptr, nullptr, &targets);
+  NS_ENSURE_SUCCESS(rv, false);
+  for (size_t i = 0; i < targets.Length(); i++) {
+    if (targets[i]->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool
 IsEditableNode(nsINode* aNode)
 {
   return aNode && aNode->IsEditable();
 }
 
 FocusTarget::FocusTarget()
   : mSequenceNumber(0)
   , mFocusHasKeyEventListeners(false)
@@ -111,37 +131,46 @@ FocusTarget::FocusTarget(nsIPresShell* a
     mType = FocusTarget::eNone;
     return;
   }
 
   // Find the focused content and use it to determine whether there are key event
   // listeners or whether key events will be targeted at a different process
   // through a remote browser.
   nsCOMPtr<nsIContent> focusedContent = presShell->GetFocusedContentInOurWindow();
+  nsCOMPtr<nsIContent> keyEventTarget = focusedContent;
+
+  // If there is no focused element then event dispatch goes to the body of
+  // the page if it exists or the root element.
+  if (!keyEventTarget) {
+    keyEventTarget = document->GetUnfocusedKeyEventTarget();
+  }
 
   // Check if there are key event listeners that could prevent default or change
   // the focus or selection of the page.
-  mFocusHasKeyEventListeners =
-    HasListenersForKeyEvents(focusedContent ? focusedContent.get()
-                                            : document->GetUnfocusedKeyEventTarget());
+  if (gfxPrefs::APZKeyboardPassiveListeners()) {
+    mFocusHasKeyEventListeners = HasListenersForNonPassiveKeyEvents(keyEventTarget.get());
+  } else {
+    mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get());
+  }
 
-  // Check if the focused element is content editable or if the document
+  // Check if the key event target is content editable or if the document
   // is in design mode.
-  if (IsEditableNode(focusedContent) ||
+  if (IsEditableNode(keyEventTarget) ||
       IsEditableNode(document)) {
     FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for editable node)\n",
            aFocusSequenceNumber,
            static_cast<int>(mFocusHasKeyEventListeners));
 
     mType = FocusTarget::eNone;
     return;
   }
 
-  // Check if the focused element is a remote browser
-  if (TabParent* browserParent = TabParent::GetFrom(focusedContent)) {
+  // Check if the key event target is a remote browser
+  if (TabParent* browserParent = TabParent::GetFrom(keyEventTarget)) {
     RenderFrameParent* rfp = browserParent->GetRenderFrame();
 
     // The globally focused element for scrolling is in a remote layer tree
     if (rfp) {
       FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 "\n",
              aFocusSequenceNumber,
              mFocusHasKeyEventListeners,
              rfp->GetLayersId());
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -301,17 +301,20 @@ InputQueue::ReceiveKeyboardInput(const R
   if (aOutInputBlockId) {
     *aOutInputBlockId = block->GetBlockId();
   }
 
   mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
 
   ProcessQueue();
 
-  return nsEventStatus_eConsumeNoDefault;
+  // If APZ is allowing passive listeners then we must dispatch the event to
+  // content, otherwise we can consume the event.
+  return gfxPrefs::APZKeyboardPassiveListeners() ? nsEventStatus_eConsumeDoDefault
+                                                 : nsEventStatus_eConsumeNoDefault;
 }
 
 static bool
 CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent,
                             PanGestureBlockState* aBlock)
 {
   PanGestureInput horizontalComponent = aInitialEvent;
   horizontalComponent.mPanDisplacement.y = 0;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -309,16 +309,17 @@ private:
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
   DECL_GFX_PREF(Live, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold",      APZFlingMinVelocityThreshold, float, 0.5f);
   DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold",       APZFlingStopOnTapThreshold, float, 0.05f);
   DECL_GFX_PREF(Live, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Live, "apz.frame_delay.enabled",               APZFrameDelayEnabled, bool, false);
   DECL_GFX_PREF(Once, "apz.keyboard.enabled",                  APZKeyboardEnabled, bool, false);
+  DECL_GFX_PREF(Live, "apz.keyboard.passive-listeners",        APZKeyboardPassiveListeners, bool, false);
   DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.minimap.enabled",                   APZMinimap, bool, false);
   DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled",        APZMinimapVisibilityEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.one_touch_pinch.enabled",           APZOneTouchPinchEnabled, bool, true);
   DECL_GFX_PREF(Live, "apz.overscroll.enabled",                APZOverscrollEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.overscroll.min_pan_distance_ratio", APZMinPanDistanceRatio, float, 1.0f);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -715,16 +715,17 @@ pref("apz.frame_delay.enabled", true);
 #else
 pref("apz.frame_delay.enabled", false);
 #endif
 #if defined(NIGHTLY_BUILD) && !defined(MOZ_WIDGET_ANDROID)
 pref("apz.keyboard.enabled", true);
 #else
 pref("apz.keyboard.enabled", false);
 #endif
+pref("apz.keyboard.passive-listeners", false);
 pref("apz.max_velocity_inches_per_ms", "-1.0");
 pref("apz.max_velocity_queue_size", 5);
 pref("apz.min_skate_speed", "1.0");
 pref("apz.minimap.enabled", false);
 pref("apz.minimap.visibility.enabled", false);
 pref("apz.one_touch_pinch.enabled", true);
 pref("apz.overscroll.enabled", false);
 pref("apz.overscroll.min_pan_distance_ratio", "1.0");