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
--- 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");