/* -*- 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/. */ #ifndef mozilla_WheelHandlingHelper_h_ #define mozilla_WheelHandlingHelper_h_ #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "nsCoord.h" #include "nsIFrame.h" // for AutoWeakFrame only #include "nsPoint.h" class nsIFrame; class nsIScrollableFrame; class nsITimer; namespace mozilla { class EventStateManager; /** * DeltaValues stores two delta values which are along X and Y axis. This is * useful for arguments and results of some methods. */ struct DeltaValues { DeltaValues() : deltaX(0.0), deltaY(0.0) {} DeltaValues(double aDeltaX, double aDeltaY) : deltaX(aDeltaX), deltaY(aDeltaY) {} explicit DeltaValues(WidgetWheelEvent* aEvent); double deltaX; double deltaY; }; /** * WheelHandlingUtils provides some static methods which are useful at handling * wheel events. */ class WheelHandlingUtils { public: /** * Returns true if aFrame is a scrollable frame and it can be scrolled to * either aDirectionX or aDirectionY along each axis. Or if aFrame is a * plugin frame (in this case, aDirectionX and aDirectionY are ignored). * Otherwise, false. */ static bool CanScrollOn(nsIFrame* aFrame, double aDirectionX, double aDirectionY); /** * Returns true if the scrollable frame can be scrolled to either aDirectionX * or aDirectionY along each axis. Otherwise, false. */ static bool CanScrollOn(nsIScrollableFrame* aScrollFrame, double aDirectionX, double aDirectionY); // For more details about the concept of a disregarded direction, refer to the // code in struct mozilla::layers::ScrollMetadata which defines // mDisregardedDirection. static Maybe GetDisregardedWheelScrollDirection( const nsIFrame* aFrame); private: static bool CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax, double aDirection); }; /** * ScrollbarsForWheel manages scrollbars state during wheel operation. * E.g., on some platforms, scrollbars should show only while user attempts to * scroll. At that time, scrollbars which may be possible to scroll by * operation of wheel at the point should show temporarily. */ class ScrollbarsForWheel { public: static void PrepareToScrollText(EventStateManager* aESM, nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent); static void SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget); // Hide all scrollbars (both mActiveOwner's and mActivatedScrollTargets') static void MayInactivate(); static void Inactivate(); static bool IsActive(); static void OwnWheelTransaction(bool aOwn); protected: static const size_t kNumberOfTargets = 4; static const DeltaValues directions[kNumberOfTargets]; static AutoWeakFrame sActiveOwner; static AutoWeakFrame sActivatedScrollTargets[kNumberOfTargets]; static bool sHadWheelStart; static bool sOwnWheelTransaction; /** * These two methods are called upon eWheelOperationStart/eWheelOperationEnd * events to show/hide the right scrollbars. */ static void TemporarilyActivateAllPossibleScrollTargets( EventStateManager* aESM, nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent); static void DeactivateAllTemporarilyActivatedScrollTargets(); }; /** * WheelTransaction manages a series of wheel events as a transaction. * While in a transaction, every wheel event should scroll the same scrollable * element even if a different scrollable element is under the mouse cursor. * * Additionally, this class also manages wheel scroll speed acceleration. */ class WheelTransaction { public: /** * Get the target scroll frame for this wheel transaction. This should * the the scrollable fame that will scroll for all wheel events in * this wheel transaction. */ static nsIFrame* GetScrollTargetFrame() { return sScrollTargetFrame; } /* * The event target to use for all wheel events in this wheel transaction. * This should be the event target for all wheel events in this wheel * transaction. Note that this frame will likely be a child of the * scrollable frame. */ static nsIFrame* GetEventTargetFrame() { return sEventTargetFrame; } static bool HandledByApz() { return sHandledByApz; } static void EndTransaction(); /** * WillHandleDefaultAction() is called before handling aWheelEvent on * aScrollTargetWeakFrame given the event target aEventTargetWeakFrame. * * @return false if the caller cannot continue to handle the default * action. Otherwise, true. */ static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aScrollTargetWeakFrame, AutoWeakFrame& aEventTargetWeakFrame); static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, nsIFrame* aScrollTargetFrame, nsIFrame* aEventTargetFrame) { AutoWeakFrame scrollTargetWeakFrame(aScrollTargetFrame); AutoWeakFrame eventTargetWeakFrame(aEventTargetFrame); return WillHandleDefaultAction(aWheelEvent, scrollTargetWeakFrame, eventTargetWeakFrame); } static void OnEvent(WidgetEvent* aEvent); static void OnRemoveElement(nsIContent* aContent); static void Shutdown(); static void OwnScrollbars(bool aOwn); static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent); protected: static void BeginTransaction(nsIFrame* aScrollTargetFrame, nsIFrame* aEventTargetFrame, const WidgetWheelEvent* aEvent); // Be careful, UpdateTransaction may fire a DOM event, therefore, the target // frame might be destroyed in the event handler. static bool UpdateTransaction(const WidgetWheelEvent* aEvent); static void MayEndTransaction(); static LayoutDeviceIntPoint GetScreenPoint(WidgetGUIEvent* aEvent); static void OnFailToScrollTarget(); static void OnTimeout(nsITimer* aTimer, void* aClosure); static void SetTimeout(); static DeltaValues OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent); static double ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor); static bool OutOfTime(uint32_t aBaseTime, uint32_t aThreshold); /** * The scrollable element the current wheel event group is bound to. */ static AutoWeakFrame sScrollTargetFrame; /** * The initial target of the first wheel event in the wheel event group. * This frame is typically a child of the scrollable element. The wheel * event should target the topmost-event-target. For a wheel event * group, we'll use this target for the entire group. * * See https://w3c.github.io/uievents/#topmost-event-target and * https://w3c.github.io/uievents/#event-type-wheel for details. * * Note: this is only populated if dom.event.wheel-event-groups.enabled is * set. */ static AutoWeakFrame sEventTargetFrame; /** * The wheel events for this transaction are handled by APZ. */ static bool sHandledByApz; static uint32_t sTime; // in milliseconds static uint32_t sMouseMoved; // in milliseconds static nsITimer* sTimer; static int32_t sScrollSeriesCounter; static bool sOwnScrollbars; }; // For some kinds of scrollings, the delta values of WidgetWheelEvent are // possbile to be adjusted. For example, the user has configured the pref to let // [vertical wheel + Shift key] to perform horizontal scrolling instead of // vertical scrolling. // The values in this enumeration list all kinds of scrollings whose delta // values are possible to be adjusted. enum class WheelDeltaAdjustmentStrategy : uint8_t { // There is no strategy, don't adjust delta values in any cases. eNone, // This strategy means we're receiving a horizontalized scroll, so we should // apply horizontalization strategy for its delta values. // Horizontalized scrolling means treating vertical wheel scrolling as // horizontal scrolling by adjusting delta values. // It's important to keep in mind with the percise concept of horizontalized // scrolling: Delta values are *ONLY* going to be adjusted during the process // of its default action handling; in views of any programmes other than the // default action handler, such as a DOM event listener or a plugin, delta // values are never going to be adjusted, they will still retrive original // delta values when horizontalization occured for default actions. eHorizontalize, // The following two strategies mean we're receving an auto-dir scroll, so we // should apply auto-dir adjustment to the delta of the wheel event if needed. // Auto-dir is a feature which treats any single-wheel scroll as a scroll in // the only one scrollable direction if the target has only one scrollable // direction. For example, if the user scrolls a vertical wheel inside a // target which is horizontally scrollable but vertical unscrollable, then the // vertical scroll is converted to a horizontal scroll for that target. // So why do we need two different strategies for auto-dir scrolling? That's // because when a wheel scroll is converted due to auto-dir, there is one // thing called "honoured target" which decides which side the converted // scroll goes towards. If the content of the honoured target horizontally // is RTL content, then an upward scroll maps to a rightward scroll and a // downward scroll maps to a leftward scroll; otherwise, an upward scroll maps // to a leftward scroll and a downward scroll maps to a rightward scroll. // |eAutoDir| considers the scrolling target as the honoured target. // |eAutoDirWithRootHonour| takes the root element of the document with the // scrolling element, and considers that as the honoured target. But note that // there's one exception: for targets in an HTML document, the real root // element(I.e. the element) is typically not considered as a root // element, but the element is typically considered as a root element. // If there is no element, then consider the element instead. // And also note that like |eHorizontalize|, delta values are *ONLY* going to // be adjusted during the process of its default action handling; in views of // any programmes other than the default action handler, such as a DOM event // listener or a plugin, delta values are never going to be adjusted. eAutoDir, eAutoDirWithRootHonour, // Not an actual strategy. This is just used as an upper bound for // ContiguousEnumSerializer. eSentinel, }; /** * When a *pure* vertical wheel event should be treated as if it was a * horizontal scroll because the user wants to horizontalize the wheel scroll, * an instance of this class will adjust the delta values upon calling * Horizontalize(). And the horizontalized delta values will be restored * automatically when the instance of this class is being destructed. Or you can * restore them in advance by calling CancelHorizontalization(). */ class MOZ_STACK_CLASS WheelDeltaHorizontalizer final { public: /** * @param aWheelEvent A wheel event whose delta values will be adjusted * upon calling Horizontalize(). */ explicit WheelDeltaHorizontalizer(WidgetWheelEvent& aWheelEvent) : mWheelEvent(aWheelEvent), mOldDeltaX(0.0), mOldDeltaZ(0.0), mOldOverflowDeltaX(0.0), mOldLineOrPageDeltaX(0), mHorizontalized(false) {} /** * Converts vertical scrolling into horizontal scrolling by adjusting the * its delta values. */ void Horizontalize(); ~WheelDeltaHorizontalizer(); void CancelHorizontalization(); private: WidgetWheelEvent& mWheelEvent; double mOldDeltaX; double mOldDeltaZ; double mOldOverflowDeltaX; int32_t mOldLineOrPageDeltaX; bool mHorizontalized; }; /** * This class is used to adjust the delta values for wheel scrolling with the * auto-dir functionality. * A traditional wheel scroll only allows the user use the wheel in the same * scrollable direction as that of the scrolling target to scroll the target, * whereas an auto-dir scroll lets the user use any wheel(either a vertical * wheel or a horizontal tilt wheel) to scroll a frame which is scrollable in * only one direction. For detailed information on auto-dir scrolling, * @see mozilla::WheelDeltaAdjustmentStrategy. */ class MOZ_STACK_CLASS AutoDirWheelDeltaAdjuster { protected: /** * @param aDeltaX DeltaX for a wheel event whose delta values will * be adjusted upon calling Adjust() when * ShouldBeAdjusted() returns true. * @param aDeltaY DeltaY for a wheel event, like DeltaX. */ AutoDirWheelDeltaAdjuster(double& aDeltaX, double& aDeltaY) : mDeltaX(aDeltaX), mDeltaY(aDeltaY), mCheckedIfShouldBeAdjusted(false), mShouldBeAdjusted(false) {} public: /** * Gets whether the values of the delta should be adjusted for auto-dir * scrolling. Note that if Adjust() has been called, this function simply * returns false. * * @return true if the delta should be adjusted; otherwise false. */ bool ShouldBeAdjusted(); /** * Adjusts the values of the delta values for auto-dir scrolling when * ShouldBeAdjusted() returns true. If you call it when ShouldBeAdjusted() * returns false, this function will simply do nothing. */ void Adjust(); private: /** * Called by Adjust() if Adjust() successfully adjusted the delta values. */ virtual void OnAdjusted() {} virtual bool CanScrollAlongXAxis() const = 0; virtual bool CanScrollAlongYAxis() const = 0; virtual bool CanScrollUpwards() const = 0; virtual bool CanScrollDownwards() const = 0; virtual bool CanScrollLeftwards() const = 0; virtual bool CanScrollRightwards() const = 0; /** * Gets whether the horizontal content starts at rightside. * * @return If the content is in vertical-RTL writing mode(E.g. "writing-mode: * vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode * (E.g. "writing-mode: horizontal-tb; direction: rtl;" in CSS), then * this function returns true. From the representation perspective, * frames whose horizontal contents start at rightside also cause * their horizontal scrollbars, if any, initially start at rightside. * So we can also learn about the initial side of the horizontal * scrollbar for the frame by calling this function. */ virtual bool IsHorizontalContentRightToLeft() const = 0; protected: double& mDeltaX; double& mDeltaY; private: bool mCheckedIfShouldBeAdjusted; bool mShouldBeAdjusted; }; /** * This is the implementation of AutoDirWheelDeltaAdjuster for EventStateManager * * Detailed comments about some member functions are given in the base class * AutoDirWheelDeltaAdjuster. */ class MOZ_STACK_CLASS ESMAutoDirWheelDeltaAdjuster final : public AutoDirWheelDeltaAdjuster { public: /** * @param aEvent The auto-dir wheel scroll event. * @param aScrollFrame The scroll target for the event. * @param aHonoursRoot If set to true, the honoured frame is the root * frame in the same document where the target is; * If false, the honoured frame is the scroll * target. For the concept of an honoured target, * @see mozilla::WheelDeltaAdjustmentStrategy */ ESMAutoDirWheelDeltaAdjuster(WidgetWheelEvent& aEvent, nsIFrame& aScrollFrame, bool aHonoursRoot); private: virtual void OnAdjusted() override; virtual bool CanScrollAlongXAxis() const override; virtual bool CanScrollAlongYAxis() const override; virtual bool CanScrollUpwards() const override; virtual bool CanScrollDownwards() const override; virtual bool CanScrollLeftwards() const override; virtual bool CanScrollRightwards() const override; virtual bool IsHorizontalContentRightToLeft() const override; nsIScrollableFrame* mScrollTargetFrame; bool mIsHorizontalContentRightToLeft; int32_t& mLineOrPageDeltaX; int32_t& mLineOrPageDeltaY; double& mOverflowDeltaX; double& mOverflowDeltaY; }; /** * This class is used for restoring the delta in an auto-dir wheel. * * An instance of this calss monitors auto-dir adjustment which may happen * during its lifetime. If the delta values is adjusted during its lifetime, the * instance will restore the adjusted delta when it's being destrcuted. */ class MOZ_STACK_CLASS ESMAutoDirWheelDeltaRestorer final { public: /** * @param aEvent The wheel scroll event to be monitored. */ explicit ESMAutoDirWheelDeltaRestorer(WidgetWheelEvent& aEvent); ~ESMAutoDirWheelDeltaRestorer(); private: WidgetWheelEvent& mEvent; double mOldDeltaX; double mOldDeltaY; int32_t mOldLineOrPageDeltaX; int32_t mOldLineOrPageDeltaY; double mOldOverflowDeltaX; double mOldOverflowDeltaY; }; } // namespace mozilla #endif // mozilla_WheelHandlingHelper_h_