forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			7309 lines
		
	
	
	
		
			276 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			7309 lines
		
	
	
	
		
			276 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "EventStateManager.h"
 | |
| 
 | |
| #include "mozilla/AsyncEventDispatcher.h"
 | |
| #include "mozilla/Attributes.h"
 | |
| #include "mozilla/EditorBase.h"
 | |
| #include "mozilla/EventDispatcher.h"
 | |
| #include "mozilla/EventForwards.h"
 | |
| #include "mozilla/Hal.h"
 | |
| #include "mozilla/HTMLEditor.h"
 | |
| #include "mozilla/IMEStateManager.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/FocusModel.h"
 | |
| #include "mozilla/MiscEvents.h"
 | |
| #include "mozilla/MathAlgorithms.h"
 | |
| #include "mozilla/MouseEvents.h"
 | |
| #include "mozilla/PointerLockManager.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/ScopeExit.h"
 | |
| #include "mozilla/ScrollTypes.h"
 | |
| #include "mozilla/TextComposition.h"
 | |
| #include "mozilla/TextControlElement.h"
 | |
| #include "mozilla/TextEditor.h"
 | |
| #include "mozilla/TextEvents.h"
 | |
| #include "mozilla/TouchEvents.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "mozilla/dom/BrowserBridgeChild.h"
 | |
| #include "mozilla/dom/BrowsingContext.h"
 | |
| #include "mozilla/dom/CanonicalBrowsingContext.h"
 | |
| #include "mozilla/dom/ContentChild.h"
 | |
| #include "mozilla/dom/DOMIntersectionObserver.h"
 | |
| #include "mozilla/dom/DragEvent.h"
 | |
| #include "mozilla/dom/Event.h"
 | |
| #include "mozilla/dom/FrameLoaderBinding.h"
 | |
| #include "mozilla/dom/HTMLLabelElement.h"
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| #include "mozilla/dom/MouseEventBinding.h"
 | |
| #include "mozilla/dom/BrowserChild.h"
 | |
| #include "mozilla/dom/PointerEventHandler.h"
 | |
| #include "mozilla/dom/UIEvent.h"
 | |
| #include "mozilla/dom/UIEventBinding.h"
 | |
| #include "mozilla/dom/UserActivation.h"
 | |
| #include "mozilla/dom/WheelEventBinding.h"
 | |
| #include "mozilla/glean/GleanMetrics.h"
 | |
| #include "mozilla/StaticPrefs_accessibility.h"
 | |
| #include "mozilla/StaticPrefs_browser.h"
 | |
| #include "mozilla/StaticPrefs_dom.h"
 | |
| #include "mozilla/StaticPrefs_layout.h"
 | |
| #include "mozilla/StaticPrefs_mousewheel.h"
 | |
| #include "mozilla/StaticPrefs_ui.h"
 | |
| #include "mozilla/StaticPrefs_zoom.h"
 | |
| 
 | |
| #include "ContentEventHandler.h"
 | |
| #include "IMEContentObserver.h"
 | |
| #include "WheelHandlingHelper.h"
 | |
| #include "RemoteDragStartData.h"
 | |
| 
 | |
| #include "nsCommandParams.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsCopySupport.h"
 | |
| #include "nsFocusManager.h"
 | |
| #include "nsGenericHTMLElement.h"
 | |
| #include "nsIClipboard.h"
 | |
| #include "nsIContent.h"
 | |
| #include "nsIContentInlines.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "nsICookieJarSettings.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsFrameLoaderOwner.h"
 | |
| #include "nsIWeakReferenceUtils.h"
 | |
| #include "nsIWidget.h"
 | |
| #include "nsLiteralString.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsTArray.h"
 | |
| #include "nsGkAtoms.h"
 | |
| #include "nsIFormControl.h"
 | |
| #include "nsComboboxControlFrame.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsIDOMXULControlElement.h"
 | |
| #include "nsNameSpaceManager.h"
 | |
| #include "nsIBaseWindow.h"
 | |
| #include "nsFrameSelection.h"
 | |
| #include "nsPIDOMWindow.h"
 | |
| #include "nsPIWindowRoot.h"
 | |
| #include "nsIWebNavigation.h"
 | |
| #include "nsIDocumentViewer.h"
 | |
| #include "nsFrameManager.h"
 | |
| #include "nsIBrowserChild.h"
 | |
| #include "nsMenuPopupFrame.h"
 | |
| 
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsIDocShell.h"
 | |
| 
 | |
| #include "nsSubDocumentFrame.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsIInterfaceRequestorUtils.h"
 | |
| #include "nsUnicharUtils.h"
 | |
| #include "nsContentUtils.h"
 | |
| 
 | |
| #include "imgIContainer.h"
 | |
| #include "nsIProperties.h"
 | |
| #include "nsISupportsPrimitives.h"
 | |
| 
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsITimer.h"
 | |
| #include "nsFontMetrics.h"
 | |
| #include "nsIDragService.h"
 | |
| #include "nsIDragSession.h"
 | |
| #include "mozilla/dom/DataTransfer.h"
 | |
| #include "nsContentAreaDragDrop.h"
 | |
| #include "nsTreeBodyFrame.h"
 | |
| #include "nsIController.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/dom/ContentParent.h"
 | |
| #include "mozilla/dom/Record.h"
 | |
| #include "mozilla/dom/Selection.h"
 | |
| 
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/LookAndFeel.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "Units.h"
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
| #  import <ApplicationServices/ApplicationServices.h>
 | |
| #endif
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using namespace dom;
 | |
| 
 | |
| static const LayoutDeviceIntPoint kInvalidRefPoint =
 | |
|     LayoutDeviceIntPoint(-1, -1);
 | |
| 
 | |
| static uint32_t gMouseOrKeyboardEventCounter = 0;
 | |
| static nsITimer* gUserInteractionTimer = nullptr;
 | |
| static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
 | |
| 
 | |
| static const double kCursorLoadingTimeout = 1000;  // ms
 | |
| static AutoWeakFrame gLastCursorSourceFrame;
 | |
| static TimeStamp gLastCursorUpdateTime;
 | |
| static TimeStamp gTypingStartTime;
 | |
| static TimeStamp gTypingEndTime;
 | |
| static int32_t gTypingInteractionKeyPresses = 0;
 | |
| static dom::InteractionData gTypingInteraction = {};
 | |
| 
 | |
| static inline int32_t RoundDown(double aDouble) {
 | |
|   return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble))
 | |
|                        : static_cast<int32_t>(ceil(aDouble));
 | |
| }
 | |
| 
 | |
| static bool IsSelectingLink(nsIFrame* aTargetFrame) {
 | |
|   if (!aTargetFrame) {
 | |
|     return false;
 | |
|   }
 | |
|   const nsFrameSelection* frameSel = aTargetFrame->GetConstFrameSelection();
 | |
|   if (!frameSel || !frameSel->GetDragState()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!nsContentUtils::GetClosestLinkInFlatTree(aTargetFrame->GetContent())) {
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
 | |
|     WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
 | |
|     EventTarget* aRelatedTarget);
 | |
| 
 | |
| /**
 | |
|  * Returns the common ancestor for mouseup purpose, given the
 | |
|  * current mouseup target and the previous mousedown target.
 | |
|  */
 | |
| static nsINode* GetCommonAncestorForMouseUp(
 | |
|     nsINode* aCurrentMouseUpTarget, nsINode* aLastMouseDownTarget,
 | |
|     Maybe<FormControlType>& aLastMouseDownInputControlType) {
 | |
|   if (!aCurrentMouseUpTarget || !aLastMouseDownTarget) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (aCurrentMouseUpTarget == aLastMouseDownTarget) {
 | |
|     return aCurrentMouseUpTarget;
 | |
|   }
 | |
| 
 | |
|   // Build the chain of parents
 | |
|   AutoTArray<nsINode*, 30> parents1;
 | |
|   do {
 | |
|     parents1.AppendElement(aCurrentMouseUpTarget);
 | |
|     aCurrentMouseUpTarget = aCurrentMouseUpTarget->GetFlattenedTreeParentNode();
 | |
|   } while (aCurrentMouseUpTarget);
 | |
| 
 | |
|   AutoTArray<nsINode*, 30> parents2;
 | |
|   do {
 | |
|     parents2.AppendElement(aLastMouseDownTarget);
 | |
|     if (aLastMouseDownTarget == parents1.LastElement()) {
 | |
|       break;
 | |
|     }
 | |
|     aLastMouseDownTarget = aLastMouseDownTarget->GetFlattenedTreeParentNode();
 | |
|   } while (aLastMouseDownTarget);
 | |
| 
 | |
|   // Find where the parent chain differs
 | |
|   uint32_t pos1 = parents1.Length();
 | |
|   uint32_t pos2 = parents2.Length();
 | |
|   nsINode* parent = nullptr;
 | |
|   for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
 | |
|     nsINode* child1 = parents1.ElementAt(--pos1);
 | |
|     nsINode* child2 = parents2.ElementAt(--pos2);
 | |
|     if (child1 != child2) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // If the input control type is different between mouseup and mousedown,
 | |
|     // this is not a valid click.
 | |
|     if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(child1)) {
 | |
|       if (aLastMouseDownInputControlType.isSome() &&
 | |
|           aLastMouseDownInputControlType.ref() != input->ControlType()) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     parent = child1;
 | |
|   }
 | |
| 
 | |
|   return parent;
 | |
| }
 | |
| 
 | |
| LazyLogModule sMouseBoundaryLog("MouseBoundaryEvents");
 | |
| LazyLogModule sPointerBoundaryLog("PointerBoundaryEvents");
 | |
| 
 | |
| /******************************************************************/
 | |
| /* mozilla::UITimerCallback                                       */
 | |
| /******************************************************************/
 | |
| 
 | |
| class UITimerCallback final : public nsITimerCallback, public nsINamed {
 | |
|  public:
 | |
|   UITimerCallback() : mPreviousCount(0) {}
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSITIMERCALLBACK
 | |
|   NS_DECL_NSINAMED
 | |
|  private:
 | |
|   ~UITimerCallback() = default;
 | |
|   uint32_t mPreviousCount;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)
 | |
| 
 | |
| // If aTimer is nullptr, this method always sends "user-interaction-inactive"
 | |
| // notification.
 | |
| NS_IMETHODIMP
 | |
| UITimerCallback::Notify(nsITimer* aTimer) {
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   if (!obs) return NS_ERROR_FAILURE;
 | |
|   if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
 | |
|     gMouseOrKeyboardEventCounter = 0;
 | |
|     obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
 | |
|     if (gUserInteractionTimer) {
 | |
|       gUserInteractionTimer->Cancel();
 | |
|       NS_RELEASE(gUserInteractionTimer);
 | |
|     }
 | |
|   } else {
 | |
|     obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
 | |
|     EventStateManager::UpdateUserActivityTimer();
 | |
| 
 | |
|     if (XRE_IsParentProcess()) {
 | |
|       hal::BatteryInformation batteryInfo;
 | |
|       hal::GetCurrentBatteryInformation(&batteryInfo);
 | |
|       glean::power_battery::percentage_when_user_active.AccumulateSingleSample(
 | |
|           uint64_t(batteryInfo.level() * 100));
 | |
|     }
 | |
|   }
 | |
|   mPreviousCount = gMouseOrKeyboardEventCounter;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| UITimerCallback::GetName(nsACString& aName) {
 | |
|   aName.AssignLiteral("UITimerCallback_timer");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /******************************************************************/
 | |
| /* mozilla::OverOutElementsWrapper                                */
 | |
| /******************************************************************/
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, mDeepestEnterEventTarget,
 | |
|                          mDispatchingOverEventTarget,
 | |
|                          mDispatchingOutOrDeepestLeaveEventTarget)
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| already_AddRefed<nsIWidget> OverOutElementsWrapper::GetLastOverWidget() const {
 | |
|   nsCOMPtr<nsIWidget> widget = do_QueryReferent(mLastOverWidget);
 | |
|   return widget.forget();
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::ContentRemoved(nsIContent& aContent) {
 | |
|   if (!mDeepestEnterEventTarget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|           mDeepestEnterEventTarget, &aContent)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LogModule* const logModule = mType == BoundaryEventType::Mouse
 | |
|                                    ? sMouseBoundaryLog
 | |
|                                    : sPointerBoundaryLog;
 | |
| 
 | |
|   if (!StaticPrefs::
 | |
|           dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed()) {
 | |
|     MOZ_LOG(logModule, LogLevel::Info,
 | |
|             ("The last \"over\" event target (%p) is removed",
 | |
|              mDeepestEnterEventTarget.get()));
 | |
|     StoreOverEventTargetAndDeepestEnterEventTarget(nullptr);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mDispatchingOverEventTarget &&
 | |
|       (mDeepestEnterEventTarget == mDispatchingOverEventTarget ||
 | |
|        nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|            mDispatchingOverEventTarget, &aContent))) {
 | |
|     if (mDispatchingOverEventTarget ==
 | |
|         mDispatchingOutOrDeepestLeaveEventTarget) {
 | |
|       MOZ_LOG(logModule, LogLevel::Info,
 | |
|               ("The dispatching \"%s\" event target (%p) is removed",
 | |
|                LastOverEventTargetIsOutEventTarget() ? "out" : "leave",
 | |
|                mDispatchingOutOrDeepestLeaveEventTarget.get()));
 | |
|       mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
 | |
|     }
 | |
|     MOZ_LOG(logModule, LogLevel::Info,
 | |
|             ("The dispatching \"over\" event target (%p) is removed",
 | |
|              mDispatchingOverEventTarget.get()));
 | |
|     mDispatchingOverEventTarget = nullptr;
 | |
|   }
 | |
|   if (mDispatchingOutOrDeepestLeaveEventTarget &&
 | |
|       (mDeepestEnterEventTarget == mDispatchingOutOrDeepestLeaveEventTarget ||
 | |
|        nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|            mDispatchingOutOrDeepestLeaveEventTarget, &aContent))) {
 | |
|     MOZ_LOG(logModule, LogLevel::Info,
 | |
|             ("The dispatching \"%s\" event target (%p) is removed",
 | |
|              LastOverEventTargetIsOutEventTarget() ? "out" : "leave",
 | |
|              mDispatchingOutOrDeepestLeaveEventTarget.get()));
 | |
|     mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
 | |
|   }
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("The last \"%s\" event target (%p) is removed and now the last "
 | |
|            "deepest enter target becomes %s(%p)",
 | |
|            LastOverEventTargetIsOutEventTarget() ? "over" : "enter",
 | |
|            mDeepestEnterEventTarget.get(),
 | |
|            aContent.GetFlattenedTreeParent()
 | |
|                ? ToString(*aContent.GetFlattenedTreeParent()).c_str()
 | |
|                : "nullptr",
 | |
|            aContent.GetFlattenedTreeParent()));
 | |
