summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/APZInputBridge.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz/src/APZInputBridge.cpp')
-rw-r--r--gfx/layers/apz/src/APZInputBridge.cpp435
1 files changed, 435 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/APZInputBridge.cpp b/gfx/layers/apz/src/APZInputBridge.cpp
new file mode 100644
index 0000000000..bce801a6d2
--- /dev/null
+++ b/gfx/layers/apz/src/APZInputBridge.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "mozilla/layers/APZInputBridge.h"
+
+#include "AsyncPanZoomController.h"
+#include "InputData.h" // for MouseInput, etc
+#include "InputBlockState.h" // for InputBlockState
+#include "OverscrollHandoffState.h" // for OverscrollHandoffState
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/WheelEventBinding.h" // for WheelEvent constants
+#include "mozilla/EventStateManager.h" // for EventStateManager
+#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
+#include "mozilla/MouseEvents.h" // for WidgetMouseEvent
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_general.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/TextEvents.h" // for WidgetKeyboardEvent
+#include "mozilla/TouchEvents.h" // for WidgetTouchEvent
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaHorizontalizer,
+ // WheelDeltaAdjustmentStrategy
+
+namespace mozilla {
+namespace layers {
+
+APZEventResult::APZEventResult()
+ : mStatus(nsEventStatus_eIgnore),
+ mInputBlockId(InputBlockState::NO_BLOCK_ID) {}
+
+APZEventResult::APZEventResult(
+ const RefPtr<AsyncPanZoomController>& aInitialTarget,
+ TargetConfirmationFlags aFlags)
+ : APZEventResult() {
+ mHandledResult = [&]() -> Maybe<APZHandledResult> {
+ if (!aInitialTarget->IsRootContent()) {
+ // If the initial target is not the root, this will definitely not be
+ // handled by the root. (The confirmed target is either the initial
+ // target, or a descendant.)
+ return Some(
+ APZHandledResult{APZHandledPlace::HandledByContent, aInitialTarget});
+ }
+
+ if (!aFlags.mDispatchToContent) {
+ // If the initial target is the root and we don't need to dispatch to
+ // content, the event will definitely be handled by the root.
+ return Some(
+ APZHandledResult{APZHandledPlace::HandledByRoot, aInitialTarget});
+ }
+
+ // Otherwise, we're not sure.
+ return Nothing();
+ }();
+ aInitialTarget->GetGuid(&mTargetGuid);
+}
+
+void APZEventResult::SetStatusAsConsumeDoDefault(
+ const InputBlockState& aBlock) {
+ SetStatusAsConsumeDoDefault(aBlock.GetTargetApzc());
+}
+
+void APZEventResult::SetStatusAsConsumeDoDefault(
+ const RefPtr<AsyncPanZoomController>& aTarget) {
+ mStatus = nsEventStatus_eConsumeDoDefault;
+ mHandledResult =
+ Some(aTarget && aTarget->IsRootContent()
+ ? APZHandledResult{APZHandledPlace::HandledByRoot, aTarget}
+ : APZHandledResult{APZHandledPlace::HandledByContent, aTarget});
+}
+
+void APZEventResult::SetStatusForTouchEvent(
+ const InputBlockState& aBlock, TargetConfirmationFlags aFlags,
+ PointerEventsConsumableFlags aConsumableFlags,
+ const AsyncPanZoomController* aTarget) {
+ // Note, we need to continue setting mStatus to eIgnore in the {mHasRoom=true,
+ // mAllowedByTouchAction=false} case because this is the behaviour expected by
+ // APZEventState::ProcessTouchEvent() when it determines when to send a
+ // `pointercancel` event. TODO: Use something more descriptive than
+ // nsEventStatus for this purpose.
+ mStatus = aConsumableFlags.IsConsumable() ? nsEventStatus_eConsumeDoDefault
+ : nsEventStatus_eIgnore;
+
+ UpdateHandledResult(aBlock, aConsumableFlags, aTarget,
+ aFlags.mDispatchToContent);
+}
+
+void APZEventResult::UpdateHandledResult(
+ const InputBlockState& aBlock,
+ PointerEventsConsumableFlags aConsumableFlags,
+ const AsyncPanZoomController* aTarget, bool aDispatchToContent) {
+ // If the touch event's effect is disallowed by touch-action, treat it as if
+ // a touch event listener had preventDefault()-ed it (i.e. return
+ // HandledByContent, except we can do it eagerly rather than having to wait
+ // for the listener to run).
+ if (!aConsumableFlags.mAllowedByTouchAction) {
+ mHandledResult =
+ Some(APZHandledResult{APZHandledPlace::HandledByContent, aTarget});
+ return;
+ }
+
+ if (mHandledResult && !aDispatchToContent && !aConsumableFlags.mHasRoom) {
+ // Set result to Unhandled if we have no room to scroll, unless it
+ // was HandledByContent because we're over a dispatch-to-content region,
+ // in which case it should remain HandledByContent.
+ mHandledResult->mPlace = APZHandledPlace::Unhandled;
+ }
+
+ if (aTarget && !aTarget->IsRootContent()) {
+ auto [result, rootApzc] =
+ aBlock.GetOverscrollHandoffChain()->ScrollingDownWillMoveDynamicToolbar(
+ aTarget);
+ if (result) {
+ MOZ_ASSERT(rootApzc && rootApzc->IsRootContent());
+ // The event is actually consumed by a non-root APZC but scroll
+ // positions in all relevant APZCs are at the bottom edge, so if there's
+ // still contents covered by the dynamic toolbar we need to move the
+ // dynamic toolbar to make the covered contents visible, thus we need
+ // to tell it to GeckoView so we handle it as if it's consumed in the
+ // root APZC.
+ // IMPORTANT NOTE: If the incoming TargetConfirmationFlags has
+ // mDispatchToContent, we need to change it to Nothing() so that
+ // GeckoView can properly wait for results from the content on the
+ // main-thread.
+ mHandledResult =
+ aDispatchToContent
+ ? Nothing()
+ : Some(APZHandledResult{aConsumableFlags.IsConsumable()
+ ? APZHandledPlace::HandledByRoot
+ : APZHandledPlace::Unhandled,
+ rootApzc});
+ }
+ }
+}
+
+void APZEventResult::SetStatusForFastFling(
+ const TouchBlockState& aBlock, TargetConfirmationFlags aFlags,
+ PointerEventsConsumableFlags aConsumableFlags,
+ const AsyncPanZoomController* aTarget) {
+ MOZ_ASSERT(aBlock.IsDuringFastFling());
+
+ // Set eConsumeNoDefault for fast fling since we don't want to send the event
+ // to content at all.
+ mStatus = nsEventStatus_eConsumeNoDefault;
+
+ // In the case of fast fling, the event will never be sent to content, so we
+ // want a result where `aDispatchToContent` is false whatever the original
+ // `aFlags.mDispatchToContent` is.
+ UpdateHandledResult(aBlock, aConsumableFlags, aTarget, false /*
+ aDispatchToContent */);
+}
+
+static bool WillHandleMouseEvent(const WidgetMouseEventBase& aEvent) {
+ return aEvent.mMessage == eMouseMove || aEvent.mMessage == eMouseDown ||
+ aEvent.mMessage == eMouseUp || aEvent.mMessage == eDragEnd ||
+ (StaticPrefs::test_events_async_enabled() &&
+ aEvent.mMessage == eMouseHitTest);
+}
+
+/* static */
+Maybe<APZWheelAction> APZInputBridge::ActionForWheelEvent(
+ WidgetWheelEvent* aEvent) {
+ if (!(aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_LINE ||
+ aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_PIXEL ||
+ aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_PAGE)) {
+ return Nothing();
+ }
+ return EventStateManager::APZWheelActionFor(aEvent);
+}
+
+APZEventResult APZInputBridge::ReceiveInputEvent(
+ WidgetInputEvent& aEvent, InputBlockCallback&& aCallback) {
+ APZThreadUtils::AssertOnControllerThread();
+
+ APZEventResult result;
+
+ switch (aEvent.mClass) {
+ case eMouseEventClass:
+ case eDragEventClass: {
+ WidgetMouseEvent& mouseEvent = *aEvent.AsMouseEvent();
+ if (WillHandleMouseEvent(mouseEvent)) {
+ MouseInput input(mouseEvent);
+ input.mOrigin =
+ ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y);
+
+ result = ReceiveInputEvent(input, std::move(aCallback));
+
+ mouseEvent.mRefPoint = TruncatedToInt(ViewAs<LayoutDevicePixel>(
+ input.mOrigin,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+ mouseEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
+#ifdef XP_MACOSX
+ // It's not assumed that the click event has already been prevented,
+ // except mousedown event with ctrl key is pressed where we prevent
+ // click event from widget on Mac platform.
+ MOZ_ASSERT_IF(!mouseEvent.IsControl() ||
+ mouseEvent.mMessage != eMouseDown ||
+ mouseEvent.mButton != MouseButton::ePrimary,
+ !mouseEvent.mClickEventPrevented);
+#else
+ MOZ_ASSERT(
+ !mouseEvent.mClickEventPrevented,
+ "It's not assumed that the click event has already been prevented");
+#endif
+ mouseEvent.mClickEventPrevented |= input.mPreventClickEvent;
+ MOZ_ASSERT_IF(mouseEvent.mClickEventPrevented,
+ mouseEvent.mMessage == eMouseDown ||
+ mouseEvent.mMessage == eMouseUp);
+ aEvent.mLayersId = input.mLayersId;
+
+ if (mouseEvent.IsReal()) {
+ UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage,
+ Some(result.mTargetGuid));
+ }
+
+ return result;
+ }
+
+ if (mouseEvent.IsReal()) {
+ UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage,
+ Nothing());
+ }
+
+ ProcessUnhandledEvent(&mouseEvent.mRefPoint, &result.mTargetGuid,
+ &aEvent.mFocusSequenceNumber, &aEvent.mLayersId);
+ return result;
+ }
+ case eTouchEventClass: {
+ WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent();
+ MultiTouchInput touchInput(touchEvent);
+ result = ReceiveInputEvent(touchInput, std::move(aCallback));
+ // touchInput was modified in-place to possibly remove some
+ // touch points (if we are overscrolled), and the coordinates were
+ // modified using the APZ untransform. We need to copy these changes
+ // back into the WidgetInputEvent.
+ touchEvent.mTouches.Clear();
+ touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length());
+ for (size_t i = 0; i < touchInput.mTouches.Length(); i++) {
+ *touchEvent.mTouches.AppendElement() =
+ touchInput.mTouches[i].ToNewDOMTouch();
+ }
+ touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ;
+ touchEvent.mFocusSequenceNumber = touchInput.mFocusSequenceNumber;
+ aEvent.mLayersId = touchInput.mLayersId;
+ return result;
+ }
+ case eWheelEventClass: {
+ WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent();
+
+ if (Maybe<APZWheelAction> action = ActionForWheelEvent(&wheelEvent)) {
+ ScrollWheelInput::ScrollMode scrollMode =
+ ScrollWheelInput::SCROLLMODE_INSTANT;
+ if (StaticPrefs::general_smoothScroll() &&
+ ((wheelEvent.mDeltaMode ==
+ dom::WheelEvent_Binding::DOM_DELTA_LINE &&
+ StaticPrefs::general_smoothScroll_mouseWheel()) ||
+ (wheelEvent.mDeltaMode ==
+ dom::WheelEvent_Binding::DOM_DELTA_PAGE &&
+ StaticPrefs::general_smoothScroll_pages()))) {
+ scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
+ }
+
+ WheelDeltaAdjustmentStrategy strategy =
+ EventStateManager::GetWheelDeltaAdjustmentStrategy(wheelEvent);
+ // Adjust the delta values of the wheel event if the current default
+ // action is to horizontalize scrolling. I.e., deltaY values are set to
+ // deltaX and deltaY and deltaZ values are set to 0.
+ // If horizontalized, the delta values will be restored and its overflow
+ // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
+ // being destroyed.
+ WheelDeltaHorizontalizer horizontalizer(wheelEvent);
+ if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
+ horizontalizer.Horizontalize();
+ }
+
+ // If the wheel event becomes no-op event, don't handle it as scroll.
+ if (wheelEvent.mDeltaX || wheelEvent.mDeltaY) {
+ ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y);
+ ScrollWheelInput input(
+ wheelEvent.mTimeStamp, 0, scrollMode,
+ ScrollWheelInput::DeltaTypeForDeltaMode(wheelEvent.mDeltaMode),
+ origin, wheelEvent.mDeltaX, wheelEvent.mDeltaY,
+ wheelEvent.mAllowToOverrideSystemScrollSpeed, strategy);
+ input.mAPZAction = action.value();
+
+ // We add the user multiplier as a separate field, rather than
+ // premultiplying it, because if the input is converted back to a
+ // WidgetWheelEvent, then EventStateManager would apply the delta a
+ // second time. We could in theory work around this by asking ESM to
+ // customize the event much sooner, and then save the
+ // "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for now,
+ // this seems easier.
+ EventStateManager::GetUserPrefsForWheelEvent(
+ &wheelEvent, &input.mUserDeltaMultiplierX,
+ &input.mUserDeltaMultiplierY);
+
+ result = ReceiveInputEvent(input, std::move(aCallback));
+ wheelEvent.mRefPoint = TruncatedToInt(ViewAs<LayoutDevicePixel>(
+ input.mOrigin, PixelCastJustification::
+ LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+ wheelEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
+ aEvent.mLayersId = input.mLayersId;
+
+ return result;
+ }
+ }
+
+ UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage, Nothing());
+ ProcessUnhandledEvent(&aEvent.mRefPoint, &result.mTargetGuid,
+ &aEvent.mFocusSequenceNumber, &aEvent.mLayersId);
+ MOZ_ASSERT(result.GetStatus() == nsEventStatus_eIgnore);
+ return result;
+ }
+ case eKeyboardEventClass: {
+ WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent();
+
+ KeyboardInput input(keyboardEvent);
+
+ result = ReceiveInputEvent(input, std::move(aCallback));
+
+ keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+ keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
+ return result;
+ }
+ default: {
+ UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage, Nothing());
+ ProcessUnhandledEvent(&aEvent.mRefPoint, &result.mTargetGuid,
+ &aEvent.mFocusSequenceNumber, &aEvent.mLayersId);
+ return result;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type.");
+ result.SetStatusAsConsumeNoDefault();
+ return result;
+}
+
+APZHandledResult::APZHandledResult(APZHandledPlace aPlace,
+ const AsyncPanZoomController* aTarget)
+ : mPlace(aPlace) {
+ MOZ_ASSERT(aTarget);
+ switch (aPlace) {
+ case APZHandledPlace::Unhandled:
+ break;
+ case APZHandledPlace::HandledByContent:
+ if (aTarget) {
+ mScrollableDirections = aTarget->ScrollableDirections();
+ mOverscrollDirections = aTarget->GetAllowedHandoffDirections();
+ }
+ break;
+ case APZHandledPlace::HandledByRoot: {
+ MOZ_ASSERT(aTarget->IsRootContent());
+ if (aTarget) {
+ mScrollableDirections = aTarget->ScrollableDirections();
+ mOverscrollDirections = aTarget->GetAllowedHandoffDirections();
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid APZHandledPlace");
+ break;
+ }
+}
+
+std::ostream& operator<<(std::ostream& aOut, const SideBits& aSideBits) {
+ if ((aSideBits & SideBits::eAll) == SideBits::eAll) {
+ aOut << "all";
+ } else {
+ AutoTArray<nsCString, 4> strings;
+ if (aSideBits & SideBits::eTop) {
+ strings.AppendElement("top"_ns);
+ }
+ if (aSideBits & SideBits::eRight) {
+ strings.AppendElement("right"_ns);
+ }
+ if (aSideBits & SideBits::eBottom) {
+ strings.AppendElement("bottom"_ns);
+ }
+ if (aSideBits & SideBits::eLeft) {
+ strings.AppendElement("left"_ns);
+ }
+ aOut << strings;
+ }
+ return aOut;
+}
+
+std::ostream& operator<<(std::ostream& aOut,
+ const ScrollDirections& aScrollDirections) {
+ if (aScrollDirections.contains(EitherScrollDirection)) {
+ aOut << "either";
+ } else if (aScrollDirections.contains(HorizontalScrollDirection)) {
+ aOut << "horizontal";
+ } else if (aScrollDirections.contains(VerticalScrollDirection)) {
+ aOut << "vertical";
+ } else {
+ aOut << "none";
+ }
+ return aOut;
+}
+
+std::ostream& operator<<(std::ostream& aOut,
+ const APZHandledPlace& aHandledPlace) {
+ switch (aHandledPlace) {
+ case APZHandledPlace::Unhandled:
+ aOut << "unhandled";
+ break;
+ case APZHandledPlace::HandledByRoot: {
+ aOut << "handled-by-root";
+ break;
+ }
+ case APZHandledPlace::HandledByContent: {
+ aOut << "handled-by-content";
+ break;
+ }
+ case APZHandledPlace::Invalid: {
+ aOut << "INVALID";
+ break;
+ }
+ }
+ return aOut;
+}
+
+std::ostream& operator<<(std::ostream& aOut,
+ const APZHandledResult& aHandledResult) {
+ aOut << "handled: " << aHandledResult.mPlace << ", ";
+ aOut << "scrollable: " << aHandledResult.mScrollableDirections << ", ";
+ aOut << "overscroll: " << aHandledResult.mOverscrollDirections << std::endl;
+ return aOut;
+}
+
+} // namespace layers
+} // namespace mozilla