|   UpdateDeepestEnterEventTarget(aContent.GetFlattenedTreeParent());
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::TryToRestorePendingRemovedOverTarget(
 | |
|     const WidgetEvent* aEvent) {
 | |
|   if (!MaybeHasPendingRemovingOverEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LogModule* const logModule = mType == BoundaryEventType::Mouse
 | |
|                                    ? sMouseBoundaryLog
 | |
|                                    : sPointerBoundaryLog;
 | |
| 
 | |
|   // If we receive a mouse event immediately, let's try to restore the last
 | |
|   // "over" event target as the following "out" event target.  We assume that a
 | |
|   // synthesized mousemove or another mouse event is being dispatched at latest
 | |
|   // the next animation frame from the removal.  However, synthesized mouse move
 | |
|   // which is enqueued by ContentRemoved() may not sent to this instance because
 | |
|   // the target is considered with the latest layout, so the document of this
 | |
|   // instance may be moved somewhere before the next animation frame.
 | |
|   // Therefore, we should not restore the last "over" target if we receive an
 | |
|   // unexpected event like a keyboard event, a wheel event, etc.
 | |
|   if (aEvent->AsMouseEvent()) {
 | |
|     // Restore the original "over" event target should be allowed only when it's
 | |
|     // reconnected under the last deepest "enter" event target because we need
 | |
|     // to dispatch "leave" events later at least on the ancestors which have
 | |
|     // never been removed from the tree.
 | |
|     // XXX If new ancestor is inserted between mDeepestEnterEventTarget and
 | |
|     // mPendingToRemoveLastOverEventTarget, we will dispatch "leave" event even
 | |
|     // though we have not dispatched "enter" event on the element.  For fixing
 | |
|     // this, we need to store the full path of the last "out" event target when
 | |
|     // it's removed from the tree.  I guess we can be relax for this issue
 | |
|     // because this hack is required for web apps which reconnect the target
 | |
|     // to the same position immediately.
 | |
|     // XXX Should be IsInclusiveFlatTreeDescendantOf()?  However, it may
 | |
|     // be reconnected into a subtree which is different from where the
 | |
|     // last over element was.
 | |
|     nsCOMPtr<nsIContent> pendingRemovingOverEventTarget =
 | |
|         GetPendingRemovingOverEventTarget();
 | |
|     if (pendingRemovingOverEventTarget &&
 | |
|         pendingRemovingOverEventTarget->IsInclusiveDescendantOf(
 | |
|             mDeepestEnterEventTarget)) {
 | |
|       // StoreOverEventTargetAndDeepestEnterEventTarget() always resets
 | |
|       // mLastOverWidget.  When we restore the pending removing "over" event
 | |
|       // target, we need to keep storing the original "over" widget too.
 | |
|       nsCOMPtr<nsIWeakReference> widget = std::move(mLastOverWidget);
 | |
|       StoreOverEventTargetAndDeepestEnterEventTarget(
 | |
|           pendingRemovingOverEventTarget);
 | |
|       mLastOverWidget = std::move(widget);
 | |
|       MOZ_LOG(logModule, LogLevel::Info,
 | |
|               ("The \"over\" event target (%p) is restored",
 | |
|                mDeepestEnterEventTarget.get()));
 | |
|       return;
 | |
|     }
 | |
|     MOZ_LOG(logModule, LogLevel::Debug,
 | |
|             ("Forgetting the last \"over\" event target (%p) because it is not "
 | |
|              "reconnected under the deepest enter event target (%p)",
 | |
|              mPendingRemovingOverEventTarget.get(),
 | |
|              mDeepestEnterEventTarget.get()));
 | |
|   } else {
 | |
|     MOZ_LOG(logModule, LogLevel::Debug,
 | |
|             ("Forgetting the last \"over\" event target (%p) because an "
 | |
|              "unexpected event (%s) is being dispatched, that means that "
 | |
|              "EventStateManager didn't receive a synthesized mousemove which "
 | |
|              "should be dispatched at next animation frame from the removal",
 | |
|              mPendingRemovingOverEventTarget.get(), ToChar(aEvent->mMessage)));
 | |
|   }
 | |
| 
 | |
|   // Now, we should not restore mPendingRemovingOverEventTarget to
 | |
|   // mDeepestEnterEventTarget anymore since mPendingRemovingOverEventTarget was
 | |
|   // moved outside the subtree of mDeepestEnterEventTarget.
 | |
|   mPendingRemovingOverEventTarget = nullptr;
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::WillDispatchOverAndEnterEvent(
 | |
|     nsIContent* aOverEventTarget) {
 | |
|   StoreOverEventTargetAndDeepestEnterEventTarget(aOverEventTarget);
 | |
|   // Store the first "over" event target we fire and don't refire "over" event
 | |
|   // to that element while the first "over" event is still ongoing.
 | |
|   mDispatchingOverEventTarget = aOverEventTarget;
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::DidDispatchOverAndEnterEvent(
 | |
|     nsIContent* aOriginalOverTargetInComposedDoc,
 | |
|     nsIWidget* aOverEventTargetWidget) {
 | |
|   mDispatchingOverEventTarget = nullptr;
 | |
|   mLastOverWidget = do_GetWeakReference(aOverEventTargetWidget);
 | |
| 
 | |
|   // Pointer Events define that once the `pointerover` event target is removed
 | |
|   // from the tree, `pointerout` should not be fired on that and the closest
 | |
|   // connected ancestor at the target removal should be kept as the deepest
 | |
|   // `pointerleave` target.  Therefore, we don't need the special handling for
 | |
|   // `pointerout` event target if the last `pointerover` target is temporarily
 | |
|   // removed from the tree.
 | |
|   if (mType == OverOutElementsWrapper::BoundaryEventType::Pointer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Assume that the caller checks whether aOriginalOverTarget is in the
 | |
|   // original document.  If we don't enable the strict mouse/pointer event
 | |
|   // boundary event dispatching by the pref (see below),
 | |
|   // mDeepestEnterEventTarget is set to nullptr when the last "over" target is
 | |
|   // removed.  Therefore, we cannot check whether aOriginalOverTarget is in the
 | |
|   // original document here.
 | |
|   if (!aOriginalOverTargetInComposedDoc) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT_IF(mDeepestEnterEventTarget,
 | |
|                 mDeepestEnterEventTarget->GetComposedDoc() ==
 | |
|                     aOriginalOverTargetInComposedDoc->GetComposedDoc());
 | |
|   // If the "mouseover" event target is removed temporarily while we're
 | |
|   // dispatching "mouseover" and "mouseenter" events and the target gets back
 | |
|   // under the deepest enter event target, we should restore the "mouseover"
 | |
|   // target.
 | |
|   if ((!StaticPrefs::
 | |
|            dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed() &&
 | |
|        !mDeepestEnterEventTarget) ||
 | |
|       (!LastOverEventTargetIsOutEventTarget() && mDeepestEnterEventTarget &&
 | |
|        nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|            aOriginalOverTargetInComposedDoc, mDeepestEnterEventTarget))) {
 | |
|     StoreOverEventTargetAndDeepestEnterEventTarget(
 | |
|         aOriginalOverTargetInComposedDoc);
 | |
|     LogModule* const logModule = mType == BoundaryEventType::Mouse
 | |
|                                      ? sMouseBoundaryLog
 | |
|                                      : sPointerBoundaryLog;
 | |
|     MOZ_LOG(logModule, LogLevel::Info,
 | |
|             ("The \"over\" event target (%p) is restored",
 | |
|              mDeepestEnterEventTarget.get()));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::StoreOverEventTargetAndDeepestEnterEventTarget(
 | |
|     nsIContent* aOverEventTargetAndDeepestEnterEventTarget) {
 | |
|   mDeepestEnterEventTarget = aOverEventTargetAndDeepestEnterEventTarget;
 | |
|   mPendingRemovingOverEventTarget = nullptr;
 | |
|   mDeepestEnterEventTargetIsOverEventTarget = !!mDeepestEnterEventTarget;
 | |
|   mLastOverWidget = nullptr;  // Set it after dispatching the "over" event.
 | |
| }
 | |
| 
 | |
| void OverOutElementsWrapper::UpdateDeepestEnterEventTarget(
 | |
|     nsIContent* aDeepestEnterEventTarget) {
 | |
|   if (MOZ_UNLIKELY(mDeepestEnterEventTarget == aDeepestEnterEventTarget)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!aDeepestEnterEventTarget) {
 | |
|     // If the root element is removed, we don't need to dispatch "leave"
 | |
|     // events on any elements.  Therefore, we can forget everything.
 | |
|     StoreOverEventTargetAndDeepestEnterEventTarget(nullptr);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (LastOverEventTargetIsOutEventTarget()) {
 | |
|     MOZ_ASSERT(mDeepestEnterEventTarget);
 | |
|     if (mType == BoundaryEventType::Pointer) {
 | |
|       // The spec of Pointer Events defines that once the `pointerover` event
 | |
|       // target is removed from the tree, `pointerout` should not be fired on
 | |
|       // that and the closest connected ancestor at the target removal should be
 | |
|       // kept as the deepest `pointerleave` target.  All browsers considers the
 | |
|       // last `pointerover` event target is removed immediately when it occurs.
 | |
|       // Therefore, we don't need the special handling which we do for the
 | |
|       // `mouseout` event target below for considering whether we'll dispatch
 | |
|       // `pointerout` on the last `pointerover` target.
 | |
|       mPendingRemovingOverEventTarget = nullptr;
 | |
|     } else {
 | |
|       // Now, the `mouseout` event target is removed from the DOM at least
 | |
|       // temporarily.  Let's keep storing it for restoring it if it's
 | |
|       // reconnected into mDeepestEnterEventTarget in a tick because the other
 | |
|       // browsers do not treat temporary removal of the last `mouseover` target
 | |
|       // keeps storing it as the next `mouseout` event target.
 | |
|       MOZ_ASSERT(!mPendingRemovingOverEventTarget);
 | |
|       MOZ_ASSERT(mDeepestEnterEventTarget);
 | |
|       mPendingRemovingOverEventTarget =
 | |
|           do_GetWeakReference(mDeepestEnterEventTarget);
 | |
|     }
 | |
|   } else {
 | |
|     MOZ_ASSERT(!mDeepestEnterEventTargetIsOverEventTarget);
 | |
|     // If mDeepestEnterEventTarget is not the last "over" event target, we've
 | |
|     // already done the complicated state managing above.  Therefore, we only
 | |
|     // need to update mDeepestEnterEventTarget in this case.
 | |
|   }
 | |
|   mDeepestEnterEventTarget = aDeepestEnterEventTarget;
 | |
|   mDeepestEnterEventTargetIsOverEventTarget = false;
 | |
|   // Do not update mLastOverWidget here because it's required to ignore some
 | |
|   // following pointer events which are fired on widget under different top
 | |
|   // level widget.
 | |
| }
 | |
| 
 | |
| /******************************************************************/
 | |
| /* mozilla::EventStateManager                                     */
 | |
| /******************************************************************/
 | |
| 
 | |
| static uint32_t sESMInstanceCount = 0;
 | |
| 
 | |
| bool EventStateManager::sNormalLMouseEventInProcess = false;
 | |
| int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
 | |
| EventStateManager* EventStateManager::sActiveESM = nullptr;
 | |
| EventStateManager* EventStateManager::sCursorSettingManager = nullptr;
 | |
| AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
 | |
| LayoutDeviceIntPoint EventStateManager::sPreLockScreenPoint =
 | |
|     LayoutDeviceIntPoint(0, 0);
 | |
| LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
 | |
| CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
 | |
| LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
 | |
| CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
 | |
| nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
 | |
| 
 | |
| EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::sInstance =
 | |
|     nullptr;
 | |
| EventStateManager::DeltaAccumulator*
 | |
|     EventStateManager::DeltaAccumulator::sInstance = nullptr;
 | |
| 
 | |
| constexpr const StyleCursorKind kInvalidCursorKind =
 | |
|     static_cast<StyleCursorKind>(255);
 | |
| 
 | |
| EventStateManager::EventStateManager()
 | |
|     : mLockCursor(kInvalidCursorKind),
 | |
|       mCurrentTarget(nullptr),
 | |
|       // init d&d gesture state machine variables
 | |
|       mGestureDownPoint(0, 0),
 | |
|       mGestureModifiers(0),
 | |
|       mGestureDownButtons(0),
 | |
|       mPresContext(nullptr),
 | |
|       mShouldAlwaysUseLineDeltas(false),
 | |
|       mShouldAlwaysUseLineDeltasInitialized(false),
 | |
|       mGestureDownInTextControl(false),
 | |
|       mInTouchDrag(false),
 | |
|       m_haveShutdown(false) {
 | |
|   if (sESMInstanceCount == 0) {
 | |
|     gUserInteractionTimerCallback = new UITimerCallback();
 | |
|     if (gUserInteractionTimerCallback) NS_ADDREF(gUserInteractionTimerCallback);
 | |
|     UpdateUserActivityTimer();
 | |
|   }
 | |
|   ++sESMInstanceCount;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::UpdateUserActivityTimer() {
 | |
|   if (!gUserInteractionTimerCallback) return NS_OK;
 | |
| 
 | |
|   if (!gUserInteractionTimer) {
 | |
|     gUserInteractionTimer = NS_NewTimer().take();
 | |
|   }
 | |
| 
 | |
|   if (gUserInteractionTimer) {
 | |
|     gUserInteractionTimer->InitWithCallback(
 | |
|         gUserInteractionTimerCallback,
 | |
|         StaticPrefs::dom_events_user_interaction_interval(),
 | |
|         nsITimer::TYPE_ONE_SHOT);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::Init() {
 | |
|   nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   if (!observerService) return NS_ERROR_FAILURE;
 | |
| 
 | |
|   observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool EventStateManager::ShouldAlwaysUseLineDeltas() {
 | |
|   if (MOZ_UNLIKELY(!mShouldAlwaysUseLineDeltasInitialized)) {
 | |
|     mShouldAlwaysUseLineDeltasInitialized = true;
 | |
|     mShouldAlwaysUseLineDeltas =
 | |
|         !StaticPrefs::dom_event_wheel_deltaMode_lines_disabled();
 | |
|     if (!mShouldAlwaysUseLineDeltas && mDocument) {
 | |
|       if (nsIPrincipal* principal =
 | |
|               mDocument->GetPrincipalForPrefBasedHacks()) {
 | |
|         mShouldAlwaysUseLineDeltas = principal->IsURIInPrefList(
 | |
|             "dom.event.wheel-deltaMode-lines.always-enabled");
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return mShouldAlwaysUseLineDeltas;
 | |
| }
 | |
| 
 | |
| EventStateManager::~EventStateManager() {
 | |
|   ReleaseCurrentIMEContentObserver();
 | |
| 
 | |
|   if (sActiveESM == this) {
 | |
|     sActiveESM = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::ui_click_hold_context_menus()) {
 | |
|     KillClickHoldTimer();
 | |
|   }
 | |
| 
 | |
|   if (sCursorSettingManager == this) {
 | |
|     sCursorSettingManager = nullptr;
 | |
|   }
 | |
| 
 | |
|   --sESMInstanceCount;
 | |
|   if (sESMInstanceCount == 0) {
 | |
|     WheelTransaction::Shutdown();
 | |
|     if (gUserInteractionTimerCallback) {
 | |
|       gUserInteractionTimerCallback->Notify(nullptr);
 | |
|       NS_RELEASE(gUserInteractionTimerCallback);
 | |
|     }
 | |
|     if (gUserInteractionTimer) {
 | |
|       gUserInteractionTimer->Cancel();
 | |
|       NS_RELEASE(gUserInteractionTimer);
 | |
|     }
 | |
|     WheelPrefs::Shutdown();
 | |
|     DeltaAccumulator::Shutdown();
 | |
|   }
 | |
| 
 | |
|   if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
 | |
|     sDragOverContent = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!m_haveShutdown) {
 | |
|     Shutdown();
 | |
| 
 | |
|     // Don't remove from Observer service in Shutdown because Shutdown also
 | |
|     // gets called from xpcom shutdown observer.  And we don't want to remove
 | |
|     // from the service in that case.
 | |
| 
 | |
|     nsCOMPtr<nsIObserverService> observerService =
 | |
|         mozilla::services::GetObserverService();
 | |
|     if (observerService) {
 | |
|       observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::Shutdown() {
 | |
|   m_haveShutdown = true;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventStateManager::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                            const char16_t* someData) {
 | |
|   if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
 | |
|     Shutdown();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
 | |
|   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_WEAK(EventStateManager, mCurrentTargetContent,
 | |
|                               mGestureDownContent, mGestureDownFrameOwner,
 | |
|                               mLastLeftMouseDownInfo.mLastMouseDownContent,
 | |
|                               mLastMiddleMouseDownInfo.mLastMouseDownContent,
 | |
|                               mLastRightMouseDownInfo.mLastMouseDownContent,
 | |
|                               mActiveContent, mHoverContent, mURLTargetContent,
 | |
|                               mPopoverPointerDownTarget, mMouseEnterLeaveHelper,
 | |
|                               mPointersEnterLeaveHelper, mDocument,
 | |
|                               mIMEContentObserver, mAccessKeys)
 | |
| 
 | |
| void EventStateManager::ReleaseCurrentIMEContentObserver() {
 | |
|   if (mIMEContentObserver) {
 | |
|     mIMEContentObserver->DisconnectFromEventStateManager();
 | |
|   }
 | |
|   mIMEContentObserver = nullptr;
 | |
| }
 | |
| 
 | |
| void EventStateManager::OnStartToObserveContent(
 | |
|     IMEContentObserver* aIMEContentObserver) {
 | |
|   if (mIMEContentObserver == aIMEContentObserver) {
 | |
|     return;
 | |
|   }
 | |
|   ReleaseCurrentIMEContentObserver();
 | |
|   mIMEContentObserver = aIMEContentObserver;
 | |
| }
 | |
| 
 | |
| void EventStateManager::OnStopObservingContent(
 | |
|     IMEContentObserver* aIMEContentObserver) {
 | |
|   aIMEContentObserver->DisconnectFromEventStateManager();
 | |
|   NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
 | |
|   mIMEContentObserver = nullptr;
 | |
| }
 | |
| 
 | |
| void EventStateManager::TryToFlushPendingNotificationsToIME() {
 | |
|   if (mIMEContentObserver) {
 | |
|     mIMEContentObserver->TryToFlushPendingNotifications(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool IsMessageMouseUserActivity(EventMessage aMessage) {
 | |
|   return aMessage == eMouseMove || aMessage == eMouseUp ||
 | |
|          aMessage == eMouseDown || aMessage == eMouseAuxClick ||
 | |
|          aMessage == eMouseDoubleClick || aMessage == eMouseClick ||
 | |
|          aMessage == eMouseActivate || aMessage == eMouseLongTap;
 | |
| }
 | |
| 
 | |
| static bool IsMessageGamepadUserActivity(EventMessage aMessage) {
 | |
|   return aMessage == eGamepadButtonDown || aMessage == eGamepadButtonUp ||
 | |
|          aMessage == eGamepadAxisMove;
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool EventStateManager::IsKeyboardEventUserActivity(WidgetEvent* aEvent) {
 | |
|   // We ignore things that shouldn't cause popups, but also things that look
 | |
|   // like shortcut presses. In some obscure cases these may actually be
 | |
|   // website input, but any meaningful website will have other input anyway,
 | |
|   // and we can't very well tell whether shortcut input was supposed to be
 | |
|   // directed at chrome or the document.
 | |
| 
 | |
|   WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 | |
|   // Access keys should be treated as page interaction.
 | |
|   if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
 | |
|     return true;
 | |
|   }
 | |
|   if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
 | |
|       keyEvent->IsMeta() || keyEvent->IsAlt()) {
 | |
|     return false;
 | |
|   }
 | |
|   // Deal with function keys:
 | |
|   switch (keyEvent->mKeyNameIndex) {
 | |
|     case KEY_NAME_INDEX_F1:
 | |
|     case KEY_NAME_INDEX_F2:
 | |
|     case KEY_NAME_INDEX_F3:
 | |
|     case KEY_NAME_INDEX_F4:
 | |
|     case KEY_NAME_INDEX_F5:
 | |
|     case KEY_NAME_INDEX_F6:
 | |
|     case KEY_NAME_INDEX_F7:
 | |
|     case KEY_NAME_INDEX_F8:
 | |
|     case KEY_NAME_INDEX_F9:
 | |
|     case KEY_NAME_INDEX_F10:
 | |
|     case KEY_NAME_INDEX_F11:
 | |
|     case KEY_NAME_INDEX_F12:
 | |
|     case KEY_NAME_INDEX_F13:
 | |
|     case KEY_NAME_INDEX_F14:
 | |
|     case KEY_NAME_INDEX_F15:
 | |
|     case KEY_NAME_INDEX_F16:
 | |
|     case KEY_NAME_INDEX_F17:
 | |
|     case KEY_NAME_INDEX_F18:
 | |
|     case KEY_NAME_INDEX_F19:
 | |
|     case KEY_NAME_INDEX_F20:
 | |
|     case KEY_NAME_INDEX_F21:
 | |
|     case KEY_NAME_INDEX_F22:
 | |
|     case KEY_NAME_INDEX_F23:
 | |
|     case KEY_NAME_INDEX_F24:
 | |
|       return false;
 | |
|     default:
 | |
|       return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void OnTypingInteractionEnded() {
 | |
|   // We don't consider a single keystroke to be typing.
 | |
|   if (gTypingInteractionKeyPresses > 1) {
 | |
|     gTypingInteraction.mInteractionCount += gTypingInteractionKeyPresses;
 | |
|     gTypingInteraction.mInteractionTimeInMilliseconds += static_cast<uint32_t>(
 | |
|         std::ceil((gTypingEndTime - gTypingStartTime).ToMilliseconds()));
 | |
|   }
 | |
| 
 | |
|   gTypingInteractionKeyPresses = 0;
 | |
|   gTypingStartTime = TimeStamp();
 | |
|   gTypingEndTime = TimeStamp();
 | |
| }
 | |
| 
 | |
| static void HandleKeyUpInteraction(WidgetKeyboardEvent* aKeyEvent) {
 | |
|   if (EventStateManager::IsKeyboardEventUserActivity(aKeyEvent)) {
 | |
|     TimeStamp now = TimeStamp::Now();
 | |
|     if (gTypingEndTime.IsNull()) {
 | |
|       gTypingEndTime = now;
 | |
|     }
 | |
|     TimeDuration delay = now - gTypingEndTime;
 | |
|     // Has it been too long since the last keystroke to be considered typing?
 | |
|     if (gTypingInteractionKeyPresses > 0 &&
 | |
|         delay >
 | |
|             TimeDuration::FromMilliseconds(
 | |
|                 StaticPrefs::browser_places_interactions_typing_timeout_ms())) {
 | |
|       OnTypingInteractionEnded();
 | |
|     }
 | |
|     gTypingInteractionKeyPresses++;
 | |
|     if (gTypingStartTime.IsNull()) {
 | |
|       gTypingStartTime = now;
 | |
|     }
 | |
|     gTypingEndTime = now;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
 | |
|                                            WidgetEvent* aEvent,
 | |
|                                            nsIFrame* aTargetFrame,
 | |
|                                            nsIContent* aTargetContent,
 | |
|                                            nsEventStatus* aStatus,
 | |
|                                            nsIContent* aOverrideClickTarget) {
 | |
|   NS_ENSURE_ARG_POINTER(aStatus);
 | |
|   NS_ENSURE_ARG(aPresContext);
 | |
|   if (!aEvent) {
 | |
|     NS_ERROR("aEvent is null.  This should never happen.");
 | |
|     return NS_ERROR_NULL_POINTER;
 | |
|   }
 | |
| 
 | |
|   NS_WARNING_ASSERTION(
 | |
|       !aTargetFrame || !aTargetFrame->GetContent() ||
 | |
|           aTargetFrame->GetContent() == aTargetContent ||
 | |
|           aTargetFrame->GetContent()->GetFlattenedTreeParent() ==
 | |
|               aTargetContent ||
 | |
|           aTargetFrame->IsGeneratedContentFrame(),
 | |
|       "aTargetFrame should be related with aTargetContent");
 | |
| #if DEBUG
 | |
|   if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
 | |
|     nsCOMPtr<nsIContent> targetContent;
 | |
|     aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
 | |
|     MOZ_ASSERT(aTargetContent == targetContent,
 | |
|                "Unexpected target for generated content frame!");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   mCurrentTarget = aTargetFrame;
 | |
|   mCurrentTargetContent = nullptr;
 | |
| 
 | |
|   // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
 | |
|   // a page when user is not active doesn't change the state to active.
 | |
|   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
 | |
|   if (aEvent->IsTrusted() &&
 | |
|       ((mouseEvent && mouseEvent->IsReal() &&
 | |
|         IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
 | |
|        aEvent->mClass == eWheelEventClass ||
 | |
|        aEvent->mClass == ePointerEventClass ||
 | |
|        aEvent->mClass == eTouchEventClass ||
 | |
|        aEvent->mClass == eKeyboardEventClass ||
 | |
|        (aEvent->mClass == eDragEventClass && aEvent->mMessage == eDrop) ||
 | |
|        IsMessageGamepadUserActivity(aEvent->mMessage))) {
 | |
|     if (gMouseOrKeyboardEventCounter == 0) {
 | |
|       nsCOMPtr<nsIObserverService> obs =
 | |
|           mozilla::services::GetObserverService();
 | |
|       if (obs) {
 | |
|         obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
 | |
|         UpdateUserActivityTimer();
 | |
|       }
 | |
|     }
 | |
|     ++gMouseOrKeyboardEventCounter;
 | |
| 
 | |
|     nsCOMPtr<nsINode> node = aTargetContent;
 | |
|     if (node &&
 | |
|         ((aEvent->mMessage == eKeyUp && IsKeyboardEventUserActivity(aEvent)) ||
 | |
|          aEvent->mMessage == eMouseUp || aEvent->mMessage == eWheel ||
 | |
|          aEvent->mMessage == eTouchEnd || aEvent->mMessage == ePointerUp ||
 | |
|          aEvent->mMessage == eDrop)) {
 | |
|       Document* doc = node->OwnerDoc();
 | |
|       while (doc) {
 | |
|         doc->SetUserHasInteracted();
 | |
|         doc = nsContentUtils::IsChildOfSameType(doc)
 | |
|                   ? doc->GetInProcessParentDocument()
 | |
|                   : nullptr;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   WheelTransaction::OnEvent(aEvent);
 | |
| 
 | |
|   // Focus events don't necessarily need a frame.
 | |
|   if (!mCurrentTarget && !aTargetContent) {
 | |
|     NS_ERROR("mCurrentTarget and aTargetContent are null");
 | |
|     return NS_ERROR_NULL_POINTER;
 | |
|   }
 | |
| #ifdef DEBUG
 | |
|   if (aEvent->HasDragEventMessage() && PointerLockManager::IsLocked()) {
 | |
|     NS_ASSERTION(PointerLockManager::IsLocked(),
 | |
|                  "Pointer is locked. Drag events should be suppressed when "
 | |
|                  "the pointer is locked.");
 | |
|   }
 | |
| #endif
 | |
|   // Store last known screenPoint and clientPoint so pointer lock
 | |
|   // can use these values as constants.
 | |
|   if (aEvent->IsTrusted() &&
 | |
|       ((mouseEvent && mouseEvent->IsReal()) ||
 | |
|        aEvent->mClass == eWheelEventClass) &&
 | |
|       !PointerLockManager::IsLocked()) {
 | |
|     // XXX Probably doesn't matter much, but storing these in CSS pixels instead
 | |
|     // of device pixels means behavior can be a bit odd if you zoom while
 | |
|     // pointer-locked.
 | |
|     sLastScreenPoint =
 | |
|         Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
 | |
|             .extract();
 | |
|     sLastClientPoint = Event::GetClientCoords(
 | |
|         aPresContext, aEvent, aEvent->mRefPoint, CSSIntPoint(0, 0));
 | |
|   }
 | |
| 
 | |
|   *aStatus = nsEventStatus_eIgnore;
 | |
| 
 | |
|   if (aEvent->mClass == eQueryContentEventClass) {
 | |
|     HandleQueryContentEvent(aEvent->AsQueryContentEvent());
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
 | |
|   if (touchEvent && mInTouchDrag) {
 | |
|     if (touchEvent->mMessage == eTouchMove) {
 | |
|       GenerateDragGesture(aPresContext, touchEvent);
 | |
|     } else {
 | |
|       mInTouchDrag = false;
 | |
|       StopTrackingDragGesture(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mMouseEnterLeaveHelper && aEvent->IsTrusted()) {
 | |
|     // When the last `mouseover` event target is removed from the document,
 | |
|     // we makes mMouseEnterLeaveHelper update the last deepest `mouseenter`
 | |
|     // event target to the removed node parent and mark it as not the following
 | |
|     // `mouseout` event target.  However, the other browsers may dispatch
 | |
|     // `mouseout` on it if it's restored "immediately".  Therefore, we use
 | |
|     // the next animation frame as the deadline.  ContentRemoved() enqueues a
 | |
|     // synthesized `mousemove` to dispatch mouse boundary events under the
 | |
|     // mouse cursor soon and the synthesized event (or eMouseExitFromWidget if
 | |
|     // our window is moved) will reach here at latest the next animation frame.
 | |
|     // Therefore, we can use the event as the deadline.  If the removed last
 | |
|     // `mouseover` target is reconnected before a synthesized mouse event or
 | |
|     // a real mouse event, let's restore it as the following `mouseout` event
 | |
|     // target.  Otherwise, e.g., a keyboard event, let's forget it.
 | |
|     mMouseEnterLeaveHelper->TryToRestorePendingRemovedOverTarget(aEvent);
 | |
|   }
 | |
| 
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eContextMenu:
 | |
|       if (PointerLockManager::IsLocked()) {
 | |
|         return NS_ERROR_DOM_INVALID_STATE_ERR;
 | |
|       }
 | |
|       break;
 | |
|     case eMouseTouchDrag:
 | |
|       mInTouchDrag = true;
 | |
|       BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
 | |
|       break;
 | |
|     case eMouseDown: {
 | |
|       switch (mouseEvent->mButton) {
 | |
|         case MouseButton::ePrimary:
 | |
|           BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
 | |
|           mLastLeftMouseDownInfo.mClickCount = mouseEvent->mClickCount;
 | |
|           SetClickCount(mouseEvent, aStatus);
 | |
|           sNormalLMouseEventInProcess = true;
 | |
|           break;
 | |
|         case MouseButton::eMiddle:
 | |
|           mLastMiddleMouseDownInfo.mClickCount = mouseEvent->mClickCount;
 | |
|           SetClickCount(mouseEvent, aStatus);
 | |
|           break;
 | |
|         case MouseButton::eSecondary:
 | |
|           mLastRightMouseDownInfo.mClickCount = mouseEvent->mClickCount;
 | |
|           SetClickCount(mouseEvent, aStatus);
 | |
|           break;
 | |
|       }
 | |
|       NotifyTargetUserActivation(aEvent, aTargetContent);
 | |
|       break;
 | |
|     }
 | |
|     case eMouseUp: {
 | |
|       switch (mouseEvent->mButton) {
 | |
|         case MouseButton::ePrimary:
 | |
|           if (StaticPrefs::ui_click_hold_context_menus()) {
 | |
|             KillClickHoldTimer();
 | |
|           }
 | |
|           mInTouchDrag = false;
 | |
|           StopTrackingDragGesture(true);
 | |
|           sNormalLMouseEventInProcess = false;
 | |
|           // then fall through...
 | |
|           [[fallthrough]];
 | |
|         case MouseButton::eSecondary:
 | |
|         case MouseButton::eMiddle:
 | |
|           RefPtr<EventStateManager> esm =
 | |
|               ESMFromContentOrThis(aOverrideClickTarget);
 | |
|           esm->SetClickCount(mouseEvent, aStatus, aOverrideClickTarget);
 | |
|           break;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case eMouseEnterIntoWidget:
 | |
|       PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
 | |
|       // In some cases on e10s eMouseEnterIntoWidget
 | |
|       // event was sent twice into child process of content.
 | |
|       // (From specific widget code (sending is not permanent) and
 | |
|       // from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
 | |
|       // IsCrossProcessForwardingStopped() helps to suppress sending accidental
 | |
|       // event from widget code.
 | |
|       aEvent->StopCrossProcessForwarding();
 | |
|       break;
 | |
|     case eMouseExitFromWidget:
 | |
|       // If this is a remote frame, we receive eMouseExitFromWidget from the
 | |
|       // parent the mouse exits our content. Since the parent may update the
 | |
|       // cursor while the mouse is outside our frame, and since PuppetWidget
 | |
|       // caches the current cursor internally, re-entering our content (say from
 | |
|       // over a window edge) wont update the cursor if the cached value and the
 | |
|       // current cursor match. So when the mouse exits a remote frame, clear the
 | |
|       // cached widget cursor so a proper update will occur when the mouse
 | |
|       // re-enters.
 | |
|       if (XRE_IsContentProcess()) {
 | |
|         ClearCachedWidgetCursor(mCurrentTarget);
 | |
|       }
 | |
| 
 | |
|       // IsCrossProcessForwardingStopped() helps to suppress double event
 | |
|       // sending into process of content. For more information see comment
 | |
|       // above, at eMouseEnterIntoWidget case.
 | |
|       aEvent->StopCrossProcessForwarding();
 | |
| 
 | |
|       // If the event is not a top-level window or puppet widget exit, then it's
 | |
|       // not really an exit --- we may have traversed widget boundaries but
 | |
|       // we're still in our toplevel window or puppet widget.
 | |
|       if (mouseEvent->mExitFrom.value() !=
 | |
|               WidgetMouseEvent::ePlatformTopLevel &&
 | |
|           mouseEvent->mExitFrom.value() != WidgetMouseEvent::ePuppet) {
 | |
|         // Treat it as a synthetic move so we don't generate spurious
 | |
|         // "exit" or "move" events.  Any necessary "out" or "over" events
 | |
|         // will be generated by GenerateMouseEnterExit
 | |
|         mouseEvent->mMessage = eMouseMove;
 | |
|         mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
 | |
|         // then fall through...
 | |
|       } else {
 | |
|         MOZ_ASSERT_IF(XRE_IsParentProcess(),
 | |
|                       mouseEvent->mExitFrom.value() ==
 | |
|                           WidgetMouseEvent::ePlatformTopLevel);
 | |
|         MOZ_ASSERT_IF(XRE_IsContentProcess(), mouseEvent->mExitFrom.value() ==
 | |
|                                                   WidgetMouseEvent::ePuppet);
 | |
|         // We should synthetize corresponding pointer events
 | |
|         GeneratePointerEnterExit(ePointerLeave, mouseEvent);
 | |
|         GenerateMouseEnterExit(mouseEvent);
 | |
|         // This is really an exit and should stop here
 | |
|         aEvent->mMessage = eVoidEvent;
 | |
|         break;
 | |
|       }
 | |
|       [[fallthrough]];
 | |
|     case eMouseMove:
 | |
|     case ePointerDown:
 | |
|       if (aEvent->mMessage == ePointerDown) {
 | |
|         PointerEventHandler::UpdateActivePointerState(mouseEvent,
 | |
|                                                       aTargetContent);
 | |
|         PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
 | |
|         if (mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
 | |
|           NotifyTargetUserActivation(aEvent, aTargetContent);
 | |
|         }
 | |
| 
 | |
|         LightDismissOpenPopovers(aEvent, aTargetContent);
 | |
|       }
 | |
|       [[fallthrough]];
 | |
|     case ePointerMove: {
 | |
|       if (!mInTouchDrag &&
 | |
|           PointerEventHandler::IsDragAndDropEnabled(*mouseEvent)) {
 | |
|         GenerateDragGesture(aPresContext, mouseEvent);
 | |
|       }
 | |
|       // on the Mac, GenerateDragGesture() may not return until the drag
 | |
|       // has completed and so |aTargetFrame| may have been deleted (moving
 | |
|       // a bookmark, for example).  If this is the case, however, we know
 | |
|       // that ClearFrameRefs() has been called and it cleared out
 | |
|       // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
 | |
|       // into UpdateCursor().
 | |
|       UpdateCursor(aPresContext, mouseEvent, mCurrentTarget, aStatus);
 | |
| 
 | |
|       UpdateLastRefPointOfMouseEvent(mouseEvent);
 | |
|       if (PointerLockManager::IsLocked()) {
 | |
|         ResetPointerToWindowCenterWhilePointerLocked(mouseEvent);
 | |
|       }
 | |
|       UpdateLastPointerPosition(mouseEvent);
 | |
| 
 | |
|       GenerateMouseEnterExit(mouseEvent);
 | |
|       // Flush pending layout changes, so that later mouse move events
 | |
|       // will go to the right nodes.
 | |
|       FlushLayout(aPresContext);
 | |
|       break;
 | |
|     }
 | |
|     case ePointerUp:
 | |
|       LightDismissOpenPopovers(aEvent, aTargetContent);
 | |
|       GenerateMouseEnterExit(mouseEvent);
 | |
|       break;
 | |
|     case ePointerGotCapture:
 | |
|       GenerateMouseEnterExit(mouseEvent);
 | |
|       break;
 | |
|     case eDragStart:
 | |
|       if (StaticPrefs::ui_click_hold_context_menus()) {
 | |
|         // an external drag gesture event came in, not generated internally
 | |
|         // by Gecko. Make sure we get rid of the click-hold timer.
 | |
|         KillClickHoldTimer();
 | |
|       }
 | |
|       break;
 | |
|     case eDragOver: {
 | |
|       WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
 | |
|       MOZ_ASSERT(dragEvent);
 | |
|       if (dragEvent->mFlags.mIsSynthesizedForTests) {
 | |
|         dragEvent->InitDropEffectForTests();
 | |
|       }
 | |
|       // Send the enter/exit events before eDrop.
 | |
|       GenerateDragDropEnterExit(aPresContext, dragEvent);
 | |
|       break;
 | |
|     }
 | |
|     case eDrop:
 | |
|       if (aEvent->mFlags.mIsSynthesizedForTests) {
 | |
|         MOZ_ASSERT(aEvent->AsDragEvent());
 | |
|         aEvent->AsDragEvent()->InitDropEffectForTests();
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case eKeyPress: {
 | |
|       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 | |
|       if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
 | |
|           keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
 | |
|         // If the eKeyPress event will be sent to a remote process, this
 | |
|         // process needs to wait reply from the remote process for checking if
 | |
|         // preceding eKeyDown event is consumed.  If preceding eKeyDown event
 | |
|         // is consumed in the remote process, BrowserChild won't send the event
 | |
|         // back to this process.  So, only when this process receives a reply
 | |
|         // eKeyPress event in BrowserParent, we should handle accesskey in this
 | |
|         // process.
 | |
|         if (IsTopLevelRemoteTarget(GetFocusedElement())) {
 | |
|           // However, if there is no accesskey target for the key combination,
 | |
|           // we don't need to wait reply from the remote process.  Otherwise,
 | |
|           // Mark the event as waiting reply from remote process and stop
 | |
|           // propagation in this process.
 | |
|           if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
 | |
|             keyEvent->StopPropagation();
 | |
|             keyEvent->MarkAsWaitingReplyFromRemoteProcess();
 | |
|           }
 | |
|         }
 | |
|         // If the event target is in this process, we can handle accesskey now
 | |
|         // since if preceding eKeyDown event was consumed, eKeyPress event
 | |
|         // won't be dispatched by widget.  So, coming eKeyPress event means
 | |
|         // that the preceding eKeyDown event wasn't consumed in this case.
 | |
|         else {
 | |
|           AutoTArray<uint32_t, 10> accessCharCodes;
 | |
|           keyEvent->GetAccessKeyCandidates(accessCharCodes);
 | |
| 
 | |
|           if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
 | |
|             *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|       // then fall through...
 | |
|       [[fallthrough]];
 | |
|     case eKeyDown:
 | |
|       if (aEvent->mMessage == eKeyDown) {
 | |
|         NotifyTargetUserActivation(aEvent, aTargetContent);
 | |
|       }
 | |
|       [[fallthrough]];
 | |
|     case eKeyUp: {
 | |
|       Element* element = GetFocusedElement();
 | |
|       if (element) {
 | |
|         mCurrentTargetContent = element;
 | |
|       }
 | |
| 
 | |
|       // NOTE: Don't refer TextComposition::IsComposing() since UI Events
 | |
|       //       defines that KeyboardEvent.isComposing is true when it's
 | |
|       //       dispatched after compositionstart and compositionend.
 | |
|       //       TextComposition::IsComposing() is false even before
 | |
|       //       compositionend if there is no composing string.
 | |
|       //       And also don't expose other document's composition state.
 | |
|       //       A native IME context is typically shared by multiple documents.
 | |
|       //       So, don't use GetTextCompositionFor(nsIWidget*) here.
 | |
|       RefPtr<TextComposition> composition =
 | |
|           IMEStateManager::GetTextCompositionFor(aPresContext);
 | |
|       aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
 | |
| 
 | |
|       // Widget may need to perform default action for specific keyboard
 | |
|       // event if it's not consumed.  In this case, widget has already marked
 | |
|       // the event as "waiting reply from remote process".  However, we need
 | |
|       // to reset it if the target (focused content) isn't in a remote process
 | |
|       // because PresShell needs to check if it's marked as so before
 | |
|       // dispatching events into the DOM tree.
 | |
|       if (aEvent->IsWaitingReplyFromRemoteProcess() &&
 | |
|           !aEvent->PropagationStopped() && !IsTopLevelRemoteTarget(element)) {
 | |
|         aEvent->ResetWaitingReplyFromRemoteProcessState();
 | |
|       }
 | |
|     } break;
 | |
|     case eWheel:
 | |
|     case eWheelOperationStart:
 | |
|     case eWheelOperationEnd: {
 | |
|       NS_ASSERTION(aEvent->IsTrusted(),
 | |
|                    "Untrusted wheel event shouldn't be here");
 | |
|       using DeltaModeCheckingState = WidgetWheelEvent::DeltaModeCheckingState;
 | |
| 
 | |
|       if (Element* element = GetFocusedElement()) {
 | |
|         mCurrentTargetContent = element;
 | |
|       }
 | |
| 
 | |
|       if (aEvent->mMessage != eWheel) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
 | |
|       WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);
 | |
| 
 | |
|       // If we won't dispatch a DOM event for this event, nothing to do anymore.
 | |
|       if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (StaticPrefs::dom_event_wheel_deltaMode_lines_always_disabled()) {
 | |
|         wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unchecked;
 | |
|       } else if (ShouldAlwaysUseLineDeltas()) {
 | |
|         wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Checked;
 | |
|       } else {
 | |
|         wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unknown;
 | |
|       }
 | |
| 
 | |
|       // Init lineOrPageDelta values for line scroll events for some devices
 | |
|       // on some platforms which might dispatch wheel events which don't
 | |
|       // have lineOrPageDelta values.  And also, if delta values are
 | |
|       // customized by prefs, this recomputes them.
 | |
|       DeltaAccumulator::GetInstance()->InitLineOrPageDelta(aTargetFrame, this,
 | |
|                                                            wheelEvent);
 | |
|     } break;
 | |
|     case eSetSelection: {
 | |
|       RefPtr<Element> focuedElement = GetFocusedElement();
 | |
|       IMEStateManager::HandleSelectionEvent(aPresContext, focuedElement,
 | |
|                                             aEvent->AsSelectionEvent());
 | |
|       break;
 | |
|     }
 | |
|     case eContentCommandCut:
 | |
|     case eContentCommandCopy:
 | |
|     case eContentCommandPaste:
 | |
|     case eContentCommandDelete:
 | |
|     case eContentCommandUndo:
 | |
|     case eContentCommandRedo:
 | |
|     case eContentCommandPasteTransferable:
 | |
|     case eContentCommandLookUpDictionary:
 | |
|       DoContentCommandEvent(aEvent->AsContentCommandEvent());
 | |
|       break;
 | |
|     case eContentCommandInsertText:
 | |
|       DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
 | |
|       break;
 | |
|     case eContentCommandScroll:
 | |
|       DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
 | |
|       break;
 | |
|     case eCompositionStart:
 | |
|       if (aEvent->IsTrusted()) {
 | |
|         // If the event is trusted event, set the selected text to data of
 | |
|         // composition event.
 | |
|         WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
 | |
|         WidgetQueryContentEvent querySelectedTextEvent(
 | |
|             true, eQuerySelectedText, compositionEvent->mWidget);
 | |
|         HandleQueryContentEvent(&querySelectedTextEvent);
 | |
|         if (querySelectedTextEvent.FoundSelection()) {
 | |
|           compositionEvent->mData = querySelectedTextEvent.mReply->DataRef();
 | |
|         }
 | |
|         NS_ASSERTION(querySelectedTextEvent.Succeeded(),
 | |
|                      "Failed to get selected text");
 | |
|       }
 | |
|       break;
 | |
|     case eTouchStart:
 | |
|       SetGestureDownPoint(aEvent->AsTouchEvent());
 | |
|       break;
 | |
|     case eTouchEnd:
 | |
|       NotifyTargetUserActivation(aEvent, aTargetContent);
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // Returns true if this event is likely an user activation for a link or
 | |
| // a link-like button, where modifier keys are likely be used for controlling
 | |
| // where the link is opened.
 | |
| //
 | |
| // The modifiers associated with the user activation is used for controlling
 | |
| // where the `window.open` is opened into.
 | |
| static bool CanReflectModifiersToUserActivation(WidgetInputEvent* aEvent) {
 | |
|   MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
 | |
|              aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
 | |
| 
 | |
|   WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 | |
|   if (keyEvent) {
 | |
|     return keyEvent->CanReflectModifiersToUserActivation();
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
 | |
|                                                    nsIContent* aTargetContent) {
 | |
|   if (!aEvent->IsTrusted()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
 | |
|   if (mouseEvent && !mouseEvent->IsReal()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsINode> node = aTargetContent;
 | |
|   if (!node) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document* doc = node->OwnerDoc();
 | |
|   if (!doc) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Don't gesture activate for key events for keys which are likely
 | |
|   // to be interaction with the browser, OS.
 | |
|   WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 | |
|   if (keyEvent && !keyEvent->CanUserGestureActivateTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Touch gestures that end outside the drag target were touches that turned
 | |
|   // into scroll/pan/swipe actions. We don't want to gesture activate on such
 | |
|   // actions, we want to only gesture activate on touches that are taps.
 | |
|   // That is, touches that end in roughly the same place that they started.
 | |
|   if (aEvent->mMessage == eTouchEnd && aEvent->AsTouchEvent() &&
 | |
|       IsEventOutsideDragThreshold(aEvent->AsTouchEvent())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Do not treat the click on scrollbar as a user interaction with the web
 | |
|   // content.
 | |
|   if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
 | |
|       (aEvent->mMessage == eMouseDown || aEvent->mMessage == ePointerDown) &&
 | |
|       aTargetContent->IsInNativeAnonymousSubtree()) {
 | |
|     nsIContent* current = aTargetContent;
 | |
|     do {
 | |
|       nsIContent* root = current->GetClosestNativeAnonymousSubtreeRoot();
 | |
|       if (!root) {
 | |
|         break;
 | |
|       }
 | |
|       if (root->IsXULElement(nsGkAtoms::scrollbar)) {
 | |
|         return;
 | |
|       }
 | |
|       current = root->GetParent();
 | |
|     } while (current);
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
 | |
|              aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
 | |
|   UserActivation::Modifiers modifiers;
 | |
|   if (WidgetInputEvent* inputEvent = aEvent->AsInputEvent()) {
 | |
|     if (CanReflectModifiersToUserActivation(inputEvent)) {
 | |
|       if (inputEvent->IsShift()) {
 | |
|         modifiers.SetShift();
 | |
|       }
 | |
|       if (inputEvent->IsMeta()) {
 | |
|         modifiers.SetMeta();
 | |
|       }
 | |
|       if (inputEvent->IsControl()) {
 | |
|         modifiers.SetControl();
 | |
|       }
 | |
|       if (inputEvent->IsAlt()) {
 | |
|         modifiers.SetAlt();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   doc->NotifyUserGestureActivation(modifiers);
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/multipage/popover.html#popover-light-dismiss
 | |
| void EventStateManager::LightDismissOpenPopovers(WidgetEvent* aEvent,
 | |
|                                                  nsIContent* aTargetContent) {
 | |
|   MOZ_ASSERT(aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp,
 | |
|              "Light dismiss must be called for pointer up/down only");
 | |
| 
 | |
|   if (!StaticPrefs::dom_element_popover_enabled() || !aEvent->IsTrusted() ||
 | |
|       !aTargetContent) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Element* topmostPopover = aTargetContent->OwnerDoc()->GetTopmostAutoPopover();
 | |
|   if (!topmostPopover) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Pointerdown: set document's popover pointerdown target to the result of
 | |
|   // running topmost clicked popover given target.
 | |
|   if (aEvent->mMessage == ePointerDown) {
 | |
|     mPopoverPointerDownTarget = aTargetContent->GetTopmostClickedPopover();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Pointerup: hide open popovers.
 | |
|   RefPtr<nsINode> ancestor = aTargetContent->GetTopmostClickedPopover();
 | |
|   bool sameTarget = mPopoverPointerDownTarget == ancestor;
 | |
|   mPopoverPointerDownTarget = nullptr;
 | |
|   if (!sameTarget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!ancestor) {
 | |
|     ancestor = aTargetContent->OwnerDoc();
 | |
|   }
 | |
|   RefPtr<Document> doc(ancestor->OwnerDoc());
 | |
|   doc->HideAllPopoversUntil(*ancestor, false, true);
 | |
| }
 | |
| 
 | |
| already_AddRefed<EventStateManager> EventStateManager::ESMFromContentOrThis(
 | |
|     nsIContent* aContent) {
 | |
|   if (aContent) {
 | |
|     PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
 | |
|     if (presShell) {
 | |
|       nsPresContext* prescontext = presShell->GetPresContext();
 | |
|       if (prescontext) {
 | |
|         RefPtr<EventStateManager> esm = prescontext->EventStateManager();
 | |
|         if (esm) {
 | |
|           return esm.forget();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RefPtr<EventStateManager> esm = this;
 | |
|   return esm.forget();
 | |
| }
 | |
| 
 | |
| EventStateManager::LastMouseDownInfo& EventStateManager::GetLastMouseDownInfo(
 | |
|     int16_t aButton) {
 | |
|   switch (aButton) {
 | |
|     case MouseButton::ePrimary:
 | |
|       return mLastLeftMouseDownInfo;
 | |
|     case MouseButton::eMiddle:
 | |
|       return mLastMiddleMouseDownInfo;
 | |
|     case MouseButton::eSecondary:
 | |
|       return mLastRightMouseDownInfo;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("This button shouldn't use this method");
 | |
|       return mLastLeftMouseDownInfo;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::HandleQueryContentEvent(
 | |
|     WidgetQueryContentEvent* aEvent) {
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eQuerySelectedText:
 | |
|     case eQueryTextContent:
 | |
|     case eQueryCaretRect:
 | |
|     case eQueryTextRect:
 | |
|     case eQueryEditorRect:
 | |
|       if (!IsTargetCrossProcess(aEvent)) {
 | |
|         break;
 | |
|       }
 | |
|       // Will not be handled locally, remote the event
 | |
|       GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
 | |
|       return;
 | |
|     // Following events have not been supported in e10s mode yet.
 | |
|     case eQueryContentState:
 | |
|     case eQuerySelectionAsTransferable:
 | |
|     case eQueryCharacterAtPoint:
 | |
|     case eQueryDOMWidgetHittest:
 | |
|     case eQueryTextRectArray:
 | |
|       break;
 | |
|     default:
 | |
|       return;
 | |
|   }
 | |
| 
 | |
|   // If there is an IMEContentObserver, we need to handle QueryContentEvent
 | |
|   // with it.
 | |
|   if (mIMEContentObserver) {
 | |
|     RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
 | |
|     contentObserver->HandleQueryContentEvent(aEvent);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ContentEventHandler handler(mPresContext);
 | |
|   handler.HandleQueryContentEvent(aEvent);
 | |
| }
 | |
| 
 | |
| static AccessKeyType GetAccessKeyTypeFor(nsISupports* aDocShell) {
 | |
|   nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
 | |
|   if (!treeItem) {
 | |
|     return AccessKeyType::eNone;
 | |
|   }
 | |
| 
 | |
|   switch (treeItem->ItemType()) {
 | |
|     case nsIDocShellTreeItem::typeChrome:
 | |
|       return AccessKeyType::eChrome;
 | |
|     case nsIDocShellTreeItem::typeContent:
 | |
|       return AccessKeyType::eContent;
 | |
|     default:
 | |
|       return AccessKeyType::eNone;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool IsAccessKeyTarget(Element* aElement, nsAString& aKey) {
 | |
|   // Use GetAttr because we want Unicode case=insensitive matching
 | |
|   // XXXbz shouldn't this be case-sensitive, per spec?
 | |
|   nsString contentKey;
 | |
|   if (!aElement || !aElement->GetAttr(nsGkAtoms::accesskey, contentKey) ||
 | |
|       !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!aElement->IsXULElement()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // For XUL we do visibility checks.
 | |
|   nsIFrame* frame = aElement->GetPrimaryFrame();
 | |
|   if (!frame) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (frame->IsFocusable()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (!frame->IsVisibleConsideringAncestors()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // XUL controls can be activated.
 | |
|   nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl();
 | |
|   if (control) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // XUL label elements are never focusable, so we need to check for them
 | |
|   // explicitly before giving up.
 | |
|   if (aElement->IsXULElement(nsGkAtoms::label)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool EventStateManager::CheckIfEventMatchesAccessKey(
 | |
|     WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext) {
 | |
|   AutoTArray<uint32_t, 10> accessCharCodes;
 | |
|   aEvent->GetAccessKeyCandidates(accessCharCodes);
 | |
|   return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, accessCharCodes,
 | |
|                                       nullptr, eAccessKeyProcessingNormal,
 | |
|                                       false);
 | |
| }
 | |
| 
 | |
| bool EventStateManager::LookForAccessKeyAndExecute(
 | |
|     nsTArray<uint32_t>& aAccessCharCodes, bool aIsTrustedEvent, bool aIsRepeat,
 | |
|     bool aExecute) {
 | |
|   int32_t count, start = -1;
 | |
|   if (Element* focusedElement = GetFocusedElement()) {
 | |
|     start = mAccessKeys.IndexOf(focusedElement);
 | |
|     if (start == -1 && focusedElement->IsInNativeAnonymousSubtree()) {
 | |
|       start = mAccessKeys.IndexOf(Element::FromNodeOrNull(
 | |
|           focusedElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()));
 | |
|     }
 | |
|   }
 | |
|   RefPtr<Element> element;
 | |
|   int32_t length = mAccessKeys.Count();
 | |
|   for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
 | |
|     uint32_t ch = aAccessCharCodes[i];
 | |
|     nsAutoString accessKey;
 | |
|     AppendUCS4ToUTF16(ch, accessKey);
 | |
|     for (count = 1; count <= length; ++count) {
 | |
|       // mAccessKeys always stores Element instances.
 | |
|       MOZ_DIAGNOSTIC_ASSERT(length == mAccessKeys.Count());
 | |
|       element = mAccessKeys[(start + count) % length];
 | |
|       if (IsAccessKeyTarget(element, accessKey)) {
 | |
|         if (!aExecute) {
 | |
|           return true;
 | |
|         }
 | |
|         Document* doc = element->OwnerDoc();
 | |
|         const bool shouldActivate = [&] {
 | |
|           if (!StaticPrefs::accessibility_accesskeycausesactivation()) {
 | |
|             return false;
 | |
|           }
 | |
|           if (aIsRepeat && nsContentUtils::IsChromeDoc(doc)) {
 | |
|             return false;
 | |
|           }
 | |
| 
 | |
|           // XXXedgar, Bug 1700646, maybe we could use other data structure to
 | |
|           // make searching target with same accesskey easier, and current setup
 | |
|           // could not ensure we cycle the target with tree order.
 | |
|           int32_t j = 0;
 | |
|           while (++j < length) {
 | |
|             Element* el = mAccessKeys[(start + count + j) % length];
 | |
|             if (IsAccessKeyTarget(el, accessKey)) {
 | |
|               return false;
 | |
|             }
 | |
|           }
 | |
|           return true;
 | |
|         }();
 | |
| 
 | |
|         // TODO(bug 1641171): This shouldn't be needed if we considered the
 | |
|         // accesskey combination properly.
 | |
|         if (aIsTrustedEvent) {
 | |
|           doc->NotifyUserGestureActivation();
 | |
|         }
 | |
| 
 | |
|         auto result =
 | |
|             element->PerformAccesskey(shouldActivate, aIsTrustedEvent);
 | |
|         if (result.isOk()) {
 | |
|           if (result.unwrap() && aIsTrustedEvent) {
 | |
|             // If this is a child process, inform the parent that we want the
 | |
|             // focus, but pass false since we don't want to change the window
 | |
|             // order.
 | |
|             nsIDocShell* docShell = mPresContext->GetDocShell();
 | |
|             nsCOMPtr<nsIBrowserChild> child =
 | |
|                 docShell ? docShell->GetBrowserChild() : nullptr;
 | |
|             if (child) {
 | |
|               child->SendRequestFocus(false, CallerType::System);
 | |
|             }
 | |
|           }
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // static
 | |
| void EventStateManager::GetAccessKeyLabelPrefix(Element* aElement,
 | |
|                                                 nsAString& aPrefix) {
 | |
|   aPrefix.Truncate();
 | |
|   nsAutoString separator, modifierText;
 | |
|   nsContentUtils::GetModifierSeparatorText(separator);
 | |
| 
 | |
|   AccessKeyType accessKeyType =
 | |
|       GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
 | |
|   if (accessKeyType == AccessKeyType::eNone) {
 | |
|     return;
 | |
|   }
 | |
|   Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
 | |
|   if (modifiers == MODIFIER_NONE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (modifiers & MODIFIER_CONTROL) {
 | |
|     nsContentUtils::GetControlText(modifierText);
 | |
|     aPrefix.Append(modifierText + separator);
 | |
|   }
 | |
|   if (modifiers & MODIFIER_META) {
 | |
|     nsContentUtils::GetCommandOrWinText(modifierText);
 | |
|     aPrefix.Append(modifierText + separator);
 | |
|   }
 | |
|   if (modifiers & MODIFIER_ALT) {
 | |
|     nsContentUtils::GetAltText(modifierText);
 | |
|     aPrefix.Append(modifierText + separator);
 | |
|   }
 | |
|   if (modifiers & MODIFIER_SHIFT) {
 | |
|     nsContentUtils::GetShiftText(modifierText);
 | |
|     aPrefix.Append(modifierText + separator);
 | |
|   }
 | |
| }
 | |
| 
 | |
| struct MOZ_STACK_CLASS AccessKeyInfo {
 | |
|   WidgetKeyboardEvent* event;
 | |
|   nsTArray<uint32_t>& charCodes;
 | |
| 
 | |
|   AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes)
 | |
|       : event(aEvent), charCodes(aCharCodes) {}
 | |
| };
 | |
| 
 | |
| bool EventStateManager::WalkESMTreeToHandleAccessKey(
 | |
|     WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext,
 | |
|     nsTArray<uint32_t>& aAccessCharCodes, nsIDocShellTreeItem* aBubbledFrom,
 | |
|     ProcessingAccessKeyState aAccessKeyState, bool aExecute) {
 | |
|   EnsureDocument(mPresContext);
 | |
|   nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
 | |
|   if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
 | |
|     return false;
 | |
|   }
 | |
|   AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
 | |
|   if (accessKeyType == AccessKeyType::eNone) {
 | |
|     return false;
 | |
|   }
 | |
|   // Alt or other accesskey modifier is down, we may need to do an accesskey.
 | |
|   if (mAccessKeys.Count() > 0 &&
 | |
|       aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
 | |
|     // Someone registered an accesskey.  Find and activate it.
 | |
|     if (LookForAccessKeyAndExecute(aAccessCharCodes, aEvent->IsTrusted(),
 | |
|                                    aEvent->mIsRepeat, aExecute)) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   int32_t childCount;
 | |
|   docShell->GetInProcessChildCount(&childCount);
 | |
|   for (int32_t counter = 0; counter < childCount; counter++) {
 | |
|     // Not processing the child which bubbles up the handling
 | |
|     nsCOMPtr<nsIDocShellTreeItem> subShellItem;
 | |
|     docShell->GetInProcessChildAt(counter, getter_AddRefs(subShellItem));
 | |
|     if (aAccessKeyState == eAccessKeyProcessingUp &&
 | |
|         subShellItem == aBubbledFrom) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
 | |
|     if (subDS && IsShellVisible(subDS)) {
 | |
|       // Guarantee subPresShell lifetime while we're handling access key
 | |
|       // since somebody may assume that it won't be deleted before the
 | |
|       // corresponding nsPresContext and EventStateManager.
 | |
|       RefPtr<PresShell> subPresShell = subDS->GetPresShell();
 | |
| 
 | |
|       // Docshells need not have a presshell (eg. display:none
 | |
|       // iframes, docshells in transition between documents, etc).
 | |
|       if (!subPresShell) {
 | |
|         // Oh, well.  Just move on to the next child
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       RefPtr<nsPresContext> subPresContext = subPresShell->GetPresContext();
 | |
| 
 | |
|       RefPtr<EventStateManager> esm =
 | |
|           static_cast<EventStateManager*>(subPresContext->EventStateManager());
 | |
| 
 | |
|       if (esm && esm->WalkESMTreeToHandleAccessKey(
 | |
|                      aEvent, subPresContext, aAccessCharCodes, nullptr,
 | |
|                      eAccessKeyProcessingDown, aExecute)) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }  // if end . checking all sub docshell ends here.
 | |
| 
 | |
|   // bubble up the process to the parent docshell if necessary
 | |
|   if (eAccessKeyProcessingDown != aAccessKeyState) {
 | |
|     nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
 | |
|     docShell->GetInProcessParent(getter_AddRefs(parentShellItem));
 | |
|     nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
 | |
|     if (parentDS) {
 | |
|       // Guarantee parentPresShell lifetime while we're handling access key
 | |
|       // since somebody may assume that it won't be deleted before the
 | |
|       // corresponding nsPresContext and EventStateManager.
 | |
|       RefPtr<PresShell> parentPresShell = parentDS->GetPresShell();
 | |
|       NS_ASSERTION(parentPresShell,
 | |
|                    "Our PresShell exists but the parent's does not?");
 | |
| 
 | |
|       RefPtr<nsPresContext> parentPresContext =
 | |
|           parentPresShell->GetPresContext();
 | |
|       NS_ASSERTION(parentPresContext, "PresShell without PresContext");
 | |
| 
 | |
|       RefPtr<EventStateManager> esm = static_cast<EventStateManager*>(
 | |
|           parentPresContext->EventStateManager());
 | |
|       if (esm && esm->WalkESMTreeToHandleAccessKey(
 | |
|                      aEvent, parentPresContext, aAccessCharCodes, docShell,
 | |
|                      eAccessKeyProcessingDown, aExecute)) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }  // if end. bubble up process
 | |
| 
 | |
|   // If the content access key modifier is pressed, try remote children
 | |
|   if (aExecute &&
 | |
|       aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
 | |
|       mDocument && mDocument->GetWindow()) {
 | |
|     // If the focus is currently on a node with a BrowserParent, the key event
 | |
|     // should've gotten forwarded to the child process and HandleAccessKey
 | |
|     // called from there.
 | |
|     if (BrowserParent::GetFrom(GetFocusedElement())) {
 | |
|       // If access key may be only in remote contents, this method won't handle
 | |
|       // access key synchronously.  In this case, only reply event should reach
 | |
|       // here.
 | |
|       MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
 | |
|                  !aEvent->IsWaitingReplyFromRemoteProcess());
 | |
|     }
 | |
|     // If focus is somewhere else, then we need to check the remote children.
 | |
|     // However, if the event has already been handled in a remote process,
 | |
|     // then, focus is moved from the remote process after posting the event.
 | |
|     // In such case, we shouldn't retry to handle access keys in remote
 | |
|     // processes.
 | |
|     else if (!aEvent->IsHandledInRemoteProcess()) {
 | |
|       AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
 | |
|       nsContentUtils::CallOnAllRemoteChildren(
 | |
|           mDocument->GetWindow(),
 | |
|           [&accessKeyInfo](BrowserParent* aBrowserParent) -> CallState {
 | |
|             // Only forward accesskeys for the active tab.
 | |
|             if (aBrowserParent->GetDocShellIsActive()) {
 | |
|               // Even if there is no target for the accesskey in this process,
 | |
|               // the event may match with a content accesskey.  If so, the
 | |
|               // keyboard event should be handled with reply event for
 | |
|               // preventing double action. (e.g., Alt+Shift+F on Windows may
 | |
|               // focus a content in remote and open "File" menu.)
 | |
|               accessKeyInfo.event->StopPropagation();
 | |
|               accessKeyInfo.event->MarkAsWaitingReplyFromRemoteProcess();
 | |
|               aBrowserParent->HandleAccessKey(*accessKeyInfo.event,
 | |
|                                               accessKeyInfo.charCodes);
 | |
|               return CallState::Stop;
 | |
|             }
 | |
| 
 | |
|             return CallState::Continue;
 | |
|           });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }  // end of HandleAccessKey
 | |
| 
 | |
| static BrowserParent* GetBrowserParentAncestor(BrowserParent* aBrowserParent) {
 | |
|   MOZ_ASSERT(aBrowserParent);
 | |
| 
 | |
|   BrowserBridgeParent* bbp = aBrowserParent->GetBrowserBridgeParent();
 | |
|   if (!bbp) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return bbp->Manager();
 | |
| }
 | |
| 
 | |
| static void DispatchCrossProcessMouseExitEvents(WidgetMouseEvent* aMouseEvent,
 | |
|                                                 BrowserParent* aRemoteTarget,
 | |
|                                                 BrowserParent* aStopAncestor,
 | |
|                                                 bool aIsReallyExit) {
 | |
|   MOZ_ASSERT(aMouseEvent);
 | |
|   MOZ_ASSERT(aRemoteTarget);
 | |
|   MOZ_ASSERT(aRemoteTarget != aStopAncestor);
 | |
|   MOZ_ASSERT_IF(aStopAncestor, nsContentUtils::GetCommonBrowserParentAncestor(
 | |
|                                    aRemoteTarget, aStopAncestor));
 | |
| 
 | |
|   while (aRemoteTarget != aStopAncestor) {
 | |
|     UniquePtr<WidgetMouseEvent> mouseExitEvent =
 | |
|         CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
 | |
|                                         aMouseEvent->mRelatedTarget);
 | |
|     mouseExitEvent->mExitFrom =
 | |
|         Some(aIsReallyExit ? WidgetMouseEvent::ePuppet
 | |
|                            : WidgetMouseEvent::ePuppetParentToPuppetChild);
 | |
|     aRemoteTarget->SendRealMouseEvent(*mouseExitEvent);
 | |
| 
 | |
|     aRemoteTarget = GetBrowserParentAncestor(aRemoteTarget);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
 | |
|                                                   BrowserParent* aRemoteTarget,
 | |
|                                                   nsEventStatus* aStatus) {
 | |
|   MOZ_ASSERT(aEvent);
 | |
|   MOZ_ASSERT(aRemoteTarget);
 | |
|   MOZ_ASSERT(aStatus);
 | |
| 
 | |
|   BrowserParent* remote = aRemoteTarget;
 | |
| 
 | |
|   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
 | |
|   bool isContextMenuKey = mouseEvent && mouseEvent->IsContextMenuKeyEvent();
 | |
|   if (aEvent->mClass == eKeyboardEventClass || isContextMenuKey) {
 | |
|     // APZ attaches a LayersId to hit-testable events, for keyboard events,
 | |
|     // we use focus.
 | |
|     BrowserParent* preciseRemote = BrowserParent::GetFocused();
 | |
|     if (preciseRemote) {
 | |
|       remote = preciseRemote;
 | |
|     }
 | |
|     // else there is a race between layout and focus tracking,
 | |
|     // so fall back to delivering the event to the topmost child process.
 | |
|   } else if (aEvent->mLayersId.IsValid()) {
 | |
|     BrowserParent* preciseRemote =
 | |
|         BrowserParent::GetBrowserParentFromLayersId(aEvent->mLayersId);
 | |
|     if (preciseRemote) {
 | |
|       remote = preciseRemote;
 | |
|     }
 | |
|     // else there is a race between APZ and the LayersId to BrowserParent
 | |
|     // mapping, so fall back to delivering the event to the topmost child
 | |
|     // process.
 | |
|   }
 | |
| 
 | |
|   // SendReal* will transform the coordinate to the child process coordinate
 | |
|   // space. So restore the coordinate after the event has been dispatched to the
 | |
|   // child process to avoid using the transformed coordinate afterward.
 | |
|   AutoRestore<LayoutDeviceIntPoint> restore(aEvent->mRefPoint);
 | |
|   switch (aEvent->mClass) {
 | |
|     case eMouseEventClass: {
 | |
|       BrowserParent* oldRemote = BrowserParent::GetLastMouseRemoteTarget();
 | |
| 
 | |
|       // If this is a eMouseExitFromWidget event, need to redirect the event to
 | |
|       // the last remote and and notify all its ancestors about the exit, if
 | |
|       // any.
 | |
|       if (mouseEvent->mMessage == eMouseExitFromWidget) {
 | |
|         MOZ_ASSERT(mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet);
 | |
|         MOZ_ASSERT(mouseEvent->mReason == WidgetMouseEvent::eReal);
 | |
|         MOZ_ASSERT(!mouseEvent->mLayersId.IsValid());
 | |
|         MOZ_ASSERT(remote->GetBrowserHost());
 | |
| 
 | |
|         if (oldRemote && oldRemote != remote) {
 | |
|           Unused << NS_WARN_IF(nsContentUtils::GetCommonBrowserParentAncestor(
 | |
|                                    remote, oldRemote) != remote);
 | |
|           remote = oldRemote;
 | |
|         }
 | |
| 
 | |
|         DispatchCrossProcessMouseExitEvents(mouseEvent, remote, nullptr, true);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (BrowserParent* pointerLockedRemote =
 | |
|               PointerLockManager::GetLockedRemoteTarget()) {
 | |
|         remote = pointerLockedRemote;
 | |
|       } else if (BrowserParent* pointerCapturedRemote =
 | |
|                      PointerEventHandler::GetPointerCapturingRemoteTarget(
 | |
|                          mouseEvent->pointerId)) {
 | |
|         remote = pointerCapturedRemote;
 | |
|       } else if (BrowserParent* capturingRemote =
 | |
|                      PresShell::GetCapturingRemoteTarget()) {
 | |
|         remote = capturingRemote;
 | |
|       }
 | |
| 
 | |
|       // If a mouse is over a remote target A, and then moves to
 | |
|       // remote target B, we'd deliver the event directly to remote target B
 | |
|       // after the moving, A would never get notified that the mouse left.
 | |
|       // So we generate a exit event to notify A after the move.
 | |
|       // XXXedgar, if the synthesized mouse events could deliver to the correct
 | |
|       // process directly (see
 | |
|       // https://bugzilla.mozilla.org/show_bug.cgi?id=1549355), we probably
 | |
|       // don't need to check mReason then.
 | |
|       if (mouseEvent->mReason == WidgetMouseEvent::eReal &&
 | |
|           remote != oldRemote) {
 | |
|         MOZ_ASSERT(mouseEvent->mMessage != eMouseExitFromWidget);
 | |
|         if (oldRemote) {
 | |
|           BrowserParent* commonAncestor =
 | |
|               nsContentUtils::GetCommonBrowserParentAncestor(remote, oldRemote);
 | |
|           if (commonAncestor == oldRemote) {
 | |
|             // Mouse moves to the inner OOP frame, it is not a really exit.
 | |
|             DispatchCrossProcessMouseExitEvents(
 | |
|                 mouseEvent, GetBrowserParentAncestor(remote),
 | |
|                 GetBrowserParentAncestor(commonAncestor), false);
 | |
|           } else if (commonAncestor == remote) {
 | |
|             // Mouse moves to the outer OOP frame, it is a really exit.
 | |
|             DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
 | |
|                                                 commonAncestor, true);
 | |
|           } else {
 | |
|             // Mouse moves to OOP frame in other subtree, it is a really exit,
 | |
|             // need to notify all its ancestors before common ancestor about the
 | |
|             // exit.
 | |
|             DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
 | |
|                                                 commonAncestor, true);
 | |
|             if (commonAncestor) {
 | |
|               UniquePtr<WidgetMouseEvent> mouseExitEvent =
 | |
|                   CreateMouseOrPointerWidgetEvent(mouseEvent,
 | |
|                                                   eMouseExitFromWidget,
 | |
|                                                   mouseEvent->mRelatedTarget);
 | |
|               mouseExitEvent->mExitFrom =
 | |
|                   Some(WidgetMouseEvent::ePuppetParentToPuppetChild);
 | |
|               commonAncestor->SendRealMouseEvent(*mouseExitEvent);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (mouseEvent->mMessage != eMouseExitFromWidget &&
 | |
|             mouseEvent->mMessage != eMouseEnterIntoWidget) {
 | |
|           // This is to make cursor would be updated correctly.
 | |
|           remote->MouseEnterIntoWidget();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       remote->SendRealMouseEvent(*mouseEvent);
 | |
|       return;
 | |
|     }
 | |
|     case eKeyboardEventClass: {
 | |
|       auto* keyboardEvent = aEvent->AsKeyboardEvent();
 | |
|       if (aEvent->mMessage == eKeyUp) {
 | |
|         HandleKeyUpInteraction(keyboardEvent);
 | |
|       }
 | |
|       remote->SendRealKeyEvent(*keyboardEvent);
 | |
|       return;
 | |
|     }
 | |
|     case eWheelEventClass: {
 | |
|       if (BrowserParent* pointerLockedRemote =
 | |
|               PointerLockManager::GetLockedRemoteTarget()) {
 | |
|         remote = pointerLockedRemote;
 | |
|       }
 | |
|       remote->SendMouseWheelEvent(*aEvent->AsWheelEvent());
 | |
|       return;
 | |
|     }
 | |
|     case eTouchEventClass: {
 | |
|       // Let the child process synthesize a mouse event if needed, and
 | |
|       // ensure we don't synthesize one in this process.
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
 | |
|       return;
 | |
|     }
 | |
|     case eDragEventClass: {
 | |
|       RefPtr<BrowserParent> browserParent = remote;
 | |
|       browserParent->Manager()->MaybeInvokeDragSession(browserParent,
 | |
|                                                        aEvent->mMessage);
 | |
| 
 | |
|       nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
 | |
|       uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
 | |
|       uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
 | |
|       nsCOMPtr<nsIPrincipal> principal;
 | |
|       nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
| 
 | |
|       if (dragSession) {
 | |
|         dragSession->DragEventDispatchedToChildProcess();
 | |
|         dragSession->GetDragAction(&action);
 | |
|         dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
 | |
|         dragSession->GetCsp(getter_AddRefs(csp));
 | |
|         RefPtr<DataTransfer> initialDataTransfer =
 | |
|             dragSession->GetDataTransfer();
 | |
|         if (initialDataTransfer) {
 | |
|           dropEffect = initialDataTransfer->DropEffectInt();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       browserParent->SendRealDragEvent(*aEvent->AsDragEvent(), action,
 | |
|                                        dropEffect, principal, csp);
 | |
|       return;
 | |
|     }
 | |
|     default: {
 | |
|       MOZ_CRASH("Attempt to send non-whitelisted event?");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool EventStateManager::IsRemoteTarget(nsIContent* target) {
 | |
|   return BrowserParent::GetFrom(target) || BrowserBridgeChild::GetFrom(target);
 | |
| }
 | |
| 
 | |
| bool EventStateManager::IsTopLevelRemoteTarget(nsIContent* target) {
 | |
|   return !!BrowserParent::GetFrom(target);
 | |
| }
 | |
| 
 | |
| bool EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
 | |
|                                                 nsEventStatus* aStatus) {
 | |
|   if (!aEvent->CanBeSentToRemoteProcess()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!aEvent->HasBeenPostedToRemoteProcess(),
 | |
|              "Why do we need to post same event to remote processes again?");
 | |
| 
 | |
|   // Collect the remote event targets we're going to forward this
 | |
|   // event to.
 | |
|   //
 | |
|   // NB: the elements of |remoteTargets| must be unique, for correctness.
 | |
|   AutoTArray<RefPtr<BrowserParent>, 1> remoteTargets;
 | |
|   if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) {
 | |
|     // If this event only has one target, and it's remote, add it to
 | |
|     // the array.
 | |
|     nsIFrame* frame = aEvent->mMessage == eDragExit
 | |
|                           ? sLastDragOverFrame.GetFrame()
 | |
|                           : GetEventTarget();
 | |
|     nsIContent* target = frame ? frame->GetContent() : nullptr;
 | |
|     if (BrowserParent* remoteTarget = BrowserParent::GetFrom(target)) {
 | |
|       remoteTargets.AppendElement(remoteTarget);
 | |
|     }
 | |
|   } else {
 | |
|     // This is a touch event with possibly multiple touch points.
 | |
|     // Each touch point may have its own target.  So iterate through
 | |
|     // all of them and collect the unique set of targets for event
 | |
|     // forwarding.
 | |
|     //
 | |
|     // This loop is similar to the one used in
 | |
|     // PresShell::DispatchTouchEvent().
 | |
|     const WidgetTouchEvent::TouchArray& touches =
 | |
|         aEvent->AsTouchEvent()->mTouches;
 | |
|     for (uint32_t i = 0; i < touches.Length(); ++i) {
 | |
|       Touch* touch = touches[i];
 | |
|       // NB: the |mChanged| check is an optimization, subprocesses can
 | |
|       // compute this for themselves.  If the touch hasn't changed, we
 | |
|       // may be able to avoid forwarding the event entirely (which is
 | |
|       // not free).
 | |
|       if (!touch || !touch->mChanged) {
 | |
|         continue;
 | |
|       }
 | |
|       nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
 | |
|       if (!targetPtr) {
 | |
|         continue;
 | |
|       }
 | |
|       nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr);
 | |
|       BrowserParent* remoteTarget = BrowserParent::GetFrom(target);
 | |
|       if (remoteTarget && !remoteTargets.Contains(remoteTarget)) {
 | |
|         remoteTargets.AppendElement(remoteTarget);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (remoteTargets.Length() == 0) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Dispatch the event to the remote target.
 | |
|   for (uint32_t i = 0; i < remoteTargets.Length(); ++i) {
 | |
|     DispatchCrossProcessEvent(aEvent, remoteTargets[i], aStatus);
 | |
|   }
 | |
|   return aEvent->HasBeenPostedToRemoteProcess();
 | |
| }
 | |
| 
 | |
| //
 | |
| // CreateClickHoldTimer
 | |
| //
 | |
| // Fire off a timer for determining if the user wants click-hold. This timer
 | |
| // is a one-shot that will be cancelled when the user moves enough to fire
 | |
| // a drag.
 | |
| //
 | |
| void EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext,
 | |
|                                              nsIFrame* inDownFrame,
 | |
|                                              WidgetGUIEvent* inMouseDownEvent) {
 | |
|   if (!inMouseDownEvent->IsTrusted() ||
 | |
|       IsTopLevelRemoteTarget(mGestureDownContent) ||
 | |
|       PointerLockManager::IsLocked()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // just to be anal (er, safe)
 | |
|   if (mClickHoldTimer) {
 | |
|     mClickHoldTimer->Cancel();
 | |
|     mClickHoldTimer = nullptr;
 | |
|   }
 | |
| 
 | |
|   // if content clicked on has a popup, don't even start the timer
 | |
|   // since we'll end up conflicting and both will show.
 | |
|   if (mGestureDownContent &&
 | |
|       nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None,
 | |
|                                       nsGkAtoms::popup)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int32_t clickHoldDelay = StaticPrefs::ui_click_hold_context_menus_delay();
 | |
|   NS_NewTimerWithFuncCallback(
 | |
|       getter_AddRefs(mClickHoldTimer), sClickHoldCallback, this, clickHoldDelay,
 | |
|       nsITimer::TYPE_ONE_SHOT, "EventStateManager::CreateClickHoldTimer");
 | |
| }  // CreateClickHoldTimer
 | |
| 
 | |
| //
 | |
| // KillClickHoldTimer
 | |
| //
 | |
| // Stop the timer that would show the context menu dead in its tracks
 | |
| //
 | |
| void EventStateManager::KillClickHoldTimer() {
 | |
|   if (mClickHoldTimer) {
 | |
|     mClickHoldTimer->Cancel();
 | |
|     mClickHoldTimer = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| //
 | |
| // sClickHoldCallback
 | |
| //
 | |
| // This fires after the mouse has been down for a certain length of time.
 | |
| //
 | |
| void EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM) {
 | |
|   RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM);
 | |
|   if (self) {
 | |
|     self->FireContextClick();
 | |
|   }
 | |
| 
 | |
|   // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling
 | |
|   // ClosePopup();
 | |
| 
 | |
| }  // sAutoHideCallback
 | |
| 
 | |
| //
 | |
| // FireContextClick
 | |
| //
 | |
| // If we're this far, our timer has fired, which means the mouse has been down
 | |
| // for a certain period of time and has not moved enough to generate a
 | |
| // dragGesture. We can be certain the user wants a context-click at this stage,
 | |
| // so generate a dom event and fire it in.
 | |
| //
 | |
| // After the event fires, check if PreventDefault() has been set on the event
 | |
| // which means that someone either ate the event or put up a context menu. This
 | |
| // is our cue to stop tracking the drag gesture. If we always did this,
 | |
| // draggable items w/out a context menu wouldn't be draggable after a certain
 | |
| // length of time, which is _not_ what we want.
 | |
| //
 | |
| void EventStateManager::FireContextClick() {
 | |
|   if (!mGestureDownContent || !mPresContext || PointerLockManager::IsLocked()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
|   // Hack to ensure that we don't show a context menu when the user
 | |
|   // let go of the mouse after a long cpu-hogging operation prevented
 | |
|   // us from handling any OS events. See bug 117589.
 | |
|   if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState,
 | |
|                                 kCGMouseButtonLeft))
 | |
|     return;
 | |
| #endif
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
| 
 | |
|   // Dispatch to the DOM. We have to fake out the ESM and tell it that the
 | |
|   // current target frame is actually where the mouseDown occurred, otherwise it
 | |
|   // will use the frame the mouse is currently over which may or may not be
 | |
|   // the same. (Note: saari and I have decided that we don't have to reset
 | |
|   // |mCurrentTarget| when we're through because no one else is doing anything
 | |
|   // more with this event and it will get reset on the very next event to the
 | |
|   // correct frame).
 | |
|   mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent);
 | |
|   // make sure the widget sticks around
 | |
|   nsCOMPtr<nsIWidget> targetWidget;
 | |
|   if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) {
 | |
|     NS_ASSERTION(
 | |
|         mPresContext == mCurrentTarget->PresContext(),
 | |
|         "a prescontext returned a primary frame that didn't belong to it?");
 | |
| 
 | |
|     // before dispatching, check that we're not on something that
 | |
|     // doesn't get a context menu
 | |
|     bool allowedToDispatch = true;
 | |
| 
 | |
|     if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar,
 | |
|                                                 nsGkAtoms::scrollbarbutton,
 | |
|                                                 nsGkAtoms::button)) {
 | |
|       allowedToDispatch = false;
 | |
|     } else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) {
 | |
|       // a <toolbarbutton> that has the container attribute set
 | |
|       // will already have its own dropdown.
 | |
|       if (nsContentUtils::HasNonEmptyAttr(
 | |
|               mGestureDownContent, kNameSpaceID_None, nsGkAtoms::container)) {
 | |
|         allowedToDispatch = false;
 | |
|       } else {
 | |
|         // If the toolbar button has an open menu, don't attempt to open
 | |
|         // a second menu
 | |
|         if (mGestureDownContent->IsElement() &&
 | |
|             mGestureDownContent->AsElement()->AttrValueIs(
 | |
|                 kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true,
 | |
|                 eCaseMatters)) {
 | |
|           allowedToDispatch = false;
 | |
|         }
 | |
|       }
 | |
|     } else if (mGestureDownContent->IsHTMLElement()) {
 | |
|       nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent));
 | |
| 
 | |
|       if (formCtrl) {
 | |
|         allowedToDispatch =
 | |
|             formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
 | |
|             formCtrl->ControlType() == FormControlType::InputFile;
 | |
|       } else if (mGestureDownContent->IsAnyOfHTMLElements(
 | |
|                      nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {
 | |
|         allowedToDispatch = false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (allowedToDispatch) {
 | |
|       // init the event while mCurrentTarget is still good
 | |
|       WidgetMouseEvent event(true, eContextMenu, targetWidget,
 | |
|                              WidgetMouseEvent::eReal);
 | |
|       event.mClickCount = 1;
 | |
|       FillInEventFromGestureDown(&event);
 | |
| 
 | |
|       // stop selection tracking, we're in control now
 | |
|       if (mCurrentTarget) {
 | |
|         RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
 | |
| 
 | |
|         if (frameSel && frameSel->GetDragState()) {
 | |
|           // note that this can cause selection changed events to fire if we're
 | |
|           // in a text field, which will null out mCurrentTarget
 | |
|           frameSel->SetDragState(false);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       AutoHandlingUserInputStatePusher userInpStatePusher(true, &event);
 | |
| 
 | |
|       // dispatch to DOM
 | |
|       RefPtr<nsIContent> gestureDownContent = mGestureDownContent;
 | |
|       RefPtr<nsPresContext> presContext = mPresContext;
 | |
|       EventDispatcher::Dispatch(gestureDownContent, presContext, &event,
 | |
|                                 nullptr, &status);
 | |
| 
 | |
|       // We don't need to dispatch to frame handling because no frames
 | |
|       // watch eContextMenu except for nsMenuFrame and that's only for
 | |
|       // dismissal. That's just as well since we don't really know
 | |
|       // which frame to send it to.
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // now check if the event has been handled. If so, stop tracking a drag
 | |
|   if (status == nsEventStatus_eConsumeNoDefault) {
 | |
|     StopTrackingDragGesture(true);
 | |
|   }
 | |
| 
 | |
|   KillClickHoldTimer();
 | |
| 
 | |
| }  // FireContextClick
 | |
| 
 | |
| //
 | |
| // BeginTrackingDragGesture
 | |
| //
 | |
| // Record that the mouse has gone down and that we should move to TRACKING state
 | |
| // of d&d gesture tracker.
 | |
| //
 | |
| // We also use this to track click-hold context menus. When the mouse goes down,
 | |
| // fire off a short timer. If the timer goes off and we have yet to fire the
 | |
| // drag gesture (ie, the mouse hasn't moved a certain distance), then we can
 | |
| // assume the user wants a click-hold, so fire a context-click event. We only
 | |
| // want to cancel the drag gesture if the context-click event is handled.
 | |
| //
 | |
| void EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext,
 | |
|                                                  WidgetMouseEvent* inDownEvent,
 | |
|                                                  nsIFrame* inDownFrame) {
 | |
|   if (!inDownEvent->mWidget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Note that |inDownEvent| could be either a mouse down event or a
 | |
|   // synthesized mouse move event.
 | |
|   SetGestureDownPoint(inDownEvent);
 | |
| 
 | |
|   if (inDownFrame) {
 | |
|     inDownFrame->GetContentForEvent(inDownEvent,
 | |
|                                     getter_AddRefs(mGestureDownContent));
 | |
| 
 | |
|     mGestureDownFrameOwner = inDownFrame->GetContent();
 | |
|     if (!mGestureDownFrameOwner) {
 | |
|       mGestureDownFrameOwner = mGestureDownContent;
 | |
|     }
 | |
|   }
 | |
|   mGestureModifiers = inDownEvent->mModifiers;
 | |
|   mGestureDownButtons = inDownEvent->mButtons;
 | |
| 
 | |
|   if (inDownEvent->mMessage != eMouseTouchDrag &&
 | |
|       StaticPrefs::ui_click_hold_context_menus()) {
 | |
|     // fire off a timer to track click-hold
 | |
|     CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::SetGestureDownPoint(WidgetGUIEvent* aEvent) {
 | |
|   mGestureDownPoint =
 | |
|       GetEventRefPoint(aEvent) + aEvent->mWidget->WidgetToScreenOffset();
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint EventStateManager::GetEventRefPoint(
 | |
|     WidgetEvent* aEvent) const {
 | |
|   auto touchEvent = aEvent->AsTouchEvent();
 | |
|   return (touchEvent && !touchEvent->mTouches.IsEmpty())
 | |
|              ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
 | |
|              : aEvent->mRefPoint;
 | |
| }
 | |
| 
 | |
| void EventStateManager::BeginTrackingRemoteDragGesture(
 | |
|     nsIContent* aContent, RemoteDragStartData* aDragStartData) {
 | |
|   mGestureDownContent = aContent;
 | |
|   mGestureDownFrameOwner = aContent;
 | |
|   mGestureDownInTextControl =
 | |
|       aContent && aContent->IsInNativeAnonymousSubtree() &&
 | |
|       TextControlElement::FromNodeOrNull(
 | |
|           aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost());
 | |
|   mGestureDownDragStartData = aDragStartData;
 | |
| }
 | |
| 
 | |
| //
 | |
| // StopTrackingDragGesture
 | |
| //
 | |
| // Record that the mouse has gone back up so that we should leave the TRACKING
 | |
| // state of d&d gesture tracker and return to the START state.
 | |
| //
 | |
| void EventStateManager::StopTrackingDragGesture(bool aClearInChildProcesses) {
 | |
|   mGestureDownContent = nullptr;
 | |
|   mGestureDownFrameOwner = nullptr;
 | |
|   mGestureDownInTextControl = false;
 | |
|   mGestureDownDragStartData = nullptr;
 | |
| 
 | |
|   // If a content process starts a drag but the mouse is released before the
 | |
|   // parent starts the actual drag, the content process will think a drag is
 | |
|   // still happening. Inform any child processes with active drags that the drag
 | |
|   // should be stopped.
 | |
|   if (aClearInChildProcesses) {
 | |
|     nsCOMPtr<nsIDragService> dragService =
 | |
|         do_GetService("@mozilla.org/widget/dragservice;1");
 | |
|     if (dragService) {
 | |
|       nsCOMPtr<nsIDragSession> dragSession;
 | |
|       dragService->GetCurrentSession(getter_AddRefs(dragSession));
 | |
|       if (!dragSession) {
 | |
|         // Only notify if there isn't a drag session active.
 | |
|         dragService->RemoveAllChildProcesses();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent) {
 | |
|   NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
 | |
|                "Incorrect widget in event");
 | |
| 
 | |
|   // Set the coordinates in the new event to the coordinates of
 | |
|   // the old event, adjusted for the fact that the widget might be
 | |
|   // different
 | |
|   aEvent->mRefPoint =
 | |
|       mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
 | |
|   aEvent->mModifiers = mGestureModifiers;
 | |
|   aEvent->mButtons = mGestureDownButtons;
 | |
| }
 | |
| 
 | |
| void EventStateManager::MaybeFirePointerCancel(WidgetInputEvent* aEvent) {
 | |
|   RefPtr<PresShell> presShell = mPresContext->GetPresShell();
 | |
|   AutoWeakFrame targetFrame = mCurrentTarget;
 | |
| 
 | |
|   if (!presShell || !targetFrame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> content;
 | |
|   targetFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
 | |
|   if (!content) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
| 
 | |
|   if (WidgetMouseEvent* aMouseEvent = aEvent->AsMouseEvent()) {
 | |
|     WidgetPointerEvent event(*aMouseEvent);
 | |
|     PointerEventHandler::InitPointerEventFromMouse(&event, aMouseEvent,
 | |
|                                                    ePointerCancel);
 | |
| 
 | |
|     event.convertToPointer = false;
 | |
|     presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
 | |
|   } else if (WidgetTouchEvent* aTouchEvent = aEvent->AsTouchEvent()) {
 | |
|     WidgetPointerEvent event(aTouchEvent->IsTrusted(), ePointerCancel,
 | |
|                              aTouchEvent->mWidget);
 | |
| 
 | |
|     PointerEventHandler::InitPointerEventFromTouch(event, *aTouchEvent,
 | |
|                                                    *aTouchEvent->mTouches[0]);
 | |
| 
 | |
|     event.convertToPointer = false;
 | |
|     presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
 | |
|   } else {
 | |
|     MOZ_ASSERT(false);
 | |
|   }
 | |
| 
 | |
|   // HandleEventWithTarget clears out mCurrentTarget, which may be used in the
 | |
|   // caller GenerateDragGesture. We have to restore mCurrentTarget.
 | |
|   mCurrentTarget = targetFrame;
 | |
| }
 | |
| 
 | |
| bool EventStateManager::IsEventOutsideDragThreshold(
 | |
|     WidgetInputEvent* aEvent) const {
 | |
|   static int32_t sPixelThresholdX = 0;
 | |
|   static int32_t sPixelThresholdY = 0;
 | |
| 
 | |
|   if (!sPixelThresholdX) {
 | |
|     sPixelThresholdX =
 | |
|         LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdX, 0);
 | |
|     sPixelThresholdY =
 | |
|         LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdY, 0);
 | |
|     if (sPixelThresholdX <= 0) {
 | |
|       sPixelThresholdX = 5;
 | |
|     }
 | |
|     if (sPixelThresholdY <= 0) {
 | |
|       sPixelThresholdY = 5;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LayoutDeviceIntPoint pt =
 | |
|       aEvent->mWidget->WidgetToScreenOffset() + GetEventRefPoint(aEvent);
 | |
|   LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
 | |
|   return Abs(distance.x) > sPixelThresholdX ||
 | |
|          Abs(distance.y) > sPixelThresholdY;
 | |
| }
 | |
| 
 | |
| //
 | |
| // GenerateDragGesture
 | |
| //
 | |
| // If we're in the TRACKING state of the d&d gesture tracker, check the current
 | |
| // position of the mouse in relation to the old one. If we've moved a sufficient
 | |
| // amount from the mouse down, then fire off a drag gesture event.
 | |
| void EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
 | |
|                                             WidgetInputEvent* aEvent) {
 | |
|   NS_ASSERTION(aPresContext, "This shouldn't happen.");
 | |
|   if (!IsTrackingDragGesture()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoWeakFrame targetFrameBefore = mCurrentTarget;
 | |
|   auto autoRestore = MakeScopeExit([&] { mCurrentTarget = targetFrameBefore; });
 | |
|   mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
 | |
| 
 | |
|   if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
 | |
|     StopTrackingDragGesture(true);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Check if selection is tracking drag gestures, if so
 | |
|   // don't interfere!
 | |
|   if (mCurrentTarget) {
 | |
|     RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
 | |
|     if (frameSel && frameSel->GetDragState()) {
 | |
|       StopTrackingDragGesture(true);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If non-native code is capturing the mouse don't start a drag.
 | |
|   if (PresShell::IsMouseCapturePreventingDrag()) {
 | |
|     StopTrackingDragGesture(true);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!IsEventOutsideDragThreshold(aEvent)) {
 | |
|     // To keep the old behavior, flush layout even if we don't start dnd.
 | |
|     FlushLayout(aPresContext);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::ui_click_hold_context_menus()) {
 | |
|     // stop the click-hold before we fire off the drag gesture, in case
 | |
|     // it takes a long time
 | |
|     KillClickHoldTimer();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell();
 | |
|   if (!docshell) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow();
 | |
|   if (!window) return;
 | |
| 
 | |
|   RefPtr<DataTransfer> dataTransfer =
 | |
|       new DataTransfer(window, eDragStart, false, -1);
 | |
|   auto protectDataTransfer = MakeScopeExit([&] {
 | |
|     if (dataTransfer) {
 | |
|       dataTransfer->Disconnect();
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   RefPtr<Selection> selection;
 | |
|   RefPtr<RemoteDragStartData> remoteDragStartData;
 | |
|   nsCOMPtr<nsIContent> eventContent, targetContent;
 | |
|   nsCOMPtr<nsIPrincipal> principal;
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|   nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
 | |
|   bool allowEmptyDataTransfer = false;
 | |
|   mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
 | |
|   if (eventContent) {
 | |
|     // If the content is a text node in a password field, we shouldn't
 | |
|     // allow to drag its raw text.  Note that we've supported drag from
 | |
|     // password fields but dragging data was masked text.  So, it doesn't
 | |
|     // make sense anyway.
 | |
|     if (eventContent->IsText() && eventContent->HasFlag(NS_MAYBE_MASKED)) {
 | |
|       // However, it makes sense to allow to drag selected password text
 | |
|       // when copying selected password is allowed because users may want
 | |
|       // to use drag and drop rather than copy and paste when web apps
 | |
|       // request to input password twice for conforming new password but
 | |
|       // they used password generator.
 | |
|       TextEditor* textEditor =
 | |
|           nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
 | |
|               eventContent);
 | |
|       if (!textEditor || !textEditor->IsCopyToClipboardAllowed()) {
 | |
|         StopTrackingDragGesture(true);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|     DetermineDragTargetAndDefaultData(
 | |
|         window, eventContent, dataTransfer, &allowEmptyDataTransfer,
 | |
|         getter_AddRefs(selection), getter_AddRefs(remoteDragStartData),
 | |
|         getter_AddRefs(targetContent), getter_AddRefs(principal),
 | |
|         getter_AddRefs(csp), getter_AddRefs(cookieJarSettings));
 | |
|   }
 | |
| 
 | |
|   // Stop tracking the drag gesture now. This should stop us from
 | |
|   // reentering GenerateDragGesture inside DOM event processing.
 | |
|   // Pass false to avoid clearing the child process state since a real
 | |
|   // drag should be starting.
 | |
|   StopTrackingDragGesture(false);
 | |
| 
 | |
|   if (!targetContent) return;
 | |
| 
 | |
|   // Use our targetContent, now that we've determined it, as the
 | |
|   // parent object of the DataTransfer.
 | |
|   nsCOMPtr<nsIContent> parentContent =
 | |
|       targetContent->FindFirstNonChromeOnlyAccessContent();
 | |
|   dataTransfer->SetParentObject(parentContent);
 | |
| 
 | |
|   sLastDragOverFrame = nullptr;
 | |
|   nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
 | |
| 
 | |
|   // get the widget from the target frame
 | |
|   WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
 | |
|   startEvent.mFlags.mIsSynthesizedForTests =
 | |
|       aEvent->mFlags.mIsSynthesizedForTests;
 | |
|   FillInEventFromGestureDown(&startEvent);
 | |
| 
 | |
|   startEvent.mDataTransfer = dataTransfer;
 | |
|   if (aEvent->AsMouseEvent()) {
 | |
|     startEvent.mInputSource = aEvent->AsMouseEvent()->mInputSource;
 | |
|   } else if (aEvent->AsTouchEvent()) {
 | |
|     startEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
 | |
|   } else {
 | |
|     MOZ_ASSERT(false);
 | |
|   }
 | |
| 
 | |
|   // Dispatch to the DOM. By setting mCurrentTarget we are faking
 | |
|   // out the ESM and telling it that the current target frame is
 | |
|   // actually where the mouseDown occurred, otherwise it will use
 | |
|   // the frame the mouse is currently over which may or may not be
 | |
|   // the same.
 | |
| 
 | |
|   // Hold onto old target content through the event and reset after.
 | |
|   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 | |
| 
 | |
|   // Set the current target to the content for the mouse down
 | |
|   mCurrentTargetContent = targetContent;
 | |
| 
 | |
|   // Dispatch the dragstart event to the DOM.
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent, nullptr,
 | |
|                             &status);
 | |
| 
 | |
|   WidgetDragEvent* event = &startEvent;
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   // Emit observer event to allow addons to modify the DataTransfer
 | |
|   // object.
 | |
|   if (observerService) {
 | |
|     observerService->NotifyObservers(dataTransfer, "on-datatransfer-available",
 | |
|                                      nullptr);
 | |
|   }
 | |
| 
 | |
|   if (status != nsEventStatus_eConsumeNoDefault) {
 | |
|     bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
 | |
|                                           allowEmptyDataTransfer, targetContent,
 | |
|                                           selection, remoteDragStartData,
 | |
|                                           principal, csp, cookieJarSettings);
 | |
|     if (dragStarted) {
 | |
|       sActiveESM = nullptr;
 | |
|       MaybeFirePointerCancel(aEvent);
 | |
|       aEvent->StopPropagation();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Reset mCurretTargetContent to what it was
 | |
|   mCurrentTargetContent = targetBeforeEvent;
 | |
| 
 | |
|   // Now flush all pending notifications, for better responsiveness
 | |
|   // while dragging.
 | |
|   FlushLayout(aPresContext);
 | |
| }  // GenerateDragGesture
 | |
| 
 | |
| void EventStateManager::DetermineDragTargetAndDefaultData(
 | |
|     nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget,
 | |
|     DataTransfer* aDataTransfer, bool* aAllowEmptyDataTransfer,
 | |
|     Selection** aSelection, RemoteDragStartData** aRemoteDragStartData,
 | |
|     nsIContent** aTargetNode, nsIPrincipal** aPrincipal,
 | |
|     nsIContentSecurityPolicy** aCsp,
 | |
|     nsICookieJarSettings** aCookieJarSettings) {
 | |
|   *aTargetNode = nullptr;
 | |
|   *aAllowEmptyDataTransfer = false;
 | |
|   nsCOMPtr<nsIContent> dragDataNode;
 | |
| 
 | |
|   nsIContent* editingElement = aSelectionTarget->IsEditable()
 | |
|                                    ? aSelectionTarget->GetEditingHost()
 | |
|                                    : nullptr;
 | |
| 
 | |
|   // In chrome, only allow dragging inside editable areas.
 | |
|   bool isChromeContext = !aWindow->GetBrowsingContext()->IsContent();
 | |
|   if (isChromeContext && !editingElement) {
 | |
|     if (mGestureDownDragStartData) {
 | |
|       // A child process started a drag so use any data it assigned for the dnd
 | |
|       // session.
 | |
|       mGestureDownDragStartData->AddInitialDnDDataTo(aDataTransfer, aPrincipal,
 | |
|                                                      aCsp, aCookieJarSettings);
 | |
|       mGestureDownDragStartData.forget(aRemoteDragStartData);
 | |
|       *aAllowEmptyDataTransfer = true;
 | |
|     }
 | |
|   } else {
 | |
|     mGestureDownDragStartData = nullptr;
 | |
| 
 | |
|     // GetDragData determines if a selection, link or image in the content
 | |
|     // should be dragged, and places the data associated with the drag in the
 | |
|     // data transfer.
 | |
|     // mGestureDownContent is the node where the mousedown event for the drag
 | |
|     // occurred, and aSelectionTarget is the node to use when a selection is
 | |
|     // used
 | |
|     bool canDrag;
 | |
|     bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
 | |
|     nsresult rv = nsContentAreaDragDrop::GetDragData(
 | |
|         aWindow, mGestureDownContent, aSelectionTarget, wasAlt, aDataTransfer,
 | |
|         &canDrag, aSelection, getter_AddRefs(dragDataNode), aCsp,
 | |
|         aCookieJarSettings);
 | |
|     if (NS_FAILED(rv) || !canDrag) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // if GetDragData returned a node, use that as the node being dragged.
 | |
|   // Otherwise, if a selection is being dragged, use the node within the
 | |
|   // selection that was dragged. Otherwise, just use the mousedown target.
 | |
|   nsIContent* dragContent = mGestureDownContent;
 | |
|   if (dragDataNode)
 | |
|     dragContent = dragDataNode;
 | |
|   else if (*aSelection)
 | |
|     dragContent = aSelectionTarget;
 | |
| 
 | |
|   nsIContent* originalDragContent = dragContent;
 | |
| 
 | |
|   // If a selection isn't being dragged, look for an ancestor with the
 | |
|   // draggable property set. If one is found, use that as the target of the
 | |
|   // drag instead of the node that was clicked on. If a draggable node wasn't
 | |
|   // found, just use the clicked node.
 | |
|   if (!*aSelection) {
 | |
|     while (dragContent) {
 | |
|       if (auto htmlElement = nsGenericHTMLElement::FromNode(dragContent)) {
 | |
|         if (htmlElement->Draggable()) {
 | |
|           // We let draggable elements to trigger dnd even if there is no data
 | |
|           // in the DataTransfer.
 | |
|           *aAllowEmptyDataTransfer = true;
 | |
|           break;
 | |
|         }
 | |
|       } else {
 | |
|         if (dragContent->IsXULElement()) {
 | |
|           // All XUL elements are draggable, so if a XUL element is
 | |
|           // encountered, stop looking for draggable nodes and just use the
 | |
|           // original clicked node instead.
 | |
|           // XXXndeakin
 | |
|           // In the future, we will want to improve this so that XUL has a
 | |
|           // better way to specify whether something is draggable than just
 | |
|           // on/off.
 | |
|           dragContent = mGestureDownContent;
 | |
|           break;
 | |
|         }
 | |
|         // otherwise, it's not an HTML or XUL element, so just keep looking
 | |
|       }
 | |
|       dragContent = dragContent->GetFlattenedTreeParent();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // if no node in the hierarchy was found to drag, but the GetDragData method
 | |
|   // returned a node, use that returned node. Otherwise, nothing is draggable.
 | |
|   if (!dragContent && dragDataNode) dragContent = dragDataNode;
 | |
| 
 | |
|   if (dragContent) {
 | |
|     // if an ancestor node was used instead, clear the drag data
 | |
|     // XXXndeakin rework this a bit. Find a way to just not call GetDragData if
 | |
|     // we don't need to.
 | |
|     if (dragContent != originalDragContent) aDataTransfer->ClearAll();
 | |
|     *aTargetNode = dragContent;
 | |
|     NS_ADDREF(*aTargetNode);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool EventStateManager::DoDefaultDragStart(
 | |
|     nsPresContext* aPresContext, WidgetDragEvent* aDragEvent,
 | |
|     DataTransfer* aDataTransfer, bool aAllowEmptyDataTransfer,
 | |
|     nsIContent* aDragTarget, Selection* aSelection,
 | |
|     RemoteDragStartData* aDragStartData, nsIPrincipal* aPrincipal,
 | |
|     nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings) {
 | |
|   nsCOMPtr<nsIDragService> dragService =
 | |
|       do_GetService("@mozilla.org/widget/dragservice;1");
 | |
|   if (!dragService) return false;
 | |
| 
 | |
|   // Default handling for the dragstart event.
 | |
|   //
 | |
|   // First, check if a drag session already exists. This means that the drag
 | |
|   // service was called directly within a draggesture handler. In this case,
 | |
|   // don't do anything more, as it is assumed that the handler is managing
 | |
|   // drag and drop manually. Make sure to return true to indicate that a drag
 | |
|   // began.  However, if we're handling drag session for synthesized events,
 | |
|   // we need to initialize some information of the session.  Therefore, we
 | |
|   // need to keep going for synthesized case.
 | |
|   nsCOMPtr<nsIDragSession> dragSession;
 | |
|   dragService->GetCurrentSession(getter_AddRefs(dragSession));
 | |
|   if (dragSession && !dragSession->IsSynthesizedForTests()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // No drag session is currently active, so check if a handler added
 | |
|   // any items to be dragged. If not, there isn't anything to drag.
 | |
|   uint32_t count = 0;
 | |
|   if (aDataTransfer) {
 | |
|     count = aDataTransfer->MozItemCount();
 | |
|   }
 | |
|   if (!aAllowEmptyDataTransfer && !count) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Get the target being dragged, which may not be the same as the
 | |
|   // target of the mouse event. If one wasn't set in the
 | |
|   // aDataTransfer during the event handler, just use the original
 | |
|   // target instead.
 | |
|   nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget();
 | |
|   if (!dragTarget) {
 | |
|     dragTarget = aDragTarget;
 | |
|     if (!dragTarget) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // check which drag effect should initially be used. If the effect was not
 | |
|   // set, just use all actions, otherwise Windows won't allow a drop.
 | |
|   uint32_t action = aDataTransfer->EffectAllowedInt();
 | |
|   if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
 | |
|     action = nsIDragService::DRAGDROP_ACTION_COPY |
 | |
|              nsIDragService::DRAGDROP_ACTION_MOVE |
 | |
|              nsIDragService::DRAGDROP_ACTION_LINK;
 | |
|   }
 | |
| 
 | |
|   // get any custom drag image that was set
 | |
|   int32_t imageX, imageY;
 | |
|   RefPtr<Element> dragImage = aDataTransfer->GetDragImage(&imageX, &imageY);
 | |
| 
 | |
|   nsCOMPtr<nsIArray> transArray = aDataTransfer->GetTransferables(dragTarget);
 | |
|   if (!transArray) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   RefPtr<DataTransfer> dataTransfer;
 | |
|   if (!dragSession) {
 | |
|     // After this function returns, the DataTransfer will be cleared so it
 | |
|     // appears empty to content. We need to pass a DataTransfer into the Drag
 | |
|     // Session, so we need to make a copy.
 | |
|     aDataTransfer->Clone(aDragTarget, eDrop, aDataTransfer->MozUserCancelled(),
 | |
|                          false, getter_AddRefs(dataTransfer));
 | |
| 
 | |
|     // Copy over the drop effect, as Clone doesn't copy it for us.
 | |
|     dataTransfer->SetDropEffectInt(aDataTransfer->DropEffectInt());
 | |
|   } else {
 | |
|     MOZ_ASSERT(dragSession->IsSynthesizedForTests());
 | |
|     MOZ_ASSERT(aDragEvent->mFlags.mIsSynthesizedForTests);
 | |
|     // If we're initializing synthesized drag session, we should use given
 | |
|     // DataTransfer as is because it'll be used with following drag events
 | |
|     // in any tests, therefore it should be set to nsIDragSession.dataTransfer
 | |
|     // because it and DragEvent.dataTransfer should be same instance.
 | |
|     dataTransfer = aDataTransfer;
 | |
|   }
 | |
| 
 | |
|   // XXXndeakin don't really want to create a new drag DOM event
 | |
|   // here, but we need something to pass to the InvokeDragSession
 | |
|   // methods.
 | |
|   RefPtr<DragEvent> event =
 | |
|       NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
 | |
| 
 | |
|   // Use InvokeDragSessionWithSelection if a selection is being dragged,
 | |
|   // such that the image can be generated from the selected text. However,
 | |
|   // use InvokeDragSessionWithImage if a custom image was set or something
 | |
|   // other than a selection is being dragged.
 | |
|   if (!dragImage && aSelection) {
 | |
|     dragService->InvokeDragSessionWithSelection(aSelection, aPrincipal, aCsp,
 | |
|                                                 aCookieJarSettings, transArray,
 | |
|                                                 action, event, dataTransfer);
 | |
|   } else if (aDragStartData) {
 | |
|     MOZ_ASSERT(XRE_IsParentProcess());
 | |
|     dragService->InvokeDragSessionWithRemoteImage(
 | |
|         dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
 | |
|         aDragStartData, event, dataTransfer);
 | |
|   } else {
 | |
|     dragService->InvokeDragSessionWithImage(
 | |
|         dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
 | |
|         dragImage, imageX, imageY, event, dataTransfer);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void EventStateManager::ChangeZoom(bool aIncrease) {
 | |
|   // Send the zoom change to the top level browser so it will be handled by the
 | |
|   // front end in the same way as other zoom actions.
 | |
|   nsIDocShell* docShell = mDocument->GetDocShell();
 | |
|   if (!docShell) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   BrowsingContext* bc = docShell->GetBrowsingContext();
 | |
|   if (!bc) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (XRE_IsParentProcess()) {
 | |
|     bc->Canonical()->DispatchWheelZoomChange(aIncrease);
 | |
|   } else if (BrowserChild* child = BrowserChild::GetFrom(docShell)) {
 | |
|     child->SendWheelZoomChange(aIncrease);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::DoScrollHistory(int32_t direction) {
 | |
|   nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
 | |
|   if (pcContainer) {
 | |
|     nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer));
 | |
|     if (webNav) {
 | |
|       // positive direction to go back one step, nonpositive to go forward
 | |
|       // This is doing user-initiated history traversal, hence we want
 | |
|       // to require that history entries we navigate to have user interaction.
 | |
|       if (direction > 0)
 | |
|         webNav->GoBack(StaticPrefs::browser_navigation_requireUserInteraction(),
 | |
|                        true);
 | |
|       else
 | |
|         webNav->GoForward(
 | |
|             StaticPrefs::browser_navigation_requireUserInteraction(), true);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
 | |
|                                      int32_t adjustment) {
 | |
|   // Exclude content in chrome docshells.
 | |
|   nsIContent* content = aTargetFrame->GetContent();
 | |
|   if (content && !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) {
 | |
|     // Positive adjustment to decrease zoom, negative to increase
 | |
|     const bool increase = adjustment <= 0;
 | |
|     EnsureDocument(mPresContext);
 | |
|     ChangeZoom(increase);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static nsIFrame* GetParentFrameToScroll(nsIFrame* aFrame) {
 | |
|   if (!aFrame) return nullptr;
 | |
| 
 | |
|   if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
 | |
|       nsLayoutUtils::IsReallyFixedPos(aFrame))
 | |
|     return aFrame->PresShell()->GetRootScrollFrame();
 | |
| 
 | |
|   return aFrame->GetParent();
 | |
| }
 | |
| 
 | |
| void EventStateManager::DispatchLegacyMouseScrollEvents(
 | |
|     nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent, nsEventStatus* aStatus) {
 | |
|   MOZ_ASSERT(aEvent);
 | |
|   MOZ_ASSERT(aStatus);
 | |
| 
 | |
|   if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Ignore mouse wheel transaction for computing legacy mouse wheel
 | |
|   // events' delta value.
 | |
|   // DOM event's delta vales are computed from CSS pixels.
 | |
|   auto scrollAmountInCSSPixels =
 | |
|       CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
 | |
| 
 | |
|   // XXX We don't deal with fractional amount in legacy event, though the
 | |
|   //     default action handler (DoScrollText()) deals with it.
 | |
|   //     If we implemented such strict computation, we would need additional
 | |
|   //     accumulated delta values. It would made the code more complicated.
 | |
|   //     And also it would computes different delta values from older version.
 | |
|   //     It doesn't make sense to implement such code for legacy events and
 | |
|   //     rare cases.
 | |
|   int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
 | |
|   switch (aEvent->mDeltaMode) {
 | |
|     case WheelEvent_Binding::DOM_DELTA_PAGE:
 | |
|       scrollDeltaX = !aEvent->mLineOrPageDeltaX
 | |
|                          ? 0
 | |
|                          : (aEvent->mLineOrPageDeltaX > 0
 | |
|                                 ? UIEvent_Binding::SCROLL_PAGE_DOWN
 | |
|                                 : UIEvent_Binding::SCROLL_PAGE_UP);
 | |
|       scrollDeltaY = !aEvent->mLineOrPageDeltaY
 | |
|                          ? 0
 | |
|                          : (aEvent->mLineOrPageDeltaY > 0
 | |
|                                 ? UIEvent_Binding::SCROLL_PAGE_DOWN
 | |
|                                 : UIEvent_Binding::SCROLL_PAGE_UP);
 | |
|       pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
 | |
|       pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
 | |
|       break;
 | |
| 
 | |
|     case WheelEvent_Binding::DOM_DELTA_LINE:
 | |
|       scrollDeltaX = aEvent->mLineOrPageDeltaX;
 | |
|       scrollDeltaY = aEvent->mLineOrPageDeltaY;
 | |
|       pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
 | |
|       pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
 | |
|       break;
 | |
| 
 | |
|     case WheelEvent_Binding::DOM_DELTA_PIXEL:
 | |
|       scrollDeltaX = aEvent->mLineOrPageDeltaX;
 | |
|       scrollDeltaY = aEvent->mLineOrPageDeltaY;
 | |
|       pixelDeltaX = RoundDown(aEvent->mDeltaX);
 | |
|       pixelDeltaY = RoundDown(aEvent->mDeltaY);
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       MOZ_CRASH("Invalid deltaMode value comes");
 | |
|   }
 | |
| 
 | |
|   // Send the legacy events in following order:
 | |
|   // 1. Vertical scroll
 | |
|   // 2. Vertical pixel scroll (even if #1 isn't consumed)
 | |
|   // 3. Horizontal scroll (even if #1 and/or #2 are consumed)
 | |
|   // 4. Horizontal pixel scroll (even if #3 isn't consumed)
 | |
| 
 | |
|   AutoWeakFrame targetFrame(aTargetFrame);
 | |
| 
 | |
|   MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
 | |
|                  !aEvent->DefaultPrevented(),
 | |
|              "If you make legacy events dispatched for default prevented wheel "
 | |
|              "event, you need to initialize stateX and stateY");
 | |
|   EventState stateX, stateY;
 | |
|   if (scrollDeltaY) {
 | |
|     SendLineScrollEvent(aTargetFrame, aEvent, stateY, scrollDeltaY,
 | |
|                         DELTA_DIRECTION_Y);
 | |
|     if (!targetFrame.IsAlive()) {
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (pixelDeltaY) {
 | |
|     SendPixelScrollEvent(aTargetFrame, aEvent, stateY, pixelDeltaY,
 | |
|                          DELTA_DIRECTION_Y);
 | |
|     if (!targetFrame.IsAlive()) {
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (scrollDeltaX) {
 | |
|     SendLineScrollEvent(aTargetFrame, aEvent, stateX, scrollDeltaX,
 | |
|                         DELTA_DIRECTION_X);
 | |
|     if (!targetFrame.IsAlive()) {
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (pixelDeltaX) {
 | |
|     SendPixelScrollEvent(aTargetFrame, aEvent, stateX, pixelDeltaX,
 | |
|                          DELTA_DIRECTION_X);
 | |
|     if (!targetFrame.IsAlive()) {
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (stateY.mDefaultPrevented) {
 | |
|     *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     aEvent->PreventDefault(!stateY.mDefaultPreventedByContent);
 | |
|   }
 | |
| 
 | |
|   if (stateX.mDefaultPrevented) {
 | |
|     *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     aEvent->PreventDefault(!stateX.mDefaultPreventedByContent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
 | |
|                                             WidgetWheelEvent* aEvent,
 | |
|                                             EventState& aState, int32_t aDelta,
 | |
|                                             DeltaDirection aDeltaDirection) {
 | |
|   nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
 | |
|   if (!targetContent) {
 | |
|     targetContent = GetFocusedElement();
 | |
|     if (!targetContent) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   while (targetContent->IsText()) {
 | |
|     targetContent = targetContent->GetFlattenedTreeParent();
 | |
|   }
 | |
| 
 | |
|   WidgetMouseScrollEvent event(aEvent->IsTrusted(),
 | |
|                                eLegacyMouseLineOrPageScroll, aEvent->mWidget);
 | |
|   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
 | |
|   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
 | |
|   event.mRefPoint = aEvent->mRefPoint;
 | |
|   event.mTimeStamp = aEvent->mTimeStamp;
 | |
|   event.mModifiers = aEvent->mModifiers;
 | |
|   event.mButtons = aEvent->mButtons;
 | |
|   event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
 | |
|   event.mDelta = aDelta;
 | |
|   event.mInputSource = aEvent->mInputSource;
 | |
| 
 | |
|   RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
 | |
|                             &status);
 | |
|   aState.mDefaultPrevented =
 | |
|       event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
 | |
|   aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
 | |
| }
 | |
| 
 | |
| void EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
 | |
|                                              WidgetWheelEvent* aEvent,
 | |
|                                              EventState& aState,
 | |
|                                              int32_t aPixelDelta,
 | |
|                                              DeltaDirection aDeltaDirection) {
 | |
|   nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
 | |
|   if (!targetContent) {
 | |
|     targetContent = GetFocusedElement();
 | |
|     if (!targetContent) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   while (targetContent->IsText()) {
 | |
|     targetContent = targetContent->GetFlattenedTreeParent();
 | |
|   }
 | |
| 
 | |
|   WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMousePixelScroll,
 | |
|                                aEvent->mWidget);
 | |
|   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
 | |
|   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
 | |
|   event.mRefPoint = aEvent->mRefPoint;
 | |
|   event.mTimeStamp = aEvent->mTimeStamp;
 | |
|   event.mModifiers = aEvent->mModifiers;
 | |
|   event.mButtons = aEvent->mButtons;
 | |
|   event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
 | |
|   event.mDelta = aPixelDelta;
 | |
|   event.mInputSource = aEvent->mInputSource;
 | |
| 
 | |
|   RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
 | |
|                             &status);
 | |
|   aState.mDefaultPrevented =
 | |
|       event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
 | |
|   aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
 | |
| }
 | |
| 
 | |
| nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|     nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent,
 | |
|     ComputeScrollTargetOptions aOptions) {
 | |
|   return ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|       aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY, aEvent, aOptions);
 | |
| }
 | |
| 
 | |
| // Overload ComputeScrollTargetAndMayAdjustWheelEvent method to allow passing
 | |
| // "test" dx and dy when looking for which scrollbarmediators to activate when
 | |
| // two finger down on trackpad and before any actual motion
 | |
| nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|     nsIFrame* aTargetFrame, double aDirectionX, double aDirectionY,
 | |
|     WidgetWheelEvent* aEvent, ComputeScrollTargetOptions aOptions) {
 | |
|   bool isAutoDir = false;
 | |
|   bool honoursRoot = false;
 | |
|   if (MAY_BE_ADJUSTED_BY_AUTO_DIR & aOptions) {
 | |
|     // If the scroll is respected as auto-dir, aDirection* should always be
 | |
|     // equivalent to the event's delta vlaues(Currently, there are only one case
 | |
|     // where aDirection*s have different values from the widget wheel event's
 | |
|     // original delta values and the only case isn't auto-dir, see
 | |
|     // ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets).
 | |
|     MOZ_ASSERT(aDirectionX == aEvent->mDeltaX &&
 | |
|                aDirectionY == aEvent->mDeltaY);
 | |
| 
 | |
|     WheelDeltaAdjustmentStrategy strategy =
 | |
|         GetWheelDeltaAdjustmentStrategy(*aEvent);
 | |
|     switch (strategy) {
 | |
|       case WheelDeltaAdjustmentStrategy::eAutoDir:
 | |
|         isAutoDir = true;
 | |
|         honoursRoot = false;
 | |
|         break;
 | |
|       case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
 | |
|         isAutoDir = true;
 | |
|         honoursRoot = true;
 | |
|         break;
 | |
|       default:
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
 | |
|     // If the user recently scrolled with the mousewheel, then they probably
 | |
|     // want to scroll the same view as before instead of the view under the
 | |
|     // cursor.  WheelTransaction tracks the frame currently being
 | |
|     // scrolled with the mousewheel. We consider the transaction ended when the
 | |
|     // mouse moves more than "mousewheel.transaction.ignoremovedelay"
 | |
|     // milliseconds after the last scroll operation, or any time the mouse moves
 | |
|     // out of the frame, or when more than "mousewheel.transaction.timeout"
 | |
|     // milliseconds have passed after the last operation, even if the mouse
 | |
|     // hasn't moved.
 | |
|     nsIFrame* lastScrollFrame = WheelTransaction::GetScrollTargetFrame();
 | |
|     if (lastScrollFrame) {
 | |
|       nsIScrollableFrame* scrollableFrame =
 | |
|           lastScrollFrame->GetScrollTargetFrame();
 | |
|       if (scrollableFrame) {
 | |
|         nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
 | |
|         MOZ_ASSERT(frameToScroll);
 | |
|         if (isAutoDir) {
 | |
|           ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *lastScrollFrame,
 | |
|                                                 honoursRoot);
 | |
|           // Note that calling this function will not always cause the delta to
 | |
|           // be adjusted, it only adjusts the delta when it should, because
 | |
|           // Adjust() internally calls ShouldBeAdjusted() before making
 | |
|           // adjustment.
 | |
|           adjuster.Adjust();
 | |
|         }
 | |
|         return frameToScroll;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If the event doesn't cause scroll actually, we cannot find scroll target
 | |
|   // because we check if the event can cause scroll actually on each found
 | |
|   // scrollable frame.
 | |
|   if (!aDirectionX && !aDirectionY) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool checkIfScrollableX;
 | |
|   bool checkIfScrollableY;
 | |
|   if (isAutoDir) {
 | |
|     // Always check the frame's scrollability in both the two directions for an
 | |
|     // auto-dir scroll. That is, for an auto-dir scroll,
 | |
|     // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS and
 | |
|     // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS should be ignored.
 | |
|     checkIfScrollableX = true;
 | |
|     checkIfScrollableY = true;
 | |
|   } else {
 | |
|     checkIfScrollableX =
 | |
|         aDirectionX &&
 | |
|         (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
 | |
|     checkIfScrollableY =
 | |
|         aDirectionY &&
 | |
|         (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
 | |
|   }
 | |
| 
 | |
|   nsIFrame* scrollFrame = !(aOptions & START_FROM_PARENT)
 | |
|                               ? aTargetFrame
 | |
|                               : GetParentFrameToScroll(aTargetFrame);
 | |
|   for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
 | |
|     // Check whether the frame wants to provide us with a scrollable view.
 | |
|     nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
 | |
|     if (!scrollableFrame) {
 | |
|       nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
 | |
|       if (menuPopupFrame) {
 | |
|         return nullptr;
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
 | |
|     MOZ_ASSERT(frameToScroll);
 | |
| 
 | |
|     if (!checkIfScrollableX && !checkIfScrollableY) {
 | |
|       return frameToScroll;
 | |
|     }
 | |
| 
 | |
|     // If the frame disregards the direction the user is trying to scroll, then
 | |
|     // it should just bubbles the scroll event up to its parental scroll frame
 | |
| 
 | |
|     Maybe<layers::ScrollDirection> disregardedDirection =
 | |
|         WheelHandlingUtils::GetDisregardedWheelScrollDirection(scrollFrame);
 | |
|     if (disregardedDirection) {
 | |
|       switch (disregardedDirection.ref()) {
 | |
|         case layers::ScrollDirection::eHorizontal:
 | |
|           if (checkIfScrollableX) {
 | |
|             continue;
 | |
|           }
 | |
|           break;
 | |
|         case layers::ScrollDirection::eVertical:
 | |
|           if (checkIfScrollableY) {
 | |
|             continue;
 | |
|           }
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     layers::ScrollDirections directions =
 | |
|         scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
 | |
|     if ((!(directions.contains(layers::ScrollDirection::eVertical)) &&
 | |
|          !(directions.contains(layers::ScrollDirection::eHorizontal))) ||
 | |
|         (checkIfScrollableY && !checkIfScrollableX &&
 | |
|          !(directions.contains(layers::ScrollDirection::eVertical))) ||
 | |
|         (checkIfScrollableX && !checkIfScrollableY &&
 | |
|          !(directions.contains(layers::ScrollDirection::eHorizontal)))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Computes whether the currently checked frame is scrollable by this wheel
 | |
|     // event.
 | |
|     bool canScroll = false;
 | |
|     if (isAutoDir) {
 | |
|       ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *scrollFrame, honoursRoot);
 | |
|       if (adjuster.ShouldBeAdjusted()) {
 | |
|         adjuster.Adjust();
 | |
|         canScroll = true;
 | |
|       } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
 | |
|                                                  aDirectionY)) {
 | |
|         canScroll = true;
 | |
|       }
 | |
|     } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
 | |
|                                                aDirectionY)) {
 | |
|       canScroll = true;
 | |
|     }
 | |
| 
 | |
|     if (canScroll) {
 | |
|       return frameToScroll;
 | |
|     }
 | |
| 
 | |
|     // Where we are at is the block ending in a for loop.
 | |
|     // The current frame has been checked to be unscrollable by this wheel
 | |
|     // event, continue the loop to check its parent, if any.
 | |
|   }
 | |
| 
 | |
|   nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(
 | |
|       aTargetFrame->PresShell()->GetRootFrame());
 | |
|   aOptions =
 | |
|       static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
 | |
|   if (!newFrame) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return ComputeScrollTargetAndMayAdjustWheelEvent(newFrame, aEvent, aOptions);
 | |
| }
 | |
| 
 | |
| nsSize EventStateManager::GetScrollAmount(
 | |
|     nsPresContext* aPresContext, WidgetWheelEvent* aEvent,
 | |
|     nsIScrollableFrame* aScrollableFrame) {
 | |
|   MOZ_ASSERT(aPresContext);
 | |
|   MOZ_ASSERT(aEvent);
 | |
| 
 | |
|   const bool isPage = aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PAGE;
 | |
|   if (!aScrollableFrame) {
 | |
|     // If there is no scrollable frame, we should use root, see below.
 | |
|     aScrollableFrame =
 | |
|         aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
 | |
|   }
 | |
| 
 | |
|   if (aScrollableFrame) {
 | |
|     return isPage ? aScrollableFrame->GetPageScrollAmount()
 | |
|                   : aScrollableFrame->GetLineScrollAmount();
 | |
|   }
 | |
| 
 | |
|   // If there is no scrollable frame and page scrolling, use viewport size.
 | |
|   if (isPage) {
 | |
|     return aPresContext->GetVisibleArea().Size();
 | |
|   }
 | |
| 
 | |
|   // Otherwise use root frame's font metrics.
 | |
|   //
 | |
|   // FIXME(emilio): Should this use the root element's style frame? The root
 | |
|   // frame will always have the initial font. Then again it should never matter
 | |
|   // for content, we should always have a root scrollable frame in html
 | |
|   // documents.
 | |
|   nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
 | |
|   if (!rootFrame) {
 | |
|     return nsSize(0, 0);
 | |
|   }
 | |
|   RefPtr<nsFontMetrics> fm =
 | |
|       nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame);
 | |
|   NS_ENSURE_TRUE(fm, nsSize(0, 0));
 | |
|   return nsSize(fm->AveCharWidth(), fm->MaxHeight());
 | |
| }
 | |
| 
 | |
| void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
 | |
|                                      WidgetWheelEvent* aEvent) {
 | |
|   MOZ_ASSERT(aScrollableFrame);
 | |
|   MOZ_ASSERT(aEvent);
 | |
| 
 | |
|   nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
 | |
|   MOZ_ASSERT(scrollFrame);
 | |
| 
 | |
|   AutoWeakFrame scrollFrameWeak(scrollFrame);
 | |
|   AutoWeakFrame eventFrameWeak(mCurrentTarget);
 | |
|   if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak,
 | |
|                                                  eventFrameWeak)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Default action's actual scroll amount should be computed from device
 | |
|   // pixels.
 | |
|   nsPresContext* pc = scrollFrame->PresContext();
 | |
|   nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame);
 | |
|   nsIntSize scrollAmountInDevPixels(
 | |
|       pc->AppUnitsToDevPixels(scrollAmount.width),
 | |
|       pc->AppUnitsToDevPixels(scrollAmount.height));
 | |
|   nsIntPoint actualDevPixelScrollAmount =
 | |
|       DeltaAccumulator::GetInstance()->ComputeScrollAmountForDefaultAction(
 | |
|           aEvent, scrollAmountInDevPixels);
 | |
| 
 | |
|   // Don't scroll around the axis whose overflow style is hidden.
 | |
|   ScrollStyles overflowStyle = aScrollableFrame->GetScrollStyles();
 | |
|   if (overflowStyle.mHorizontal == StyleOverflow::Hidden) {
 | |
|     actualDevPixelScrollAmount.x = 0;
 | |
|   }
 | |
|   if (overflowStyle.mVertical == StyleOverflow::Hidden) {
 | |
|     actualDevPixelScrollAmount.y = 0;
 | |
|   }
 | |
| 
 | |
|   ScrollSnapFlags snapFlags = ScrollSnapFlags::Disabled;
 | |
|   mozilla::ScrollOrigin origin = mozilla::ScrollOrigin::NotSpecified;
 | |
|   switch (aEvent->mDeltaMode) {
 | |
|     case WheelEvent_Binding::DOM_DELTA_LINE:
 | |
|       origin = mozilla::ScrollOrigin::MouseWheel;
 | |
|       snapFlags = ScrollSnapFlags::IntendedDirection;
 | |
|       break;
 | |
|     case WheelEvent_Binding::DOM_DELTA_PAGE:
 | |
|       origin = mozilla::ScrollOrigin::Pages;
 | |
|       snapFlags = ScrollSnapFlags::IntendedDirection |
 | |
|                   ScrollSnapFlags::IntendedEndPosition;
 | |
|       break;
 | |
|     case WheelEvent_Binding::DOM_DELTA_PIXEL:
 | |
|       origin = mozilla::ScrollOrigin::Pixels;
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_CRASH("Invalid deltaMode value comes");
 | |
|   }
 | |
| 
 | |
|   // We shouldn't scroll more one page at once except when over one page scroll
 | |
|   // is allowed for the event.
 | |
|   nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
 | |
|   nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
 | |
|                              pc->AppUnitsToDevPixels(pageSize.height));
 | |
|   if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
 | |
|       DeprecatedAbs(actualDevPixelScrollAmount.x.value) >
 | |
|           devPixelPageSize.width) {
 | |
|     actualDevPixelScrollAmount.x = (actualDevPixelScrollAmount.x >= 0)
 | |
|                                        ? devPixelPageSize.width
 | |
|                                        : -devPixelPageSize.width;
 | |
|   }
 | |
| 
 | |
|   if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
 | |
|       DeprecatedAbs(actualDevPixelScrollAmount.y.value) >
 | |
|           devPixelPageSize.height) {
 | |
|     actualDevPixelScrollAmount.y = (actualDevPixelScrollAmount.y >= 0)
 | |
|                                        ? devPixelPageSize.height
 | |
|                                        : -devPixelPageSize.height;
 | |
|   }
 | |
| 
 | |
|   bool isDeltaModePixel =
 | |
|       (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL);
 | |
| 
 | |
|   ScrollMode mode;
 | |
|   switch (aEvent->mScrollType) {
 | |
|     case WidgetWheelEvent::SCROLL_DEFAULT:
 | |
|       if (isDeltaModePixel) {
 | |
|         mode = ScrollMode::Normal;
 | |
|       } else if (aEvent->mFlags.mHandledByAPZ) {
 | |
|         mode = ScrollMode::SmoothMsd;
 | |
|       } else {
 | |
|         mode = ScrollMode::Smooth;
 | |
|       }
 | |
|       break;
 | |
|     case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
 | |
|       mode = ScrollMode::Instant;
 | |
|       break;
 | |
|     case WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY:
 | |
|       mode = ScrollMode::Normal;
 | |
|       break;
 | |
|     case WidgetWheelEvent::SCROLL_SMOOTHLY:
 | |
|       mode = ScrollMode::Smooth;
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_CRASH("Invalid mScrollType value comes");
 | |
|   }
 | |
| 
 | |
|   nsIScrollableFrame::ScrollMomentum momentum =
 | |
|       aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
 | |
|                           : nsIScrollableFrame::NOT_MOMENTUM;
 | |
| 
 | |
|   nsIntPoint overflow;
 | |
|   aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
 | |
|                              ScrollUnit::DEVICE_PIXELS, mode, &overflow, origin,
 | |
|                              momentum, snapFlags);
 | |
| 
 | |
|   if (!scrollFrameWeak.IsAlive()) {
 | |
|     // If the scroll causes changing the layout, we can think that the event
 | |
|     // has been completely consumed by the content.  Then, users probably don't
 | |
|     // want additional action.
 | |
|     aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0;
 | |
|   } else if (isDeltaModePixel) {
 | |
|     aEvent->mOverflowDeltaX = overflow.x;
 | |
|     aEvent->mOverflowDeltaY = overflow.y;
 | |
|   } else {
 | |
|     aEvent->mOverflowDeltaX =
 | |
|         static_cast<double>(overflow.x) / scrollAmountInDevPixels.width;
 | |
|     aEvent->mOverflowDeltaY =
 | |
|         static_cast<double>(overflow.y) / scrollAmountInDevPixels.height;
 | |
|   }
 | |
| 
 | |
|   // If CSS overflow properties caused not to scroll, the overflowDelta* values
 | |
|   // should be same as delta* values since they may be used as gesture event by
 | |
|   // widget.  However, if there is another scrollable element in the ancestor
 | |
|   // along the axis, probably users don't want the operation to cause
 | |
|   // additional action such as moving history.  In such case, overflowDelta
 | |
|   // values should stay zero.
 | |
|   if (scrollFrameWeak.IsAlive()) {
 | |
|     if (aEvent->mDeltaX && overflowStyle.mHorizontal == StyleOverflow::Hidden &&
 | |
|         !ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|             scrollFrame, aEvent,
 | |
|             COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_WITH_AUTO_DIR)) {
 | |
|       aEvent->mOverflowDeltaX = aEvent->mDeltaX;
 | |
|     }
 | |
|     if (aEvent->mDeltaY && overflowStyle.mVertical == StyleOverflow::Hidden &&
 | |
|         !ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|             scrollFrame, aEvent,
 | |
|             COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_WITH_AUTO_DIR)) {
 | |
|       aEvent->mOverflowDeltaY = aEvent->mDeltaY;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(
 | |
|       aEvent->mOverflowDeltaX == 0 ||
 | |
|           (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
 | |
|       "The sign of mOverflowDeltaX is different from the scroll direction");
 | |
|   NS_ASSERTION(
 | |
|       aEvent->mOverflowDeltaY == 0 ||
 | |
|           (aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0),
 | |
|       "The sign of mOverflowDeltaY is different from the scroll direction");
 | |
| 
 | |
|   WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent);
 | |
| }
 | |
| 
 | |
| void EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
 | |
|                                            nsIFrame* targetFrame) {
 | |
|   NS_ASSERTION(aEvent->mMessage == eGestureNotify,
 | |
|                "DecideGestureEvent called with a non-gesture event");
 | |
| 
 | |
|   /* Check the ancestor tree to decide if any frame is willing* to receive
 | |
|    * a MozPixelScroll event. If that's the case, the current touch gesture
 | |
|    * will be used as a pan gesture; otherwise it will be a regular
 | |
|    * mousedown/mousemove/click event.
 | |
|    *
 | |
|    * *willing: determine if it makes sense to pan the element using scroll
 | |
|    * events:
 | |
|    *  - For web content: if there are any visible scrollbars on the touch point
 | |
|    *  - For XUL: if it's an scrollable element that can currently scroll in some
 | |
|    *    direction.
 | |
|    *
 | |
|    * Note: we'll have to one-off various cases to ensure a good usable behavior
 | |
|    */
 | |
|   WidgetGestureNotifyEvent::PanDirection panDirection =
 | |
|       WidgetGestureNotifyEvent::ePanNone;
 | |
|   bool displayPanFeedback = false;
 | |
|   for (nsIFrame* current = targetFrame; current;
 | |
|        current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
 | |
|     // e10s - mark remote content as pannable. This is a work around since
 | |
|     // we don't have access to remote frame scroll info here. Apz data may
 | |
|     // assist is solving this.
 | |
|     if (current && IsTopLevelRemoteTarget(current->GetContent())) {
 | |
|       panDirection = WidgetGestureNotifyEvent::ePanBoth;
 | |
|       // We don't know when we reach bounds, so just disable feedback for now.
 | |
|       displayPanFeedback = false;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     LayoutFrameType currentFrameType = current->Type();
 | |
| 
 | |
|     // Scrollbars should always be draggable
 | |
|     if (currentFrameType == LayoutFrameType::Scrollbar) {
 | |
|       panDirection = WidgetGestureNotifyEvent::ePanNone;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Special check for trees
 | |
|     if (nsTreeBodyFrame* treeFrame = do_QueryFrame(current)) {
 | |
|       if (treeFrame->GetHorizontalOverflow()) {
 | |
|         panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
 | |
|       }
 | |
|       if (treeFrame->GetVerticalOverflow()) {
 | |
|         panDirection = WidgetGestureNotifyEvent::ePanVertical;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(current)) {
 | |
|       layers::ScrollDirections scrollbarVisibility =
 | |
|           scrollableFrame->GetScrollbarVisibility();
 | |
| 
 | |
|       // Check if we have visible scrollbars
 | |
|       if (scrollbarVisibility.contains(layers::ScrollDirection::eVertical)) {
 | |
|         panDirection = WidgetGestureNotifyEvent::ePanVertical;
 | |
|         displayPanFeedback = true;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (scrollbarVisibility.contains(layers::ScrollDirection::eHorizontal)) {
 | |
|         panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
 | |
|         displayPanFeedback = true;
 | |
|       }
 | |
|     }  // scrollableFrame
 | |
|   }  // ancestor chain
 | |
|   aEvent->mDisplayPanFeedback = displayPanFeedback;
 | |
|   aEvent->mPanDirection = panDirection;
 | |
| }
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
| static nsINode* GetCrossDocParentNode(nsINode* aChild) {
 | |
|   MOZ_ASSERT(aChild, "The child is null!");
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
| 
 | |
|   nsINode* parent = aChild->GetParentNode();
 | |
|   if (parent && parent->IsContent() && aChild->IsContent()) {
 | |
|     parent = aChild->AsContent()->GetFlattenedTreeParent();
 | |
|   }
 | |
| 
 | |
|   if (parent || !aChild->IsDocument()) {
 | |
|     return parent;
 | |
|   }
 | |
| 
 | |
|   return aChild->AsDocument()->GetEmbedderElement();
 | |
| }
 | |
| 
 | |
| static bool NodeAllowsClickThrough(nsINode* aNode) {
 | |
|   while (aNode) {
 | |
|     if (aNode->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::tree)) {
 | |
|       return false;
 | |
|     }
 | |
|     if (aNode->IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::resizer)) {
 | |
|       return true;
 | |
|     }
 | |
|     aNode = GetCrossDocParentNode(aNode);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void EventStateManager::PostHandleKeyboardEvent(
 | |
|     WidgetKeyboardEvent* aKeyboardEvent, nsIFrame* aTargetFrame,
 | |
|     nsEventStatus& aStatus) {
 | |
|   if (aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPresContext> presContext = mPresContext;
 | |
| 
 | |
|   if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
 | |
|     if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
 | |
|       RefPtr<BrowserParent> remote =
 | |
|           aTargetFrame ? BrowserParent::GetFrom(aTargetFrame->GetContent())
 | |
|                        : nullptr;
 | |
|       if (remote) {
 | |
|         // remote is null-checked above in order to let pre-existing event
 | |
|         // targeting code's chrome vs. content decision override in case of
 | |
|         // disagreement in order not to disrupt non-Fission e10s mode in case
 | |
|         // there are still bugs in the Fission-mode code. That is, if remote
 | |
|         // is nullptr, the pre-existing event targeting code has deemed this
 | |
|         // event to belong to chrome rather than content.
 | |
|         BrowserParent* preciseRemote = BrowserParent::GetFocused();
 | |
|         if (preciseRemote) {
 | |
|           remote = preciseRemote;
 | |
|         }
 | |
|         // else there was a race between layout and focus tracking
 | |
|       }
 | |
|       if (remote && !remote->IsReadyToHandleInputEvents()) {
 | |
|         // We need to dispatch the event to the browser element again if we were
 | |
|         // waiting for the key reply but the event wasn't sent to the content
 | |
|         // process due to the remote browser wasn't ready.
 | |
|         WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
 | |
|         aKeyboardEvent->MarkAsHandledInRemoteProcess();
 | |
|         RefPtr<Element> ownerElement = remote->GetOwnerElement();
 | |
|         EventDispatcher::Dispatch(ownerElement, presContext, &keyEvent);
 | |
|         if (keyEvent.DefaultPrevented()) {
 | |
|           aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
 | |
|           aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // The widget expects a reply for every keyboard event. If the event wasn't
 | |
|     // dispatched to a content process (non-e10s or no content process
 | |
|     // running), we need to short-circuit here. Otherwise, we need to wait for
 | |
|     // the content process to handle the event.
 | |
|     if (aKeyboardEvent->mWidget) {
 | |
|       aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
 | |
|     }
 | |
|     if (aKeyboardEvent->DefaultPrevented()) {
 | |
|       aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // XXX Currently, our automated tests don't support mKeyNameIndex.
 | |
|   //     Therefore, we still need to handle this with keyCode.
 | |
|   switch (aKeyboardEvent->mKeyCode) {
 | |
|     case NS_VK_TAB:
 | |
|     case NS_VK_F6:
 | |
|       // This is to prevent keyboard scrolling while alt modifier in use.
 | |
|       if (!aKeyboardEvent->IsAlt()) {
 | |
|         aStatus = nsEventStatus_eConsumeNoDefault;
 | |
| 
 | |
|         // Handling the tab event after it was sent to content is bad,
 | |
|         // because to the FocusManager the remote-browser looks like one
 | |
|         // element, so we would just move the focus to the next element
 | |
|         // in chrome, instead of handling it in content.
 | |
|         if (aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         EnsureDocument(presContext);
 | |
|         nsFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|         if (fm && mDocument) {
 | |
|           // Shift focus forward or back depending on shift key
 | |
|           bool isDocMove = aKeyboardEvent->IsControl() ||
 | |
|                            aKeyboardEvent->mKeyCode == NS_VK_F6;
 | |
|           uint32_t dir =
 | |
|               aKeyboardEvent->IsShift()
 | |
|                   ? (isDocMove ? static_cast<uint32_t>(
 | |
|                                      nsIFocusManager::MOVEFOCUS_BACKWARDDOC)
 | |
|                                : static_cast<uint32_t>(
 | |
|                                      nsIFocusManager::MOVEFOCUS_BACKWARD))
 | |
|                   : (isDocMove ? static_cast<uint32_t>(
 | |
|                                      nsIFocusManager::MOVEFOCUS_FORWARDDOC)
 | |
|                                : static_cast<uint32_t>(
 | |
|                                      nsIFocusManager::MOVEFOCUS_FORWARD));
 | |
|           RefPtr<Element> result;
 | |
|           fm->MoveFocus(mDocument->GetWindow(), nullptr, dir,
 | |
|                         nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
 | |
|         }
 | |
|       }
 | |
|       return;
 | |
|     case 0:
 | |
|       // We handle keys with no specific keycode value below.
 | |
|       break;
 | |
|     default:
 | |
|       return;
 | |
|   }
 | |
| 
 | |
|   switch (aKeyboardEvent->mKeyNameIndex) {
 | |
|     case KEY_NAME_INDEX_ZoomIn:
 | |
|     case KEY_NAME_INDEX_ZoomOut:
 | |
|       ChangeZoom(aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn);
 | |
|       aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool NeedsActiveContentChange(const WidgetMouseEvent* aMouseEvent) {
 | |
|   // If the mouse event is a synthesized mouse event due to a touch, do
 | |
|   // not set/clear the activation state. Element activation is handled by APZ.
 | |
|   return !aMouseEvent ||
 | |
|          aMouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
 | |
|                                             WidgetEvent* aEvent,
 | |
|                                             nsIFrame* aTargetFrame,
 | |
|                                             nsEventStatus* aStatus,
 | |
|                                             nsIContent* aOverrideClickTarget) {
 | |
|   NS_ENSURE_ARG(aPresContext);
 | |
|   NS_ENSURE_ARG_POINTER(aStatus);
 | |
| 
 | |
|   mCurrentTarget = aTargetFrame;
 | |
|   mCurrentTargetContent = nullptr;
 | |
| 
 | |
|   HandleCrossProcessEvent(aEvent, aStatus);
 | |
|   // NOTE: the above call may have destroyed aTargetFrame, please use
 | |
|   // mCurrentTarget henceforth.  This is to avoid using it accidentally:
 | |
|   aTargetFrame = nullptr;
 | |
| 
 | |
|   // Most of the events we handle below require a frame.
 | |
|   // Add special cases here.
 | |
|   if (!mCurrentTarget && aEvent->mMessage != eMouseUp &&
 | |
|       aEvent->mMessage != eMouseDown && aEvent->mMessage != eDragEnter &&
 | |
|       aEvent->mMessage != eDragOver && aEvent->mMessage != ePointerUp &&
 | |
|       aEvent->mMessage != ePointerCancel) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Keep the prescontext alive, we might need it after event dispatch
 | |
|   RefPtr<nsPresContext> presContext = aPresContext;
 | |
|   nsresult ret = NS_OK;
 | |
| 
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eMouseDown: {
 | |
|       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
 | |
|       if (mouseEvent->mButton == MouseButton::ePrimary &&
 | |
|           !sNormalLMouseEventInProcess) {
 | |
|         // We got a mouseup event while a mousedown event was being processed.
 | |
|         // Make sure that the capturing content is cleared.
 | |
|         PresShell::ReleaseCapturingContent();
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       // For remote content, capture the event in the parent process at the
 | |
|       // <xul:browser remote> element. This will ensure that subsequent
 | |
|       // mousemove/mouseup events will continue to be dispatched to this element
 | |
|       // and therefore forwarded to the child.
 | |
|       if (aEvent->HasBeenPostedToRemoteProcess() &&
 | |
|           !PresShell::GetCapturingContent()) {
 | |
|         if (nsIContent* content =
 | |
|                 mCurrentTarget ? mCurrentTarget->GetContent() : nullptr) {
 | |
|           PresShell::SetCapturingContent(content, CaptureFlags::None, aEvent);
 | |
|         } else {
 | |
|           PresShell::ReleaseCapturingContent();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // If MouseEvent::PreventClickEvent() was called by chrome script,
 | |
|       // we need to forget the clicking content and click count for the
 | |
|       // following eMouseUp event.
 | |
|       if (mouseEvent->mClickEventPrevented) {
 | |
|         RefPtr<EventStateManager> esm =
 | |
|             ESMFromContentOrThis(aOverrideClickTarget);
 | |
|         switch (mouseEvent->mButton) {
 | |
|           case MouseButton::ePrimary:
 | |
|           case MouseButton::eSecondary:
 | |
|           case MouseButton::eMiddle: {
 | |
|             LastMouseDownInfo& mouseDownInfo =
 | |
|                 GetLastMouseDownInfo(mouseEvent->mButton);
 | |
|             mouseDownInfo.mLastMouseDownContent = nullptr;
 | |
|             mouseDownInfo.mClickCount = 0;
 | |
|             mouseDownInfo.mLastMouseDownInputControlType = Nothing();
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|           default:
 | |
|             break;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIContent> activeContent;
 | |
|       // When content calls PreventDefault on pointerdown, we also call
 | |
|       // PreventDefault on the subsequent mouse events to suppress default
 | |
|       // behaviors. Normally, aStatus should be nsEventStatus_eConsumeNoDefault
 | |
|       // when the event is DefaultPrevented but it's reset to
 | |
|       // nsEventStatus_eIgnore in EventStateManager::PreHandleEvent. So we also
 | |
|       // check if the event is DefaultPrevented.
 | |
|       if (nsEventStatus_eConsumeNoDefault != *aStatus &&
 | |
|           !aEvent->DefaultPrevented()) {
 | |
|         nsCOMPtr<nsIContent> newFocus;
 | |
|         bool suppressBlur = false;
 | |
|         if (mCurrentTarget) {
 | |
|           mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
 | |
|           activeContent = mCurrentTarget->GetContent();
 | |
| 
 | |
|           // In some cases, we do not want to even blur the current focused
 | |
|           // element. Those cases are:
 | |
|           // 1. -moz-user-focus CSS property is set to 'ignore';
 | |
|           // 2. XUL control element has the disabled property set to 'true'.
 | |
|           //
 | |
|           // We can't use nsIFrame::IsFocusable() because we want to blur when
 | |
|           // we click on a visibility: none element.
 | |
|           // We can't use nsIContent::IsFocusable() because we want to blur when
 | |
|           // we click on a non-focusable element like a <div>.
 | |
|           // We have to use |aEvent->mTarget| to not make sure we do not check
 | |
|           // an anonymous node of the targeted element.
 | |
|           suppressBlur =
 | |
|               mCurrentTarget->StyleUI()->UserFocus() == StyleUserFocus::Ignore;
 | |
| 
 | |
|           if (!suppressBlur) {
 | |
|             if (Element* element =
 | |
|                     Element::FromEventTargetOrNull(aEvent->mTarget)) {
 | |
|               if (nsCOMPtr<nsIDOMXULControlElement> xulControl =
 | |
|                       element->AsXULControl()) {
 | |
|                 bool disabled = false;
 | |
|                 xulControl->GetDisabled(&disabled);
 | |
|                 suppressBlur = disabled;
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // When a root content which isn't editable but has an editable HTML
 | |
|         // <body> element is clicked, we should redirect the focus to the
 | |
|         // the <body> element.  E.g., when an user click bottom of the editor
 | |
|         // where is outside of the <body> element, the <body> should be focused
 | |
|         // and the user can edit immediately after that.
 | |
|         //
 | |
|         // NOTE: The newFocus isn't editable that also means it's not in
 | |
|         // designMode.  In designMode, all contents are not focusable.
 | |
|         if (newFocus && !newFocus->IsEditable()) {
 | |
|           Document* doc = newFocus->GetComposedDoc();
 | |
|           if (doc && newFocus == doc->GetRootElement()) {
 | |
|             nsIContent* bodyContent =
 | |
|                 nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
 | |
|             if (bodyContent && bodyContent->GetPrimaryFrame()) {
 | |
|               newFocus = bodyContent;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // When the mouse is pressed, the default action is to focus the
 | |
|         // target. Look for the nearest enclosing focusable frame.
 | |
|         //
 | |
|         // TODO: Probably this should be moved to Element::PostHandleEvent.
 | |
|         for (; newFocus; newFocus = newFocus->GetFlattenedTreeParent()) {
 | |
|           if (!newFocus->IsElement()) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           nsIFrame* frame = newFocus->GetPrimaryFrame();
 | |
|           if (!frame) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           // If the mousedown happened inside a popup, don't try to set focus on
 | |
|           // one of its containing elements
 | |
|           if (frame->IsMenuPopupFrame()) {
 | |
|             newFocus = nullptr;
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|           auto flags = IsFocusableFlags::WithMouse;
 | |
|           if (frame->IsFocusable(flags)) {
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|           if (ShadowRoot* root = newFocus->GetShadowRoot()) {
 | |
|             if (root->DelegatesFocus()) {
 | |
|               if (Element* firstFocusable = root->GetFocusDelegate(flags)) {
 | |
|                 newFocus = firstFocusable;
 | |
|                 break;
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         MOZ_ASSERT_IF(newFocus, newFocus->IsElement());
 | |
| 
 | |
|         if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
 | |
|           // if something was found to focus, focus it. Otherwise, if the
 | |
|           // element that was clicked doesn't have -moz-user-focus: ignore,
 | |
|           // clear the existing focus. For -moz-user-focus: ignore, the focus
 | |
|           // is just left as is.
 | |
|           // Another effect of mouse clicking, handled in Selection, is that
 | |
|           // it should update the caret position to where the mouse was
 | |
|           // clicked. Because the focus is cleared when clicking on a
 | |
|           // non-focusable node, the next press of the tab key will cause
 | |
|           // focus to be shifted from the caret position instead of the root.
 | |
|           if (newFocus) {
 | |
|             // use the mouse flag and the noscroll flag so that the content
 | |
|             // doesn't unexpectedly scroll when clicking an element that is
 | |
|             // only half visible
 | |
|             uint32_t flags =
 | |
|                 nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
 | |
|             // If this was a touch-generated event, pass that information:
 | |
|             if (mouseEvent->mInputSource ==
 | |
|                 MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
 | |
|               flags |= nsIFocusManager::FLAG_BYTOUCH;
 | |
|             }
 | |
|             fm->SetFocus(MOZ_KnownLive(newFocus->AsElement()), flags);
 | |
|           } else if (!suppressBlur) {
 | |
|             // clear the focus within the frame and then set it as the
 | |
|             // focused frame
 | |
|             EnsureDocument(mPresContext);
 | |
|             if (mDocument) {
 | |
|               nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mDocument->GetWindow();
 | |
| #ifdef XP_MACOSX
 | |
|               if (!activeContent || !activeContent->IsXULElement())
 | |
| #endif
 | |
|                 fm->ClearFocus(outerWindow);
 | |
|               // Prevent switch frame if we're already not in the foreground tab
 | |
|               // and we're in a content process.
 | |
|               // TODO: If we were inactive frame in this tab, and now in
 | |
|               //       background tab, we shouldn't make the tab foreground, but
 | |
|               //       we should set focus to clicked document in the background
 | |
|               //       tab.  However, nsFocusManager does not have proper method
 | |
|               //       for doing this.  Therefore, we should skip setting focus
 | |
|               //       to clicked document for now.
 | |
|               if (XRE_IsParentProcess() || IsInActiveTab(mDocument)) {
 | |
|                 fm->SetFocusedWindow(outerWindow);
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // The rest is left button-specific.
 | |
|         if (mouseEvent->mButton != MouseButton::ePrimary) {
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         // The nearest enclosing element goes into the :active state.  If we're
 | |
|         // not an element (so we're text or something) we need to obtain
 | |
|         // our parent element and put it into :active instead.
 | |
|         if (activeContent && !activeContent->IsElement()) {
 | |
|           if (nsIContent* par = activeContent->GetFlattenedTreeParent()) {
 | |
|             activeContent = par;
 | |
|           }
 | |
|         }
 | |
|       } else {
 | |
|         // if we're here, the event handler returned false, so stop
 | |
|         // any of our own processing of a drag. Workaround for bug 43258.
 | |
|         StopTrackingDragGesture(true);
 | |
|       }
 | |
|       // XXX Why do we always set this is active?  Active window may be changed
 | |
|       //     by a mousedown event listener.
 | |
|       if (NeedsActiveContentChange(mouseEvent)) {
 | |
|         SetActiveManager(this, activeContent);
 | |
|       }
 | |
|     } break;
 | |
|     case ePointerCancel:
 | |
|     case ePointerUp: {
 | |
|       WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
 | |
|       MOZ_ASSERT(pointerEvent);
 | |
|       // Implicitly releasing capture for given pointer. ePointerLostCapture
 | |
|       // should be send after ePointerUp or ePointerCancel.
 | |
|       PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
 | |
|       PointerEventHandler::UpdateActivePointerState(pointerEvent);
 | |
| 
 | |
|       if (
 | |
|           // After pointercancel, pointer becomes invalid so we can remove
 | |
|           // relevant helper from table.
 | |
|           pointerEvent->mMessage == ePointerCancel ||
 | |
|           // pointerup for non-hoverable pointer needs to dispatch pointerout
 | |
|           // and pointerleave events because the pointer is valid only while the
 | |
|           // pointer is "down".
 | |
|           !pointerEvent->InputSourceSupportsHover()) {
 | |
|         GenerateMouseEnterExit(pointerEvent);
 | |
|         mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case eMouseUp: {
 | |
|       // We can unconditionally stop capturing because
 | |
|       // we should never be capturing when the mouse button is up
 | |
|       PresShell::ReleaseCapturingContent();
 | |
| 
 | |
|       WidgetMouseEvent* mouseUpEvent = aEvent->AsMouseEvent();
 | |
|       if (NeedsActiveContentChange(mouseUpEvent)) {
 | |
|         ClearGlobalActiveContent(this);
 | |
|       }
 | |
|       if (mouseUpEvent && EventCausesClickEvents(*mouseUpEvent)) {
 | |
|         // Make sure to dispatch the click even if there is no frame for
 | |
|         // the current target element. This is required for Web compatibility.
 | |
|         RefPtr<EventStateManager> esm =
 | |
|             ESMFromContentOrThis(aOverrideClickTarget);
 | |
|         ret =
 | |
|             esm->PostHandleMouseUp(mouseUpEvent, aStatus, aOverrideClickTarget);
 | |
|       }
 | |
| 
 | |
|       if (PresShell* presShell = presContext->GetPresShell()) {
 | |
|         RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
 | |
|         frameSelection->SetDragState(false);
 | |
|       }
 | |
|     } break;
 | |
|     case eWheelOperationEnd: {
 | |
|       MOZ_ASSERT(aEvent->IsTrusted());
 | |
|       ScrollbarsForWheel::MayInactivate();
 | |
|       WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
 | |
|       nsIScrollableFrame* scrollTarget =
 | |
|           do_QueryFrame(ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|               mCurrentTarget, wheelEvent,
 | |
|               COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR));
 | |
|       // If the wheel event was handled by APZ, APZ will perform the scroll
 | |
|       // snap.
 | |
|       if (scrollTarget && !WheelTransaction::HandledByApz()) {
 | |
|         scrollTarget->ScrollSnap();
 | |
|       }
 | |
|     } break;
 | |
|     case eWheel:
 | |
|     case eWheelOperationStart: {
 | |
|       MOZ_ASSERT(aEvent->IsTrusted());
 | |
| 
 | |
|       if (*aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|         ScrollbarsForWheel::Inactivate();
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
 | |
|       MOZ_ASSERT(wheelEvent);
 | |
| 
 | |
|       // When APZ is enabled, the actual scroll animation might be handled by
 | |
|       // the compositor.
 | |
|       WheelPrefs::Action action =
 | |
|           wheelEvent->mFlags.mHandledByAPZ
 | |
|               ? WheelPrefs::ACTION_NONE
 | |
|               : WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
 | |
| 
 | |
|       WheelDeltaAdjustmentStrategy strategy =
 | |
|           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();
 | |
|       }
 | |
| 
 | |
|       // Since ComputeScrollTargetAndMayAdjustWheelEvent() may adjust the delta
 | |
|       // if the event is auto-dir. So we use |ESMAutoDirWheelDeltaRestorer|
 | |
|       // here.
 | |
|       // An instance of |ESMAutoDirWheelDeltaRestorer| is used to monitor
 | |
|       // 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.
 | |
|       ESMAutoDirWheelDeltaRestorer restorer(*wheelEvent);
 | |
|       nsIFrame* frameToScroll = ComputeScrollTargetAndMayAdjustWheelEvent(
 | |
|           mCurrentTarget, wheelEvent,
 | |
|           COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR);
 | |
| 
 | |
|       switch (action) {
 | |
|         case WheelPrefs::ACTION_SCROLL:
 | |
|         case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL: {
 | |
|           // For scrolling of default action, we should honor the mouse wheel
 | |
|           // transaction.
 | |
| 
 | |
|           ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget,
 | |
|                                                   wheelEvent);
 | |
| 
 | |
|           if (aEvent->mMessage != eWheel ||
 | |
|               (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|           nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll);
 | |
|           ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget);
 | |
| 
 | |
|           nsIFrame* rootScrollFrame =
 | |
|               !mCurrentTarget
 | |
|                   ? nullptr
 | |
|                   : mCurrentTarget->PresShell()->GetRootScrollFrame();
 | |
|           nsIScrollableFrame* rootScrollableFrame = nullptr;
 | |
|           if (rootScrollFrame) {
 | |
|             rootScrollableFrame = do_QueryFrame(rootScrollFrame);
 | |
|           }
 | |
|           if (!scrollTarget || scrollTarget == rootScrollableFrame) {
 | |
|             wheelEvent->mViewPortIsOverscrolled = true;
 | |
|           }
 | |
|           wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
 | |
|           wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
 | |
|           WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
 | |
|               wheelEvent);
 | |
|           if (scrollTarget) {
 | |
|             DoScrollText(scrollTarget, wheelEvent);
 | |
|           } else {
 | |
|             WheelTransaction::EndTransaction();
 | |
|             ScrollbarsForWheel::Inactivate();
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         case WheelPrefs::ACTION_HISTORY: {
 | |
|           // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
 | |
|           // the direction is oblique, don't perform history back/forward.
 | |
|           int32_t intDelta = wheelEvent->GetPreferredIntDelta();
 | |
|           if (!intDelta) {
 | |
|             break;
 | |
|           }
 | |
|           DoScrollHistory(intDelta);
 | |
|           break;
 | |
|         }
 | |
|         case WheelPrefs::ACTION_ZOOM: {
 | |
|           // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
 | |
|           // the direction is oblique, don't perform zoom in/out.
 | |
|           int32_t intDelta = wheelEvent->GetPreferredIntDelta();
 | |
|           if (!intDelta) {
 | |
|             break;
 | |
|           }
 | |
|           DoScrollZoom(mCurrentTarget, intDelta);
 | |
|           break;
 | |
|         }
 | |
|         case WheelPrefs::ACTION_NONE:
 | |
|         default:
 | |
|           bool allDeltaOverflown = false;
 | |
|           if (StaticPrefs::dom_event_wheel_event_groups_enabled() &&
 | |
|               (wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0)) {
 | |
|             if (frameToScroll) {
 | |
|               WheelTransaction::WillHandleDefaultAction(
 | |
|                   wheelEvent, frameToScroll, mCurrentTarget);
 | |
|             } else {
 | |
|               WheelTransaction::EndTransaction();
 | |
|             }
 | |
|           }
 | |
|           if (wheelEvent->mFlags.mHandledByAPZ) {
 | |
|             if (wheelEvent->mCanTriggerSwipe) {
 | |
|               // For events that can trigger swipes, APZ needs to know whether
 | |
|               // scrolling is possible in the requested direction. It does this
 | |
|               // by looking at the scroll overflow values on mCanTriggerSwipe
 | |
|               // events after they have been processed.
 | |
|               allDeltaOverflown = !ComputeScrollTarget(
 | |
|                   mCurrentTarget, wheelEvent, COMPUTE_DEFAULT_ACTION_TARGET);
 | |
|             }
 | |
|           } else {
 | |
|             // The event was processed neither by APZ nor by us, so all of the
 | |
|             // delta values must be overflown delta values.
 | |
|             allDeltaOverflown = true;
 | |
|           }
 | |
| 
 | |
|           if (!allDeltaOverflown) {
 | |
|             break;
 | |
|           }
 | |
|           wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
 | |
|           wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
 | |
|           WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
 | |
|               wheelEvent);
 | |
|           wheelEvent->mViewPortIsOverscrolled = true;
 | |
|           break;
 | |
|       }
 | |
|       *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     } break;
 | |
| 
 | |
|     case eGestureNotify: {
 | |
|       if (nsEventStatus_eConsumeNoDefault != *aStatus) {
 | |
|         DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
 | |
|       }
 | |
|     } break;
 | |
| 
 | |
|     case eDragEnter:
 | |
|     case eDragOver: {
 | |
|       NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event");
 | |
| 
 | |
|       // Check if the drag is occurring inside a scrollable area. If so, scroll
 | |
|       // the area when the mouse is near the edges.
 | |
|       if (mCurrentTarget && aEvent->mMessage == eDragOver) {
 | |
|         nsIFrame* checkFrame = mCurrentTarget;
 | |
|         while (checkFrame) {
 | |
|           nsIScrollableFrame* scrollFrame = do_QueryFrame(checkFrame);
 | |
|           // Break out so only the innermost scrollframe is scrolled.
 | |
|           if (scrollFrame && scrollFrame->DragScroll(aEvent)) {
 | |
|             break;
 | |
|           }
 | |
|           checkFrame = checkFrame->GetParent();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
 | |
|       if (!dragSession) break;
 | |
| 
 | |
|       // Reset the flag.
 | |
|       dragSession->SetOnlyChromeDrop(false);
 | |
|       if (mPresContext) {
 | |
|         EnsureDocument(mPresContext);
 | |
|       }
 | |
|       bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
 | |
| 
 | |
|       // the initial dataTransfer is the one from the dragstart event that
 | |
|       // was set on the dragSession when the drag began.
 | |
|       RefPtr<DataTransfer> dataTransfer;
 | |
|       RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
 | |
| 
 | |
|       WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
 | |
| 
 | |
|       // collect any changes to moz cursor settings stored in the event's
 | |
|       // data transfer.
 | |
|       UpdateDragDataTransfer(dragEvent);
 | |
| 
 | |
|       // cancelling a dragenter or dragover event means that a drop should be
 | |
|       // allowed, so update the dropEffect and the canDrop state to indicate
 | |
|       // that a drag is allowed. If the event isn't cancelled, a drop won't be
 | |
|       // allowed. Essentially, to allow a drop somewhere, specify the effects
 | |
|       // using the effectAllowed and dropEffect properties in a dragenter or
 | |
|       // dragover event and cancel the event. To not allow a drop somewhere,
 | |
|       // don't cancel the event or set the effectAllowed or dropEffect to
 | |
|       // "none". This way, if the event is just ignored, no drop will be
 | |
|       // allowed.
 | |
|       uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
 | |
|       uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
 | |
|       if (nsEventStatus_eConsumeNoDefault == *aStatus) {
 | |
|         // If the event has initialized its mDataTransfer, use it.
 | |
|         // Or the event has not been initialized its mDataTransfer, but
 | |
|         // it's set before dispatch because of synthesized, but without
 | |
|         // testing session (e.g., emulating drag from another app), use it
 | |
|         // coming from outside.
 | |
|         // XXX Perhaps, for the latter case, we need new API because we don't
 | |
|         //     have a chance to initialize allowed effects of the session.
 | |
|         if (dragEvent->mDataTransfer) {
 | |
|           // get the dataTransfer and the dropEffect that was set on it
 | |
|           dataTransfer = dragEvent->mDataTransfer;
 | |
|           dropEffect = dataTransfer->DropEffectInt();
 | |
|         } else {
 | |
|           // if dragEvent->mDataTransfer is null, it means that no attempt was
 | |
|           // made to access the dataTransfer during the event, yet the event
 | |
|           // was cancelled. Instead, use the initial data transfer available
 | |
|           // from the drag session. The drop effect would not have been
 | |
|           // initialized (which is done in DragEvent::GetDataTransfer),
 | |
|           // so set it from the drag action. We'll still want to filter it
 | |
|           // based on the effectAllowed below.
 | |
|           dataTransfer = initialDataTransfer;
 | |
| 
 | |
|           dragSession->GetDragAction(&action);
 | |
| 
 | |
|           // filter the drop effect based on the action. Use UNINITIALIZED as
 | |
|           // any effect is allowed.
 | |
|           dropEffect = nsContentUtils::FilterDropEffect(
 | |
|               action, nsIDragService::DRAGDROP_ACTION_UNINITIALIZED);
 | |
|         }
 | |
| 
 | |
|         // At this point, if the dataTransfer is null, it means that the
 | |
|         // drag was originally started by directly calling the drag service.
 | |
|         // Just assume that all effects are allowed.
 | |
|         uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
 | |
|         if (dataTransfer) {
 | |
|           effectAllowed = dataTransfer->EffectAllowedInt();
 | |
|         }
 | |
| 
 | |
|         // set the drag action based on the drop effect and effect allowed.
 | |
|         // The drop effect field on the drag transfer object specifies the
 | |
|         // desired current drop effect. However, it cannot be used if the
 | |
|         // effectAllowed state doesn't include that type of action. If the
 | |
|         // dropEffect is "none", then the action will be 'none' so a drop will
 | |
|         // not be allowed.
 | |
|         if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED ||
 | |
|             dropEffect & effectAllowed)
 | |
|           action = dropEffect;
 | |
| 
 | |
|         if (action == nsIDragService::DRAGDROP_ACTION_NONE)
 | |
|           dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
 | |
| 
 | |
|         // inform the drag session that a drop is allowed on this node.
 | |
|         dragSession->SetDragAction(action);
 | |
|         dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE);
 | |
| 
 | |
|         // For now, do this only for dragover.
 | |
|         // XXXsmaug dragenter needs some more work.
 | |
|         if (aEvent->mMessage == eDragOver && !isChromeDoc) {
 | |
|           // Someone has called preventDefault(), check whether is was on
 | |
|           // content or chrome.
 | |
|           dragSession->SetOnlyChromeDrop(
 | |
|               !dragEvent->mDefaultPreventedOnContent);
 | |
|         }
 | |
|       } else if (aEvent->mMessage == eDragOver && !isChromeDoc) {
 | |
|         // No one called preventDefault(), so handle drop only in chrome.
 | |
|         dragSession->SetOnlyChromeDrop(true);
 | |
|       }
 | |
|       if (ContentChild* child = ContentChild::GetSingleton()) {
 | |
|         child->SendUpdateDropEffect(action, dropEffect);
 | |
|       }
 | |
|       if (aEvent->HasBeenPostedToRemoteProcess()) {
 | |
|         dragSession->SetCanDrop(true);
 | |
|       } else if (initialDataTransfer) {
 | |
|         // Now set the drop effect in the initial dataTransfer. This ensures
 | |
|         // that we can get the desired drop effect in the drop event. For events
 | |
|         // dispatched to content, the content process will take care of setting
 | |
|         // this.
 | |
|         initialDataTransfer->SetDropEffectInt(dropEffect);
 | |
|       }
 | |
|     } break;
 | |
| 
 | |
|     case eDrop: {
 | |
|       if (aEvent->mFlags.mIsSynthesizedForTests) {
 | |
|         if (nsCOMPtr<nsIDragSession> dragSession =
 | |
|                 nsContentUtils::GetDragSession()) {
 | |
|           MOZ_ASSERT(dragSession->IsSynthesizedForTests());
 | |
|           RefPtr<WindowContext> sourceWC;
 | |
|           DebugOnly<nsresult> rvIgnored =
 | |
|               dragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
 | |
|           NS_WARNING_ASSERTION(
 | |
|               NS_SUCCEEDED(rvIgnored),
 | |
|               "nsIDragSession::GetSourceDocument() failed, but ignored");
 | |
|           // If the drag source hasn't been initialized, i.e., dragstart was
 | |
|           // consumed by the test, the test needs to dispatch "dragend" event
 | |
|           // instead of the drag session.  Therefore, it does not make sense
 | |
|           // to set drag end point in such case (you hit assersion if you do
 | |
|           // it).
 | |
|           if (sourceWC) {
 | |
|             CSSIntPoint dropPointInScreen =
 | |
|                 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
 | |
|                     .extract();
 | |
|             dragSession->SetDragEndPointForTests(dropPointInScreen.x,
 | |
|                                                  dropPointInScreen.y);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       sLastDragOverFrame = nullptr;
 | |
|       ClearGlobalActiveContent(this);
 | |
|       break;
 | |
|     }
 | |
|     case eDragExit:
 | |
|       // make sure to fire the enter and exit_synth events after the
 | |
|       // eDragExit event, otherwise we'll clean up too early
 | |
|       GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
 | |
|       if (ContentChild* child = ContentChild::GetSingleton()) {
 | |
|         // SendUpdateDropEffect to prevent nsIDragService from waiting for
 | |
|         // response of forwarded dragexit event.
 | |
|         child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
 | |
|                                     nsIDragService::DRAGDROP_ACTION_NONE);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case eKeyUp:
 | |
|       // If space key is released, we need to inactivate the element which was
 | |
|       // activated by preceding space key down.
 | |
|       // XXX Currently, we don't store the reason of activation.  Therefore,
 | |
|       //     this may cancel what is activated by a mousedown, but it must not
 | |
|       //     cause actual problem in web apps in the wild since it must be
 | |
|       //     rare case that users release space key during a mouse click/drag.
 | |
|       if (aEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
 | |
|         ClearGlobalActiveContent(this);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case eKeyPress: {
 | |
|       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 | |
|       PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
 | |
|     } break;
 | |
| 
 | |
|     case eMouseEnterIntoWidget:
 | |
|       if (mCurrentTarget) {
 | |
|         nsCOMPtr<nsIContent> targetContent;
 | |
|         mCurrentTarget->GetContentForEvent(aEvent,
 | |
|                                            getter_AddRefs(targetContent));
 | |
|         SetContentState(targetContent, ElementState::HOVER);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case eMouseExitFromWidget:
 | |
|       PointerEventHandler::UpdateActivePointerState(aEvent->AsMouseEvent());
 | |
|       break;
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
|     case eMouseActivate:
 | |
|       if (mCurrentTarget) {
 | |
|         nsCOMPtr<nsIContent> targetContent;
 | |
|         mCurrentTarget->GetContentForEvent(aEvent,
 | |
|                                            getter_AddRefs(targetContent));
 | |
|         if (!NodeAllowsClickThrough(targetContent)) {
 | |
|           *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| #endif
 | |
| 
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // Reset target frame to null to avoid mistargeting after reentrant event
 | |
|   mCurrentTarget = nullptr;
 | |
|   mCurrentTargetContent = nullptr;
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| BrowserParent* EventStateManager::GetCrossProcessTarget() {
 | |
|   return IMEStateManager::GetActiveBrowserParent();
 | |
| }
 | |
| 
 | |
| bool EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent) {
 | |
|   // Check to see if there is a focused, editable content in chrome,
 | |
|   // in that case, do not forward IME events to content
 | |
|   Element* focusedElement = GetFocusedElement();
 | |
|   if (focusedElement && focusedElement->IsEditable()) {
 | |
|     return false;
 | |
|   }
 | |
|   return IMEStateManager::GetActiveBrowserParent() != nullptr;
 | |
| }
 | |
| 
 | |
| void EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) {
 | |
|   RefPtr<nsPresContext> presContext = aPresContext;
 | |
|   if (presContext) {
 | |
|     IMEStateManager::OnDestroyPresContext(*presContext);
 | |
|   }
 | |
| 
 | |
|   // Bug 70855: Presentation is going away, possibly for a reframe.
 | |
|   // Reset the hover state so that if we're recreating the presentation,
 | |
|   // we won't have the old hover state still set in the new presentation,
 | |
|   // as if the new presentation is resized, a new element may be hovered.
 | |
|   ResetHoverState();
 | |
| 
 | |
|   mMouseEnterLeaveHelper = nullptr;
 | |
|   mPointersEnterLeaveHelper.Clear();
 | |
|   PointerEventHandler::NotifyDestroyPresContext(presContext);
 | |
| }
 | |
| 
 | |
| void EventStateManager::ResetHoverState() {
 | |
|   if (mHoverContent) {
 | |
|     SetContentState(nullptr, ElementState::HOVER);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::SetPresContext(nsPresContext* aPresContext) {
 | |
|   mPresContext = aPresContext;
 | |
| }
 | |
| 
 | |
| void EventStateManager::ClearFrameRefs(nsIFrame* aFrame) {
 | |
|   if (aFrame && aFrame == mCurrentTarget) {
 | |
|     mCurrentTargetContent = aFrame->GetContent();
 | |
|   }
 | |
| }
 | |
| 
 | |
| struct CursorImage {
 | |
|   gfx::IntPoint mHotspot;
 | |
|   nsCOMPtr<imgIContainer> mContainer;
 | |
|   ImageResolution mResolution;
 | |
|   bool mEarlierCursorLoading = false;
 | |
| };
 | |
| 
 | |
| // Given the event that we're processing, and the computed cursor and hotspot,
 | |
| // determine whether the custom CSS cursor should be blocked (that is, not
 | |
| // honored).
 | |
| //
 | |
| // We will not honor it all of the following are true:
 | |
| //
 | |
| //  * the size of the custom cursor is bigger than layout.cursor.block.max-size.
 | |
| //  * the bounds of the cursor would end up outside of the viewport of the
 | |
| //    top-level content document.
 | |
| //
 | |
| // This is done in order to prevent hijacking the cursor, see bug 1445844 and
 | |
| // co.
 | |
| static bool ShouldBlockCustomCursor(nsPresContext* aPresContext,
 | |
|                                     WidgetEvent* aEvent,
 | |
|                                     const CursorImage& aCursor) {
 | |
|   int32_t width = 0;
 | |
|   int32_t height = 0;
 | |
|   aCursor.mContainer->GetWidth(&width);
 | |
|   aCursor.mContainer->GetHeight(&height);
 | |
|   aCursor.mResolution.ApplyTo(width, height);
 | |
| 
 | |
|   int32_t maxSize = StaticPrefs::layout_cursor_block_max_size();
 | |
| 
 | |
|   if (width <= maxSize && height <= maxSize) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   auto input = DOMIntersectionObserver::ComputeInput(*aPresContext->Document(),
 | |
|                                                      nullptr, nullptr);
 | |
| 
 | |
|   if (!input.mRootFrame) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
 | |
|       aEvent, RelativeTo{input.mRootFrame});
 | |
| 
 | |
|   // The cursor size won't be affected by our full zoom in the parent process,
 | |
|   // so undo that before checking the rect.
 | |
|   float zoom = aPresContext->GetFullZoom();
 | |
| 
 | |
|   // Also adjust for accessibility cursor scaling factor.
 | |
|   zoom /= LookAndFeel::GetFloat(LookAndFeel::FloatID::CursorScale, 1.0f);
 | |
| 
 | |
|   nsSize size(CSSPixel::ToAppUnits(width / zoom),
 | |
|               CSSPixel::ToAppUnits(height / zoom));
 | |
|   nsPoint hotspot(
 | |
|       CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.x / zoom)),
 | |
|       CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.y / zoom)));
 | |
| 
 | |
|   const nsRect cursorRect(point - hotspot, size);
 | |
|   auto output = DOMIntersectionObserver::Intersect(input, cursorRect);
 | |
|   return !output.mIntersectionRect ||
 | |
|          !(*output.mIntersectionRect == cursorRect);
 | |
| }
 | |
| 
 | |
| static gfx::IntPoint ComputeHotspot(imgIContainer* aContainer,
 | |
|                                     const Maybe<gfx::Point>& aHotspot) {
 | |
|   MOZ_ASSERT(aContainer);
 | |
| 
 | |
|   // css3-ui says to use the CSS-specified hotspot if present,
 | |
|   // otherwise use the intrinsic hotspot, otherwise use the top left
 | |
|   // corner.
 | |
|   if (aHotspot) {
 | |
|     int32_t imgWidth, imgHeight;
 | |
|     aContainer->GetWidth(&imgWidth);
 | |
|     aContainer->GetHeight(&imgHeight);
 | |
|     auto hotspot = gfx::IntPoint::Round(*aHotspot);
 | |
|     return {std::max(std::min(hotspot.x.value, imgWidth - 1), 0),
 | |
|             std::max(std::min(hotspot.y.value, imgHeight - 1), 0)};
 | |
|   }
 | |
| 
 | |
|   gfx::IntPoint hotspot;
 | |
|   aContainer->GetHotspotX(&hotspot.x.value);
 | |
|   aContainer->GetHotspotY(&hotspot.y.value);
 | |
|   return hotspot;
 | |
| }
 | |
| 
 | |
| static CursorImage ComputeCustomCursor(nsPresContext* aPresContext,
 | |
|                                        WidgetEvent* aEvent,
 | |
|                                        const nsIFrame& aFrame,
 | |
|                                        const nsIFrame::Cursor& aCursor) {
 | |
|   if (aCursor.mAllowCustomCursor == nsIFrame::AllowCustomCursorImage::No) {
 | |
|     return {};
 | |
|   }
 | |
|   const ComputedStyle& style =
 | |
|       aCursor.mStyle ? *aCursor.mStyle : *aFrame.Style();
 | |
| 
 | |
|   // If we are falling back because any cursor before us is loading, let the
 | |
|   // consumer know.
 | |
|   bool loading = false;
 | |
|   for (const auto& image : style.StyleUI()->Cursor().images.AsSpan()) {
 | |
|     MOZ_ASSERT(image.image.IsImageRequestType(),
 | |
|                "Cursor image should only parse url() types");
 | |
|     uint32_t status;
 | |
|     imgRequestProxy* req = image.image.GetImageRequest();
 | |
|     if (!req || NS_FAILED(req->GetImageStatus(&status))) {
 | |
|       continue;
 | |
|     }
 | |
|     if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
 | |
|       loading = true;
 | |
|       continue;
 | |
|     }
 | |
|     if (status & imgIRequest::STATUS_ERROR) {
 | |
|       continue;
 | |
|     }
 | |
|     nsCOMPtr<imgIContainer> container;
 | |
|     req->GetImage(getter_AddRefs(container));
 | |
|     if (!container) {
 | |
|       continue;
 | |
|     }
 | |
|     StyleImageOrientation orientation =
 | |
|         aFrame.StyleVisibility()->UsedImageOrientation(req);
 | |
|     container = nsLayoutUtils::OrientImage(container, orientation);
 | |
|     Maybe<gfx::Point> specifiedHotspot =
 | |
|         image.has_hotspot ? Some(gfx::Point{image.hotspot_x, image.hotspot_y})
 | |
|                           : Nothing();
 | |
|     gfx::IntPoint hotspot = ComputeHotspot(container, specifiedHotspot);
 | |
|     CursorImage result{hotspot, std::move(container),
 | |
|                        image.image.GetResolution(style), loading};
 | |
|     if (ShouldBlockCustomCursor(aPresContext, aEvent, result)) {
 | |
|       continue;
 | |
|     }
 | |
|     // This is the one we want!
 | |
|     return result;
 | |
|   }
 | |
|   return {{}, nullptr, {}, loading};
 | |
| }
 | |
| 
 | |
| void EventStateManager::UpdateCursor(nsPresContext* aPresContext,
 | |
|                                      WidgetMouseEvent* aEvent,
 | |
|                                      nsIFrame* aTargetFrame,
 | |
|                                      nsEventStatus* aStatus) {
 | |
|   if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto cursor = StyleCursorKind::Default;
 | |
|   nsCOMPtr<imgIContainer> container;
 | |
|   ImageResolution resolution;
 | |
|   Maybe<gfx::IntPoint> hotspot;
 | |
| 
 | |
|   if (mHidingCursorWhileTyping && aEvent->IsReal()) {
 | |
|     // Any non-synthetic mouse event makes us show the cursor again.
 | |
|     mHidingCursorWhileTyping = false;
 | |
|   }
 | |
| 
 | |
|   if (mHidingCursorWhileTyping) {
 | |
|     cursor = StyleCursorKind::None;
 | |
|   } else if (mLockCursor != kInvalidCursorKind) {
 | |
|     // If cursor is locked just use the locked one
 | |
|     cursor = mLockCursor;
 | |
|   } else if (aTargetFrame) {
 | |
|     // If not locked, look for correct cursor
 | |
|     nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
 | |
|         aEvent, RelativeTo{aTargetFrame});
 | |
|     const nsIFrame::Cursor framecursor = aTargetFrame->GetCursor(pt);
 | |
|     const CursorImage customCursor =
 | |
|         ComputeCustomCursor(aPresContext, aEvent, *aTargetFrame, framecursor);
 | |
| 
 | |
|     // If the current cursor is from the same frame, and it is now
 | |
|     // loading some new image for the cursor, we should wait for a
 | |
|     // while rather than taking its fallback cursor directly.
 | |
|     if (customCursor.mEarlierCursorLoading &&
 | |
|         gLastCursorSourceFrame == aTargetFrame &&
 | |
|         TimeStamp::NowLoRes() - gLastCursorUpdateTime <
 | |
|             TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
 | |
|       return;
 | |
|     }
 | |
|     cursor = framecursor.mCursor;
 | |
|     container = std::move(customCursor.mContainer);
 | |
|     resolution = customCursor.mResolution;
 | |
|     hotspot = Some(customCursor.mHotspot);
 | |
|   }
 | |
| 
 | |
|   if (aTargetFrame) {
 | |
|     if (cursor == StyleCursorKind::Pointer && IsSelectingLink(aTargetFrame)) {
 | |
|       cursor = aTargetFrame->GetWritingMode().IsVertical()
 | |
|                    ? StyleCursorKind::VerticalText
 | |
|                    : StyleCursorKind::Text;
 | |
|     }
 | |
|     SetCursor(cursor, container, resolution, hotspot,
 | |
|               aTargetFrame->GetNearestWidget(), false);
 | |
|     gLastCursorSourceFrame = aTargetFrame;
 | |
|     gLastCursorUpdateTime = TimeStamp::NowLoRes();
 | |
|   }
 | |
| 
 | |
|   if (mLockCursor != kInvalidCursorKind || StyleCursorKind::Auto != cursor) {
 | |
|     *aStatus = nsEventStatus_eConsumeDoDefault;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame) {
 | |
|   if (!aTargetFrame) {
 | |
|     return;
 | |
|   }
 | |
|   nsIWidget* aWidget = aTargetFrame->GetNearestWidget();
 | |
|   if (!aWidget) {
 | |
|     return;
 | |
|   }
 | |
|   aWidget->ClearCachedCursor();
 | |
| }
 | |
| 
 | |
| void EventStateManager::StartHidingCursorWhileTyping(nsIWidget* aWidget) {
 | |
|   if (mHidingCursorWhileTyping || sCursorSettingManager != this) {
 | |
|     return;
 | |
|   }
 | |
|   mHidingCursorWhileTyping = true;
 | |
|   SetCursor(StyleCursorKind::None, nullptr, {}, {}, aWidget, false);
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
 | |
|                                       imgIContainer* aContainer,
 | |
|                                       const ImageResolution& aResolution,
 | |
|                                       const Maybe<gfx::IntPoint>& aHotspot,
 | |
|                                       nsIWidget* aWidget, bool aLockCursor) {
 | |
|   EnsureDocument(mPresContext);
 | |
|   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 | |
|   sCursorSettingManager = this;
 | |
| 
 | |
|   NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
 | |
|   if (aLockCursor) {
 | |
|     if (StyleCursorKind::Auto != aCursor) {
 | |
|       mLockCursor = aCursor;
 | |
|     } else {
 | |
|       // If cursor style is set to auto we unlock the cursor again.
 | |
|       mLockCursor = kInvalidCursorKind;
 | |
|     }
 | |
|   }
 | |
|   nsCursor c;
 | |
|   switch (aCursor) {
 | |
|     case StyleCursorKind::Auto:
 | |
|     case StyleCursorKind::Default:
 | |
|       c = eCursor_standard;
 | |
|       break;
 | |
|     case StyleCursorKind::Pointer:
 | |
|       c = eCursor_hyperlink;
 | |
|       break;
 | |
|     case StyleCursorKind::Crosshair:
 | |
|       c = eCursor_crosshair;
 | |
|       break;
 | |
|     case StyleCursorKind::Move:
 | |
|       c = eCursor_move;
 | |
|       break;
 | |
|     case StyleCursorKind::Text:
 | |
|       c = eCursor_select;
 | |
|       break;
 | |
|     case StyleCursorKind::Wait:
 | |
|       c = eCursor_wait;
 | |
|       break;
 | |
|     case StyleCursorKind::Help:
 | |
|       c = eCursor_help;
 | |
|       break;
 | |
|     case StyleCursorKind::NResize:
 | |
|       c = eCursor_n_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::SResize:
 | |
|       c = eCursor_s_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::WResize:
 | |
|       c = eCursor_w_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::EResize:
 | |
|       c = eCursor_e_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::NwResize:
 | |
|       c = eCursor_nw_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::SeResize:
 | |
|       c = eCursor_se_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::NeResize:
 | |
|       c = eCursor_ne_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::SwResize:
 | |
|       c = eCursor_sw_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::Copy:  // CSS3
 | |
|       c = eCursor_copy;
 | |
|       break;
 | |
|     case StyleCursorKind::Alias:
 | |
|       c = eCursor_alias;
 | |
|       break;
 | |
|     case StyleCursorKind::ContextMenu:
 | |
|       c = eCursor_context_menu;
 | |
|       break;
 | |
|     case StyleCursorKind::Cell:
 | |
|       c = eCursor_cell;
 | |
|       break;
 | |
|     case StyleCursorKind::Grab:
 | |
|       c = eCursor_grab;
 | |
|       break;
 | |
|     case StyleCursorKind::Grabbing:
 | |
|       c = eCursor_grabbing;
 | |
|       break;
 | |
|     case StyleCursorKind::Progress:
 | |
|       c = eCursor_spinning;
 | |
|       break;
 | |
|     case StyleCursorKind::ZoomIn:
 | |
|       c = eCursor_zoom_in;
 | |
|       break;
 | |
|     case StyleCursorKind::ZoomOut:
 | |
|       c = eCursor_zoom_out;
 | |
|       break;
 | |
|     case StyleCursorKind::NotAllowed:
 | |
|       c = eCursor_not_allowed;
 | |
|       break;
 | |
|     case StyleCursorKind::ColResize:
 | |
|       c = eCursor_col_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::RowResize:
 | |
|       c = eCursor_row_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::NoDrop:
 | |
|       c = eCursor_no_drop;
 | |
|       break;
 | |
|     case StyleCursorKind::VerticalText:
 | |
|       c = eCursor_vertical_text;
 | |
|       break;
 | |
|     case StyleCursorKind::AllScroll:
 | |
|       c = eCursor_all_scroll;
 | |
|       break;
 | |
|     case StyleCursorKind::NeswResize:
 | |
|       c = eCursor_nesw_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::NwseResize:
 | |
|       c = eCursor_nwse_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::NsResize:
 | |
|       c = eCursor_ns_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::EwResize:
 | |
|       c = eCursor_ew_resize;
 | |
|       break;
 | |
|     case StyleCursorKind::None:
 | |
|       c = eCursor_none;
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("Unknown cursor kind");
 | |
|       c = eCursor_standard;
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   uint32_t x = aHotspot ? aHotspot->x.value : 0;
 | |
|   uint32_t y = aHotspot ? aHotspot->y.value : 0;
 | |
|   aWidget->SetCursor(nsIWidget::Cursor{c, aContainer, x, y, aResolution});
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool EventStateManager::CursorSettingManagerHasLockedCursor() {
 | |
|   return sCursorSettingManager &&
 | |
|          sCursorSettingManager->mLockCursor != kInvalidCursorKind;
 | |
| }
 | |
| 
 | |
| class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback {
 | |
|  public:
 | |
|   explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
 | |
| 
 | |
|   MOZ_CAN_RUN_SCRIPT
 | |
|   void HandleEvent(EventChainPostVisitor& aVisitor) override {
 | |
|     if (aVisitor.mPresContext) {
 | |
|       nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget);
 | |
|       if (frame) {
 | |
|         frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
 | |
|                            &aVisitor.mEventStatus);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> mTarget;
 | |
| };
 | |
| 
 | |
| static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
 | |
|     WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
 | |
|     EventTarget* aRelatedTarget) {
 | |
|   // This method does not support creating a mouse/pointer button change event
 | |
|   // because of no data about the changing state.
 | |
|   MOZ_ASSERT(aMessage != eMouseDown);
 | |
|   MOZ_ASSERT(aMessage != eMouseUp);
 | |
|   MOZ_ASSERT(aMessage != ePointerDown);
 | |
|   MOZ_ASSERT(aMessage != ePointerUp);
 | |
|   // This method is currently designed to create the following events.
 | |
|   MOZ_ASSERT(aMessage == eMouseOver || aMessage == eMouseEnter ||
 | |
|              aMessage == eMouseOut || aMessage == eMouseLeave ||
 | |
|              aMessage == ePointerOver || aMessage == ePointerEnter ||
 | |
|              aMessage == ePointerOut || aMessage == ePointerLeave ||
 | |
|              aMessage == eMouseEnterIntoWidget ||
 | |
|              aMessage == eMouseExitFromWidget);
 | |
| 
 | |
|   WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
 | |
|   UniquePtr<WidgetMouseEvent> newEvent;
 | |
|   if (sourcePointer) {
 | |
|     AUTO_PROFILER_LABEL("CreateMouseOrPointerWidgetEvent", OTHER);
 | |
| 
 | |
|     WidgetPointerEvent* newPointerEvent = new WidgetPointerEvent(
 | |
|         aMouseEvent->IsTrusted(), aMessage, aMouseEvent->mWidget);
 | |
|     newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary;
 | |
|     newPointerEvent->mWidth = sourcePointer->mWidth;
 | |
|     newPointerEvent->mHeight = sourcePointer->mHeight;
 | |
|     newPointerEvent->mInputSource = sourcePointer->mInputSource;
 | |
| 
 | |
|     newEvent = WrapUnique(newPointerEvent);
 | |
|   } else {
 | |
|     newEvent = MakeUnique<WidgetMouseEvent>(aMouseEvent->IsTrusted(), aMessage,
 | |
|                                             aMouseEvent->mWidget,
 | |
|                                             WidgetMouseEvent::eReal);
 | |
|   }
 | |
| 
 | |
|   // Inherit whether the event is synthesized by the test API or not.
 | |
|   // Then, when the event is synthesized by a test API and handled in a remote
 | |
|   // process, it won't be ignored.  See PresShell::HandleEvent().
 | |
|   newEvent->mFlags.mIsSynthesizedForTests =
 | |
|       aMouseEvent->mFlags.mIsSynthesizedForTests;
 | |
| 
 | |
|   newEvent->mRelatedTarget = aRelatedTarget;
 | |
|   newEvent->mRefPoint = aMouseEvent->mRefPoint;
 | |
|   newEvent->mModifiers = aMouseEvent->mModifiers;
 | |
|   if (!aMouseEvent->mFlags.mDispatchedAtLeastOnce &&
 | |
|       aMouseEvent->InputSourceSupportsHover()) {
 | |
|     // If we synthesize a pointer event or a mouse event from another event
 | |
|     // which changes a button state whose input soucre supports hover state and
 | |
|     // the source event has not been dispatched yet, we should set to the button
 | |
|     // state of the synthesizing event to previous one.
 | |
|     // Note that we don't need to do this if the input source does not support
 | |
|     // hover state because a WPT check the behavior (see below) and the other
 | |
|     // browsers pass the test even though this is inconsistent behavior.
 | |
|     newEvent->mButton =
 | |
|         sourcePointer ? MouseButton::eNotPressed : MouseButton::ePrimary;
 | |
|     if (aMouseEvent->IsPressingButton()) {
 | |
|       // If the source event has not been dispatched into the DOM yet, we
 | |
|       // need to remove the flag which is being pressed.
 | |
|       newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
 | |
|           aMouseEvent->mButtons &
 | |
|           ~MouseButtonsFlagToChange(
 | |
|               static_cast<MouseButton>(aMouseEvent->mButton)));
 | |
|     } else if (aMouseEvent->IsReleasingButton()) {
 | |
|       // If the source event has not been dispatched into the DOM yet, we
 | |
|       // need to add the flag which is being released.
 | |
|       newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
 | |
|           aMouseEvent->mButtons |
 | |
|           MouseButtonsFlagToChange(
 | |
|               static_cast<MouseButton>(aMouseEvent->mButton)));
 | |
|     } else {
 | |
|       // The source event does not change the buttons state so that we can
 | |
|       // set mButtons value as-is.
 | |
|       newEvent->mButtons = aMouseEvent->mButtons;
 | |
|     }
 | |
|     // Adjust pressure if it does not matches with mButtons.
 | |
|     // FIXME: We may use wrong pressure value if the source event has not been
 | |
|     // dispatched into the DOM yet.  However, fixing this requires to store the
 | |
|     // last pressure value somewhere.
 | |
|     if (newEvent->mButtons && aMouseEvent->mPressure == 0) {
 | |
|       newEvent->mPressure = 0.5f;
 | |
|     } else if (!newEvent->mButtons && aMouseEvent->mPressure != 0) {
 | |
|       newEvent->mPressure = 0;
 | |
|     } else {
 | |
|       newEvent->mPressure = aMouseEvent->mPressure;
 | |
|     }
 | |
|   } else {
 | |
|     // If the event has already been dispatched into the tree, web apps has
 | |
|     // already handled the button state change, so the button state of the
 | |
|     // source event has already synced.
 | |
|     // If the input source does not have hover state, we don't need to modify
 | |
|     // the state because the other browsers behave so and tested by
 | |
|     // pointerevent_attributes_nohover_pointers.html even though this is
 | |
|     // different expectation from
 | |
|     // pointerevent_attributes_hoverable_pointers.html, but the other browsers
 | |
|     // pass both of them.
 | |
|     newEvent->mButton = aMouseEvent->mButton;
 | |
|     newEvent->mButtons = aMouseEvent->mButtons;
 | |
|     newEvent->mPressure = aMouseEvent->mPressure;
 | |
|   }
 | |
| 
 | |
|   newEvent->mInputSource = aMouseEvent->mInputSource;
 | |
|   newEvent->pointerId = aMouseEvent->pointerId;
 | |
| 
 | |
|   return newEvent;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIWidget> EventStateManager::DispatchMouseOrPointerEvent(
 | |
|     WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
 | |
|     nsIContent* aTargetContent, nsIContent* aRelatedContent) {
 | |
|   // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
 | |
|   // "[When the mouse is locked on an element...e]vents that require the concept
 | |
|   // of a mouse cursor must not be dispatched (for example: mouseover,
 | |
|   // mouseout).
 | |
|   if (PointerLockManager::IsLocked() &&
 | |
|       (aMessage == eMouseLeave || aMessage == eMouseEnter ||
 | |
|        aMessage == eMouseOver || aMessage == eMouseOut)) {
 | |
|     mCurrentTargetContent = nullptr;
 | |
|     nsCOMPtr<Element> pointerLockedElement =
 | |
|         PointerLockManager::GetLockedElement();
 | |
|     if (!pointerLockedElement) {
 | |
|       NS_WARNING("Should have pointer locked element, but didn't.");
 | |
|       return nullptr;
 | |
|     }
 | |
|     nsIFrame* const pointerLockedFrame =
 | |
|         mPresContext->GetPrimaryFrameFor(pointerLockedElement);
 | |
|     if (NS_WARN_IF(!pointerLockedFrame)) {
 | |
|       return nullptr;
 | |
|     }
 | |
|     return do_AddRef(pointerLockedFrame->GetNearestWidget());
 | |
|   }
 | |
| 
 | |
|   mCurrentTargetContent = nullptr;
 | |
| 
 | |
|   if (!aTargetContent) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Store the widget before dispatching the event because some event listeners
 | |
|   // of the dispatching event may cause reframe the target or remove the target
 | |
|   // from the tree.
 | |
|   nsCOMPtr<nsIWidget> targetWidget;
 | |
|   if (nsIFrame* const targetFrame =
 | |
|           mPresContext->GetPrimaryFrameFor(aTargetContent)) {
 | |
|     targetWidget = targetFrame->GetNearestWidget();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> targetContent = aTargetContent;
 | |
|   nsCOMPtr<nsIContent> relatedContent = aRelatedContent;
 | |
| 
 | |
|   UniquePtr<WidgetMouseEvent> dispatchEvent =
 | |
|       CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, relatedContent);
 | |
| 
 | |
|   AutoWeakFrame previousTarget = mCurrentTarget;
 | |
|   mCurrentTargetContent = targetContent;
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   ESMEventCB callback(targetContent);
 | |
|   RefPtr<nsPresContext> presContext = mPresContext;
 | |
|   EventDispatcher::Dispatch(targetContent, presContext, dispatchEvent.get(),
 | |
|                             nullptr, &status, &callback);
 | |
| 
 | |
|   if (mPresContext) {
 | |
|     // If we are entering/leaving remote content, dispatch a mouse enter/exit
 | |
|     // event to the remote frame.
 | |
|     if (IsTopLevelRemoteTarget(targetContent)) {
 | |
|       if (aMessage == eMouseOut) {
 | |
|         // For remote content, send a puppet widget mouse exit event.
 | |
|         UniquePtr<WidgetMouseEvent> remoteEvent =
 | |
|             CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
 | |
|                                             relatedContent);
 | |
|         remoteEvent->mExitFrom = Some(WidgetMouseEvent::ePuppet);
 | |
| 
 | |
|         // mCurrentTarget is set to the new target, so we must reset it to the
 | |
|         // old target and then dispatch a cross-process event. (mCurrentTarget
 | |
|         // will be set back below.) HandleCrossProcessEvent will query for the
 | |
|         // proper target via GetEventTarget which will return mCurrentTarget.
 | |
|         mCurrentTarget = mPresContext->GetPrimaryFrameFor(targetContent);
 | |
|         HandleCrossProcessEvent(remoteEvent.get(), &status);
 | |
|       } else if (aMessage == eMouseOver) {
 | |
|         UniquePtr<WidgetMouseEvent> remoteEvent =
 | |
|             CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget,
 | |
|                                             relatedContent);
 | |
|         HandleCrossProcessEvent(remoteEvent.get(), &status);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mCurrentTargetContent = nullptr;
 | |
|   mCurrentTarget = previousTarget;
 | |
| 
 | |
|   return targetWidget.forget();
 | |
| }
 | |
| 
 | |
| static nsIContent* FindCommonAncestor(nsIContent* aNode1, nsIContent* aNode2) {
 | |
|   if (!aNode1 || !aNode2) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return nsContentUtils::GetCommonFlattenedTreeAncestor(aNode1, aNode2);
 | |
| }
 | |
| 
 | |
| class EnterLeaveDispatcher {
 | |
|  public:
 | |
|   EnterLeaveDispatcher(EventStateManager* aESM, nsIContent* aTarget,
 | |
|                        nsIContent* aRelatedTarget,
 | |
|                        WidgetMouseEvent* aMouseEvent,
 | |
|                        EventMessage aEventMessage)
 | |
|       : mESM(aESM), mMouseEvent(aMouseEvent), mEventMessage(aEventMessage) {
 | |
|     nsPIDOMWindowInner* win =
 | |
|         aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr;
 | |
|     if (aMouseEvent->AsPointerEvent()
 | |
|             ? win && win->HasPointerEnterLeaveEventListeners()
 | |
|             : win && win->HasMouseEnterLeaveEventListeners()) {
 | |
|       mRelatedTarget =
 | |
|           aRelatedTarget ? aRelatedTarget->FindFirstNonChromeOnlyAccessContent()
 | |
|                          : nullptr;
 | |
|       nsINode* commonParent = FindCommonAncestor(aTarget, aRelatedTarget);
 | |
|       nsIContent* current = aTarget;
 | |
|       // Note, it is ok if commonParent is null!
 | |
|       while (current && current != commonParent) {
 | |
|         if (!current->ChromeOnlyAccess()) {
 | |
|           mTargets.AppendObject(current);
 | |
|         }
 | |
|         // mouseenter/leave is fired only on elements.
 | |
|         current = current->GetFlattenedTreeParent();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
 | |
|   MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch() {
 | |
|     if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
 | |
|       for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
 | |
|         nsCOMPtr<nsIWidget> widget = mESM->DispatchMouseOrPointerEvent(
 | |
|             mMouseEvent, mEventMessage, MOZ_KnownLive(mTargets[i]),
 | |
|             mRelatedTarget);
 | |
|       }
 | |
|     } else {
 | |
|       for (int32_t i = 0; i < mTargets.Count(); ++i) {
 | |
|         nsCOMPtr<nsIWidget> widget = mESM->DispatchMouseOrPointerEvent(
 | |
|             mMouseEvent, mEventMessage, MOZ_KnownLive(mTargets[i]),
 | |
|             mRelatedTarget);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Nothing overwrites anything after constructor. Please remove MOZ_KnownLive
 | |
|   // and MOZ_KNOWN_LIVE if anything marked as such becomes mutable.
 | |
|   const RefPtr<EventStateManager> mESM;
 | |
|   nsCOMArray<nsIContent> mTargets;
 | |
|   MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRelatedTarget;
 | |
|   WidgetMouseEvent* mMouseEvent;
 | |
|   EventMessage mEventMessage;
 | |
| };
 | |
| 
 | |
| void EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
 | |
|                                        nsIContent* aMovingInto) {
 | |
|   const bool isPointer = aMouseEvent->mClass == ePointerEventClass;
 | |
|   LogModule* const logModule =
 | |
|       isPointer ? sPointerBoundaryLog : sMouseBoundaryLog;
 | |
| 
 | |
|   RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
 | |
| 
 | |
|   // If there is no deepest "leave" event target, that means the last "over"
 | |
|   // target has already been removed from the tree.  Therefore, checking only
 | |
|   // the "leave" event target is enough.
 | |
|   if (!wrapper || !wrapper->GetDeepestLeaveEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
|   // Before firing "out" and/or "leave" events, check for recursion
 | |
|   if (wrapper->IsDispatchingOutEventOnLastOverEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("NotifyMouseOut: the source event is %s (IsReal()=%s)",
 | |
|            ToChar(aMouseEvent->mMessage),
 | |
|            aMouseEvent->IsReal() ? "true" : "false"));
 | |
| 
 | |
|   // XXX If a content node is a container of remove content, it should be
 | |
|   // replaced with them and its children should not be visible.  Therefore,
 | |
|   // if the deepest "enter" target is not the last "over" target, i.e., the
 | |
|   // last "over" target has been removed from the DOM tree, it means that the
 | |
|   // child/descendant was not replaced by remote content.  So,
 | |
|   // wrapper->GetOutEventTaget() may be enough here.
 | |
|   if (RefPtr<nsFrameLoaderOwner> flo =
 | |
|           do_QueryObject(wrapper->GetDeepestLeaveEventTarget())) {
 | |
|     if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
 | |
|       if (nsIDocShell* docshell = bc->GetDocShell()) {
 | |
|         if (RefPtr<nsPresContext> presContext = docshell->GetPresContext()) {
 | |
|           EventStateManager* kidESM = presContext->EventStateManager();
 | |
|           // Not moving into any element in this subdocument
 | |
|           MOZ_LOG(logModule, LogLevel::Info,
 | |
|                   ("Notifying child EventStateManager (%p) of \"out\" "
 | |
|                    "event...",
 | |
|                    kidESM));
 | |
|           kidESM->NotifyMouseOut(aMouseEvent, nullptr);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   // That could have caused DOM events which could wreak havoc. Reverify
 | |
|   // things and be careful.
 | |
|   if (!wrapper->GetDeepestLeaveEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   wrapper->WillDispatchOutAndOrLeaveEvent();
 | |
| 
 | |
|   // Don't touch hover state if aMovingInto is non-null.  Caller will update
 | |
|   // hover state itself, and we have optimizations for hover switching between
 | |
|   // two nearby elements both deep in the DOM tree that would be defeated by
 | |
|   // switching the hover state to null here.
 | |
|   if (!aMovingInto && !isPointer) {
 | |
|     // Unset :hover
 | |
|     SetContentState(nullptr, ElementState::HOVER);
 | |
|   }
 | |
| 
 | |
|   EnterLeaveDispatcher leaveDispatcher(
 | |
|       this, wrapper->GetDeepestLeaveEventTarget(), aMovingInto, aMouseEvent,
 | |
|       isPointer ? ePointerLeave : eMouseLeave);
 | |
| 
 | |
|   // "out" events hould be fired only when the deepest "leave" event target
 | |
|   // is the last "over" event target.
 | |
|   if (nsCOMPtr<nsIContent> outEventTarget = wrapper->GetOutEventTarget()) {
 | |
|     MOZ_LOG(logModule, LogLevel::Info,
 | |
|             ("Dispatching %s event to %s (%p)",
 | |
|              isPointer ? "ePointerOut" : "eMouseOut",
 | |
|              outEventTarget ? ToString(*outEventTarget).c_str() : "nullptr",
 | |
|              outEventTarget.get()));
 | |
|     nsCOMPtr<nsIWidget> widget = DispatchMouseOrPointerEvent(
 | |
|         aMouseEvent, isPointer ? ePointerOut : eMouseOut, outEventTarget,
 | |
|         aMovingInto);
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("Dispatching %s event to %s (%p) and its ancestors",
 | |
|            isPointer ? "ePointerLeave" : "eMouseLeave",
 | |
|            wrapper->GetDeepestLeaveEventTarget()
 | |
|                ? ToString(*wrapper->GetDeepestLeaveEventTarget()).c_str()
 | |
|                : "nullptr",
 | |
|            wrapper->GetDeepestLeaveEventTarget()));
 | |
|   leaveDispatcher.Dispatch();
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("Dispatched \"out\" and/or \"leave\" events"));
 | |
|   wrapper->DidDispatchOutAndOrLeaveEvent();
 | |
| }
 | |
| 
 | |
| void EventStateManager::RecomputeMouseEnterStateForRemoteFrame(
 | |
|     Element& aElement) {
 | |
|   if (!mMouseEnterLeaveHelper ||
 | |
|       mMouseEnterLeaveHelper->GetDeepestLeaveEventTarget() != &aElement) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
 | |
|     remote->MouseEnterIntoWidget();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
 | |
|                                         nsIContent* aContent) {
 | |
|   NS_ASSERTION(aContent, "Mouse must be over something");
 | |
| 
 | |
|   const bool isPointer = aMouseEvent->mClass == ePointerEventClass;
 | |
|   LogModule* const logModule =
 | |
|       isPointer ? sPointerBoundaryLog : sMouseBoundaryLog;
 | |
| 
 | |
|   RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
 | |
| 
 | |
|   // If we have next "out" event target and it's the new "over" target, we don't
 | |
|   // need to dispatch "out" nor "enter" event.
 | |
|   if (!wrapper || aContent == wrapper->GetOutEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Before firing "over" and "enter" events, check for recursion
 | |
|   if (wrapper->IsDispatchingOverEventOn(aContent)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("NotifyMouseOver: the source event is %s (IsReal()=%s)",
 | |
|            ToChar(aMouseEvent->mMessage),
 | |
|            aMouseEvent->IsReal() ? "true" : "false"));
 | |
| 
 | |
|   // Check to see if we're a subdocument and if so update the parent
 | |
|   // document's ESM state to indicate that the mouse is over the
 | |
|   // content associated with our subdocument.
 | |
|   EnsureDocument(mPresContext);
 | |
|   if (Document* parentDoc = mDocument->GetInProcessParentDocument()) {
 | |
|     if (nsCOMPtr<nsIContent> docContent = mDocument->GetEmbedderElement()) {
 | |
|       if (PresShell* parentPresShell = parentDoc->GetPresShell()) {
 | |
|         RefPtr<EventStateManager> parentESM =
 | |
|             parentPresShell->GetPresContext()->EventStateManager();
 | |
|         MOZ_LOG(logModule, LogLevel::Info,
 | |
|                 ("Notifying parent EventStateManager (%p) of \"over\" "
 | |
|                  "event...",
 | |
|                  parentESM.get()));
 | |
|         parentESM->NotifyMouseOver(aMouseEvent, docContent);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   // Firing the DOM event in the parent document could cause all kinds
 | |
|   // of havoc.  Reverify and take care.
 | |
|   if (aContent == wrapper->GetOutEventTarget()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Remember the deepest leave event target as the related content for the
 | |
|   // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it,
 | |
|   // bug 298477.
 | |
|   nsCOMPtr<nsIContent> deepestLeaveEventTarget =
 | |
|       wrapper->GetDeepestLeaveEventTarget();
 | |
| 
 | |
|   EnterLeaveDispatcher enterDispatcher(this, aContent, deepestLeaveEventTarget,
 | |
|                                        aMouseEvent,
 | |
|                                        isPointer ? ePointerEnter : eMouseEnter);
 | |
| 
 | |
|   if (!isPointer) {
 | |
|     SetContentState(aContent, ElementState::HOVER);
 | |
|   }
 | |
| 
 | |
|   NotifyMouseOut(aMouseEvent, aContent);
 | |
| 
 | |
|   wrapper->WillDispatchOverAndEnterEvent(aContent);
 | |
| 
 | |
|   // Fire mouseover
 | |
|   // XXX If aContent has already been removed from the DOM tree, what should we
 | |
|   // do? At least, dispatching `mouseover` on it is odd.
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("Dispatching %s event to %s (%p)",
 | |
|            isPointer ? "ePointerOver" : "eMoustOver",
 | |
|            aContent ? ToString(*aContent).c_str() : "nullptr", aContent));
 | |
|   nsCOMPtr<nsIWidget> targetWidget = DispatchMouseOrPointerEvent(
 | |
|       aMouseEvent, isPointer ? ePointerOver : eMouseOver, aContent,
 | |
|       deepestLeaveEventTarget);
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("Dispatching %s event to %s (%p) and its ancestors",
 | |
|            isPointer ? "ePointerEnter" : "eMouseEnter",
 | |
|            aContent ? ToString(*aContent).c_str() : "nullptr", aContent));
 | |
|   enterDispatcher.Dispatch();
 | |
| 
 | |
|   MOZ_LOG(logModule, LogLevel::Info,
 | |
|           ("Dispatched \"over\" and \"enter\" events (the original \"over\" "
 | |
|            "event target was in the document %p, and now in %p)",
 | |
|            aContent->GetComposedDoc(), mDocument.get()));
 | |
|   wrapper->DidDispatchOverAndEnterEvent(
 | |
|       aContent->GetComposedDoc() == mDocument ? aContent : nullptr,
 | |
|       targetWidget);
 | |
| }
 | |
| 
 | |
| // Returns the center point of the window's client area. This is
 | |
| // in widget coordinates, i.e. relative to the widget's top-left
 | |
| // corner, not in screen coordinates, the same units that UIEvent::
 | |
| // refpoint is in. It may not be the exact center of the window if
 | |
| // the platform requires rounding the coordinate.
 | |
| static LayoutDeviceIntPoint GetWindowClientRectCenter(nsIWidget* aWidget) {
 | |
|   NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0));
 | |
| 
 | |
|   LayoutDeviceIntRect rect = aWidget->GetClientBounds();
 | |
|   LayoutDeviceIntPoint point(rect.width / 2, rect.height / 2);
 | |
|   int32_t round = aWidget->RoundsWidgetCoordinatesTo();
 | |
|   point.x = point.x / round * round;
 | |
|   point.y = point.y / round * round;
 | |
|   return point;
 | |
| }
 | |
| 
 | |
| void EventStateManager::GeneratePointerEnterExit(EventMessage aMessage,
 | |
|                                                  WidgetMouseEvent* aEvent) {
 | |
|   WidgetPointerEvent pointerEvent(*aEvent);
 | |
|   pointerEvent.mMessage = aMessage;
 | |
|   GenerateMouseEnterExit(&pointerEvent);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void EventStateManager::UpdateLastRefPointOfMouseEvent(
 | |
|     WidgetMouseEvent* aMouseEvent) {
 | |
|   if (aMouseEvent->mMessage != eMouseMove &&
 | |
|       aMouseEvent->mMessage != ePointerMove) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
 | |
|   // Movement is calculated in UIEvent::GetMovementPoint() as:
 | |
|   //   previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
 | |
|   if (PointerLockManager::IsLocked() && aMouseEvent->mWidget) {
 | |
|     // The pointer is locked. If the pointer is not located at the center of
 | |
|     // the window, dispatch a synthetic mousemove to return the pointer there.
 | |
|     // Doing this between "real" pointer moves gives the impression that the
 | |
|     // (locked) pointer can continue moving and won't stop at the screen
 | |
|     // boundary. We cancel the synthetic event so that we don't end up
 | |
|     // dispatching the centering move event to content.
 | |
|     aMouseEvent->mLastRefPoint =
 | |
|         GetWindowClientRectCenter(aMouseEvent->mWidget);
 | |
| 
 | |
|   } else if (sLastRefPoint == kInvalidRefPoint) {
 | |
|     // We don't have a valid previous mousemove mRefPoint. This is either
 | |
|     // the first move we've encountered, or the mouse has just re-entered
 | |
|     // the application window. We should report (0,0) movement for this
 | |
|     // case, so make the current and previous mRefPoints the same.
 | |
|     aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
 | |
|   } else {
 | |
|     aMouseEvent->mLastRefPoint = sLastRefPoint;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void EventStateManager::ResetPointerToWindowCenterWhilePointerLocked(
 | |
|     WidgetMouseEvent* aMouseEvent) {
 | |
|   MOZ_ASSERT(PointerLockManager::IsLocked());
 | |
|   if ((aMouseEvent->mMessage != eMouseMove &&
 | |
|        aMouseEvent->mMessage != ePointerMove) ||
 | |
|       !aMouseEvent->mWidget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We generate pointermove from mousemove event, so only synthesize native
 | |
|   // mouse move and update sSynthCenteringPoint by mousemove event.
 | |
|   bool updateSynthCenteringPoint = aMouseEvent->mMessage == eMouseMove;
 | |
| 
 | |
|   // The pointer is locked. If the pointer is not located at the center of
 | |
|   // the window, dispatch a synthetic mousemove to return the pointer there.
 | |
|   // Doing this between "real" pointer moves gives the impression that the
 | |
|   // (locked) pointer can continue moving and won't stop at the screen
 | |
|   // boundary. We cancel the synthetic event so that we don't end up
 | |
|   // dispatching the centering move event to content.
 | |
|   LayoutDeviceIntPoint center = GetWindowClientRectCenter(aMouseEvent->mWidget);
 | |
| 
 | |
|   if (aMouseEvent->mRefPoint != center && updateSynthCenteringPoint) {
 | |
|     // Mouse move doesn't finish at the center of the window. Dispatch a
 | |
|     // synthetic native mouse event to move the pointer back to the center
 | |
|     // of the window, to faciliate more movement. But first, record that
 | |
|     // we've dispatched a synthetic mouse movement, so we can cancel it
 | |
|     // in the other branch here.
 | |
|     sSynthCenteringPoint = center;
 | |
|     // XXX Once we fix XXX comments in SetPointerLock about this API, we could
 | |
|     //     restrict that this API works only in the automation mode or in the
 | |
|     //     pointer locked situation.
 | |
|     aMouseEvent->mWidget->SynthesizeNativeMouseMove(
 | |
|         center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
 | |
|   } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
 | |
|     // This is the "synthetic native" event we dispatched to re-center the
 | |
|     // pointer. Cancel it so we don't expose the centering move to content.
 | |
|     aMouseEvent->StopPropagation();
 | |
|     // Clear sSynthCenteringPoint so we don't cancel other events
 | |
|     // targeted at the center.
 | |
|     if (updateSynthCenteringPoint) {
 | |
|       sSynthCenteringPoint = kInvalidRefPoint;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void EventStateManager::UpdateLastPointerPosition(
 | |
|     WidgetMouseEvent* aMouseEvent) {
 | |
|   if (aMouseEvent->mMessage != eMouseMove) {
 | |
|     return;
 | |
|   }
 | |
|   sLastRefPoint = aMouseEvent->mRefPoint;
 | |
| }
 | |
| 
 | |
| void EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) {
 | |
|   EnsureDocument(mPresContext);
 | |
|   if (!mDocument) return;
 | |
| 
 | |
|   // Hold onto old target content through the event and reset after.
 | |
|   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 | |
| 
 | |
|   switch (aMouseEvent->mMessage) {
 | |
|     case eMouseMove:
 | |
|     case ePointerMove:
 | |
|     case ePointerDown:
 | |
|     case ePointerGotCapture: {
 | |
|       // Get the target content target (mousemove target == mouseover target)
 | |
|       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
 | |
|       if (!targetElement) {
 | |
|         // We're always over the document root, even if we're only
 | |
|         // over dead space in a page (whose frame is not associated with
 | |
|         // any content) or in print preview dead space
 | |
|         targetElement = mDocument->GetRootElement();
 | |
|       }
 | |
|       if (targetElement) {
 | |
|         NotifyMouseOver(aMouseEvent, targetElement);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case ePointerUp: {
 | |
|       if (!StaticPrefs::
 | |
|               dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed()) {
 | |
|         // In the legacy mode, we should do nothing if the event has not been
 | |
|         // dispatched yet (i.e., called by PreHandleEvent).
 | |
|         // On the other hand, if the event was dispatched (i.e., called by
 | |
|         // PostEventHandler), we need to dispatch "pointerout" and
 | |
|         // "pointerleave" events because the pointer will be removed
 | |
|         // (invalidated) by the "pointerup" operation.
 | |
|         if (!aMouseEvent->mFlags.mDispatchedAtLeastOnce) {
 | |
|           break;
 | |
|         }
 | |
|         MOZ_ASSERT(!aMouseEvent->InputSourceSupportsHover());
 | |
|         // Get the target content target (pointermove target == pointerover
 | |
|         // target)
 | |
|         nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
 | |
|         if (!targetElement) {
 | |
|           // We're always over the document root, even if we're only
 | |
|           // over dead space in a page (whose frame is not associated with
 | |
|           // any content) or in print preview dead space
 | |
|           targetElement = mDocument->GetRootElement();
 | |
|         }
 | |
|         if (targetElement) {
 | |
|           // XXX It's odd to override the `pointerout` event target with the
 | |
|           // content under the pointer or something because it may have never
 | |
|           // received `pointerover` event.  I think that this was required for
 | |
|           // digitizer which supports hover state (bug 985511).  However, this
 | |
|           // is now not called at ePointerUp if the device supports hover.
 | |
|           RefPtr<OverOutElementsWrapper> helper =
 | |
|               GetWrapperByEventID(aMouseEvent);
 | |
|           if (helper) {
 | |
|             helper->OverrideOverEventTarget(targetElement);
 | |
|           }
 | |
|           NotifyMouseOut(aMouseEvent, nullptr);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (aMouseEvent->mFlags.mDispatchedAtLeastOnce) {
 | |
|         // If we've already dispatched the pointerup event caused by
 | |
|         // non-hoverable input device like touch, we need to synthesize
 | |
|         // pointerout and pointerleave events because the poiner is valid only
 | |
|         // while it's "down".
 | |
|         if (!aMouseEvent->InputSourceSupportsHover()) {
 | |
|           NotifyMouseOut(aMouseEvent, nullptr);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       // If we're going to dispatch the pointerup event and the element under
 | |
|       // the pointer is changed from the previous pointer event dispatching, we
 | |
|       // need to dispatch pointer boundary events.  If the pointing device is
 | |
|       // hoverable, we always need to do it.  Otherwise, an element captures the
 | |
|       // pointer by default.  If so, we don't need the boundary events, but if
 | |
|       // the capture has already been released, e.g., by the capturing element
 | |
|       // is removed, we need to dispatch the pointer boundary event the same
 | |
|       // way as with hoverable pointer.
 | |
|       if (aMouseEvent->InputSourceSupportsHover() ||
 | |
|           !PointerEventHandler::GetPointerCapturingElement(
 | |
|               aMouseEvent->pointerId)) {
 | |
|         nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
 | |
|         if (!targetElement) {
 | |
|           targetElement = mDocument->GetRootElement();
 | |
|         }
 | |
|         if (targetElement) {
 | |
|           NotifyMouseOver(aMouseEvent, targetElement);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case ePointerLeave:
 | |
|     case ePointerCancel:
 | |
|     case eMouseExitFromWidget: {
 | |
|       // This is actually the window mouse exit or pointer leave event. We're
 | |
|       // not moving into any new element.
 | |
| 
 | |
|       RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
 | |
|       if (helper) {
 | |
|         nsCOMPtr<nsIWidget> lastOverWidget = helper->GetLastOverWidget();
 | |
|         if (lastOverWidget &&
 | |
|             nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) !=
 | |
|                 nsContentUtils::GetTopLevelWidget(lastOverWidget)) {
 | |
|           // the Mouse/PointerOut event widget doesn't have same top widget with
 | |
|           // the last over event target, it's a spurious event for the frame for
 | |
|           // the target.
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Reset sLastRefPoint, so that we'll know not to report any
 | |
|       // movement the next time we re-enter the window.
 | |
|       sLastRefPoint = kInvalidRefPoint;
 | |
| 
 | |
|       NotifyMouseOut(aMouseEvent, nullptr);
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // reset mCurretTargetContent to what it was
 | |
|   mCurrentTargetContent = targetBeforeEvent;
 | |
| }
 | |
| 
 | |
| OverOutElementsWrapper* EventStateManager::GetWrapperByEventID(
 | |
|     WidgetMouseEvent* aEvent) {
 | |
|   WidgetPointerEvent* pointer = aEvent->AsPointerEvent();
 | |
|   if (!pointer) {
 | |
|     MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr);
 | |
|     if (!mMouseEnterLeaveHelper) {
 | |
|       mMouseEnterLeaveHelper = new OverOutElementsWrapper(
 | |
|           OverOutElementsWrapper::BoundaryEventType::Mouse);
 | |
|     }
 | |
|     return mMouseEnterLeaveHelper;
 | |
|   }
 | |
|   return mPointersEnterLeaveHelper.GetOrInsertNew(
 | |
|       pointer->pointerId, OverOutElementsWrapper::BoundaryEventType::Pointer);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void EventStateManager::SetPointerLock(nsIWidget* aWidget,
 | |
|                                        nsPresContext* aPresContext) {
 | |
|   // Reset mouse wheel transaction
 | |
|   WheelTransaction::EndTransaction();
 | |
| 
 | |
|   // Deal with DnD events
 | |
|   nsCOMPtr<nsIDragService> dragService =
 | |
|       do_GetService("@mozilla.org/widget/dragservice;1");
 | |
| 
 | |
|   if (PointerLockManager::IsLocked()) {
 | |
|     MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
 | |
|     MOZ_ASSERT(aPresContext, "Locking pointer requires a presContext");
 | |
| 
 | |
|     // Release all pointer capture when a pointer lock is successfully applied
 | |
|     // on an element.
 | |
|     PointerEventHandler::ReleaseAllPointerCapture();
 | |
| 
 | |
|     // Store the last known ref point so we can reposition the pointer after
 | |
|     // unlock.
 | |
|     sPreLockScreenPoint = LayoutDeviceIntPoint::Round(
 | |
|         sLastScreenPoint * aPresContext->CSSToDevPixelScale());
 | |
| 
 | |
|     // Fire a synthetic mouse move to ensure event state is updated. We first
 | |
|     // set the mouse to the center of the window, so that the mouse event
 | |
|     // doesn't report any movement.
 | |
|     // XXX Cannot we do synthesize the native mousemove in the parent process
 | |
|     //     with calling LockNativePointer below?  Then, we could make this API
 | |
|     //     work only in the automation mode.
 | |
|     sLastRefPoint = GetWindowClientRectCenter(aWidget);
 | |
|     aWidget->SynthesizeNativeMouseMove(
 | |
|         sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
 | |
| 
 | |
|     // Suppress DnD
 | |
|     if (dragService) {
 | |
|       dragService->Suppress();
 | |
|     }
 | |
| 
 | |
|     // Activate native pointer lock on platforms where it is required (Wayland)
 | |
|     aWidget->LockNativePointer();
 | |
|   } else {
 | |
|     if (aWidget) {
 | |
|       // Deactivate native pointer lock on platforms where it is required
 | |
|       aWidget->UnlockNativePointer();
 | |
|     }
 | |
| 
 | |
|     // Reset SynthCenteringPoint to invalid so that next time we start
 | |
|     // locking pointer, it has its initial value.
 | |
|     sSynthCenteringPoint = kInvalidRefPoint;
 | |
|     if (aWidget) {
 | |
|       // Unlocking, so return pointer to the original position by firing a
 | |
|       // synthetic mouse event. We first reset sLastRefPoint to its
 | |
|       // pre-pointerlock position, so that the synthetic mouse event reports
 | |
|       // no movement.
 | |
|       sLastRefPoint = sPreLockScreenPoint - aWidget->WidgetToScreenOffset();
 | |
|       // XXX Cannot we do synthesize the native mousemove in the parent process
 | |
|       //     with calling `UnlockNativePointer` above?  Then, we could make this
 | |
|       //     API work only in the automation mode.
 | |
|       aWidget->SynthesizeNativeMouseMove(sPreLockScreenPoint, nullptr);
 | |
|     }
 | |
| 
 | |
|     // Unsuppress DnD
 | |
|     if (dragService) {
 | |
|       dragService->Unsuppress();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
 | |
|                                                   WidgetDragEvent* aDragEvent) {
 | |
|   // Hold onto old target content through the event and reset after.
 | |
|   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 | |
| 
 | |
|   switch (aDragEvent->mMessage) {
 | |
|     case eDragOver: {
 | |
|       // when dragging from one frame to another, events are fired in the
 | |
|       // order: dragexit, dragenter, dragleave
 | |
|       if (sLastDragOverFrame != mCurrentTarget) {
 | |
|         // We'll need the content, too, to check if it changed separately from
 | |
|         // the frames.
 | |
|         nsCOMPtr<nsIContent> lastContent;
 | |
|         nsCOMPtr<nsIContent> targetContent;
 | |
|         mCurrentTarget->GetContentForEvent(aDragEvent,
 | |
|                                            getter_AddRefs(targetContent));
 | |
|         if (targetContent && targetContent->IsText()) {
 | |
|           targetContent = targetContent->GetFlattenedTreeParent();
 | |
|         }
 | |
| 
 | |
|         if (sLastDragOverFrame) {
 | |
|           // The frame has changed but the content may not have. Check before
 | |
|           // dispatching to content
 | |
|           sLastDragOverFrame->GetContentForEvent(aDragEvent,
 | |
|                                                  getter_AddRefs(lastContent));
 | |
|           if (lastContent && lastContent->IsText()) {
 | |
|             lastContent = lastContent->GetFlattenedTreeParent();
 | |
|           }
 | |
| 
 | |
|           RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
 | |
|           FireDragEnterOrExit(presContext, aDragEvent, eDragExit, targetContent,
 | |
|                               lastContent, sLastDragOverFrame);
 | |
|           nsIContent* target = sLastDragOverFrame
 | |
|                                    ? sLastDragOverFrame.GetFrame()->GetContent()
 | |
|                                    : nullptr;
 | |
|           // XXXedgar, look like we need to consider fission OOP iframe, too.
 | |
|           if (IsTopLevelRemoteTarget(target)) {
 | |
|             // Dragging something and moving from web content to chrome only
 | |
|             // fires dragexit and dragleave to xul:browser. We have to forward
 | |
|             // dragexit to sLastDragOverFrame when its content is a remote
 | |
|             // target. We don't forward dragleave since it's generated from
 | |
|             // dragexit.
 | |
|             WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
 | |
|                                         aDragEvent->mWidget);
 | |
|             remoteEvent.AssignDragEventData(*aDragEvent, true);
 | |
|             remoteEvent.mFlags.mIsSynthesizedForTests =
 | |
|                 aDragEvent->mFlags.mIsSynthesizedForTests;
 | |
|             nsEventStatus remoteStatus = nsEventStatus_eIgnore;
 | |
|             HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         AutoWeakFrame currentTraget = mCurrentTarget;
 | |
|         FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, lastContent,
 | |
|                             targetContent, currentTraget);
 | |
| 
 | |
|         if (sLastDragOverFrame) {
 | |
|           RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
 | |
|           FireDragEnterOrExit(presContext, aDragEvent, eDragLeave,
 | |
|                               targetContent, lastContent, sLastDragOverFrame);
 | |
|         }
 | |
| 
 | |
|         sLastDragOverFrame = mCurrentTarget;
 | |
|       }
 | |
|     } break;
 | |
| 
 | |
|     case eDragExit: {
 | |
|       // This is actually the window mouse exit event.
 | |
|       if (sLastDragOverFrame) {
 | |
|         nsCOMPtr<nsIContent> lastContent;
 | |
|         sLastDragOverFrame->GetContentForEvent(aDragEvent,
 | |
|                                                getter_AddRefs(lastContent));
 | |
| 
 | |
|         RefPtr<nsPresContext> lastDragOverFramePresContext =
 | |
|             sLastDragOverFrame->PresContext();
 | |
|         FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent, eDragExit,
 | |
|                             nullptr, lastContent, sLastDragOverFrame);
 | |
|         FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent,
 | |
|                             eDragLeave, nullptr, lastContent,
 | |
|                             sLastDragOverFrame);
 | |
| 
 | |
|         sLastDragOverFrame = nullptr;
 | |
|       }
 | |
|     } break;
 | |
| 
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // reset mCurretTargetContent to what it was
 | |
|   mCurrentTargetContent = targetBeforeEvent;
 | |
| 
 | |
|   // Now flush all pending notifications, for better responsiveness.
 | |
|   FlushLayout(aPresContext);
 | |
| }
 | |
| 
 | |
| void EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
 | |
|                                             WidgetDragEvent* aDragEvent,
 | |
|                                             EventMessage aMessage,
 | |
|                                             nsIContent* aRelatedTarget,
 | |
|                                             nsIContent* aTargetContent,
 | |
|                                             AutoWeakFrame& aTargetFrame) {
 | |
|   MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
 | |
|              aMessage == eDragEnter);
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
 | |
|   event.AssignDragEventData(*aDragEvent, false);
 | |
|   event.mFlags.mIsSynthesizedForTests =
 | |
|       aDragEvent->mFlags.mIsSynthesizedForTests;
 | |
|   event.mRelatedTarget = aRelatedTarget;
 | |
|   if (aMessage == eDragExit && !StaticPrefs::dom_event_dragexit_enabled()) {
 | |
|     event.mFlags.mOnlyChromeDispatch = true;
 | |
|   }
 | |
| 
 | |
|   mCurrentTargetContent = aTargetContent;
 | |
| 
 | |
|   if (aTargetContent != aRelatedTarget) {
 | |
|     // XXX This event should still go somewhere!!
 | |
|     if (aTargetContent) {
 | |
|       EventDispatcher::Dispatch(aTargetContent, aPresContext, &event, nullptr,
 | |
|                                 &status);
 | |
|     }
 | |
| 
 | |
|     // adjust the drag hover if the dragenter event was cancelled or this is a
 | |
|     // drag exit
 | |
|     if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) {
 | |
|       SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr,
 | |
|                       ElementState::DRAGOVER);
 | |
|     }
 | |
| 
 | |
|     // collect any changes to moz cursor settings stored in the event's
 | |
|     // data transfer.
 | |
|     UpdateDragDataTransfer(&event);
 | |
|   }
 | |
| 
 | |
|   // Finally dispatch the event to the frame
 | |
|   if (aTargetFrame) {
 | |
|     aTargetFrame->HandleEvent(aPresContext, &event, &status);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent) {
 | |
|   NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!");
 | |
|   if (!dragEvent->mDataTransfer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
 | |
| 
 | |
|   if (dragSession) {
 | |
|     // the initial dataTransfer is the one from the dragstart event that
 | |
|     // was set on the dragSession when the drag began.
 | |
|     RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
 | |
|     if (initialDataTransfer) {
 | |
|       // retrieve the current moz cursor setting and save it.
 | |
|       nsAutoString mozCursor;
 | |
|       dragEvent->mDataTransfer->GetMozCursor(mozCursor);
 | |
|       initialDataTransfer->SetMozCursor(mozCursor);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
 | |
|                                           nsEventStatus* aStatus,
 | |
|                                           nsIContent* aOverrideClickTarget) {
 | |
|   nsCOMPtr<nsIContent> mouseContent = aOverrideClickTarget;
 | |
|   if (!mouseContent && mCurrentTarget) {
 | |
|     mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
 | |
|   }
 | |
|   if (mouseContent && mouseContent->IsText()) {
 | |
|     nsINode* parent = mouseContent->GetFlattenedTreeParentNode();
 | |
|     if (parent && parent->IsContent()) {
 | |
|       mouseContent = parent->AsContent();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LastMouseDownInfo& mouseDownInfo = GetLastMouseDownInfo(aEvent->mButton);
 | |
|   if (aEvent->mMessage == eMouseDown) {
 | |
|     mouseDownInfo.mLastMouseDownContent =
 | |
|         !aEvent->mClickEventPrevented ? mouseContent : nullptr;
 | |
| 
 | |
|     if (mouseDownInfo.mLastMouseDownContent) {
 | |
|       if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
 | |
|               mouseDownInfo.mLastMouseDownContent)) {
 | |
|         mouseDownInfo.mLastMouseDownInputControlType =
 | |
|             Some(input->ControlType());
 | |
|       } else if (mouseDownInfo.mLastMouseDownContent
 | |
|                      ->IsInNativeAnonymousSubtree()) {
 | |
|         if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
 | |
|                 mouseDownInfo.mLastMouseDownContent
 | |
|                     ->GetFlattenedTreeParent())) {
 | |
|           mouseDownInfo.mLastMouseDownInputControlType =
 | |
|               Some(input->ControlType());
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     aEvent->mClickTarget =
 | |
|         !aEvent->mClickEventPrevented
 | |
|             ? GetCommonAncestorForMouseUp(
 | |
|                   mouseContent, mouseDownInfo.mLastMouseDownContent,
 | |
|                   mouseDownInfo.mLastMouseDownInputControlType)
 | |
|             : nullptr;
 | |
|     if (aEvent->mClickTarget) {
 | |
|       aEvent->mClickCount = mouseDownInfo.mClickCount;
 | |
|       mouseDownInfo.mClickCount = 0;
 | |
|     } else {
 | |
|       aEvent->mClickCount = 0;
 | |
|     }
 | |
|     mouseDownInfo.mLastMouseDownContent = nullptr;
 | |
|     mouseDownInfo.mLastMouseDownInputControlType = Nothing();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool EventStateManager::EventCausesClickEvents(
 | |
|     const WidgetMouseEvent& aMouseEvent) {
 | |
|   if (NS_WARN_IF(aMouseEvent.mMessage != eMouseUp)) {
 | |
|     return false;
 | |
|   }
 | |
|   // If the mouseup event is synthesized event, we don't need to dispatch
 | |
|   // click events.
 | |
|   if (!aMouseEvent.IsReal()) {
 | |
|     return false;
 | |
|   }
 | |
|   // If mouse is still over same element, clickcount will be > 1.
 | |
|   // If it has moved it will be zero, so no click.
 | |
|   if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
 | |
|     return false;
 | |
|   }
 | |
|   // If click event was explicitly prevented, we shouldn't dispatch it.
 | |
|   if (aMouseEvent.mClickEventPrevented) {
 | |
|     return false;
 | |
|   }
 | |
|   // Check that the window isn't disabled before firing a click
 | |
|   // (see bug 366544).
 | |
|   return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::InitAndDispatchClickEvent(
 | |
|     WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
 | |
|     EventMessage aMessage, PresShell* aPresShell, nsIContent* aMouseUpContent,
 | |
|     AutoWeakFrame aCurrentTarget, bool aNoContentDispatch,
 | |
|     nsIContent* aOverrideClickTarget) {
 | |
|   MOZ_ASSERT(aMouseUpEvent);
 | |
|   MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
 | |
|   MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget);
 | |
| 
 | |
|   WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage,
 | |
|                          aMouseUpEvent->mWidget, WidgetMouseEvent::eReal);
 | |
| 
 | |
|   event.mRefPoint = aMouseUpEvent->mRefPoint;
 | |
|   event.mClickCount = aMouseUpEvent->mClickCount;
 | |
|   event.mModifiers = aMouseUpEvent->mModifiers;
 | |
|   event.mButtons = aMouseUpEvent->mButtons;
 | |
|   event.mTimeStamp = aMouseUpEvent->mTimeStamp;
 | |
|   event.mFlags.mOnlyChromeDispatch = aNoContentDispatch;
 | |
|   event.mFlags.mNoContentDispatch = aNoContentDispatch;
 | |
|   event.mButton = aMouseUpEvent->mButton;
 | |
|   event.pointerId = aMouseUpEvent->pointerId;
 | |
|   event.mInputSource = aMouseUpEvent->mInputSource;
 | |
|   nsIContent* target = aMouseUpContent;
 | |
|   nsIFrame* targetFrame = aCurrentTarget;
 | |
|   if (aOverrideClickTarget) {
 | |
|     target = aOverrideClickTarget;
 | |
|     targetFrame = aOverrideClickTarget->GetPrimaryFrame();
 | |
|   }
 | |
| 
 | |
|   if (!target->IsInComposedDoc()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Use local event status for each click event dispatching since it'll be
 | |
|   // cleared by EventStateManager::PreHandleEvent().  Therefore, dispatching
 | |
|   // an event means that previous event status will be ignored.
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   nsresult rv = aPresShell->HandleEventWithTarget(
 | |
|       &event, targetFrame, MOZ_KnownLive(target), &status);
 | |
| 
 | |
|   // Copy mMultipleActionsPrevented flag from a click event to the mouseup
 | |
|   // event only when it's set to true.  It may be set to true if an editor has
 | |
|   // already handled it.  This is important to avoid two or more default
 | |
|   // actions handled here.
 | |
|   aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
 | |
|       event.mFlags.mMultipleActionsPrevented;
 | |
|   // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
 | |
|   // overwrite it.
 | |
|   if (*aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return rv;
 | |
|   }
 | |
|   // If new status is nsEventStatus_eConsumeNoDefault or
 | |
|   // nsEventStatus_eConsumeDoDefault, use it.
 | |
|   if (status == nsEventStatus_eConsumeNoDefault ||
 | |
|       status == nsEventStatus_eConsumeDoDefault) {
 | |
|     *aStatus = status;
 | |
|     return rv;
 | |
|   }
 | |
|   // Otherwise, keep the original status.
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::PostHandleMouseUp(
 | |
|     WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
 | |
|     nsIContent* aOverrideClickTarget) {
 | |
|   MOZ_ASSERT(aMouseUpEvent);
 | |
|   MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
 | |
|   MOZ_ASSERT(aStatus);
 | |
| 
 | |
|   RefPtr<PresShell> presShell = mPresContext->GetPresShell();
 | |
|   if (!presShell) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> clickTarget =
 | |
|       nsIContent::FromEventTargetOrNull(aMouseUpEvent->mClickTarget);
 | |
|   NS_ENSURE_STATE(clickTarget);
 | |
| 
 | |
|   // Fire click events if the event target is still available.
 | |
|   // Note that do not include the eMouseUp event's status since we ignore it
 | |
|   // for compatibility with the other browsers.
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, &status,
 | |
|                                     clickTarget, aOverrideClickTarget);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // Do not do anything if preceding click events are consumed.
 | |
|   // Note that Chromium dispatches "paste" event and actually pates clipboard
 | |
|   // text into focused editor even if the preceding click events are consumed.
 | |
|   // However, this is different from our traditional behavior and does not
 | |
|   // conform to DOM events.  If we need to keep compatibility with Chromium,
 | |
|   // we should change it later.
 | |
|   if (status == nsEventStatus_eConsumeNoDefault) {
 | |
|     *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Handle middle click paste if it's enabled and the mouse button is middle.
 | |
|   if (aMouseUpEvent->mButton != MouseButton::eMiddle ||
 | |
|       !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   DebugOnly<nsresult> rvIgnored =
 | |
|       HandleMiddleClickPaste(presShell, aMouseUpEvent, &status, nullptr);
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | |
|                        "Failed to paste for a middle click");
 | |
| 
 | |
|   // If new status is nsEventStatus_eConsumeNoDefault or
 | |
|   // nsEventStatus_eConsumeDoDefault, use it.
 | |
|   if (*aStatus != nsEventStatus_eConsumeNoDefault &&
 | |
|       (status == nsEventStatus_eConsumeNoDefault ||
 | |
|        status == nsEventStatus_eConsumeDoDefault)) {
 | |
|     *aStatus = status;
 | |
|   }
 | |
| 
 | |
|   // Don't return error even if middle mouse paste fails since we haven't
 | |
|   // handled it here.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::DispatchClickEvents(
 | |
|     PresShell* aPresShell, WidgetMouseEvent* aMouseUpEvent,
 | |
|     nsEventStatus* aStatus, nsIContent* aClickTarget,
 | |
|     nsIContent* aOverrideClickTarget) {
 | |
|   MOZ_ASSERT(aPresShell);
 | |
|   MOZ_ASSERT(aMouseUpEvent);
 | |
|   MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
 | |
|   MOZ_ASSERT(aStatus);
 | |
|   MOZ_ASSERT(aClickTarget || aOverrideClickTarget);
 | |
| 
 | |
|   bool notDispatchToContents =
 | |
|       (aMouseUpEvent->mButton == MouseButton::eMiddle ||
 | |
|        aMouseUpEvent->mButton == MouseButton::eSecondary);
 | |
| 
 | |
|   bool fireAuxClick = notDispatchToContents;
 | |
| 
 | |
|   AutoWeakFrame currentTarget = aClickTarget->GetPrimaryFrame();
 | |
|   nsresult rv = InitAndDispatchClickEvent(
 | |
|       aMouseUpEvent, aStatus, eMouseClick, aPresShell, aClickTarget,
 | |
|       currentTarget, notDispatchToContents, aOverrideClickTarget);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // Fire auxclick event if necessary.
 | |
|   if (fireAuxClick && *aStatus != nsEventStatus_eConsumeNoDefault &&
 | |
|       aClickTarget && aClickTarget->IsInComposedDoc()) {
 | |
|     rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick,
 | |
|                                    aPresShell, aClickTarget, currentTarget,
 | |
|                                    false, aOverrideClickTarget);
 | |
|     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
 | |
|   }
 | |
| 
 | |
|   // Fire double click event if click count is 2.
 | |
|   if (aMouseUpEvent->mClickCount == 2 && !fireAuxClick && aClickTarget &&
 | |
|       aClickTarget->IsInComposedDoc()) {
 | |
|     rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
 | |
|                                    aPresShell, aClickTarget, currentTarget,
 | |
|                                    notDispatchToContents, aOverrideClickTarget);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::HandleMiddleClickPaste(
 | |
|     PresShell* aPresShell, WidgetMouseEvent* aMouseEvent,
 | |
|     nsEventStatus* aStatus, EditorBase* aEditorBase) {
 | |
|   MOZ_ASSERT(aPresShell);
 | |
|   MOZ_ASSERT(aMouseEvent);
 | |
|   MOZ_ASSERT((aMouseEvent->mMessage == eMouseAuxClick &&
 | |
|               aMouseEvent->mButton == MouseButton::eMiddle) ||
 | |
|              EventCausesClickEvents(*aMouseEvent));
 | |
|   MOZ_ASSERT(aStatus);
 | |
|   MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
 | |
| 
 | |
|   // Even if we're called twice or more for a mouse operation, we should
 | |
|   // handle only once.  Although mMultipleActionsPrevented may be set to
 | |
|   // true by different event handler in the future, we can use it for now.
 | |
|   if (aMouseEvent->mFlags.mMultipleActionsPrevented) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   aMouseEvent->mFlags.mMultipleActionsPrevented = true;
 | |
| 
 | |
|   RefPtr<Selection> selection;
 | |
|   if (aEditorBase) {
 | |
|     selection = aEditorBase->GetSelection();
 | |
|     if (NS_WARN_IF(!selection)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
|   } else {
 | |
|     Document* document = aPresShell->GetDocument();
 | |
|     if (NS_WARN_IF(!document)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
|     selection = nsCopySupport::GetSelectionForCopy(document);
 | |
|     if (NS_WARN_IF(!selection)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     const nsRange* range = selection->GetRangeAt(0);
 | |
|     if (range) {
 | |
|       nsINode* target = range->GetStartContainer();
 | |
|       if (target && target->OwnerDoc()->IsInChromeDocShell()) {
 | |
|         // In Chrome document, limit middle-click pasting to only the editor
 | |
|         // because it looks odd if pasting works in the focused editor when you
 | |
|         // middle-click toolbar or something which are far from the editor.
 | |
|         // However, as DevTools especially Web Console module assumes that paste
 | |
|         // event will be fired when middle-click even on not editor, don't limit
 | |
|         // it.
 | |
|         return NS_OK;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Don't modify selection here because we've already set caret to the point
 | |
|   // at "mousedown" event.
 | |
| 
 | |
|   int32_t clipboardType = nsIClipboard::kGlobalClipboard;
 | |
|   nsCOMPtr<nsIClipboard> clipboardService =
 | |
|       do_GetService("@mozilla.org/widget/clipboard;1");
 | |
|   if (clipboardService && clipboardService->IsClipboardTypeSupported(
 | |
|                               nsIClipboard::kSelectionClipboard)) {
 | |
|     clipboardType = nsIClipboard::kSelectionClipboard;
 | |
|   }
 | |
| 
 | |
|   // Fire ePaste event by ourselves since we need to dispatch "paste" event
 | |
|   // even if the middle click event was consumed for compatibility with
 | |
|   // Chromium.
 | |
|   if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType, aPresShell,
 | |
|                                          selection)) {
 | |
|     *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Although we've fired "paste" event, there is no editor to accept the
 | |
|   // clipboard content.
 | |
|   if (!aEditorBase) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Check if the editor is still the good target to paste.
 | |
|   if (aEditorBase->Destroyed() || aEditorBase->IsReadonly()) {
 | |
|     // XXX Should we consume the event when the editor is readonly and/or
 | |
|     //     disabled?
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // The selection may have been modified during reflow.  Therefore, we
 | |
|   // should adjust event target to pass IsAcceptableInputEvent().
 | |
|   const nsRange* range = selection->GetRangeAt(0);
 | |
|   if (!range) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   WidgetMouseEvent mouseEvent(*aMouseEvent);
 | |
|   mouseEvent.mOriginalTarget = range->GetStartContainer();
 | |
|   if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
 | |
|       !aEditorBase->IsAcceptableInputEvent(&mouseEvent)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // If Control key is pressed, we should paste clipboard content as
 | |
|   // quotation.  Otherwise, paste it as is.
 | |
|   if (aMouseEvent->IsControl()) {
 | |
|     DebugOnly<nsresult> rv = aEditorBase->PasteAsQuotationAsAction(
 | |
|         clipboardType, EditorBase::DispatchPasteEvent::No);
 | |
|     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
 | |
|   } else {
 | |
|     DebugOnly<nsresult> rv = aEditorBase->PasteAsAction(
 | |
|         clipboardType, EditorBase::DispatchPasteEvent::No);
 | |
|     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
 | |
|   }
 | |
|   *aStatus = nsEventStatus_eConsumeNoDefault;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventStateManager::ConsumeInteractionData(
 | |
|     Record<nsString, dom::InteractionData>& aInteractions) {
 | |
|   OnTypingInteractionEnded();
 | |
| 
 | |
|   aInteractions.Entries().Clear();
 | |
|   auto newEntry = aInteractions.Entries().AppendElement();
 | |
|   newEntry->mKey = u"Typing"_ns;
 | |
|   newEntry->mValue = gTypingInteraction;
 | |
|   gTypingInteraction = {};
 | |
| }
 | |
| 
 | |
| nsIFrame* EventStateManager::GetEventTarget() {
 | |
|   PresShell* presShell;
 | |
|   if (mCurrentTarget || !mPresContext ||
 | |
|       !(presShell = mPresContext->GetPresShell())) {
 | |
|     return mCurrentTarget;
 | |
|   }
 | |
| 
 | |
|   if (mCurrentTargetContent) {
 | |
|     mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent);
 | |
|     if (mCurrentTarget) {
 | |
|       return mCurrentTarget;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = presShell->GetCurrentEventFrame();
 | |
|   return (mCurrentTarget = frame);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIContent> EventStateManager::GetEventTargetContent(
 | |
|     WidgetEvent* aEvent) {
 | |
|   if (aEvent && (aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) {
 | |
|     nsCOMPtr<nsIContent> content = GetFocusedElement();
 | |
|     return content.forget();
 | |
|   }
 | |
| 
 | |
|   if (mCurrentTargetContent) {
 | |
|     nsCOMPtr<nsIContent> content = mCurrentTargetContent;
 | |
|     return content.forget();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> content;
 | |
|   if (PresShell* presShell = mPresContext->GetPresShell()) {
 | |
|     content = presShell->GetEventTargetContent(aEvent);
 | |
|   }
 | |
| 
 | |
|   // Some events here may set mCurrentTarget but not set the corresponding
 | |
|   // event target in the PresShell.
 | |
|   if (!content && mCurrentTarget) {
 | |
|     mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content));
 | |
|   }
 | |
| 
 | |
|   return content.forget();
 | |
| }
 | |
| 
 | |
| static Element* GetLabelTarget(nsIContent* aPossibleLabel) {
 | |
|   mozilla::dom::HTMLLabelElement* label =
 | |
|       mozilla::dom::HTMLLabelElement::FromNode(aPossibleLabel);
 | |
|   if (!label) return nullptr;
 | |
| 
 | |
|   return label->GetLabeledElement();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| inline void EventStateManager::DoStateChange(Element* aElement,
 | |
|                                              ElementState aState,
 | |
|                                              bool aAddState) {
 | |
|   if (aAddState) {
 | |
|     aElement->AddStates(aState);
 | |
|   } else {
 | |
|     aElement->RemoveStates(aState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| inline void EventStateManager::DoStateChange(nsIContent* aContent,
 | |
|                                              ElementState aState,
 | |
|                                              bool aStateAdded) {
 | |
|   if (aContent->IsElement()) {
 | |
|     DoStateChange(aContent->AsElement(), aState, aStateAdded);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
 | |
|                                             nsIContent* aStopBefore,
 | |
|                                             ElementState aState,
 | |
|                                             bool aAddState) {
 | |
|   for (; aStartNode && aStartNode != aStopBefore;
 | |
|        aStartNode = aStartNode->GetFlattenedTreeParent()) {
 | |
|     // We might be starting with a non-element (e.g. a text node) and
 | |
|     // if someone is doing something weird might be ending with a
 | |
|     // non-element too (e.g. a document fragment)
 | |
|     if (!aStartNode->IsElement()) {
 | |
|       continue;
 | |
|     }
 | |
|     Element* element = aStartNode->AsElement();
 | |
|     DoStateChange(element, aState, aAddState);
 | |
|     Element* labelTarget = GetLabelTarget(element);
 | |
|     if (labelTarget) {
 | |
|       DoStateChange(labelTarget, aState, aAddState);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aAddState) {
 | |
|     // We might be in a situation where a node was in hover both
 | |
|     // because it was hovered and because the label for it was
 | |
|     // hovered, and while we stopped hovering the node the label is
 | |
|     // still hovered.  Or we might have had two nested labels for the
 | |
|     // same node, and while one is no longer hovered the other still
 | |
|     // is.  In that situation, the label that's still hovered will be
 | |
|     // aStopBefore or some ancestor of it, and the call we just made
 | |
|     // to UpdateAncestorState with aAddState = false would have
 | |
|     // removed the hover state from the node.  But the node should
 | |
|     // still be in hover state.  To handle this situation we need to
 | |
|     // keep walking up the tree and any time we find a label mark its
 | |
|     // corresponding node as still in our state.
 | |
|     for (; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) {
 | |
|       if (!aStartNode->IsElement()) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
 | |
|       if (labelTarget && !labelTarget->State().HasState(aState)) {
 | |
|         DoStateChange(labelTarget, aState, true);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool CanContentHaveActiveState(nsIContent& aContent) {
 | |
|   // Editable content can never become active since their default actions
 | |
|   // are disabled.  Watch out for editable content in native anonymous
 | |
|   // subtrees though, as they belong to text controls.
 | |
|   return !aContent.IsEditable() || aContent.IsInNativeAnonymousSubtree();
 | |
| }
 | |
| 
 | |
| bool EventStateManager::SetContentState(nsIContent* aContent,
 | |
|                                         ElementState aState) {
 | |
|   MOZ_ASSERT(ManagesState(aState), "Unexpected state");
 | |
| 
 | |
|   nsCOMPtr<nsIContent> notifyContent1;
 | |
|   nsCOMPtr<nsIContent> notifyContent2;
 | |
|   bool updateAncestors;
 | |
| 
 | |
|   if (aState == ElementState::HOVER || aState == ElementState::ACTIVE) {
 | |
|     // Hover and active are hierarchical
 | |
|     updateAncestors = true;
 | |
| 
 | |
|     // check to see that this state is allowed by style. Check dragover too?
 | |
|     // XXX Is this even what we want?
 | |
|     if (mCurrentTarget &&
 | |
|         mCurrentTarget->StyleUI()->UserInput() == StyleUserInput::None) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (aState == ElementState::ACTIVE) {
 | |
|       if (aContent && !CanContentHaveActiveState(*aContent)) {
 | |
|         aContent = nullptr;
 | |
|       }
 | |
|       if (aContent != mActiveContent) {
 | |
|         notifyContent1 = aContent;
 | |
|         notifyContent2 = mActiveContent;
 | |
|         mActiveContent = aContent;
 | |
|       }
 | |
|     } else {
 | |
|       NS_ASSERTION(aState == ElementState::HOVER, "How did that happen?");
 | |
|       nsIContent* newHover;
 | |
| 
 | |
|       if (mPresContext->IsDynamic()) {
 | |
|         newHover = aContent;
 | |
|       } else {
 | |
|         NS_ASSERTION(!aContent || aContent->GetComposedDoc() ==
 | |
|                                       mPresContext->PresShell()->GetDocument(),
 | |
|                      "Unexpected document");
 | |
|         nsIFrame* frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
 | |
|         if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
 | |
|           // The scrollbars of viewport should not ignore the hover state.
 | |
|           // Because they are *not* the content of the web page.
 | |
|           newHover = aContent;
 | |
|         } else {
 | |
|           // All contents of the web page should ignore the hover state.
 | |
|           newHover = nullptr;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (newHover != mHoverContent) {
 | |
|         notifyContent1 = newHover;
 | |
|         notifyContent2 = mHoverContent;
 | |
|         mHoverContent = newHover;
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     updateAncestors = false;
 | |
|     if (aState == ElementState::DRAGOVER) {
 | |
|       if (aContent != sDragOverContent) {
 | |
|         notifyContent1 = aContent;
 | |
|         notifyContent2 = sDragOverContent;
 | |
|         sDragOverContent = aContent;
 | |
|       }
 | |
|     } else if (aState == ElementState::URLTARGET) {
 | |
|       if (aContent != mURLTargetContent) {
 | |
|         notifyContent1 = aContent;
 | |
|         notifyContent2 = mURLTargetContent;
 | |
|         mURLTargetContent = aContent;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We need to keep track of which of notifyContent1 and notifyContent2 is
 | |
|   // getting the state set and which is getting it unset.  If both are
 | |
|   // non-null, then notifyContent1 is having the state set and notifyContent2
 | |
|   // is having it unset.  But if one of them is null, we need to keep track of
 | |
|   // the right thing for notifyContent1 explicitly.
 | |
|   bool content1StateSet = true;
 | |
|   if (!notifyContent1) {
 | |
|     // This is ok because FindCommonAncestor wouldn't find anything
 | |
|     // anyway if notifyContent1 is null.
 | |
|     notifyContent1 = notifyContent2;
 | |
|     notifyContent2 = nullptr;
 | |
|     content1StateSet = false;
 | |
|   }
 | |
| 
 | |
|   if (notifyContent1 && mPresContext) {
 | |
|     EnsureDocument(mPresContext);
 | |
|     if (mDocument) {
 | |
|       nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|       if (updateAncestors) {
 | |
|         nsCOMPtr<nsIContent> commonAncestor =
 | |
|             FindCommonAncestor(notifyContent1, notifyContent2);
 | |
|         if (notifyContent2) {
 | |
|           // It's very important to first notify the state removal and
 | |
|           // then the state addition, because due to labels it's
 | |
|           // possible that we're removing state from some element but
 | |
|           // then adding it again (say because mHoverContent changed
 | |
|           // from a control to its label).
 | |
|           UpdateAncestorState(notifyContent2, commonAncestor, aState, false);
 | |
|         }
 | |
|         UpdateAncestorState(notifyContent1, commonAncestor, aState,
 | |
|                             content1StateSet);
 | |
|       } else {
 | |
|         if (notifyContent2) {
 | |
|           DoStateChange(notifyContent2, aState, false);
 | |
|         }
 | |
|         DoStateChange(notifyContent1, aState, content1StateSet);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void EventStateManager::RemoveNodeFromChainIfNeeded(ElementState aState,
 | |
|                                                     nsIContent* aContentRemoved,
 | |
|                                                     bool aNotify) {
 | |
|   MOZ_ASSERT(aState == ElementState::HOVER || aState == ElementState::ACTIVE);
 | |
|   if (!aContentRemoved->IsElement() ||
 | |
|       !aContentRemoved->AsElement()->State().HasState(aState)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent>& leaf =
 | |
|       aState == ElementState::HOVER ? mHoverContent : mActiveContent;
 | |
| 
 | |
|   MOZ_ASSERT(leaf);
 | |
|   // These two NS_ASSERTIONS below can fail for Shadow DOM sometimes, and it's
 | |
|   // not clear how to best handle it, see
 | |
|   // https://github.com/whatwg/html/issues/4795 and bug 1551621.
 | |
|   NS_ASSERTION(
 | |
|       nsContentUtils::ContentIsFlattenedTreeDescendantOf(leaf, aContentRemoved),
 | |
|       "Flat tree and active / hover chain got out of sync");
 | |
| 
 | |
|   nsIContent* newLeaf = aContentRemoved->GetFlattenedTreeParent();
 | |
|   MOZ_ASSERT(!newLeaf || newLeaf->IsElement());
 | |
|   NS_ASSERTION(!newLeaf || newLeaf->AsElement()->State().HasState(aState),
 | |
|                "State got out of sync because of shadow DOM");
 | |
|   if (aNotify) {
 | |
|     SetContentState(newLeaf, aState);
 | |
|   } else {
 | |
|     // We don't update the removed content's state here, since removing NAC
 | |
|     // happens from layout and we don't really want to notify at that point or
 | |
|     // what not.
 | |
|     //
 | |
|     // Also, NAC is not observable and NAC being removed will go away soon.
 | |
|     leaf = newLeaf;
 | |
|   }
 | |
|   MOZ_ASSERT(leaf == newLeaf || (aState == ElementState::ACTIVE && !leaf &&
 | |
|                                  !CanContentHaveActiveState(*newLeaf)));
 | |
| }
 | |
| 
 | |
| void EventStateManager::NativeAnonymousContentRemoved(nsIContent* aContent) {
 | |
|   MOZ_ASSERT(aContent->IsRootOfNativeAnonymousSubtree());
 | |
|   RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, false);
 | |
|   RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, false);
 | |
| 
 | |
|   nsCOMPtr<nsIContent>& lastLeftMouseDownContent =
 | |
|       mLastLeftMouseDownInfo.mLastMouseDownContent;
 | |
|   if (lastLeftMouseDownContent &&
 | |
|       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|           lastLeftMouseDownContent, aContent)) {
 | |
|     lastLeftMouseDownContent = aContent->GetFlattenedTreeParent();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent>& lastMiddleMouseDownContent =
 | |
|       mLastMiddleMouseDownInfo.mLastMouseDownContent;
 | |
|   if (lastMiddleMouseDownContent &&
 | |
|       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|           lastMiddleMouseDownContent, aContent)) {
 | |
|     lastMiddleMouseDownContent = aContent->GetFlattenedTreeParent();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent>& lastRightMouseDownContent =
 | |
|       mLastRightMouseDownInfo.mLastMouseDownContent;
 | |
|   if (lastRightMouseDownContent &&
 | |
|       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | |
|           lastRightMouseDownContent, aContent)) {
 | |
|     lastRightMouseDownContent = aContent->GetFlattenedTreeParent();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::ContentRemoved(Document* aDocument,
 | |
|                                        nsIContent* aContent) {
 | |
|   /*
 | |
|    * Anchor and area elements when focused or hovered might make the UI to show
 | |
|    * the current link. We want to make sure that the UI gets informed when they
 | |
|    * are actually removed from the DOM.
 | |
|    */
 | |
|   if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
 | |
|       (aContent->AsElement()->State().HasAtLeastOneOfStates(
 | |
|           ElementState::FOCUS | ElementState::HOVER))) {
 | |
|     Element* element = aContent->AsElement();
 | |
|     element->LeaveLink(element->GetPresContext(Element::eForComposedDoc));
 | |
|   }
 | |
| 
 | |
|   if (aContent->IsElement()) {
 | |
|     if (RefPtr<nsPresContext> presContext = mPresContext) {
 | |
|       IMEStateManager::OnRemoveContent(*presContext,
 | |
|                                        MOZ_KnownLive(*aContent->AsElement()));
 | |
|     }
 | |
|     WheelTransaction::OnRemoveElement(aContent);
 | |
|   }
 | |
| 
 | |
|   // inform the focus manager that the content is being removed. If this
 | |
|   // content is focused, the focus will be removed without firing events.
 | |
|   if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
 | |
|     fm->ContentRemoved(aDocument, aContent);
 | |
|   }
 | |
| 
 | |
|   RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, true);
 | |
|   RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, true);
 | |
| 
 | |
|   if (sDragOverContent &&
 | |
|       sDragOverContent->OwnerDoc() == aContent->OwnerDoc() &&
 | |
|       nsContentUtils::ContentIsFlattenedTreeDescendantOf(sDragOverContent,
 | |
|                                                          aContent)) {
 | |
|     sDragOverContent = nullptr;
 | |
|   }
 | |
| 
 | |
|   PointerEventHandler::ReleaseIfCaptureByDescendant(aContent);
 | |
| 
 | |
|   if (mMouseEnterLeaveHelper) {
 | |
|     const bool hadMouseOutTarget =
 | |
|         mMouseEnterLeaveHelper->GetOutEventTarget() != nullptr;
 | |
|     mMouseEnterLeaveHelper->ContentRemoved(*aContent);
 | |
|     // If we lose the mouseout target, we need to dispatch mouseover on an
 | |
|     // ancestor.  For ensuring the chance to do it before next user input, we
 | |
|     // need a synthetic mouse move.
 | |
|     if (hadMouseOutTarget && !mMouseEnterLeaveHelper->GetOutEventTarget()) {
 | |
|       if (PresShell* presShell =
 | |
|               mPresContext ? mPresContext->GetPresShell() : nullptr) {
 | |
|         presShell->SynthesizeMouseMove(false);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   for (const auto& entry : mPointersEnterLeaveHelper) {
 | |
|     if (entry.GetData()) {
 | |
|       entry.GetData()->ContentRemoved(*aContent);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::TextControlRootWillBeRemoved(
 | |
|     TextControlElement& aTextControlElement) {
 | |
|   if (!mGestureDownInTextControl || !mGestureDownFrameOwner ||
 | |
|       !mGestureDownFrameOwner->IsInNativeAnonymousSubtree()) {
 | |
|     return;
 | |
|   }
 | |
|   // If we track gesture to start drag in aTextControlElement, we should keep
 | |
|   // tracking it with aTextContrlElement itself for now because this may be
 | |
|   // caused by reframing aTextControlElement which may not be intended by the
 | |
|   // user.
 | |
|   if (&aTextControlElement ==
 | |
|       mGestureDownFrameOwner
 | |
|           ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
 | |
|     mGestureDownFrameOwner = &aTextControlElement;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::TextControlRootAdded(
 | |
|     Element& aAnonymousDivElement, TextControlElement& aTextControlElement) {
 | |
|   if (!mGestureDownInTextControl ||
 | |
|       mGestureDownFrameOwner != &aTextControlElement) {
 | |
|     return;
 | |
|   }
 | |
|   // If we track gesture to start drag in aTextControlElement, but the frame
 | |
|   // owner is the text control element itself, the anonymous nodes in it are
 | |
|   // recreated by a reframe.  If so, we should keep tracking it with the
 | |
|   // recreated native anonymous node.
 | |
|   mGestureDownFrameOwner =
 | |
|       aAnonymousDivElement.GetFirstChild()
 | |
|           ? aAnonymousDivElement.GetFirstChild()
 | |
|           : static_cast<nsIContent*>(&aAnonymousDivElement);
 | |
| }
 | |
| 
 | |
| bool EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) {
 | |
|   return !(aEvent->mMessage == eMouseDown &&
 | |
|            aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
 | |
|            !sNormalLMouseEventInProcess);
 | |
| }
 | |
| 
 | |
| //-------------------------------------------
 | |
| // Access Key Registration
 | |
| //-------------------------------------------
 | |
| void EventStateManager::RegisterAccessKey(Element* aElement, uint32_t aKey) {
 | |
|   if (aElement && !mAccessKeys.Contains(aElement)) {
 | |
|     mAccessKeys.AppendObject(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::UnregisterAccessKey(Element* aElement, uint32_t aKey) {
 | |
|   if (aElement) {
 | |
|     mAccessKeys.RemoveObject(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| uint32_t EventStateManager::GetRegisteredAccessKey(Element* aElement) {
 | |
|   MOZ_ASSERT(aElement);
 | |
| 
 | |
|   if (!mAccessKeys.Contains(aElement)) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   nsAutoString accessKey;
 | |
|   aElement->GetAttr(nsGkAtoms::accesskey, accessKey);
 | |
|   return accessKey.First();
 | |
| }
 | |
| 
 | |
| void EventStateManager::EnsureDocument(nsPresContext* aPresContext) {
 | |
|   if (!mDocument) mDocument = aPresContext->Document();
 | |
| }
 | |
| 
 | |
| void EventStateManager::FlushLayout(nsPresContext* aPresContext) {
 | |
|   MOZ_ASSERT(aPresContext, "nullptr ptr");
 | |
|   if (RefPtr<PresShell> presShell = aPresContext->GetPresShell()) {
 | |
|     presShell->FlushPendingNotifications(FlushType::InterruptibleLayout);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Element* EventStateManager::GetFocusedElement() {
 | |
|   nsFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|   EnsureDocument(mPresContext);
 | |
|   if (!fm || !mDocument) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
 | |
|   return nsFocusManager::GetFocusedDescendant(
 | |
|       mDocument->GetWindow(), nsFocusManager::eOnlyCurrentWindow,
 | |
|       getter_AddRefs(focusedWindow));
 | |
| }
 | |
| 
 | |
| //-------------------------------------------------------
 | |
| // Return true if the docshell is visible
 | |
| 
 | |
| bool EventStateManager::IsShellVisible(nsIDocShell* aShell) {
 | |
|   NS_ASSERTION(aShell, "docshell is null");
 | |
| 
 | |
|   nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell);
 | |
|   if (!basewin) return true;
 | |
| 
 | |
|   bool isVisible = true;
 | |
|   basewin->GetVisibility(&isVisible);
 | |
| 
 | |
|   // We should be doing some additional checks here so that
 | |
|   // we don't tab into hidden tabs of tabbrowser.  -bryner
 | |
| 
 | |
|   return isVisible;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::DoContentCommandEvent(
 | |
|     WidgetContentCommandEvent* aEvent) {
 | |
|   EnsureDocument(mPresContext);
 | |
|   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow());
 | |
|   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 | |
| 
 | |
|   nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
 | |
|   NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
 | |
|   const char* cmd;
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eContentCommandCut:
 | |
|       cmd = "cmd_cut";
 | |
|       break;
 | |
|     case eContentCommandCopy:
 | |
|       cmd = "cmd_copy";
 | |
|       break;
 | |
|     case eContentCommandPaste:
 | |
|       cmd = "cmd_paste";
 | |
|       break;
 | |
|     case eContentCommandDelete:
 | |
|       cmd = "cmd_delete";
 | |
|       break;
 | |
|     case eContentCommandUndo:
 | |
|       cmd = "cmd_undo";
 | |
|       break;
 | |
|     case eContentCommandRedo:
 | |
|       cmd = "cmd_redo";
 | |
|       break;
 | |
|     case eContentCommandPasteTransferable:
 | |
|       cmd = "cmd_pasteTransferable";
 | |
|       break;
 | |
|     case eContentCommandLookUpDictionary:
 | |
|       cmd = "cmd_lookUpDictionary";
 | |
|       break;
 | |
|     default:
 | |
|       return NS_ERROR_NOT_IMPLEMENTED;
 | |
|   }
 | |
|   // If user tries to do something, user must try to do it in visible window.
 | |
|   // So, let's retrieve controller of visible window.
 | |
|   nsCOMPtr<nsIController> controller;
 | |
|   nsresult rv =
 | |
|       root->GetControllerForCommand(cmd, true, getter_AddRefs(controller));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (!controller) {
 | |
|     // When GetControllerForCommand succeeded but there is no controller, the
 | |
|     // command isn't supported.
 | |
|     aEvent->mIsEnabled = false;
 | |
|   } else {
 | |
|     bool canDoIt;
 | |
|     rv = controller->IsCommandEnabled(cmd, &canDoIt);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     aEvent->mIsEnabled = canDoIt;
 | |
|     if (canDoIt && !aEvent->mOnlyEnabledCheck) {
 | |
|       switch (aEvent->mMessage) {
 | |
|         case eContentCommandPasteTransferable: {
 | |
|           BrowserParent* remote = BrowserParent::GetFocused();
 | |
|           if (remote) {
 | |
|             IPCTransferable ipcTransferable;
 | |
|             nsContentUtils::TransferableToIPCTransferable(
 | |
|                 aEvent->mTransferable, &ipcTransferable, false,
 | |
|                 remote->Manager());
 | |
|             remote->SendPasteTransferable(std::move(ipcTransferable));
 | |
|             rv = NS_OK;
 | |
|           } else {
 | |
|             nsCOMPtr<nsICommandController> commandController =
 | |
|                 do_QueryInterface(controller);
 | |
|             NS_ENSURE_STATE(commandController);
 | |
| 
 | |
|             RefPtr<nsCommandParams> params = new nsCommandParams();
 | |
|             rv = params->SetISupports("transferable", aEvent->mTransferable);
 | |
|             if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|               return rv;
 | |
|             }
 | |
|             rv = commandController->DoCommandWithParams(cmd, params);
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         case eContentCommandLookUpDictionary: {
 | |
|           nsCOMPtr<nsICommandController> commandController =
 | |
|               do_QueryInterface(controller);
 | |
|           if (NS_WARN_IF(!commandController)) {
 | |
|             return NS_ERROR_FAILURE;
 | |
|           }
 | |
| 
 | |
|           RefPtr<nsCommandParams> params = new nsCommandParams();
 | |
|           rv = params->SetInt("x", aEvent->mRefPoint.x);
 | |
|           if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|             return rv;
 | |
|           }
 | |
| 
 | |
|           rv = params->SetInt("y", aEvent->mRefPoint.y);
 | |
|           if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|             return rv;
 | |
|           }
 | |
| 
 | |
|           rv = commandController->DoCommandWithParams(cmd, params);
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         default:
 | |
|           rv = controller->DoCommand(cmd);
 | |
|           break;
 | |
|       }
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|     }
 | |
|   }
 | |
|   aEvent->mSucceeded = true;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::DoContentCommandInsertTextEvent(
 | |
|     WidgetContentCommandEvent* aEvent) {
 | |
|   MOZ_ASSERT(aEvent);
 | |
|   MOZ_ASSERT(aEvent->mMessage == eContentCommandInsertText);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aEvent->mString.isSome());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!aEvent->mString.ref().IsEmpty());
 | |
| 
 | |
|   aEvent->mIsEnabled = false;
 | |
|   aEvent->mSucceeded = false;
 | |
| 
 | |
|   NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
 | |
| 
 | |
|   if (XRE_IsParentProcess()) {
 | |
|     // Handle it in focused content process if there is.
 | |
|     if (BrowserParent* remote = BrowserParent::GetFocused()) {
 | |
|       remote->SendInsertText(aEvent->mString.ref());
 | |
|       aEvent->mIsEnabled = true;  // XXX it can be a lie...
 | |
|       aEvent->mSucceeded = true;
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If there is no active editor in this process, we should treat the command
 | |
|   // is disabled.
 | |
|   RefPtr<EditorBase> activeEditor =
 | |
|       nsContentUtils::GetActiveEditor(mPresContext);
 | |
|   if (!activeEditor) {
 | |
|     aEvent->mSucceeded = true;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = activeEditor->InsertTextAsAction(aEvent->mString.ref());
 | |
|   aEvent->mIsEnabled = rv != NS_SUCCESS_DOM_NO_OPERATION;
 | |
|   aEvent->mSucceeded = NS_SUCCEEDED(rv);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventStateManager::DoContentCommandScrollEvent(
 | |
|     WidgetContentCommandEvent* aEvent) {
 | |
|   NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
 | |
|   PresShell* presShell = mPresContext->GetPresShell();
 | |
|   NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
 | |
|   NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
 | |
| 
 | |
|   ScrollUnit scrollUnit;
 | |
|   switch (aEvent->mScroll.mUnit) {
 | |
|     case WidgetContentCommandEvent::eCmdScrollUnit_Line:
 | |
|       scrollUnit = ScrollUnit::LINES;
 | |
|       break;
 | |
|     case WidgetContentCommandEvent::eCmdScrollUnit_Page:
 | |
|       scrollUnit = ScrollUnit::PAGES;
 | |
|       break;
 | |
|     case WidgetContentCommandEvent::eCmdScrollUnit_Whole:
 | |
|       scrollUnit = ScrollUnit::WHOLE;
 | |
|       break;
 | |
|     default:
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   aEvent->mSucceeded = true;
 | |
| 
 | |
|   nsIScrollableFrame* sf =
 | |
|       presShell->GetScrollableFrameToScroll(layers::EitherScrollDirection);
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   nsIntPoint pt(0, 0);
 | |
|   if (aEvent->mScroll.mIsHorizontal) {
 | |
|     pt.x = aEvent->mScroll.mAmount;
 | |
|   } else {
 | |
|     pt.y = aEvent->mScroll.mAmount;
 | |
|   }
 | |
| 
 | |
|   // The caller may want synchronous scrolling.
 | |
|   sf->ScrollBy(pt, scrollUnit, ScrollMode::Instant);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventStateManager::SetActiveManager(EventStateManager* aNewESM,
 | |
|                                          nsIContent* aContent) {
 | |
|   if (sActiveESM && aNewESM != sActiveESM) {
 | |
|     sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
 | |
|   }
 | |
|   sActiveESM = aNewESM;
 | |
|   if (sActiveESM && aContent) {
 | |
|     sActiveESM->SetContentState(aContent, ElementState::ACTIVE);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer) {
 | |
|   if (aClearer) {
 | |
|     aClearer->SetContentState(nullptr, ElementState::ACTIVE);
 | |
|     if (sDragOverContent) {
 | |
|       aClearer->SetContentState(nullptr, ElementState::DRAGOVER);
 | |
|     }
 | |
|   }
 | |
|   if (sActiveESM && aClearer != sActiveESM) {
 | |
|     sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
 | |
|   }
 | |
|   sActiveESM = nullptr;
 | |
| }
 | |
| 
 | |
| /******************************************************************/
 | |
| /* mozilla::EventStateManager::DeltaAccumulator                   */
 | |
| /******************************************************************/
 | |
| 
 | |
| void EventStateManager::DeltaAccumulator::InitLineOrPageDelta(
 | |
|     nsIFrame* aTargetFrame, EventStateManager* aESM, WidgetWheelEvent* aEvent) {
 | |
|   MOZ_ASSERT(aESM);
 | |
|   MOZ_ASSERT(aEvent);
 | |
| 
 | |
|   // Reset if the previous wheel event is too old.
 | |
|   if (!mLastTime.IsNull()) {
 | |
|     TimeDuration duration = TimeStamp::Now() - mLastTime;
 | |
|     if (duration.ToMilliseconds() >
 | |
|         StaticPrefs::mousewheel_transaction_timeout()) {
 | |
|       Reset();
 | |
|     }
 | |
|   }
 | |
|   // If we have accumulated delta,  we may need to reset it.
 | |
|   if (IsInTransaction()) {
 | |
|     // If wheel event type is changed, reset the values.
 | |
|     if (mHandlingDeltaMode != aEvent->mDeltaMode ||
 | |
|         mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) {
 | |
|       Reset();
 | |
|     } else {
 | |
|       // If the delta direction is changed, we should reset only the
 | |
|       // accumulated values.
 | |
|       if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) {
 | |
|         mX = mPendingScrollAmountX = 0.0;
 | |
|       }
 | |
|       if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) {
 | |
|         mY = mPendingScrollAmountY = 0.0;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mHandlingDeltaMode = aEvent->mDeltaMode;
 | |
|   mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta;
 | |
| 
 | |
|   {
 | |
|     nsIFrame* frame = aESM->ComputeScrollTarget(aTargetFrame, aEvent,
 | |
|                                                 COMPUTE_DEFAULT_ACTION_TARGET);
 | |
|     nsPresContext* pc =
 | |
|         frame ? frame->PresContext() : aTargetFrame->PresContext();
 | |
|     nsIScrollableFrame* scrollTarget = do_QueryFrame(frame);
 | |
|     aEvent->mScrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget);
 | |
|   }
 | |
| 
 | |
|   // If it's handling neither a device that does not provide line or page deltas
 | |
|   // nor delta values multiplied by prefs, we must not modify lineOrPageDelta
 | |
|   // values.
 | |
|   // TODO(emilio): Does this care about overridden scroll speed?
 | |
|   if (!mIsNoLineOrPageDeltaDevice &&
 | |
|       !EventStateManager::WheelPrefs::GetInstance()
 | |
|            ->NeedToComputeLineOrPageDelta(aEvent)) {
 | |
|     // Set the delta values to mX and mY.  They would be used when above block
 | |
|     // resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction
 | |
|     // is changed.
 | |
|     // NOTE: We shouldn't accumulate the delta values, it might could cause
 | |
|     //       overflow even though it's not a realistic situation.
 | |
|     if (aEvent->mDeltaX) {
 | |
|       mX = aEvent->mDeltaX;
 | |
|     }
 | |
|     if (aEvent->mDeltaY) {
 | |
|       mY = aEvent->mDeltaY;
 | |
|     }
 | |
|     mLastTime = TimeStamp::Now();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mX += aEvent->mDeltaX;
 | |
|   mY += aEvent->mDeltaY;
 | |
| 
 | |
|   if (mHandlingDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
 | |
|     // Records pixel delta values and init mLineOrPageDeltaX and
 | |
|     // mLineOrPageDeltaY for wheel events which are caused by pixel only
 | |
|     // devices.  Ignore mouse wheel transaction for computing this.  The
 | |
|     // lineOrPageDelta values will be used by dispatching legacy
 | |
|     // eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling
 | |
|     // of default action.  The transaction should be used only for the default
 | |
|     // action.
 | |
|     auto scrollAmountInCSSPixels =
 | |
|         CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
 | |
| 
 | |
|     aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width;
 | |
|     aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height;
 | |
| 
 | |
|     mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width;
 | |
|     mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height;
 | |
|   } else {
 | |
|     aEvent->mLineOrPageDeltaX = RoundDown(mX);
 | |
|     aEvent->mLineOrPageDeltaY = RoundDown(mY);
 | |
|     mX -= aEvent->mLineOrPageDeltaX;
 | |
|     mY -= aEvent->mLineOrPageDeltaY;
 | |
|   }
 | |
| 
 | |
|   mLastTime = TimeStamp::Now();
 | |
| }
 | |
| 
 | |
| void EventStateManager::DeltaAccumulator::Reset() {
 | |
|   mX = mY = 0.0;
 | |
|   mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
 | |
|   mHandlingDeltaMode = UINT32_MAX;
 | |
|   mIsNoLineOrPageDeltaDevice = false;
 | |
| }
 | |
| 
 | |
| nsIntPoint
 | |
| EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
 | |
|     WidgetWheelEvent* aEvent, const nsIntSize& aScrollAmountInDevPixels) {
 | |
|   MOZ_ASSERT(aEvent);
 | |
| 
 | |
|   DeltaValues acceleratedDelta = WheelTransaction::AccelerateWheelDelta(aEvent);
 | |
| 
 | |
|   nsIntPoint result(0, 0);
 | |
|   if (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
 | |
|     mPendingScrollAmountX += acceleratedDelta.deltaX;
 | |
|     mPendingScrollAmountY += acceleratedDelta.deltaY;
 | |
|   } else {
 | |
|     mPendingScrollAmountX +=
 | |
|         aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
 | |
|     mPendingScrollAmountY +=
 | |
|         aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
 | |
|   }
 | |
|   result.x = RoundDown(mPendingScrollAmountX);
 | |
|   result.y = RoundDown(mPendingScrollAmountY);
 | |
|   mPendingScrollAmountX -= result.x;
 | |
|   mPendingScrollAmountY -= result.y;
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /******************************************************************/
 | |
| /* mozilla::EventStateManager::WheelPrefs                         */
 | |
| /******************************************************************/
 | |
| 
 | |
| // static
 | |
| EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::GetInstance() {
 | |
|   if (!sInstance) {
 | |
|     sInstance = new WheelPrefs();
 | |
|   }
 | |
|   return sInstance;
 | |
| }
 | |
| 
 | |
| // static
 | |
| void EventStateManager::WheelPrefs::Shutdown() {
 | |
|   delete sInstance;
 | |
|   sInstance = nullptr;
 | |
| }
 | |
| 
 | |
| // static
 | |
| void EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName,
 | |
|                                                   void* aClosure) {
 | |
|   // forget all prefs, it's not problem for performance.
 | |
|   sInstance->Reset();
 | |
|   DeltaAccumulator::GetInstance()->Reset();
 | |
| }
 | |
| 
 | |
| EventStateManager::WheelPrefs::WheelPrefs() {
 | |
|   Reset();
 | |
|   Preferences::RegisterPrefixCallback(OnPrefChanged, "mousewheel.");
 | |
| }
 | |
| 
 | |
| EventStateManager::WheelPrefs::~WheelPrefs() {
 | |
|   Preferences::UnregisterPrefixCallback(OnPrefChanged, "mousewheel.");
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::Reset() { memset(mInit, 0, sizeof(mInit)); }
 | |
| 
 | |
| EventStateManager::WheelPrefs::Index EventStateManager::WheelPrefs::GetIndexFor(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   if (!aEvent) {
 | |
|     return INDEX_DEFAULT;
 | |
|   }
 | |
| 
 | |
|   Modifiers modifiers = (aEvent->mModifiers & (MODIFIER_ALT | MODIFIER_CONTROL |
 | |
|                                                MODIFIER_META | MODIFIER_SHIFT));
 | |
| 
 | |
|   switch (modifiers) {
 | |
|     case MODIFIER_ALT:
 | |
|       return INDEX_ALT;
 | |
|     case MODIFIER_CONTROL:
 | |
|       return INDEX_CONTROL;
 | |
|     case MODIFIER_META:
 | |
|       return INDEX_META;
 | |
|     case MODIFIER_SHIFT:
 | |
|       return INDEX_SHIFT;
 | |
|     default:
 | |
|       // If two or more modifier keys are pressed, we should use default
 | |
|       // settings.
 | |
|       return INDEX_DEFAULT;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::GetBasePrefName(
 | |
|     EventStateManager::WheelPrefs::Index aIndex, nsACString& aBasePrefName) {
 | |
|   aBasePrefName.AssignLiteral("mousewheel.");
 | |
|   switch (aIndex) {
 | |
|     case INDEX_ALT:
 | |
|       aBasePrefName.AppendLiteral("with_alt.");
 | |
|       break;
 | |
|     case INDEX_CONTROL:
 | |
|       aBasePrefName.AppendLiteral("with_control.");
 | |
|       break;
 | |
|     case INDEX_META:
 | |
|       aBasePrefName.AppendLiteral("with_meta.");
 | |
|       break;
 | |
|     case INDEX_SHIFT:
 | |
|       aBasePrefName.AppendLiteral("with_shift.");
 | |
|       break;
 | |
|     case INDEX_DEFAULT:
 | |
|     default:
 | |
|       aBasePrefName.AppendLiteral("default.");
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::Init(
 | |
|     EventStateManager::WheelPrefs::Index aIndex) {
 | |
|   if (mInit[aIndex]) {
 | |
|     return;
 | |
|   }
 | |
|   mInit[aIndex] = true;
 | |
| 
 | |
|   nsAutoCString basePrefName;
 | |
|   GetBasePrefName(aIndex, basePrefName);
 | |
| 
 | |
|   nsAutoCString prefNameX(basePrefName);
 | |
|   prefNameX.AppendLiteral("delta_multiplier_x");
 | |
|   mMultiplierX[aIndex] =
 | |
|       static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100;
 | |
| 
 | |
|   nsAutoCString prefNameY(basePrefName);
 | |
|   prefNameY.AppendLiteral("delta_multiplier_y");
 | |
|   mMultiplierY[aIndex] =
 | |
|       static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100;
 | |
| 
 | |
|   nsAutoCString prefNameZ(basePrefName);
 | |
|   prefNameZ.AppendLiteral("delta_multiplier_z");
 | |
|   mMultiplierZ[aIndex] =
 | |
|       static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
 | |
| 
 | |
|   nsAutoCString prefNameAction(basePrefName);
 | |
|   prefNameAction.AppendLiteral("action");
 | |
|   int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
 | |
|   if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
 | |
|     NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
 | |
|     action = ACTION_SCROLL;
 | |
|   }
 | |
|   mActions[aIndex] = static_cast<Action>(action);
 | |
| 
 | |
|   // Compute action values overridden by .override_x pref.
 | |
|   // At present, override is possible only for the x-direction
 | |
|   // because this pref is introduced mainly for tilt wheels.
 | |
|   // Note that ACTION_HORIZONTALIZED_SCROLL isn't a valid value for this pref
 | |
|   // because it affects only to deltaY.
 | |
|   prefNameAction.AppendLiteral(".override_x");
 | |
|   int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1);
 | |
|   if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST) ||
 | |
|       actionOverrideX == ACTION_HORIZONTALIZED_SCROLL) {
 | |
|     NS_WARNING("Unsupported action override pref value, didn't override.");
 | |
|     actionOverrideX = -1;
 | |
|   }
 | |
|   mOverriddenActionsX[aIndex] = (actionOverrideX == -1)
 | |
|                                     ? static_cast<Action>(action)
 | |
|                                     : static_cast<Action>(actionOverrideX);
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::GetMultiplierForDeltaXAndY(
 | |
|     const WidgetWheelEvent* aEvent, Index aIndex, double* aMultiplierForDeltaX,
 | |
|     double* aMultiplierForDeltaY) {
 | |
|   *aMultiplierForDeltaX = mMultiplierX[aIndex];
 | |
|   *aMultiplierForDeltaY = mMultiplierY[aIndex];
 | |
|   // If the event has been horizontalized(I.e. treated as a horizontal wheel
 | |
|   // scroll for a vertical wheel scroll), then we should swap mMultiplierX and
 | |
|   // mMultiplierY. By doing this, multipliers will still apply to the delta
 | |
|   // values they origianlly corresponded to.
 | |
|   if (aEvent->mDeltaValuesHorizontalizedForDefaultHandler &&
 | |
|       ComputeActionFor(aEvent) == ACTION_HORIZONTALIZED_SCROLL) {
 | |
|     std::swap(*aMultiplierForDeltaX, *aMultiplierForDeltaY);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(
 | |
|     WidgetWheelEvent* aEvent) {
 | |
|   if (aEvent->mCustomizedByUserPrefs) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
| 
 | |
|   double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
 | |
|   GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
 | |
|                              &multiplierForDeltaY);
 | |
|   aEvent->mDeltaX *= multiplierForDeltaX;
 | |
|   aEvent->mDeltaY *= multiplierForDeltaY;
 | |
|   aEvent->mDeltaZ *= mMultiplierZ[index];
 | |
| 
 | |
|   // If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute
 | |
|   // value, we should use lineOrPageDelta values which were set by widget.
 | |
|   // Otherwise, we need to compute them from accumulated delta values.
 | |
|   if (!NeedToComputeLineOrPageDelta(aEvent)) {
 | |
|     aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(multiplierForDeltaX);
 | |
|     aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(multiplierForDeltaY);
 | |
|   } else {
 | |
|     aEvent->mLineOrPageDeltaX = 0;
 | |
|     aEvent->mLineOrPageDeltaY = 0;
 | |
|   }
 | |
| 
 | |
|   aEvent->mCustomizedByUserPrefs =
 | |
|       ((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) ||
 | |
|        (mMultiplierZ[index] != 1.0));
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta(
 | |
|     WidgetWheelEvent* aEvent) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
| 
 | |
|   // XXX If the multiplier pref value is negative, the scroll direction was
 | |
|   //     changed and caused to scroll different direction.  In such case,
 | |
|   //     this method reverts the sign of overflowDelta.  Does it make widget
 | |
|   //     happy?  Although, widget can know the pref applied delta values by
 | |
|   //     referrencing the deltaX and deltaY of the event.
 | |
| 
 | |
|   double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
 | |
|   GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
 | |
|                              &multiplierForDeltaY);
 | |
|   if (multiplierForDeltaX) {
 | |
|     aEvent->mOverflowDeltaX /= multiplierForDeltaX;
 | |
|   }
 | |
|   if (multiplierForDeltaY) {
 | |
|     aEvent->mOverflowDeltaY /= multiplierForDeltaY;
 | |
|   }
 | |
| }
 | |
| 
 | |
| EventStateManager::WheelPrefs::Action
 | |
| EventStateManager::WheelPrefs::ComputeActionFor(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
| 
 | |
|   bool deltaXPreferred = (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
 | |
|                           Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
 | |
|   Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
 | |
|   if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL ||
 | |
|       actions[index] == ACTION_HORIZONTALIZED_SCROLL) {
 | |
|     return actions[index];
 | |
|   }
 | |
| 
 | |
|   // Momentum events shouldn't run special actions.
 | |
|   if (aEvent->mIsMomentum) {
 | |
|     // Use the default action.  Note that user might kill the wheel scrolling.
 | |
|     Init(INDEX_DEFAULT);
 | |
|     if (actions[INDEX_DEFAULT] == ACTION_SCROLL ||
 | |
|         actions[INDEX_DEFAULT] == ACTION_HORIZONTALIZED_SCROLL) {
 | |
|       return actions[INDEX_DEFAULT];
 | |
|     }
 | |
|     return ACTION_NONE;
 | |
|   }
 | |
| 
 | |
|   return actions[index];
 | |
| }
 | |
| 
 | |
| bool EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
| 
 | |
|   return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
 | |
|          (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
 | |
| }
 | |
| 
 | |
| void EventStateManager::WheelPrefs::GetUserPrefsForEvent(
 | |
|     const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
 | |
|     double* aOutMultiplierY) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
| 
 | |
|   double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
 | |
|   GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
 | |
|                              &multiplierForDeltaY);
 | |
|   *aOutMultiplierX = multiplierForDeltaX;
 | |
|   *aOutMultiplierY = multiplierForDeltaY;
 | |
| }
 | |
| 
 | |
| // static
 | |
| Maybe<layers::APZWheelAction> EventStateManager::APZWheelActionFor(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   if (aEvent->mMessage != eWheel) {
 | |
|     return Nothing();
 | |
|   }
 | |
|   WheelPrefs::Action action =
 | |
|       WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
 | |
|   switch (action) {
 | |
|     case WheelPrefs::ACTION_SCROLL:
 | |
|     case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
 | |
|       return Some(layers::APZWheelAction::Scroll);
 | |
|     case WheelPrefs::ACTION_PINCH_ZOOM:
 | |
|       return Some(layers::APZWheelAction::PinchZoom);
 | |
|     default:
 | |
|       return Nothing();
 | |
|   }
 | |
| }
 | |
| 
 | |
| // static
 | |
| WheelDeltaAdjustmentStrategy EventStateManager::GetWheelDeltaAdjustmentStrategy(
 | |
|     const WidgetWheelEvent& aEvent) {
 | |
|   if (aEvent.mMessage != eWheel) {
 | |
|     return WheelDeltaAdjustmentStrategy::eNone;
 | |
|   }
 | |
|   switch (WheelPrefs::GetInstance()->ComputeActionFor(&aEvent)) {
 | |
|     case WheelPrefs::ACTION_SCROLL:
 | |
|       if (StaticPrefs::mousewheel_autodir_enabled() && 0 == aEvent.mDeltaZ) {
 | |
|         if (StaticPrefs::mousewheel_autodir_honourroot()) {
 | |
|           return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour;
 | |
|         }
 | |
|         return WheelDeltaAdjustmentStrategy::eAutoDir;
 | |
|       }
 | |
|       return WheelDeltaAdjustmentStrategy::eNone;
 | |
|     case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
 | |
|       return WheelDeltaAdjustmentStrategy::eHorizontalize;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
|   return WheelDeltaAdjustmentStrategy::eNone;
 | |
| }
 | |
| 
 | |
| void EventStateManager::GetUserPrefsForWheelEvent(
 | |
|     const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
 | |
|     double* aOutMultiplierY) {
 | |
|   WheelPrefs::GetInstance()->GetUserPrefsForEvent(aEvent, aOutMultiplierX,
 | |
|                                                   aOutMultiplierY);
 | |
| }
 | |
| 
 | |
| bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
|   return Abs(mMultiplierX[index]) >=
 | |
|          MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
 | |
| }
 | |
| 
 | |
| bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
 | |
|     const WidgetWheelEvent* aEvent) {
 | |
|   Index index = GetIndexFor(aEvent);
 | |
|   Init(index);
 | |
|   return Abs(mMultiplierY[index]) >=
 | |
|          MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
