Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / events / EventStateManager.cpp
blob02600e707925f6c8699f581e2af8193b341fc146
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "EventStateManager.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EditorBase.h"
12 #include "mozilla/EventDispatcher.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/Hal.h"
15 #include "mozilla/HTMLEditor.h"
16 #include "mozilla/IMEStateManager.h"
17 #include "mozilla/Likely.h"
18 #include "mozilla/MiscEvents.h"
19 #include "mozilla/MathAlgorithms.h"
20 #include "mozilla/MouseEvents.h"
21 #include "mozilla/PointerLockManager.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/ScrollTypes.h"
25 #include "mozilla/TextComposition.h"
26 #include "mozilla/TextControlElement.h"
27 #include "mozilla/TextEditor.h"
28 #include "mozilla/TextEvents.h"
29 #include "mozilla/TouchEvents.h"
30 #include "mozilla/Telemetry.h"
31 #include "mozilla/UniquePtr.h"
32 #include "mozilla/dom/BrowserBridgeChild.h"
33 #include "mozilla/dom/BrowsingContext.h"
34 #include "mozilla/dom/CanonicalBrowsingContext.h"
35 #include "mozilla/dom/ContentChild.h"
36 #include "mozilla/dom/DOMIntersectionObserver.h"
37 #include "mozilla/dom/DragEvent.h"
38 #include "mozilla/dom/Event.h"
39 #include "mozilla/dom/FrameLoaderBinding.h"
40 #include "mozilla/dom/HTMLLabelElement.h"
41 #include "mozilla/dom/HTMLInputElement.h"
42 #include "mozilla/dom/MouseEventBinding.h"
43 #include "mozilla/dom/BrowserChild.h"
44 #include "mozilla/dom/PointerEventHandler.h"
45 #include "mozilla/dom/UIEvent.h"
46 #include "mozilla/dom/UIEventBinding.h"
47 #include "mozilla/dom/UserActivation.h"
48 #include "mozilla/dom/WheelEventBinding.h"
49 #include "mozilla/glean/GleanMetrics.h"
50 #include "mozilla/StaticPrefs_accessibility.h"
51 #include "mozilla/StaticPrefs_browser.h"
52 #include "mozilla/StaticPrefs_dom.h"
53 #include "mozilla/StaticPrefs_layout.h"
54 #include "mozilla/StaticPrefs_mousewheel.h"
55 #include "mozilla/StaticPrefs_ui.h"
56 #include "mozilla/StaticPrefs_zoom.h"
58 #include "ContentEventHandler.h"
59 #include "IMEContentObserver.h"
60 #include "WheelHandlingHelper.h"
61 #include "RemoteDragStartData.h"
63 #include "nsCommandParams.h"
64 #include "nsCOMPtr.h"
65 #include "nsCopySupport.h"
66 #include "nsFocusManager.h"
67 #include "nsGenericHTMLElement.h"
68 #include "nsIClipboard.h"
69 #include "nsIContent.h"
70 #include "nsIContentInlines.h"
71 #include "mozilla/dom/Document.h"
72 #include "nsICookieJarSettings.h"
73 #include "nsIFrame.h"
74 #include "nsFrameLoaderOwner.h"
75 #include "nsIWidget.h"
76 #include "nsLiteralString.h"
77 #include "nsPresContext.h"
78 #include "nsTArray.h"
79 #include "nsGkAtoms.h"
80 #include "nsIFormControl.h"
81 #include "nsComboboxControlFrame.h"
82 #include "nsIScrollableFrame.h"
83 #include "nsIDOMXULControlElement.h"
84 #include "nsNameSpaceManager.h"
85 #include "nsIBaseWindow.h"
86 #include "nsFrameSelection.h"
87 #include "nsPIDOMWindow.h"
88 #include "nsPIWindowRoot.h"
89 #include "nsIWebNavigation.h"
90 #include "nsIDocumentViewer.h"
91 #include "nsFrameManager.h"
92 #include "nsIBrowserChild.h"
93 #include "nsMenuPopupFrame.h"
95 #include "nsIObserverService.h"
96 #include "nsIDocShell.h"
98 #include "nsSubDocumentFrame.h"
99 #include "nsLayoutUtils.h"
100 #include "nsIInterfaceRequestorUtils.h"
101 #include "nsUnicharUtils.h"
102 #include "nsContentUtils.h"
104 #include "imgIContainer.h"
105 #include "nsIProperties.h"
106 #include "nsISupportsPrimitives.h"
108 #include "nsServiceManagerUtils.h"
109 #include "nsITimer.h"
110 #include "nsFontMetrics.h"
111 #include "nsIDragService.h"
112 #include "nsIDragSession.h"
113 #include "mozilla/dom/DataTransfer.h"
114 #include "nsContentAreaDragDrop.h"
115 #include "nsTreeBodyFrame.h"
116 #include "nsIController.h"
117 #include "mozilla/Services.h"
118 #include "mozilla/dom/ContentParent.h"
119 #include "mozilla/dom/Record.h"
120 #include "mozilla/dom/Selection.h"
122 #include "mozilla/Preferences.h"
123 #include "mozilla/LookAndFeel.h"
124 #include "mozilla/ProfilerLabels.h"
125 #include "Units.h"
127 #ifdef XP_MACOSX
128 # import <ApplicationServices/ApplicationServices.h>
129 #endif
131 namespace mozilla {
133 using namespace dom;
135 static const LayoutDeviceIntPoint kInvalidRefPoint =
136 LayoutDeviceIntPoint(-1, -1);
138 static uint32_t gMouseOrKeyboardEventCounter = 0;
139 static nsITimer* gUserInteractionTimer = nullptr;
140 static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
142 static const double kCursorLoadingTimeout = 1000; // ms
143 static AutoWeakFrame gLastCursorSourceFrame;
144 static TimeStamp gLastCursorUpdateTime;
145 static TimeStamp gTypingStartTime;
146 static TimeStamp gTypingEndTime;
147 static int32_t gTypingInteractionKeyPresses = 0;
148 static dom::InteractionData gTypingInteraction = {};
150 static inline int32_t RoundDown(double aDouble) {
151 return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble))
152 : static_cast<int32_t>(ceil(aDouble));
155 static bool IsSelectingLink(nsIFrame* aTargetFrame) {
156 if (!aTargetFrame) {
157 return false;
159 const nsFrameSelection* frameSel = aTargetFrame->GetConstFrameSelection();
160 if (!frameSel || !frameSel->GetDragState()) {
161 return false;
164 if (!nsContentUtils::GetClosestLinkInFlatTree(aTargetFrame->GetContent())) {
165 return false;
167 return true;
170 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
171 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
172 EventTarget* aRelatedTarget);
175 * Returns the common ancestor for mouseup purpose, given the
176 * current mouseup target and the previous mousedown target.
178 static nsINode* GetCommonAncestorForMouseUp(
179 nsINode* aCurrentMouseUpTarget, nsINode* aLastMouseDownTarget,
180 Maybe<FormControlType>& aLastMouseDownInputControlType) {
181 if (!aCurrentMouseUpTarget || !aLastMouseDownTarget) {
182 return nullptr;
185 if (aCurrentMouseUpTarget == aLastMouseDownTarget) {
186 return aCurrentMouseUpTarget;
189 // Build the chain of parents
190 AutoTArray<nsINode*, 30> parents1;
191 do {
192 parents1.AppendElement(aCurrentMouseUpTarget);
193 aCurrentMouseUpTarget = aCurrentMouseUpTarget->GetFlattenedTreeParentNode();
194 } while (aCurrentMouseUpTarget);
196 AutoTArray<nsINode*, 30> parents2;
197 do {
198 parents2.AppendElement(aLastMouseDownTarget);
199 if (aLastMouseDownTarget == parents1.LastElement()) {
200 break;
202 aLastMouseDownTarget = aLastMouseDownTarget->GetFlattenedTreeParentNode();
203 } while (aLastMouseDownTarget);
205 // Find where the parent chain differs
206 uint32_t pos1 = parents1.Length();
207 uint32_t pos2 = parents2.Length();
208 nsINode* parent = nullptr;
209 for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
210 nsINode* child1 = parents1.ElementAt(--pos1);
211 nsINode* child2 = parents2.ElementAt(--pos2);
212 if (child1 != child2) {
213 break;
216 // If the input control type is different between mouseup and mousedown,
217 // this is not a valid click.
218 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(child1)) {
219 if (aLastMouseDownInputControlType.isSome() &&
220 aLastMouseDownInputControlType.ref() != input->ControlType()) {
221 break;
224 parent = child1;
227 return parent;
230 LazyLogModule sMouseBoundaryLog("MouseBoundaryEvents");
231 LazyLogModule sPointerBoundaryLog("PointerBoundaryEvents");
233 /******************************************************************/
234 /* mozilla::UITimerCallback */
235 /******************************************************************/
237 class UITimerCallback final : public nsITimerCallback, public nsINamed {
238 public:
239 UITimerCallback() : mPreviousCount(0) {}
240 NS_DECL_ISUPPORTS
241 NS_DECL_NSITIMERCALLBACK
242 NS_DECL_NSINAMED
243 private:
244 ~UITimerCallback() = default;
245 uint32_t mPreviousCount;
248 NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)
250 // If aTimer is nullptr, this method always sends "user-interaction-inactive"
251 // notification.
252 NS_IMETHODIMP
253 UITimerCallback::Notify(nsITimer* aTimer) {
254 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
255 if (!obs) return NS_ERROR_FAILURE;
256 if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
257 gMouseOrKeyboardEventCounter = 0;
258 obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
259 if (gUserInteractionTimer) {
260 gUserInteractionTimer->Cancel();
261 NS_RELEASE(gUserInteractionTimer);
263 } else {
264 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
265 EventStateManager::UpdateUserActivityTimer();
267 if (XRE_IsParentProcess()) {
268 hal::BatteryInformation batteryInfo;
269 hal::GetCurrentBatteryInformation(&batteryInfo);
270 glean::power_battery::percentage_when_user_active.AccumulateSingleSample(
271 uint64_t(batteryInfo.level() * 100));
274 mPreviousCount = gMouseOrKeyboardEventCounter;
275 return NS_OK;
278 NS_IMETHODIMP
279 UITimerCallback::GetName(nsACString& aName) {
280 aName.AssignLiteral("UITimerCallback_timer");
281 return NS_OK;
284 /******************************************************************/
285 /* mozilla::OverOutElementsWrapper */
286 /******************************************************************/
288 NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, mDeepestEnterEventTarget,
289 mDispatchingOverEventTarget,
290 mDispatchingOutOrDeepestLeaveEventTarget)
291 NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
292 NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)
294 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
295 NS_INTERFACE_MAP_ENTRY(nsISupports)
296 NS_INTERFACE_MAP_END
298 void OverOutElementsWrapper::ContentRemoved(nsIContent& aContent) {
299 if (!mDeepestEnterEventTarget) {
300 return;
303 if (!nsContentUtils::ContentIsFlattenedTreeDescendantOf(
304 mDeepestEnterEventTarget, &aContent)) {
305 return;
308 LogModule* const logModule = mType == BoundaryEventType::Mouse
309 ? sMouseBoundaryLog
310 : sPointerBoundaryLog;
312 if (!StaticPrefs::
313 dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed()) {
314 MOZ_LOG(logModule, LogLevel::Info,
315 ("The last \"over\" event target (%p) is removed",
316 mDeepestEnterEventTarget.get()));
317 mDeepestEnterEventTarget = nullptr;
318 return;
321 if (mDispatchingOverEventTarget &&
322 (mDeepestEnterEventTarget == mDispatchingOverEventTarget ||
323 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
324 mDispatchingOverEventTarget, &aContent))) {
325 if (mDispatchingOverEventTarget ==
326 mDispatchingOutOrDeepestLeaveEventTarget) {
327 MOZ_LOG(logModule, LogLevel::Info,
328 ("The dispatching \"%s\" event target (%p) is removed",
329 mDeepestEnterEventTargetIsOverEventTarget ? "out" : "leave",
330 mDispatchingOutOrDeepestLeaveEventTarget.get()));
331 mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
333 MOZ_LOG(logModule, LogLevel::Info,
334 ("The dispatching \"over\" event target (%p) is removed",
335 mDispatchingOverEventTarget.get()));
336 mDispatchingOverEventTarget = nullptr;
338 if (mDispatchingOutOrDeepestLeaveEventTarget &&
339 (mDeepestEnterEventTarget == mDispatchingOutOrDeepestLeaveEventTarget ||
340 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
341 mDispatchingOutOrDeepestLeaveEventTarget, &aContent))) {
342 MOZ_LOG(logModule, LogLevel::Info,
343 ("The dispatching \"%s\" event target (%p) is removed",
344 mDeepestEnterEventTargetIsOverEventTarget ? "out" : "leave",
345 mDispatchingOutOrDeepestLeaveEventTarget.get()));
346 mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
348 MOZ_LOG(logModule, LogLevel::Info,
349 ("The last \"%s\" event target (%p) is removed and now the last "
350 "deepest enter target becomes %s(%p)",
351 mDeepestEnterEventTargetIsOverEventTarget ? "over" : "enter",
352 mDeepestEnterEventTarget.get(),
353 aContent.GetFlattenedTreeParent()
354 ? ToString(*aContent.GetFlattenedTreeParent()).c_str()
355 : "nullptr",
356 aContent.GetFlattenedTreeParent()));
357 mDeepestEnterEventTarget = aContent.GetFlattenedTreeParent();
358 mDeepestEnterEventTargetIsOverEventTarget = false;
361 void OverOutElementsWrapper::DidDispatchOverAndEnterEvent(
362 nsIContent* aOriginalOverTargetInComposedDoc) {
363 mDispatchingOverEventTarget = nullptr;
365 // Pointer Events define that once the `pointerover` event target is removed
366 // from the tree, `pointerout` should not be fired on that and the closest
367 // connected ancestor at the target removal should be kept as the deepest
368 // `pointerleave` target. Therefore, we don't need the special handling for
369 // `pointerout` event target if the last `pointerover` target is temporarily
370 // removed from the tree.
371 if (mType == OverOutElementsWrapper::BoundaryEventType::Pointer) {
372 return;
375 // Assume that the caller checks whether aOriginalOverTarget is in the
376 // original document. If we don't enable the strict mouse/pointer event
377 // boundary event dispatching by the pref (see below),
378 // mDeepestEnterEventTarget is set to nullptr when the last "over" target is
379 // removed. Therefore, we cannot check whether aOriginalOverTarget is in the
380 // original document here.
381 if (!aOriginalOverTargetInComposedDoc) {
382 return;
384 MOZ_ASSERT_IF(mDeepestEnterEventTarget,
385 mDeepestEnterEventTarget->GetComposedDoc() ==
386 aOriginalOverTargetInComposedDoc->GetComposedDoc());
387 // If the "mouseover" event target is removed temporarily while we're
388 // dispatching "mouseover" and "mouseenter" events and the target gets back
389 // under the deepest enter event target, we should restore the "mouseover"
390 // target.
391 if ((!StaticPrefs::
392 dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed() &&
393 !mDeepestEnterEventTarget) ||
394 (!mDeepestEnterEventTargetIsOverEventTarget && mDeepestEnterEventTarget &&
395 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
396 aOriginalOverTargetInComposedDoc, mDeepestEnterEventTarget))) {
397 mDeepestEnterEventTarget = aOriginalOverTargetInComposedDoc;
398 mDeepestEnterEventTargetIsOverEventTarget = true;
399 LogModule* const logModule = mType == BoundaryEventType::Mouse
400 ? sMouseBoundaryLog
401 : sPointerBoundaryLog;
402 MOZ_LOG(logModule, LogLevel::Info,
403 ("The \"over\" event target (%p) is restored",
404 mDeepestEnterEventTarget.get()));
408 /******************************************************************/
409 /* mozilla::EventStateManager */
410 /******************************************************************/
412 static uint32_t sESMInstanceCount = 0;
414 bool EventStateManager::sNormalLMouseEventInProcess = false;
415 int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
416 EventStateManager* EventStateManager::sActiveESM = nullptr;
417 EventStateManager* EventStateManager::sCursorSettingManager = nullptr;
418 AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
419 LayoutDeviceIntPoint EventStateManager::sPreLockScreenPoint =
420 LayoutDeviceIntPoint(0, 0);
421 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
422 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
423 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
424 CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
425 nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
427 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::sInstance =
428 nullptr;
429 EventStateManager::DeltaAccumulator*
430 EventStateManager::DeltaAccumulator::sInstance = nullptr;
432 constexpr const StyleCursorKind kInvalidCursorKind =
433 static_cast<StyleCursorKind>(255);
435 EventStateManager::EventStateManager()
436 : mLockCursor(kInvalidCursorKind),
437 mCurrentTarget(nullptr),
438 // init d&d gesture state machine variables
439 mGestureDownPoint(0, 0),
440 mGestureModifiers(0),
441 mGestureDownButtons(0),
442 mPresContext(nullptr),
443 mShouldAlwaysUseLineDeltas(false),
444 mShouldAlwaysUseLineDeltasInitialized(false),
445 mGestureDownInTextControl(false),
446 mInTouchDrag(false),
447 m_haveShutdown(false) {
448 if (sESMInstanceCount == 0) {
449 gUserInteractionTimerCallback = new UITimerCallback();
450 if (gUserInteractionTimerCallback) NS_ADDREF(gUserInteractionTimerCallback);
451 UpdateUserActivityTimer();
453 ++sESMInstanceCount;
456 nsresult EventStateManager::UpdateUserActivityTimer() {
457 if (!gUserInteractionTimerCallback) return NS_OK;
459 if (!gUserInteractionTimer) {
460 gUserInteractionTimer = NS_NewTimer().take();
463 if (gUserInteractionTimer) {
464 gUserInteractionTimer->InitWithCallback(
465 gUserInteractionTimerCallback,
466 StaticPrefs::dom_events_user_interaction_interval(),
467 nsITimer::TYPE_ONE_SHOT);
469 return NS_OK;
472 nsresult EventStateManager::Init() {
473 nsCOMPtr<nsIObserverService> observerService =
474 mozilla::services::GetObserverService();
475 if (!observerService) return NS_ERROR_FAILURE;
477 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
479 return NS_OK;
482 bool EventStateManager::ShouldAlwaysUseLineDeltas() {
483 if (MOZ_UNLIKELY(!mShouldAlwaysUseLineDeltasInitialized)) {
484 mShouldAlwaysUseLineDeltasInitialized = true;
485 mShouldAlwaysUseLineDeltas =
486 !StaticPrefs::dom_event_wheel_deltaMode_lines_disabled();
487 if (!mShouldAlwaysUseLineDeltas && mDocument) {
488 if (nsIPrincipal* principal =
489 mDocument->GetPrincipalForPrefBasedHacks()) {
490 mShouldAlwaysUseLineDeltas = principal->IsURIInPrefList(
491 "dom.event.wheel-deltaMode-lines.always-enabled");
495 return mShouldAlwaysUseLineDeltas;
498 EventStateManager::~EventStateManager() {
499 ReleaseCurrentIMEContentObserver();
501 if (sActiveESM == this) {
502 sActiveESM = nullptr;
505 if (StaticPrefs::ui_click_hold_context_menus()) {
506 KillClickHoldTimer();
509 if (sCursorSettingManager == this) {
510 sCursorSettingManager = nullptr;
513 --sESMInstanceCount;
514 if (sESMInstanceCount == 0) {
515 WheelTransaction::Shutdown();
516 if (gUserInteractionTimerCallback) {
517 gUserInteractionTimerCallback->Notify(nullptr);
518 NS_RELEASE(gUserInteractionTimerCallback);
520 if (gUserInteractionTimer) {
521 gUserInteractionTimer->Cancel();
522 NS_RELEASE(gUserInteractionTimer);
524 WheelPrefs::Shutdown();
525 DeltaAccumulator::Shutdown();
528 if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
529 sDragOverContent = nullptr;
532 if (!m_haveShutdown) {
533 Shutdown();
535 // Don't remove from Observer service in Shutdown because Shutdown also
536 // gets called from xpcom shutdown observer. And we don't want to remove
537 // from the service in that case.
539 nsCOMPtr<nsIObserverService> observerService =
540 mozilla::services::GetObserverService();
541 if (observerService) {
542 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
547 nsresult EventStateManager::Shutdown() {
548 m_haveShutdown = true;
549 return NS_OK;
552 NS_IMETHODIMP
553 EventStateManager::Observe(nsISupports* aSubject, const char* aTopic,
554 const char16_t* someData) {
555 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
556 Shutdown();
559 return NS_OK;
562 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
563 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
564 NS_INTERFACE_MAP_ENTRY(nsIObserver)
565 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
566 NS_INTERFACE_MAP_END
568 NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
569 NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)
571 NS_IMPL_CYCLE_COLLECTION_WEAK(EventStateManager, mCurrentTargetContent,
572 mGestureDownContent, mGestureDownFrameOwner,
573 mLastLeftMouseDownInfo.mLastMouseDownContent,
574 mLastMiddleMouseDownInfo.mLastMouseDownContent,
575 mLastRightMouseDownInfo.mLastMouseDownContent,
576 mActiveContent, mHoverContent, mURLTargetContent,
577 mPopoverPointerDownTarget, mMouseEnterLeaveHelper,
578 mPointersEnterLeaveHelper, mDocument,
579 mIMEContentObserver, mAccessKeys)
581 void EventStateManager::ReleaseCurrentIMEContentObserver() {
582 if (mIMEContentObserver) {
583 mIMEContentObserver->DisconnectFromEventStateManager();
585 mIMEContentObserver = nullptr;
588 void EventStateManager::OnStartToObserveContent(
589 IMEContentObserver* aIMEContentObserver) {
590 if (mIMEContentObserver == aIMEContentObserver) {
591 return;
593 ReleaseCurrentIMEContentObserver();
594 mIMEContentObserver = aIMEContentObserver;
597 void EventStateManager::OnStopObservingContent(
598 IMEContentObserver* aIMEContentObserver) {
599 aIMEContentObserver->DisconnectFromEventStateManager();
600 NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
601 mIMEContentObserver = nullptr;
604 void EventStateManager::TryToFlushPendingNotificationsToIME() {
605 if (mIMEContentObserver) {
606 mIMEContentObserver->TryToFlushPendingNotifications(true);
610 static bool IsMessageMouseUserActivity(EventMessage aMessage) {
611 return aMessage == eMouseMove || aMessage == eMouseUp ||
612 aMessage == eMouseDown || aMessage == eMouseAuxClick ||
613 aMessage == eMouseDoubleClick || aMessage == eMouseClick ||
614 aMessage == eMouseActivate || aMessage == eMouseLongTap;
617 static bool IsMessageGamepadUserActivity(EventMessage aMessage) {
618 return aMessage == eGamepadButtonDown || aMessage == eGamepadButtonUp ||
619 aMessage == eGamepadAxisMove;
622 // static
623 bool EventStateManager::IsKeyboardEventUserActivity(WidgetEvent* aEvent) {
624 // We ignore things that shouldn't cause popups, but also things that look
625 // like shortcut presses. In some obscure cases these may actually be
626 // website input, but any meaningful website will have other input anyway,
627 // and we can't very well tell whether shortcut input was supposed to be
628 // directed at chrome or the document.
630 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
631 // Access keys should be treated as page interaction.
632 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
633 return true;
635 if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
636 keyEvent->IsMeta() || keyEvent->IsAlt()) {
637 return false;
639 // Deal with function keys:
640 switch (keyEvent->mKeyNameIndex) {
641 case KEY_NAME_INDEX_F1:
642 case KEY_NAME_INDEX_F2:
643 case KEY_NAME_INDEX_F3:
644 case KEY_NAME_INDEX_F4:
645 case KEY_NAME_INDEX_F5:
646 case KEY_NAME_INDEX_F6:
647 case KEY_NAME_INDEX_F7:
648 case KEY_NAME_INDEX_F8:
649 case KEY_NAME_INDEX_F9:
650 case KEY_NAME_INDEX_F10:
651 case KEY_NAME_INDEX_F11:
652 case KEY_NAME_INDEX_F12:
653 case KEY_NAME_INDEX_F13:
654 case KEY_NAME_INDEX_F14:
655 case KEY_NAME_INDEX_F15:
656 case KEY_NAME_INDEX_F16:
657 case KEY_NAME_INDEX_F17:
658 case KEY_NAME_INDEX_F18:
659 case KEY_NAME_INDEX_F19:
660 case KEY_NAME_INDEX_F20:
661 case KEY_NAME_INDEX_F21:
662 case KEY_NAME_INDEX_F22:
663 case KEY_NAME_INDEX_F23:
664 case KEY_NAME_INDEX_F24:
665 return false;
666 default:
667 return true;
671 static void OnTypingInteractionEnded() {
672 // We don't consider a single keystroke to be typing.
673 if (gTypingInteractionKeyPresses > 1) {
674 gTypingInteraction.mInteractionCount += gTypingInteractionKeyPresses;
675 gTypingInteraction.mInteractionTimeInMilliseconds += static_cast<uint32_t>(
676 std::ceil((gTypingEndTime - gTypingStartTime).ToMilliseconds()));
679 gTypingInteractionKeyPresses = 0;
680 gTypingStartTime = TimeStamp();
681 gTypingEndTime = TimeStamp();
684 static void HandleKeyUpInteraction(WidgetKeyboardEvent* aKeyEvent) {
685 if (EventStateManager::IsKeyboardEventUserActivity(aKeyEvent)) {
686 TimeStamp now = TimeStamp::Now();
687 if (gTypingEndTime.IsNull()) {
688 gTypingEndTime = now;
690 TimeDuration delay = now - gTypingEndTime;
691 // Has it been too long since the last keystroke to be considered typing?
692 if (gTypingInteractionKeyPresses > 0 &&
693 delay >
694 TimeDuration::FromMilliseconds(
695 StaticPrefs::browser_places_interactions_typing_timeout_ms())) {
696 OnTypingInteractionEnded();
698 gTypingInteractionKeyPresses++;
699 if (gTypingStartTime.IsNull()) {
700 gTypingStartTime = now;
702 gTypingEndTime = now;
706 nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
707 WidgetEvent* aEvent,
708 nsIFrame* aTargetFrame,
709 nsIContent* aTargetContent,
710 nsEventStatus* aStatus,
711 nsIContent* aOverrideClickTarget) {
712 NS_ENSURE_ARG_POINTER(aStatus);
713 NS_ENSURE_ARG(aPresContext);
714 if (!aEvent) {
715 NS_ERROR("aEvent is null. This should never happen.");
716 return NS_ERROR_NULL_POINTER;
719 NS_WARNING_ASSERTION(
720 !aTargetFrame || !aTargetFrame->GetContent() ||
721 aTargetFrame->GetContent() == aTargetContent ||
722 aTargetFrame->GetContent()->GetFlattenedTreeParent() ==
723 aTargetContent ||
724 aTargetFrame->IsGeneratedContentFrame(),
725 "aTargetFrame should be related with aTargetContent");
726 #if DEBUG
727 if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
728 nsCOMPtr<nsIContent> targetContent;
729 aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
730 MOZ_ASSERT(aTargetContent == targetContent,
731 "Unexpected target for generated content frame!");
733 #endif
735 mCurrentTarget = aTargetFrame;
736 mCurrentTargetContent = nullptr;
738 // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
739 // a page when user is not active doesn't change the state to active.
740 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
741 if (aEvent->IsTrusted() &&
742 ((mouseEvent && mouseEvent->IsReal() &&
743 IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
744 aEvent->mClass == eWheelEventClass ||
745 aEvent->mClass == ePointerEventClass ||
746 aEvent->mClass == eTouchEventClass ||
747 aEvent->mClass == eKeyboardEventClass ||
748 (aEvent->mClass == eDragEventClass && aEvent->mMessage == eDrop) ||
749 IsMessageGamepadUserActivity(aEvent->mMessage))) {
750 if (gMouseOrKeyboardEventCounter == 0) {
751 nsCOMPtr<nsIObserverService> obs =
752 mozilla::services::GetObserverService();
753 if (obs) {
754 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
755 UpdateUserActivityTimer();
758 ++gMouseOrKeyboardEventCounter;
760 nsCOMPtr<nsINode> node = aTargetContent;
761 if (node &&
762 ((aEvent->mMessage == eKeyUp && IsKeyboardEventUserActivity(aEvent)) ||
763 aEvent->mMessage == eMouseUp || aEvent->mMessage == eWheel ||
764 aEvent->mMessage == eTouchEnd || aEvent->mMessage == ePointerUp ||
765 aEvent->mMessage == eDrop)) {
766 Document* doc = node->OwnerDoc();
767 while (doc) {
768 doc->SetUserHasInteracted();
769 doc = nsContentUtils::IsChildOfSameType(doc)
770 ? doc->GetInProcessParentDocument()
771 : nullptr;
776 WheelTransaction::OnEvent(aEvent);
778 // Focus events don't necessarily need a frame.
779 if (!mCurrentTarget && !aTargetContent) {
780 NS_ERROR("mCurrentTarget and aTargetContent are null");
781 return NS_ERROR_NULL_POINTER;
783 #ifdef DEBUG
784 if (aEvent->HasDragEventMessage() && PointerLockManager::IsLocked()) {
785 NS_ASSERTION(PointerLockManager::IsLocked(),
786 "Pointer is locked. Drag events should be suppressed when "
787 "the pointer is locked.");
789 #endif
790 // Store last known screenPoint and clientPoint so pointer lock
791 // can use these values as constants.
792 if (aEvent->IsTrusted() &&
793 ((mouseEvent && mouseEvent->IsReal()) ||
794 aEvent->mClass == eWheelEventClass) &&
795 !PointerLockManager::IsLocked()) {
796 // XXX Probably doesn't matter much, but storing these in CSS pixels instead
797 // of device pixels means behavior can be a bit odd if you zoom while
798 // pointer-locked.
799 sLastScreenPoint =
800 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
801 .extract();
802 sLastClientPoint = Event::GetClientCoords(
803 aPresContext, aEvent, aEvent->mRefPoint, CSSIntPoint(0, 0));
806 *aStatus = nsEventStatus_eIgnore;
808 if (aEvent->mClass == eQueryContentEventClass) {
809 HandleQueryContentEvent(aEvent->AsQueryContentEvent());
810 return NS_OK;
813 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
814 if (touchEvent && mInTouchDrag) {
815 if (touchEvent->mMessage == eTouchMove) {
816 GenerateDragGesture(aPresContext, touchEvent);
817 } else {
818 mInTouchDrag = false;
819 StopTrackingDragGesture(true);
823 switch (aEvent->mMessage) {
824 case eContextMenu:
825 if (PointerLockManager::IsLocked()) {
826 return NS_ERROR_DOM_INVALID_STATE_ERR;
828 break;
829 case eMouseTouchDrag:
830 mInTouchDrag = true;
831 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
832 break;
833 case eMouseDown: {
834 switch (mouseEvent->mButton) {
835 case MouseButton::ePrimary:
836 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
837 mLastLeftMouseDownInfo.mClickCount = mouseEvent->mClickCount;
838 SetClickCount(mouseEvent, aStatus);
839 sNormalLMouseEventInProcess = true;
840 break;
841 case MouseButton::eMiddle:
842 mLastMiddleMouseDownInfo.mClickCount = mouseEvent->mClickCount;
843 SetClickCount(mouseEvent, aStatus);
844 break;
845 case MouseButton::eSecondary:
846 mLastRightMouseDownInfo.mClickCount = mouseEvent->mClickCount;
847 SetClickCount(mouseEvent, aStatus);
848 break;
850 NotifyTargetUserActivation(aEvent, aTargetContent);
851 break;
853 case eMouseUp: {
854 switch (mouseEvent->mButton) {
855 case MouseButton::ePrimary:
856 if (StaticPrefs::ui_click_hold_context_menus()) {
857 KillClickHoldTimer();
859 mInTouchDrag = false;
860 StopTrackingDragGesture(true);
861 sNormalLMouseEventInProcess = false;
862 // then fall through...
863 [[fallthrough]];
864 case MouseButton::eSecondary:
865 case MouseButton::eMiddle:
866 RefPtr<EventStateManager> esm =
867 ESMFromContentOrThis(aOverrideClickTarget);
868 esm->SetClickCount(mouseEvent, aStatus, aOverrideClickTarget);
869 break;
871 break;
873 case eMouseEnterIntoWidget:
874 PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
875 // In some cases on e10s eMouseEnterIntoWidget
876 // event was sent twice into child process of content.
877 // (From specific widget code (sending is not permanent) and
878 // from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
879 // IsCrossProcessForwardingStopped() helps to suppress sending accidental
880 // event from widget code.
881 aEvent->StopCrossProcessForwarding();
882 break;
883 case eMouseExitFromWidget:
884 // If this is a remote frame, we receive eMouseExitFromWidget from the
885 // parent the mouse exits our content. Since the parent may update the
886 // cursor while the mouse is outside our frame, and since PuppetWidget
887 // caches the current cursor internally, re-entering our content (say from
888 // over a window edge) wont update the cursor if the cached value and the
889 // current cursor match. So when the mouse exits a remote frame, clear the
890 // cached widget cursor so a proper update will occur when the mouse
891 // re-enters.
892 if (XRE_IsContentProcess()) {
893 ClearCachedWidgetCursor(mCurrentTarget);
896 // IsCrossProcessForwardingStopped() helps to suppress double event
897 // sending into process of content. For more information see comment
898 // above, at eMouseEnterIntoWidget case.
899 aEvent->StopCrossProcessForwarding();
901 // If the event is not a top-level window or puppet widget exit, then it's
902 // not really an exit --- we may have traversed widget boundaries but
903 // we're still in our toplevel window or puppet widget.
904 if (mouseEvent->mExitFrom.value() !=
905 WidgetMouseEvent::ePlatformTopLevel &&
906 mouseEvent->mExitFrom.value() != WidgetMouseEvent::ePuppet) {
907 // Treat it as a synthetic move so we don't generate spurious
908 // "exit" or "move" events. Any necessary "out" or "over" events
909 // will be generated by GenerateMouseEnterExit
910 mouseEvent->mMessage = eMouseMove;
911 mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
912 // then fall through...
913 } else {
914 MOZ_ASSERT_IF(XRE_IsParentProcess(),
915 mouseEvent->mExitFrom.value() ==
916 WidgetMouseEvent::ePlatformTopLevel);
917 MOZ_ASSERT_IF(XRE_IsContentProcess(), mouseEvent->mExitFrom.value() ==
918 WidgetMouseEvent::ePuppet);
919 // We should synthetize corresponding pointer events
920 GeneratePointerEnterExit(ePointerLeave, mouseEvent);
921 GenerateMouseEnterExit(mouseEvent);
922 // This is really an exit and should stop here
923 aEvent->mMessage = eVoidEvent;
924 break;
926 [[fallthrough]];
927 case eMouseMove:
928 case ePointerDown:
929 if (aEvent->mMessage == ePointerDown) {
930 PointerEventHandler::UpdateActivePointerState(mouseEvent,
931 aTargetContent);
932 PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
933 if (mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
934 NotifyTargetUserActivation(aEvent, aTargetContent);
937 LightDismissOpenPopovers(aEvent, aTargetContent);
939 [[fallthrough]];
940 case ePointerMove: {
941 if (!mInTouchDrag &&
942 PointerEventHandler::IsDragAndDropEnabled(*mouseEvent)) {
943 GenerateDragGesture(aPresContext, mouseEvent);
945 // on the Mac, GenerateDragGesture() may not return until the drag
946 // has completed and so |aTargetFrame| may have been deleted (moving
947 // a bookmark, for example). If this is the case, however, we know
948 // that ClearFrameRefs() has been called and it cleared out
949 // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
950 // into UpdateCursor().
951 UpdateCursor(aPresContext, mouseEvent, mCurrentTarget, aStatus);
953 UpdateLastRefPointOfMouseEvent(mouseEvent);
954 if (PointerLockManager::IsLocked()) {
955 ResetPointerToWindowCenterWhilePointerLocked(mouseEvent);
957 UpdateLastPointerPosition(mouseEvent);
959 GenerateMouseEnterExit(mouseEvent);
960 // Flush pending layout changes, so that later mouse move events
961 // will go to the right nodes.
962 FlushLayout(aPresContext);
963 break;
965 case ePointerUp:
966 LightDismissOpenPopovers(aEvent, aTargetContent);
967 break;
968 case ePointerGotCapture:
969 GenerateMouseEnterExit(mouseEvent);
970 break;
971 case eDragStart:
972 if (StaticPrefs::ui_click_hold_context_menus()) {
973 // an external drag gesture event came in, not generated internally
974 // by Gecko. Make sure we get rid of the click-hold timer.
975 KillClickHoldTimer();
977 break;
978 case eDragOver: {
979 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
980 MOZ_ASSERT(dragEvent);
981 if (dragEvent->mFlags.mIsSynthesizedForTests) {
982 dragEvent->InitDropEffectForTests();
984 // Send the enter/exit events before eDrop.
985 GenerateDragDropEnterExit(aPresContext, dragEvent);
986 break;
988 case eDrop:
989 if (aEvent->mFlags.mIsSynthesizedForTests) {
990 MOZ_ASSERT(aEvent->AsDragEvent());
991 aEvent->AsDragEvent()->InitDropEffectForTests();
993 break;
995 case eKeyPress: {
996 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
997 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
998 keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
999 // If the eKeyPress event will be sent to a remote process, this
1000 // process needs to wait reply from the remote process for checking if
1001 // preceding eKeyDown event is consumed. If preceding eKeyDown event
1002 // is consumed in the remote process, BrowserChild won't send the event
1003 // back to this process. So, only when this process receives a reply
1004 // eKeyPress event in BrowserParent, we should handle accesskey in this
1005 // process.
1006 if (IsTopLevelRemoteTarget(GetFocusedElement())) {
1007 // However, if there is no accesskey target for the key combination,
1008 // we don't need to wait reply from the remote process. Otherwise,
1009 // Mark the event as waiting reply from remote process and stop
1010 // propagation in this process.
1011 if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
1012 keyEvent->StopPropagation();
1013 keyEvent->MarkAsWaitingReplyFromRemoteProcess();
1016 // If the event target is in this process, we can handle accesskey now
1017 // since if preceding eKeyDown event was consumed, eKeyPress event
1018 // won't be dispatched by widget. So, coming eKeyPress event means
1019 // that the preceding eKeyDown event wasn't consumed in this case.
1020 else {
1021 AutoTArray<uint32_t, 10> accessCharCodes;
1022 keyEvent->GetAccessKeyCandidates(accessCharCodes);
1024 if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
1025 *aStatus = nsEventStatus_eConsumeNoDefault;
1030 // then fall through...
1031 [[fallthrough]];
1032 case eKeyDown:
1033 if (aEvent->mMessage == eKeyDown) {
1034 NotifyTargetUserActivation(aEvent, aTargetContent);
1036 [[fallthrough]];
1037 case eKeyUp: {
1038 Element* element = GetFocusedElement();
1039 if (element) {
1040 mCurrentTargetContent = element;
1043 // NOTE: Don't refer TextComposition::IsComposing() since UI Events
1044 // defines that KeyboardEvent.isComposing is true when it's
1045 // dispatched after compositionstart and compositionend.
1046 // TextComposition::IsComposing() is false even before
1047 // compositionend if there is no composing string.
1048 // And also don't expose other document's composition state.
1049 // A native IME context is typically shared by multiple documents.
1050 // So, don't use GetTextCompositionFor(nsIWidget*) here.
1051 RefPtr<TextComposition> composition =
1052 IMEStateManager::GetTextCompositionFor(aPresContext);
1053 aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
1055 // Widget may need to perform default action for specific keyboard
1056 // event if it's not consumed. In this case, widget has already marked
1057 // the event as "waiting reply from remote process". However, we need
1058 // to reset it if the target (focused content) isn't in a remote process
1059 // because PresShell needs to check if it's marked as so before
1060 // dispatching events into the DOM tree.
1061 if (aEvent->IsWaitingReplyFromRemoteProcess() &&
1062 !aEvent->PropagationStopped() && !IsTopLevelRemoteTarget(element)) {
1063 aEvent->ResetWaitingReplyFromRemoteProcessState();
1065 } break;
1066 case eWheel:
1067 case eWheelOperationStart:
1068 case eWheelOperationEnd: {
1069 NS_ASSERTION(aEvent->IsTrusted(),
1070 "Untrusted wheel event shouldn't be here");
1071 using DeltaModeCheckingState = WidgetWheelEvent::DeltaModeCheckingState;
1073 if (Element* element = GetFocusedElement()) {
1074 mCurrentTargetContent = element;
1077 if (aEvent->mMessage != eWheel) {
1078 break;
1081 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
1082 WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);
1084 // If we won't dispatch a DOM event for this event, nothing to do anymore.
1085 if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
1086 break;
1089 if (StaticPrefs::dom_event_wheel_deltaMode_lines_always_disabled()) {
1090 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unchecked;
1091 } else if (ShouldAlwaysUseLineDeltas()) {
1092 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Checked;
1093 } else {
1094 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unknown;
1097 // Init lineOrPageDelta values for line scroll events for some devices
1098 // on some platforms which might dispatch wheel events which don't
1099 // have lineOrPageDelta values. And also, if delta values are
1100 // customized by prefs, this recomputes them.
1101 DeltaAccumulator::GetInstance()->InitLineOrPageDelta(aTargetFrame, this,
1102 wheelEvent);
1103 } break;
1104 case eSetSelection: {
1105 RefPtr<Element> focuedElement = GetFocusedElement();
1106 IMEStateManager::HandleSelectionEvent(aPresContext, focuedElement,
1107 aEvent->AsSelectionEvent());
1108 break;
1110 case eContentCommandCut:
1111 case eContentCommandCopy:
1112 case eContentCommandPaste:
1113 case eContentCommandDelete:
1114 case eContentCommandUndo:
1115 case eContentCommandRedo:
1116 case eContentCommandPasteTransferable:
1117 case eContentCommandLookUpDictionary:
1118 DoContentCommandEvent(aEvent->AsContentCommandEvent());
1119 break;
1120 case eContentCommandInsertText:
1121 DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
1122 break;
1123 case eContentCommandScroll:
1124 DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
1125 break;
1126 case eCompositionStart:
1127 if (aEvent->IsTrusted()) {
1128 // If the event is trusted event, set the selected text to data of
1129 // composition event.
1130 WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
1131 WidgetQueryContentEvent querySelectedTextEvent(
1132 true, eQuerySelectedText, compositionEvent->mWidget);
1133 HandleQueryContentEvent(&querySelectedTextEvent);
1134 if (querySelectedTextEvent.FoundSelection()) {
1135 compositionEvent->mData = querySelectedTextEvent.mReply->DataRef();
1137 NS_ASSERTION(querySelectedTextEvent.Succeeded(),
1138 "Failed to get selected text");
1140 break;
1141 case eTouchStart:
1142 SetGestureDownPoint(aEvent->AsTouchEvent());
1143 break;
1144 case eTouchEnd:
1145 NotifyTargetUserActivation(aEvent, aTargetContent);
1146 break;
1147 default:
1148 break;
1150 return NS_OK;
1153 // Returns true if this event is likely an user activation for a link or
1154 // a link-like button, where modifier keys are likely be used for controlling
1155 // where the link is opened.
1157 // The modifiers associated with the user activation is used for controlling
1158 // where the `window.open` is opened into.
1159 static bool CanReflectModifiersToUserActivation(WidgetInputEvent* aEvent) {
1160 MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
1161 aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
1163 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
1164 if (keyEvent) {
1165 return keyEvent->CanReflectModifiersToUserActivation();
1168 return true;
1171 void EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
1172 nsIContent* aTargetContent) {
1173 if (!aEvent->IsTrusted()) {
1174 return;
1177 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1178 if (mouseEvent && !mouseEvent->IsReal()) {
1179 return;
1182 nsCOMPtr<nsINode> node = aTargetContent;
1183 if (!node) {
1184 return;
1187 Document* doc = node->OwnerDoc();
1188 if (!doc) {
1189 return;
1192 // Don't gesture activate for key events for keys which are likely
1193 // to be interaction with the browser, OS.
1194 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
1195 if (keyEvent && !keyEvent->CanUserGestureActivateTarget()) {
1196 return;
1199 // Touch gestures that end outside the drag target were touches that turned
1200 // into scroll/pan/swipe actions. We don't want to gesture activate on such
1201 // actions, we want to only gesture activate on touches that are taps.
1202 // That is, touches that end in roughly the same place that they started.
1203 if (aEvent->mMessage == eTouchEnd && aEvent->AsTouchEvent() &&
1204 IsEventOutsideDragThreshold(aEvent->AsTouchEvent())) {
1205 return;
1208 // Do not treat the click on scrollbar as a user interaction with the web
1209 // content.
1210 if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
1211 (aEvent->mMessage == eMouseDown || aEvent->mMessage == ePointerDown) &&
1212 aTargetContent->IsInNativeAnonymousSubtree()) {
1213 nsIContent* current = aTargetContent;
1214 do {
1215 nsIContent* root = current->GetClosestNativeAnonymousSubtreeRoot();
1216 if (!root) {
1217 break;
1219 if (root->IsXULElement(nsGkAtoms::scrollbar)) {
1220 return;
1222 current = root->GetParent();
1223 } while (current);
1226 MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
1227 aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
1228 UserActivation::Modifiers modifiers;
1229 if (WidgetInputEvent* inputEvent = aEvent->AsInputEvent()) {
1230 if (CanReflectModifiersToUserActivation(inputEvent)) {
1231 if (inputEvent->IsShift()) {
1232 modifiers.SetShift();
1234 if (inputEvent->IsMeta()) {
1235 modifiers.SetMeta();
1237 if (inputEvent->IsControl()) {
1238 modifiers.SetControl();
1240 if (inputEvent->IsAlt()) {
1241 modifiers.SetAlt();
1245 doc->NotifyUserGestureActivation(modifiers);
1248 // https://html.spec.whatwg.org/multipage/popover.html#popover-light-dismiss
1249 void EventStateManager::LightDismissOpenPopovers(WidgetEvent* aEvent,
1250 nsIContent* aTargetContent) {
1251 MOZ_ASSERT(aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp,
1252 "Light dismiss must be called for pointer up/down only");
1254 if (!StaticPrefs::dom_element_popover_enabled() || !aEvent->IsTrusted() ||
1255 !aTargetContent) {
1256 return;
1259 Element* topmostPopover = aTargetContent->OwnerDoc()->GetTopmostAutoPopover();
1260 if (!topmostPopover) {
1261 return;
1264 // Pointerdown: set document's popover pointerdown target to the result of
1265 // running topmost clicked popover given target.
1266 if (aEvent->mMessage == ePointerDown) {
1267 mPopoverPointerDownTarget = aTargetContent->GetTopmostClickedPopover();
1268 return;
1271 // Pointerup: hide open popovers.
1272 RefPtr<nsINode> ancestor = aTargetContent->GetTopmostClickedPopover();
1273 bool sameTarget = mPopoverPointerDownTarget == ancestor;
1274 mPopoverPointerDownTarget = nullptr;
1275 if (!sameTarget) {
1276 return;
1279 if (!ancestor) {
1280 ancestor = aTargetContent->OwnerDoc();
1282 RefPtr<Document> doc(ancestor->OwnerDoc());
1283 doc->HideAllPopoversUntil(*ancestor, false, true);
1286 already_AddRefed<EventStateManager> EventStateManager::ESMFromContentOrThis(
1287 nsIContent* aContent) {
1288 if (aContent) {
1289 PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
1290 if (presShell) {
1291 nsPresContext* prescontext = presShell->GetPresContext();
1292 if (prescontext) {
1293 RefPtr<EventStateManager> esm = prescontext->EventStateManager();
1294 if (esm) {
1295 return esm.forget();
1301 RefPtr<EventStateManager> esm = this;
1302 return esm.forget();
1305 EventStateManager::LastMouseDownInfo& EventStateManager::GetLastMouseDownInfo(
1306 int16_t aButton) {
1307 switch (aButton) {
1308 case MouseButton::ePrimary:
1309 return mLastLeftMouseDownInfo;
1310 case MouseButton::eMiddle:
1311 return mLastMiddleMouseDownInfo;
1312 case MouseButton::eSecondary:
1313 return mLastRightMouseDownInfo;
1314 default:
1315 MOZ_ASSERT_UNREACHABLE("This button shouldn't use this method");
1316 return mLastLeftMouseDownInfo;
1320 void EventStateManager::HandleQueryContentEvent(
1321 WidgetQueryContentEvent* aEvent) {
1322 switch (aEvent->mMessage) {
1323 case eQuerySelectedText:
1324 case eQueryTextContent:
1325 case eQueryCaretRect:
1326 case eQueryTextRect:
1327 case eQueryEditorRect:
1328 if (!IsTargetCrossProcess(aEvent)) {
1329 break;
1331 // Will not be handled locally, remote the event
1332 GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
1333 return;
1334 // Following events have not been supported in e10s mode yet.
1335 case eQueryContentState:
1336 case eQuerySelectionAsTransferable:
1337 case eQueryCharacterAtPoint:
1338 case eQueryDOMWidgetHittest:
1339 case eQueryTextRectArray:
1340 break;
1341 default:
1342 return;
1345 // If there is an IMEContentObserver, we need to handle QueryContentEvent
1346 // with it.
1347 if (mIMEContentObserver) {
1348 RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
1349 contentObserver->HandleQueryContentEvent(aEvent);
1350 return;
1353 ContentEventHandler handler(mPresContext);
1354 handler.HandleQueryContentEvent(aEvent);
1357 static AccessKeyType GetAccessKeyTypeFor(nsISupports* aDocShell) {
1358 nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
1359 if (!treeItem) {
1360 return AccessKeyType::eNone;
1363 switch (treeItem->ItemType()) {
1364 case nsIDocShellTreeItem::typeChrome:
1365 return AccessKeyType::eChrome;
1366 case nsIDocShellTreeItem::typeContent:
1367 return AccessKeyType::eContent;
1368 default:
1369 return AccessKeyType::eNone;
1373 static bool IsAccessKeyTarget(Element* aElement, nsAString& aKey) {
1374 // Use GetAttr because we want Unicode case=insensitive matching
1375 // XXXbz shouldn't this be case-sensitive, per spec?
1376 nsString contentKey;
1377 if (!aElement || !aElement->GetAttr(nsGkAtoms::accesskey, contentKey) ||
1378 !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator)) {
1379 return false;
1382 if (!aElement->IsXULElement()) {
1383 return true;
1386 // For XUL we do visibility checks.
1387 nsIFrame* frame = aElement->GetPrimaryFrame();
1388 if (!frame) {
1389 return false;
1392 if (frame->IsFocusable()) {
1393 return true;
1396 if (!frame->IsVisibleConsideringAncestors()) {
1397 return false;
1400 // XUL controls can be activated.
1401 nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl();
1402 if (control) {
1403 return true;
1406 // XUL label elements are never focusable, so we need to check for them
1407 // explicitly before giving up.
1408 if (aElement->IsXULElement(nsGkAtoms::label)) {
1409 return true;
1412 return false;
1415 bool EventStateManager::CheckIfEventMatchesAccessKey(
1416 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext) {
1417 AutoTArray<uint32_t, 10> accessCharCodes;
1418 aEvent->GetAccessKeyCandidates(accessCharCodes);
1419 return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, accessCharCodes,
1420 nullptr, eAccessKeyProcessingNormal,
1421 false);
1424 bool EventStateManager::LookForAccessKeyAndExecute(
1425 nsTArray<uint32_t>& aAccessCharCodes, bool aIsTrustedEvent, bool aIsRepeat,
1426 bool aExecute) {
1427 int32_t count, start = -1;
1428 if (Element* focusedElement = GetFocusedElement()) {
1429 start = mAccessKeys.IndexOf(focusedElement);
1430 if (start == -1 && focusedElement->IsInNativeAnonymousSubtree()) {
1431 start = mAccessKeys.IndexOf(Element::FromNodeOrNull(
1432 focusedElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()));
1435 RefPtr<Element> element;
1436 int32_t length = mAccessKeys.Count();
1437 for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
1438 uint32_t ch = aAccessCharCodes[i];
1439 nsAutoString accessKey;
1440 AppendUCS4ToUTF16(ch, accessKey);
1441 for (count = 1; count <= length; ++count) {
1442 // mAccessKeys always stores Element instances.
1443 MOZ_DIAGNOSTIC_ASSERT(length == mAccessKeys.Count());
1444 element = mAccessKeys[(start + count) % length];
1445 if (IsAccessKeyTarget(element, accessKey)) {
1446 if (!aExecute) {
1447 return true;
1449 Document* doc = element->OwnerDoc();
1450 const bool shouldActivate = [&] {
1451 if (!StaticPrefs::accessibility_accesskeycausesactivation()) {
1452 return false;
1454 if (aIsRepeat && nsContentUtils::IsChromeDoc(doc)) {
1455 return false;
1458 // XXXedgar, Bug 1700646, maybe we could use other data structure to
1459 // make searching target with same accesskey easier, and current setup
1460 // could not ensure we cycle the target with tree order.
1461 int32_t j = 0;
1462 while (++j < length) {
1463 Element* el = mAccessKeys[(start + count + j) % length];
1464 if (IsAccessKeyTarget(el, accessKey)) {
1465 return false;
1468 return true;
1469 }();
1471 // TODO(bug 1641171): This shouldn't be needed if we considered the
1472 // accesskey combination properly.
1473 if (aIsTrustedEvent) {
1474 doc->NotifyUserGestureActivation();
1477 auto result =
1478 element->PerformAccesskey(shouldActivate, aIsTrustedEvent);
1479 if (result.isOk()) {
1480 if (result.unwrap() && aIsTrustedEvent) {
1481 // If this is a child process, inform the parent that we want the
1482 // focus, but pass false since we don't want to change the window
1483 // order.
1484 nsIDocShell* docShell = mPresContext->GetDocShell();
1485 nsCOMPtr<nsIBrowserChild> child =
1486 docShell ? docShell->GetBrowserChild() : nullptr;
1487 if (child) {
1488 child->SendRequestFocus(false, CallerType::System);
1491 return true;
1496 return false;
1499 // static
1500 void EventStateManager::GetAccessKeyLabelPrefix(Element* aElement,
1501 nsAString& aPrefix) {
1502 aPrefix.Truncate();
1503 nsAutoString separator, modifierText;
1504 nsContentUtils::GetModifierSeparatorText(separator);
1506 AccessKeyType accessKeyType =
1507 GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
1508 if (accessKeyType == AccessKeyType::eNone) {
1509 return;
1511 Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
1512 if (modifiers == MODIFIER_NONE) {
1513 return;
1516 if (modifiers & MODIFIER_CONTROL) {
1517 nsContentUtils::GetControlText(modifierText);
1518 aPrefix.Append(modifierText + separator);
1520 if (modifiers & MODIFIER_META) {
1521 nsContentUtils::GetCommandOrWinText(modifierText);
1522 aPrefix.Append(modifierText + separator);
1524 if (modifiers & MODIFIER_ALT) {
1525 nsContentUtils::GetAltText(modifierText);
1526 aPrefix.Append(modifierText + separator);
1528 if (modifiers & MODIFIER_SHIFT) {
1529 nsContentUtils::GetShiftText(modifierText);
1530 aPrefix.Append(modifierText + separator);
1534 struct MOZ_STACK_CLASS AccessKeyInfo {
1535 WidgetKeyboardEvent* event;
1536 nsTArray<uint32_t>& charCodes;
1538 AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes)
1539 : event(aEvent), charCodes(aCharCodes) {}
1542 bool EventStateManager::WalkESMTreeToHandleAccessKey(
1543 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext,
1544 nsTArray<uint32_t>& aAccessCharCodes, nsIDocShellTreeItem* aBubbledFrom,
1545 ProcessingAccessKeyState aAccessKeyState, bool aExecute) {
1546 EnsureDocument(mPresContext);
1547 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
1548 if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
1549 return false;
1551 AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
1552 if (accessKeyType == AccessKeyType::eNone) {
1553 return false;
1555 // Alt or other accesskey modifier is down, we may need to do an accesskey.
1556 if (mAccessKeys.Count() > 0 &&
1557 aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
1558 // Someone registered an accesskey. Find and activate it.
1559 if (LookForAccessKeyAndExecute(aAccessCharCodes, aEvent->IsTrusted(),
1560 aEvent->mIsRepeat, aExecute)) {
1561 return true;
1565 int32_t childCount;
1566 docShell->GetInProcessChildCount(&childCount);
1567 for (int32_t counter = 0; counter < childCount; counter++) {
1568 // Not processing the child which bubbles up the handling
1569 nsCOMPtr<nsIDocShellTreeItem> subShellItem;
1570 docShell->GetInProcessChildAt(counter, getter_AddRefs(subShellItem));
1571 if (aAccessKeyState == eAccessKeyProcessingUp &&
1572 subShellItem == aBubbledFrom) {
1573 continue;
1576 nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
1577 if (subDS && IsShellVisible(subDS)) {
1578 // Guarantee subPresShell lifetime while we're handling access key
1579 // since somebody may assume that it won't be deleted before the
1580 // corresponding nsPresContext and EventStateManager.
1581 RefPtr<PresShell> subPresShell = subDS->GetPresShell();
1583 // Docshells need not have a presshell (eg. display:none
1584 // iframes, docshells in transition between documents, etc).
1585 if (!subPresShell) {
1586 // Oh, well. Just move on to the next child
1587 continue;
1590 RefPtr<nsPresContext> subPresContext = subPresShell->GetPresContext();
1592 RefPtr<EventStateManager> esm =
1593 static_cast<EventStateManager*>(subPresContext->EventStateManager());
1595 if (esm && esm->WalkESMTreeToHandleAccessKey(
1596 aEvent, subPresContext, aAccessCharCodes, nullptr,
1597 eAccessKeyProcessingDown, aExecute)) {
1598 return true;
1601 } // if end . checking all sub docshell ends here.
1603 // bubble up the process to the parent docshell if necessary
1604 if (eAccessKeyProcessingDown != aAccessKeyState) {
1605 nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
1606 docShell->GetInProcessParent(getter_AddRefs(parentShellItem));
1607 nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
1608 if (parentDS) {
1609 // Guarantee parentPresShell lifetime while we're handling access key
1610 // since somebody may assume that it won't be deleted before the
1611 // corresponding nsPresContext and EventStateManager.
1612 RefPtr<PresShell> parentPresShell = parentDS->GetPresShell();
1613 NS_ASSERTION(parentPresShell,
1614 "Our PresShell exists but the parent's does not?");
1616 RefPtr<nsPresContext> parentPresContext =
1617 parentPresShell->GetPresContext();
1618 NS_ASSERTION(parentPresContext, "PresShell without PresContext");
1620 RefPtr<EventStateManager> esm = static_cast<EventStateManager*>(
1621 parentPresContext->EventStateManager());
1622 if (esm && esm->WalkESMTreeToHandleAccessKey(
1623 aEvent, parentPresContext, aAccessCharCodes, docShell,
1624 eAccessKeyProcessingDown, aExecute)) {
1625 return true;
1628 } // if end. bubble up process
1630 // If the content access key modifier is pressed, try remote children
1631 if (aExecute &&
1632 aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
1633 mDocument && mDocument->GetWindow()) {
1634 // If the focus is currently on a node with a BrowserParent, the key event
1635 // should've gotten forwarded to the child process and HandleAccessKey
1636 // called from there.
1637 if (BrowserParent::GetFrom(GetFocusedElement())) {
1638 // If access key may be only in remote contents, this method won't handle
1639 // access key synchronously. In this case, only reply event should reach
1640 // here.
1641 MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
1642 !aEvent->IsWaitingReplyFromRemoteProcess());
1644 // If focus is somewhere else, then we need to check the remote children.
1645 // However, if the event has already been handled in a remote process,
1646 // then, focus is moved from the remote process after posting the event.
1647 // In such case, we shouldn't retry to handle access keys in remote
1648 // processes.
1649 else if (!aEvent->IsHandledInRemoteProcess()) {
1650 AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
1651 nsContentUtils::CallOnAllRemoteChildren(
1652 mDocument->GetWindow(),
1653 [&accessKeyInfo](BrowserParent* aBrowserParent) -> CallState {
1654 // Only forward accesskeys for the active tab.
1655 if (aBrowserParent->GetDocShellIsActive()) {
1656 // Even if there is no target for the accesskey in this process,
1657 // the event may match with a content accesskey. If so, the
1658 // keyboard event should be handled with reply event for
1659 // preventing double action. (e.g., Alt+Shift+F on Windows may
1660 // focus a content in remote and open "File" menu.)
1661 accessKeyInfo.event->StopPropagation();
1662 accessKeyInfo.event->MarkAsWaitingReplyFromRemoteProcess();
1663 aBrowserParent->HandleAccessKey(*accessKeyInfo.event,
1664 accessKeyInfo.charCodes);
1665 return CallState::Stop;
1668 return CallState::Continue;
1673 return false;
1674 } // end of HandleAccessKey
1676 static BrowserParent* GetBrowserParentAncestor(BrowserParent* aBrowserParent) {
1677 MOZ_ASSERT(aBrowserParent);
1679 BrowserBridgeParent* bbp = aBrowserParent->GetBrowserBridgeParent();
1680 if (!bbp) {
1681 return nullptr;
1684 return bbp->Manager();
1687 static void DispatchCrossProcessMouseExitEvents(WidgetMouseEvent* aMouseEvent,
1688 BrowserParent* aRemoteTarget,
1689 BrowserParent* aStopAncestor,
1690 bool aIsReallyExit) {
1691 MOZ_ASSERT(aMouseEvent);
1692 MOZ_ASSERT(aRemoteTarget);
1693 MOZ_ASSERT(aRemoteTarget != aStopAncestor);
1694 MOZ_ASSERT_IF(aStopAncestor, nsContentUtils::GetCommonBrowserParentAncestor(
1695 aRemoteTarget, aStopAncestor));
1697 while (aRemoteTarget != aStopAncestor) {
1698 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1699 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
1700 aMouseEvent->mRelatedTarget);
1701 mouseExitEvent->mExitFrom =
1702 Some(aIsReallyExit ? WidgetMouseEvent::ePuppet
1703 : WidgetMouseEvent::ePuppetParentToPuppetChild);
1704 aRemoteTarget->SendRealMouseEvent(*mouseExitEvent);
1706 aRemoteTarget = GetBrowserParentAncestor(aRemoteTarget);
1710 void EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
1711 BrowserParent* aRemoteTarget,
1712 nsEventStatus* aStatus) {
1713 MOZ_ASSERT(aEvent);
1714 MOZ_ASSERT(aRemoteTarget);
1715 MOZ_ASSERT(aStatus);
1717 BrowserParent* remote = aRemoteTarget;
1719 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1720 bool isContextMenuKey = mouseEvent && mouseEvent->IsContextMenuKeyEvent();
1721 if (aEvent->mClass == eKeyboardEventClass || isContextMenuKey) {
1722 // APZ attaches a LayersId to hit-testable events, for keyboard events,
1723 // we use focus.
1724 BrowserParent* preciseRemote = BrowserParent::GetFocused();
1725 if (preciseRemote) {
1726 remote = preciseRemote;
1728 // else there is a race between layout and focus tracking,
1729 // so fall back to delivering the event to the topmost child process.
1730 } else if (aEvent->mLayersId.IsValid()) {
1731 BrowserParent* preciseRemote =
1732 BrowserParent::GetBrowserParentFromLayersId(aEvent->mLayersId);
1733 if (preciseRemote) {
1734 remote = preciseRemote;
1736 // else there is a race between APZ and the LayersId to BrowserParent
1737 // mapping, so fall back to delivering the event to the topmost child
1738 // process.
1741 switch (aEvent->mClass) {
1742 case eMouseEventClass: {
1743 BrowserParent* oldRemote = BrowserParent::GetLastMouseRemoteTarget();
1745 // If this is a eMouseExitFromWidget event, need to redirect the event to
1746 // the last remote and and notify all its ancestors about the exit, if
1747 // any.
1748 if (mouseEvent->mMessage == eMouseExitFromWidget) {
1749 MOZ_ASSERT(mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet);
1750 MOZ_ASSERT(mouseEvent->mReason == WidgetMouseEvent::eReal);
1751 MOZ_ASSERT(!mouseEvent->mLayersId.IsValid());
1752 MOZ_ASSERT(remote->GetBrowserHost());
1754 if (oldRemote && oldRemote != remote) {
1755 Unused << NS_WARN_IF(nsContentUtils::GetCommonBrowserParentAncestor(
1756 remote, oldRemote) != remote);
1757 remote = oldRemote;
1760 DispatchCrossProcessMouseExitEvents(mouseEvent, remote, nullptr, true);
1761 return;
1764 if (BrowserParent* pointerLockedRemote =
1765 PointerLockManager::GetLockedRemoteTarget()) {
1766 remote = pointerLockedRemote;
1767 } else if (BrowserParent* pointerCapturedRemote =
1768 PointerEventHandler::GetPointerCapturingRemoteTarget(
1769 mouseEvent->pointerId)) {
1770 remote = pointerCapturedRemote;
1771 } else if (BrowserParent* capturingRemote =
1772 PresShell::GetCapturingRemoteTarget()) {
1773 remote = capturingRemote;
1776 // If a mouse is over a remote target A, and then moves to
1777 // remote target B, we'd deliver the event directly to remote target B
1778 // after the moving, A would never get notified that the mouse left.
1779 // So we generate a exit event to notify A after the move.
1780 // XXXedgar, if the synthesized mouse events could deliver to the correct
1781 // process directly (see
1782 // https://bugzilla.mozilla.org/show_bug.cgi?id=1549355), we probably
1783 // don't need to check mReason then.
1784 if (mouseEvent->mReason == WidgetMouseEvent::eReal &&
1785 remote != oldRemote) {
1786 MOZ_ASSERT(mouseEvent->mMessage != eMouseExitFromWidget);
1787 if (oldRemote) {
1788 BrowserParent* commonAncestor =
1789 nsContentUtils::GetCommonBrowserParentAncestor(remote, oldRemote);
1790 if (commonAncestor == oldRemote) {
1791 // Mouse moves to the inner OOP frame, it is not a really exit.
1792 DispatchCrossProcessMouseExitEvents(
1793 mouseEvent, GetBrowserParentAncestor(remote),
1794 GetBrowserParentAncestor(commonAncestor), false);
1795 } else if (commonAncestor == remote) {
1796 // Mouse moves to the outer OOP frame, it is a really exit.
1797 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1798 commonAncestor, true);
1799 } else {
1800 // Mouse moves to OOP frame in other subtree, it is a really exit,
1801 // need to notify all its ancestors before common ancestor about the
1802 // exit.
1803 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1804 commonAncestor, true);
1805 if (commonAncestor) {
1806 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1807 CreateMouseOrPointerWidgetEvent(mouseEvent,
1808 eMouseExitFromWidget,
1809 mouseEvent->mRelatedTarget);
1810 mouseExitEvent->mExitFrom =
1811 Some(WidgetMouseEvent::ePuppetParentToPuppetChild);
1812 commonAncestor->SendRealMouseEvent(*mouseExitEvent);
1817 if (mouseEvent->mMessage != eMouseExitFromWidget &&
1818 mouseEvent->mMessage != eMouseEnterIntoWidget) {
1819 // This is to make cursor would be updated correctly.
1820 remote->MouseEnterIntoWidget();
1824 remote->SendRealMouseEvent(*mouseEvent);
1825 return;
1827 case eKeyboardEventClass: {
1828 auto* keyboardEvent = aEvent->AsKeyboardEvent();
1829 if (aEvent->mMessage == eKeyUp) {
1830 HandleKeyUpInteraction(keyboardEvent);
1832 remote->SendRealKeyEvent(*keyboardEvent);
1833 return;
1835 case eWheelEventClass: {
1836 if (BrowserParent* pointerLockedRemote =
1837 PointerLockManager::GetLockedRemoteTarget()) {
1838 remote = pointerLockedRemote;
1840 remote->SendMouseWheelEvent(*aEvent->AsWheelEvent());
1841 return;
1843 case eTouchEventClass: {
1844 // Let the child process synthesize a mouse event if needed, and
1845 // ensure we don't synthesize one in this process.
1846 *aStatus = nsEventStatus_eConsumeNoDefault;
1847 remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
1848 return;
1850 case eDragEventClass: {
1851 RefPtr<BrowserParent> browserParent = remote;
1852 browserParent->Manager()->MaybeInvokeDragSession(browserParent,
1853 aEvent->mMessage);
1855 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1856 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
1857 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
1858 nsCOMPtr<nsIPrincipal> principal;
1859 nsCOMPtr<nsIContentSecurityPolicy> csp;
1861 if (dragSession) {
1862 dragSession->DragEventDispatchedToChildProcess();
1863 dragSession->GetDragAction(&action);
1864 dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
1865 dragSession->GetCsp(getter_AddRefs(csp));
1866 RefPtr<DataTransfer> initialDataTransfer =
1867 dragSession->GetDataTransfer();
1868 if (initialDataTransfer) {
1869 dropEffect = initialDataTransfer->DropEffectInt();
1873 browserParent->SendRealDragEvent(*aEvent->AsDragEvent(), action,
1874 dropEffect, principal, csp);
1875 return;
1877 default: {
1878 MOZ_CRASH("Attempt to send non-whitelisted event?");
1883 bool EventStateManager::IsRemoteTarget(nsIContent* target) {
1884 return BrowserParent::GetFrom(target) || BrowserBridgeChild::GetFrom(target);
1887 bool EventStateManager::IsTopLevelRemoteTarget(nsIContent* target) {
1888 return !!BrowserParent::GetFrom(target);
1891 bool EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
1892 nsEventStatus* aStatus) {
1893 if (!aEvent->CanBeSentToRemoteProcess()) {
1894 return false;
1897 MOZ_ASSERT(!aEvent->HasBeenPostedToRemoteProcess(),
1898 "Why do we need to post same event to remote processes again?");
1900 // Collect the remote event targets we're going to forward this
1901 // event to.
1903 // NB: the elements of |remoteTargets| must be unique, for correctness.
1904 AutoTArray<RefPtr<BrowserParent>, 1> remoteTargets;
1905 if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) {
1906 // If this event only has one target, and it's remote, add it to
1907 // the array.
1908 nsIFrame* frame = aEvent->mMessage == eDragExit
1909 ? sLastDragOverFrame.GetFrame()
1910 : GetEventTarget();
1911 nsIContent* target = frame ? frame->GetContent() : nullptr;
1912 if (BrowserParent* remoteTarget = BrowserParent::GetFrom(target)) {
1913 remoteTargets.AppendElement(remoteTarget);
1915 } else {
1916 // This is a touch event with possibly multiple touch points.
1917 // Each touch point may have its own target. So iterate through
1918 // all of them and collect the unique set of targets for event
1919 // forwarding.
1921 // This loop is similar to the one used in
1922 // PresShell::DispatchTouchEvent().
1923 const WidgetTouchEvent::TouchArray& touches =
1924 aEvent->AsTouchEvent()->mTouches;
1925 for (uint32_t i = 0; i < touches.Length(); ++i) {
1926 Touch* touch = touches[i];
1927 // NB: the |mChanged| check is an optimization, subprocesses can
1928 // compute this for themselves. If the touch hasn't changed, we
1929 // may be able to avoid forwarding the event entirely (which is
1930 // not free).
1931 if (!touch || !touch->mChanged) {
1932 continue;
1934 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
1935 if (!targetPtr) {
1936 continue;
1938 nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr);
1939 BrowserParent* remoteTarget = BrowserParent::GetFrom(target);
1940 if (remoteTarget && !remoteTargets.Contains(remoteTarget)) {
1941 remoteTargets.AppendElement(remoteTarget);
1946 if (remoteTargets.Length() == 0) {
1947 return false;
1950 // Dispatch the event to the remote target.
1951 for (uint32_t i = 0; i < remoteTargets.Length(); ++i) {
1952 DispatchCrossProcessEvent(aEvent, remoteTargets[i], aStatus);
1954 return aEvent->HasBeenPostedToRemoteProcess();
1958 // CreateClickHoldTimer
1960 // Fire off a timer for determining if the user wants click-hold. This timer
1961 // is a one-shot that will be cancelled when the user moves enough to fire
1962 // a drag.
1964 void EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext,
1965 nsIFrame* inDownFrame,
1966 WidgetGUIEvent* inMouseDownEvent) {
1967 if (!inMouseDownEvent->IsTrusted() ||
1968 IsTopLevelRemoteTarget(mGestureDownContent) ||
1969 PointerLockManager::IsLocked()) {
1970 return;
1973 // just to be anal (er, safe)
1974 if (mClickHoldTimer) {
1975 mClickHoldTimer->Cancel();
1976 mClickHoldTimer = nullptr;
1979 // if content clicked on has a popup, don't even start the timer
1980 // since we'll end up conflicting and both will show.
1981 if (mGestureDownContent &&
1982 nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None,
1983 nsGkAtoms::popup)) {
1984 return;
1987 int32_t clickHoldDelay = StaticPrefs::ui_click_hold_context_menus_delay();
1988 NS_NewTimerWithFuncCallback(
1989 getter_AddRefs(mClickHoldTimer), sClickHoldCallback, this, clickHoldDelay,
1990 nsITimer::TYPE_ONE_SHOT, "EventStateManager::CreateClickHoldTimer");
1991 } // CreateClickHoldTimer
1994 // KillClickHoldTimer
1996 // Stop the timer that would show the context menu dead in its tracks
1998 void EventStateManager::KillClickHoldTimer() {
1999 if (mClickHoldTimer) {
2000 mClickHoldTimer->Cancel();
2001 mClickHoldTimer = nullptr;
2006 // sClickHoldCallback
2008 // This fires after the mouse has been down for a certain length of time.
2010 void EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM) {
2011 RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM);
2012 if (self) {
2013 self->FireContextClick();
2016 // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling
2017 // ClosePopup();
2019 } // sAutoHideCallback
2022 // FireContextClick
2024 // If we're this far, our timer has fired, which means the mouse has been down
2025 // for a certain period of time and has not moved enough to generate a
2026 // dragGesture. We can be certain the user wants a context-click at this stage,
2027 // so generate a dom event and fire it in.
2029 // After the event fires, check if PreventDefault() has been set on the event
2030 // which means that someone either ate the event or put up a context menu. This
2031 // is our cue to stop tracking the drag gesture. If we always did this,
2032 // draggable items w/out a context menu wouldn't be draggable after a certain
2033 // length of time, which is _not_ what we want.
2035 void EventStateManager::FireContextClick() {
2036 if (!mGestureDownContent || !mPresContext || PointerLockManager::IsLocked()) {
2037 return;
2040 #ifdef XP_MACOSX
2041 // Hack to ensure that we don't show a context menu when the user
2042 // let go of the mouse after a long cpu-hogging operation prevented
2043 // us from handling any OS events. See bug 117589.
2044 if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState,
2045 kCGMouseButtonLeft))
2046 return;
2047 #endif
2049 nsEventStatus status = nsEventStatus_eIgnore;
2051 // Dispatch to the DOM. We have to fake out the ESM and tell it that the
2052 // current target frame is actually where the mouseDown occurred, otherwise it
2053 // will use the frame the mouse is currently over which may or may not be
2054 // the same. (Note: saari and I have decided that we don't have to reset
2055 // |mCurrentTarget| when we're through because no one else is doing anything
2056 // more with this event and it will get reset on the very next event to the
2057 // correct frame).
2058 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent);
2059 // make sure the widget sticks around
2060 nsCOMPtr<nsIWidget> targetWidget;
2061 if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) {
2062 NS_ASSERTION(
2063 mPresContext == mCurrentTarget->PresContext(),
2064 "a prescontext returned a primary frame that didn't belong to it?");
2066 // before dispatching, check that we're not on something that
2067 // doesn't get a context menu
2068 bool allowedToDispatch = true;
2070 if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar,
2071 nsGkAtoms::scrollbarbutton,
2072 nsGkAtoms::button)) {
2073 allowedToDispatch = false;
2074 } else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) {
2075 // a <toolbarbutton> that has the container attribute set
2076 // will already have its own dropdown.
2077 if (nsContentUtils::HasNonEmptyAttr(
2078 mGestureDownContent, kNameSpaceID_None, nsGkAtoms::container)) {
2079 allowedToDispatch = false;
2080 } else {
2081 // If the toolbar button has an open menu, don't attempt to open
2082 // a second menu
2083 if (mGestureDownContent->IsElement() &&
2084 mGestureDownContent->AsElement()->AttrValueIs(
2085 kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true,
2086 eCaseMatters)) {
2087 allowedToDispatch = false;
2090 } else if (mGestureDownContent->IsHTMLElement()) {
2091 nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent));
2093 if (formCtrl) {
2094 allowedToDispatch =
2095 formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
2096 formCtrl->ControlType() == FormControlType::InputFile;
2097 } else if (mGestureDownContent->IsAnyOfHTMLElements(
2098 nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {
2099 allowedToDispatch = false;
2103 if (allowedToDispatch) {
2104 // init the event while mCurrentTarget is still good
2105 WidgetMouseEvent event(true, eContextMenu, targetWidget,
2106 WidgetMouseEvent::eReal);
2107 event.mClickCount = 1;
2108 FillInEventFromGestureDown(&event);
2110 // stop selection tracking, we're in control now
2111 if (mCurrentTarget) {
2112 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
2114 if (frameSel && frameSel->GetDragState()) {
2115 // note that this can cause selection changed events to fire if we're
2116 // in a text field, which will null out mCurrentTarget
2117 frameSel->SetDragState(false);
2121 AutoHandlingUserInputStatePusher userInpStatePusher(true, &event);
2123 // dispatch to DOM
2124 RefPtr<nsIContent> gestureDownContent = mGestureDownContent;
2125 RefPtr<nsPresContext> presContext = mPresContext;
2126 EventDispatcher::Dispatch(gestureDownContent, presContext, &event,
2127 nullptr, &status);
2129 // We don't need to dispatch to frame handling because no frames
2130 // watch eContextMenu except for nsMenuFrame and that's only for
2131 // dismissal. That's just as well since we don't really know
2132 // which frame to send it to.
2136 // now check if the event has been handled. If so, stop tracking a drag
2137 if (status == nsEventStatus_eConsumeNoDefault) {
2138 StopTrackingDragGesture(true);
2141 KillClickHoldTimer();
2143 } // FireContextClick
2146 // BeginTrackingDragGesture
2148 // Record that the mouse has gone down and that we should move to TRACKING state
2149 // of d&d gesture tracker.
2151 // We also use this to track click-hold context menus. When the mouse goes down,
2152 // fire off a short timer. If the timer goes off and we have yet to fire the
2153 // drag gesture (ie, the mouse hasn't moved a certain distance), then we can
2154 // assume the user wants a click-hold, so fire a context-click event. We only
2155 // want to cancel the drag gesture if the context-click event is handled.
2157 void EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext,
2158 WidgetMouseEvent* inDownEvent,
2159 nsIFrame* inDownFrame) {
2160 if (!inDownEvent->mWidget) {
2161 return;
2164 // Note that |inDownEvent| could be either a mouse down event or a
2165 // synthesized mouse move event.
2166 SetGestureDownPoint(inDownEvent);
2168 if (inDownFrame) {
2169 inDownFrame->GetContentForEvent(inDownEvent,
2170 getter_AddRefs(mGestureDownContent));
2172 mGestureDownFrameOwner = inDownFrame->GetContent();
2173 if (!mGestureDownFrameOwner) {
2174 mGestureDownFrameOwner = mGestureDownContent;
2177 mGestureModifiers = inDownEvent->mModifiers;
2178 mGestureDownButtons = inDownEvent->mButtons;
2180 if (inDownEvent->mMessage != eMouseTouchDrag &&
2181 StaticPrefs::ui_click_hold_context_menus()) {
2182 // fire off a timer to track click-hold
2183 CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
2187 void EventStateManager::SetGestureDownPoint(WidgetGUIEvent* aEvent) {
2188 mGestureDownPoint =
2189 GetEventRefPoint(aEvent) + aEvent->mWidget->WidgetToScreenOffset();
2192 LayoutDeviceIntPoint EventStateManager::GetEventRefPoint(
2193 WidgetEvent* aEvent) const {
2194 auto touchEvent = aEvent->AsTouchEvent();
2195 return (touchEvent && !touchEvent->mTouches.IsEmpty())
2196 ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
2197 : aEvent->mRefPoint;
2200 void EventStateManager::BeginTrackingRemoteDragGesture(
2201 nsIContent* aContent, RemoteDragStartData* aDragStartData) {
2202 mGestureDownContent = aContent;
2203 mGestureDownFrameOwner = aContent;
2204 mGestureDownInTextControl =
2205 aContent && aContent->IsInNativeAnonymousSubtree() &&
2206 TextControlElement::FromNodeOrNull(
2207 aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost());
2208 mGestureDownDragStartData = aDragStartData;
2212 // StopTrackingDragGesture
2214 // Record that the mouse has gone back up so that we should leave the TRACKING
2215 // state of d&d gesture tracker and return to the START state.
2217 void EventStateManager::StopTrackingDragGesture(bool aClearInChildProcesses) {
2218 mGestureDownContent = nullptr;
2219 mGestureDownFrameOwner = nullptr;
2220 mGestureDownInTextControl = false;
2221 mGestureDownDragStartData = nullptr;
2223 // If a content process starts a drag but the mouse is released before the
2224 // parent starts the actual drag, the content process will think a drag is
2225 // still happening. Inform any child processes with active drags that the drag
2226 // should be stopped.
2227 if (aClearInChildProcesses) {
2228 nsCOMPtr<nsIDragService> dragService =
2229 do_GetService("@mozilla.org/widget/dragservice;1");
2230 if (dragService) {
2231 nsCOMPtr<nsIDragSession> dragSession;
2232 dragService->GetCurrentSession(getter_AddRefs(dragSession));
2233 if (!dragSession) {
2234 // Only notify if there isn't a drag session active.
2235 dragService->RemoveAllChildProcesses();
2241 void EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent) {
2242 NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
2243 "Incorrect widget in event");
2245 // Set the coordinates in the new event to the coordinates of
2246 // the old event, adjusted for the fact that the widget might be
2247 // different
2248 aEvent->mRefPoint =
2249 mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
2250 aEvent->mModifiers = mGestureModifiers;
2251 aEvent->mButtons = mGestureDownButtons;
2254 void EventStateManager::MaybeFirePointerCancel(WidgetInputEvent* aEvent) {
2255 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
2256 AutoWeakFrame targetFrame = mCurrentTarget;
2258 if (!presShell || !targetFrame) {
2259 return;
2262 nsCOMPtr<nsIContent> content;
2263 targetFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
2264 if (!content) {
2265 return;
2268 nsEventStatus status = nsEventStatus_eIgnore;
2270 if (WidgetMouseEvent* aMouseEvent = aEvent->AsMouseEvent()) {
2271 WidgetPointerEvent event(*aMouseEvent);
2272 PointerEventHandler::InitPointerEventFromMouse(&event, aMouseEvent,
2273 ePointerCancel);
2275 event.convertToPointer = false;
2276 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2277 } else if (WidgetTouchEvent* aTouchEvent = aEvent->AsTouchEvent()) {
2278 WidgetPointerEvent event(aTouchEvent->IsTrusted(), ePointerCancel,
2279 aTouchEvent->mWidget);
2281 PointerEventHandler::InitPointerEventFromTouch(
2282 event, *aTouchEvent, *aTouchEvent->mTouches[0], true);
2284 event.convertToPointer = false;
2285 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2286 } else {
2287 MOZ_ASSERT(false);
2290 // HandleEventWithTarget clears out mCurrentTarget, which may be used in the
2291 // caller GenerateDragGesture. We have to restore mCurrentTarget.
2292 mCurrentTarget = targetFrame;
2295 bool EventStateManager::IsEventOutsideDragThreshold(
2296 WidgetInputEvent* aEvent) const {
2297 static int32_t sPixelThresholdX = 0;
2298 static int32_t sPixelThresholdY = 0;
2300 if (!sPixelThresholdX) {
2301 sPixelThresholdX =
2302 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdX, 0);
2303 sPixelThresholdY =
2304 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdY, 0);
2305 if (sPixelThresholdX <= 0) {
2306 sPixelThresholdX = 5;
2308 if (sPixelThresholdY <= 0) {
2309 sPixelThresholdY = 5;
2313 LayoutDeviceIntPoint pt =
2314 aEvent->mWidget->WidgetToScreenOffset() + GetEventRefPoint(aEvent);
2315 LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
2316 return Abs(distance.x) > sPixelThresholdX ||
2317 Abs(distance.y) > sPixelThresholdY;
2321 // GenerateDragGesture
2323 // If we're in the TRACKING state of the d&d gesture tracker, check the current
2324 // position of the mouse in relation to the old one. If we've moved a sufficient
2325 // amount from the mouse down, then fire off a drag gesture event.
2326 void EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
2327 WidgetInputEvent* aEvent) {
2328 NS_ASSERTION(aPresContext, "This shouldn't happen.");
2329 if (!IsTrackingDragGesture()) {
2330 return;
2333 AutoWeakFrame targetFrameBefore = mCurrentTarget;
2334 auto autoRestore = MakeScopeExit([&] { mCurrentTarget = targetFrameBefore; });
2335 mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
2337 if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
2338 StopTrackingDragGesture(true);
2339 return;
2342 // Check if selection is tracking drag gestures, if so
2343 // don't interfere!
2344 if (mCurrentTarget) {
2345 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
2346 if (frameSel && frameSel->GetDragState()) {
2347 StopTrackingDragGesture(true);
2348 return;
2352 // If non-native code is capturing the mouse don't start a drag.
2353 if (PresShell::IsMouseCapturePreventingDrag()) {
2354 StopTrackingDragGesture(true);
2355 return;
2358 if (!IsEventOutsideDragThreshold(aEvent)) {
2359 // To keep the old behavior, flush layout even if we don't start dnd.
2360 FlushLayout(aPresContext);
2361 return;
2364 if (StaticPrefs::ui_click_hold_context_menus()) {
2365 // stop the click-hold before we fire off the drag gesture, in case
2366 // it takes a long time
2367 KillClickHoldTimer();
2370 nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell();
2371 if (!docshell) {
2372 return;
2375 nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow();
2376 if (!window) return;
2378 RefPtr<DataTransfer> dataTransfer =
2379 new DataTransfer(window, eDragStart, false, -1);
2380 auto protectDataTransfer = MakeScopeExit([&] {
2381 if (dataTransfer) {
2382 dataTransfer->Disconnect();
2386 RefPtr<Selection> selection;
2387 RefPtr<RemoteDragStartData> remoteDragStartData;
2388 nsCOMPtr<nsIContent> eventContent, targetContent;
2389 nsCOMPtr<nsIPrincipal> principal;
2390 nsCOMPtr<nsIContentSecurityPolicy> csp;
2391 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2392 bool allowEmptyDataTransfer = false;
2393 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
2394 if (eventContent) {
2395 // If the content is a text node in a password field, we shouldn't
2396 // allow to drag its raw text. Note that we've supported drag from
2397 // password fields but dragging data was masked text. So, it doesn't
2398 // make sense anyway.
2399 if (eventContent->IsText() && eventContent->HasFlag(NS_MAYBE_MASKED)) {
2400 // However, it makes sense to allow to drag selected password text
2401 // when copying selected password is allowed because users may want
2402 // to use drag and drop rather than copy and paste when web apps
2403 // request to input password twice for conforming new password but
2404 // they used password generator.
2405 TextEditor* textEditor =
2406 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
2407 eventContent);
2408 if (!textEditor || !textEditor->IsCopyToClipboardAllowed()) {
2409 StopTrackingDragGesture(true);
2410 return;
2413 DetermineDragTargetAndDefaultData(
2414 window, eventContent, dataTransfer, &allowEmptyDataTransfer,
2415 getter_AddRefs(selection), getter_AddRefs(remoteDragStartData),
2416 getter_AddRefs(targetContent), getter_AddRefs(principal),
2417 getter_AddRefs(csp), getter_AddRefs(cookieJarSettings));
2420 // Stop tracking the drag gesture now. This should stop us from
2421 // reentering GenerateDragGesture inside DOM event processing.
2422 // Pass false to avoid clearing the child process state since a real
2423 // drag should be starting.
2424 StopTrackingDragGesture(false);
2426 if (!targetContent) return;
2428 // Use our targetContent, now that we've determined it, as the
2429 // parent object of the DataTransfer.
2430 nsCOMPtr<nsIContent> parentContent =
2431 targetContent->FindFirstNonChromeOnlyAccessContent();
2432 dataTransfer->SetParentObject(parentContent);
2434 sLastDragOverFrame = nullptr;
2435 nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
2437 // get the widget from the target frame
2438 WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
2439 startEvent.mFlags.mIsSynthesizedForTests =
2440 aEvent->mFlags.mIsSynthesizedForTests;
2441 FillInEventFromGestureDown(&startEvent);
2443 startEvent.mDataTransfer = dataTransfer;
2444 if (aEvent->AsMouseEvent()) {
2445 startEvent.mInputSource = aEvent->AsMouseEvent()->mInputSource;
2446 } else if (aEvent->AsTouchEvent()) {
2447 startEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
2448 } else {
2449 MOZ_ASSERT(false);
2452 // Dispatch to the DOM. By setting mCurrentTarget we are faking
2453 // out the ESM and telling it that the current target frame is
2454 // actually where the mouseDown occurred, otherwise it will use
2455 // the frame the mouse is currently over which may or may not be
2456 // the same.
2458 // Hold onto old target content through the event and reset after.
2459 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
2461 // Set the current target to the content for the mouse down
2462 mCurrentTargetContent = targetContent;
2464 // Dispatch the dragstart event to the DOM.
2465 nsEventStatus status = nsEventStatus_eIgnore;
2466 EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent, nullptr,
2467 &status);
2469 WidgetDragEvent* event = &startEvent;
2471 nsCOMPtr<nsIObserverService> observerService =
2472 mozilla::services::GetObserverService();
2473 // Emit observer event to allow addons to modify the DataTransfer
2474 // object.
2475 if (observerService) {
2476 observerService->NotifyObservers(dataTransfer, "on-datatransfer-available",
2477 nullptr);
2480 if (status != nsEventStatus_eConsumeNoDefault) {
2481 bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
2482 allowEmptyDataTransfer, targetContent,
2483 selection, remoteDragStartData,
2484 principal, csp, cookieJarSettings);
2485 if (dragStarted) {
2486 sActiveESM = nullptr;
2487 MaybeFirePointerCancel(aEvent);
2488 aEvent->StopPropagation();
2492 // Reset mCurretTargetContent to what it was
2493 mCurrentTargetContent = targetBeforeEvent;
2495 // Now flush all pending notifications, for better responsiveness
2496 // while dragging.
2497 FlushLayout(aPresContext);
2498 } // GenerateDragGesture
2500 void EventStateManager::DetermineDragTargetAndDefaultData(
2501 nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget,
2502 DataTransfer* aDataTransfer, bool* aAllowEmptyDataTransfer,
2503 Selection** aSelection, RemoteDragStartData** aRemoteDragStartData,
2504 nsIContent** aTargetNode, nsIPrincipal** aPrincipal,
2505 nsIContentSecurityPolicy** aCsp,
2506 nsICookieJarSettings** aCookieJarSettings) {
2507 *aTargetNode = nullptr;
2508 *aAllowEmptyDataTransfer = false;
2509 nsCOMPtr<nsIContent> dragDataNode;
2511 nsIContent* editingElement = aSelectionTarget->IsEditable()
2512 ? aSelectionTarget->GetEditingHost()
2513 : nullptr;
2515 // In chrome, only allow dragging inside editable areas.
2516 bool isChromeContext = !aWindow->GetBrowsingContext()->IsContent();
2517 if (isChromeContext && !editingElement) {
2518 if (mGestureDownDragStartData) {
2519 // A child process started a drag so use any data it assigned for the dnd
2520 // session.
2521 mGestureDownDragStartData->AddInitialDnDDataTo(aDataTransfer, aPrincipal,
2522 aCsp, aCookieJarSettings);
2523 mGestureDownDragStartData.forget(aRemoteDragStartData);
2524 *aAllowEmptyDataTransfer = true;
2526 } else {
2527 mGestureDownDragStartData = nullptr;
2529 // GetDragData determines if a selection, link or image in the content
2530 // should be dragged, and places the data associated with the drag in the
2531 // data transfer.
2532 // mGestureDownContent is the node where the mousedown event for the drag
2533 // occurred, and aSelectionTarget is the node to use when a selection is
2534 // used
2535 bool canDrag;
2536 bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
2537 nsresult rv = nsContentAreaDragDrop::GetDragData(
2538 aWindow, mGestureDownContent, aSelectionTarget, wasAlt, aDataTransfer,
2539 &canDrag, aSelection, getter_AddRefs(dragDataNode), aCsp,
2540 aCookieJarSettings);
2541 if (NS_FAILED(rv) || !canDrag) {
2542 return;
2546 // if GetDragData returned a node, use that as the node being dragged.
2547 // Otherwise, if a selection is being dragged, use the node within the
2548 // selection that was dragged. Otherwise, just use the mousedown target.
2549 nsIContent* dragContent = mGestureDownContent;
2550 if (dragDataNode)
2551 dragContent = dragDataNode;
2552 else if (*aSelection)
2553 dragContent = aSelectionTarget;
2555 nsIContent* originalDragContent = dragContent;
2557 // If a selection isn't being dragged, look for an ancestor with the
2558 // draggable property set. If one is found, use that as the target of the
2559 // drag instead of the node that was clicked on. If a draggable node wasn't
2560 // found, just use the clicked node.
2561 if (!*aSelection) {
2562 while (dragContent) {
2563 if (auto htmlElement = nsGenericHTMLElement::FromNode(dragContent)) {
2564 if (htmlElement->Draggable()) {
2565 // We let draggable elements to trigger dnd even if there is no data
2566 // in the DataTransfer.
2567 *aAllowEmptyDataTransfer = true;
2568 break;
2570 } else {
2571 if (dragContent->IsXULElement()) {
2572 // All XUL elements are draggable, so if a XUL element is
2573 // encountered, stop looking for draggable nodes and just use the
2574 // original clicked node instead.
2575 // XXXndeakin
2576 // In the future, we will want to improve this so that XUL has a
2577 // better way to specify whether something is draggable than just
2578 // on/off.
2579 dragContent = mGestureDownContent;
2580 break;
2582 // otherwise, it's not an HTML or XUL element, so just keep looking
2584 dragContent = dragContent->GetFlattenedTreeParent();
2588 // if no node in the hierarchy was found to drag, but the GetDragData method
2589 // returned a node, use that returned node. Otherwise, nothing is draggable.
2590 if (!dragContent && dragDataNode) dragContent = dragDataNode;
2592 if (dragContent) {
2593 // if an ancestor node was used instead, clear the drag data
2594 // XXXndeakin rework this a bit. Find a way to just not call GetDragData if
2595 // we don't need to.
2596 if (dragContent != originalDragContent) aDataTransfer->ClearAll();
2597 *aTargetNode = dragContent;
2598 NS_ADDREF(*aTargetNode);
2602 bool EventStateManager::DoDefaultDragStart(
2603 nsPresContext* aPresContext, WidgetDragEvent* aDragEvent,
2604 DataTransfer* aDataTransfer, bool aAllowEmptyDataTransfer,
2605 nsIContent* aDragTarget, Selection* aSelection,
2606 RemoteDragStartData* aDragStartData, nsIPrincipal* aPrincipal,
2607 nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings) {
2608 nsCOMPtr<nsIDragService> dragService =
2609 do_GetService("@mozilla.org/widget/dragservice;1");
2610 if (!dragService) return false;
2612 // Default handling for the dragstart event.
2614 // First, check if a drag session already exists. This means that the drag
2615 // service was called directly within a draggesture handler. In this case,
2616 // don't do anything more, as it is assumed that the handler is managing
2617 // drag and drop manually. Make sure to return true to indicate that a drag
2618 // began. However, if we're handling drag session for synthesized events,
2619 // we need to initialize some information of the session. Therefore, we
2620 // need to keep going for synthesized case.
2621 nsCOMPtr<nsIDragSession> dragSession;
2622 dragService->GetCurrentSession(getter_AddRefs(dragSession));
2623 if (dragSession && !dragSession->IsSynthesizedForTests()) {
2624 return true;
2627 // No drag session is currently active, so check if a handler added
2628 // any items to be dragged. If not, there isn't anything to drag.
2629 uint32_t count = 0;
2630 if (aDataTransfer) {
2631 count = aDataTransfer->MozItemCount();
2633 if (!aAllowEmptyDataTransfer && !count) {
2634 return false;
2637 // Get the target being dragged, which may not be the same as the
2638 // target of the mouse event. If one wasn't set in the
2639 // aDataTransfer during the event handler, just use the original
2640 // target instead.
2641 nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget();
2642 if (!dragTarget) {
2643 dragTarget = aDragTarget;
2644 if (!dragTarget) {
2645 return false;
2649 // check which drag effect should initially be used. If the effect was not
2650 // set, just use all actions, otherwise Windows won't allow a drop.
2651 uint32_t action = aDataTransfer->EffectAllowedInt();
2652 if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
2653 action = nsIDragService::DRAGDROP_ACTION_COPY |
2654 nsIDragService::DRAGDROP_ACTION_MOVE |
2655 nsIDragService::DRAGDROP_ACTION_LINK;
2658 // get any custom drag image that was set
2659 int32_t imageX, imageY;
2660 RefPtr<Element> dragImage = aDataTransfer->GetDragImage(&imageX, &imageY);
2662 nsCOMPtr<nsIArray> transArray = aDataTransfer->GetTransferables(dragTarget);
2663 if (!transArray) {
2664 return false;
2667 RefPtr<DataTransfer> dataTransfer;
2668 if (!dragSession) {
2669 // After this function returns, the DataTransfer will be cleared so it
2670 // appears empty to content. We need to pass a DataTransfer into the Drag
2671 // Session, so we need to make a copy.
2672 aDataTransfer->Clone(aDragTarget, eDrop, aDataTransfer->MozUserCancelled(),
2673 false, getter_AddRefs(dataTransfer));
2675 // Copy over the drop effect, as Clone doesn't copy it for us.
2676 dataTransfer->SetDropEffectInt(aDataTransfer->DropEffectInt());
2677 } else {
2678 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
2679 MOZ_ASSERT(aDragEvent->mFlags.mIsSynthesizedForTests);
2680 // If we're initializing synthesized drag session, we should use given
2681 // DataTransfer as is because it'll be used with following drag events
2682 // in any tests, therefore it should be set to nsIDragSession.dataTransfer
2683 // because it and DragEvent.dataTransfer should be same instance.
2684 dataTransfer = aDataTransfer;
2687 // XXXndeakin don't really want to create a new drag DOM event
2688 // here, but we need something to pass to the InvokeDragSession
2689 // methods.
2690 RefPtr<DragEvent> event =
2691 NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
2693 // Use InvokeDragSessionWithSelection if a selection is being dragged,
2694 // such that the image can be generated from the selected text. However,
2695 // use InvokeDragSessionWithImage if a custom image was set or something
2696 // other than a selection is being dragged.
2697 if (!dragImage && aSelection) {
2698 dragService->InvokeDragSessionWithSelection(aSelection, aPrincipal, aCsp,
2699 aCookieJarSettings, transArray,
2700 action, event, dataTransfer);
2701 } else if (aDragStartData) {
2702 MOZ_ASSERT(XRE_IsParentProcess());
2703 dragService->InvokeDragSessionWithRemoteImage(
2704 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2705 aDragStartData, event, dataTransfer);
2706 } else {
2707 dragService->InvokeDragSessionWithImage(
2708 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2709 dragImage, imageX, imageY, event, dataTransfer);
2712 return true;
2715 void EventStateManager::ChangeZoom(bool aIncrease) {
2716 // Send the zoom change to the top level browser so it will be handled by the
2717 // front end in the same way as other zoom actions.
2718 nsIDocShell* docShell = mDocument->GetDocShell();
2719 if (!docShell) {
2720 return;
2723 BrowsingContext* bc = docShell->GetBrowsingContext();
2724 if (!bc) {
2725 return;
2728 if (XRE_IsParentProcess()) {
2729 bc->Canonical()->DispatchWheelZoomChange(aIncrease);
2730 } else if (BrowserChild* child = BrowserChild::GetFrom(docShell)) {
2731 child->SendWheelZoomChange(aIncrease);
2735 void EventStateManager::DoScrollHistory(int32_t direction) {
2736 nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
2737 if (pcContainer) {
2738 nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer));
2739 if (webNav) {
2740 // positive direction to go back one step, nonpositive to go forward
2741 // This is doing user-initiated history traversal, hence we want
2742 // to require that history entries we navigate to have user interaction.
2743 if (direction > 0)
2744 webNav->GoBack(StaticPrefs::browser_navigation_requireUserInteraction(),
2745 true);
2746 else
2747 webNav->GoForward(
2748 StaticPrefs::browser_navigation_requireUserInteraction(), true);
2753 void EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
2754 int32_t adjustment) {
2755 // Exclude content in chrome docshells.
2756 nsIContent* content = aTargetFrame->GetContent();
2757 if (content && !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) {
2758 // Positive adjustment to decrease zoom, negative to increase
2759 const bool increase = adjustment <= 0;
2760 EnsureDocument(mPresContext);
2761 ChangeZoom(increase);
2765 static nsIFrame* GetParentFrameToScroll(nsIFrame* aFrame) {
2766 if (!aFrame) return nullptr;
2768 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2769 nsLayoutUtils::IsReallyFixedPos(aFrame))
2770 return aFrame->PresShell()->GetRootScrollFrame();
2772 return aFrame->GetParent();
2775 void EventStateManager::DispatchLegacyMouseScrollEvents(
2776 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent, nsEventStatus* aStatus) {
2777 MOZ_ASSERT(aEvent);
2778 MOZ_ASSERT(aStatus);
2780 if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) {
2781 return;
2784 // Ignore mouse wheel transaction for computing legacy mouse wheel
2785 // events' delta value.
2786 // DOM event's delta vales are computed from CSS pixels.
2787 auto scrollAmountInCSSPixels =
2788 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
2790 // XXX We don't deal with fractional amount in legacy event, though the
2791 // default action handler (DoScrollText()) deals with it.
2792 // If we implemented such strict computation, we would need additional
2793 // accumulated delta values. It would made the code more complicated.
2794 // And also it would computes different delta values from older version.
2795 // It doesn't make sense to implement such code for legacy events and
2796 // rare cases.
2797 int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
2798 switch (aEvent->mDeltaMode) {
2799 case WheelEvent_Binding::DOM_DELTA_PAGE:
2800 scrollDeltaX = !aEvent->mLineOrPageDeltaX
2802 : (aEvent->mLineOrPageDeltaX > 0
2803 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2804 : UIEvent_Binding::SCROLL_PAGE_UP);
2805 scrollDeltaY = !aEvent->mLineOrPageDeltaY
2807 : (aEvent->mLineOrPageDeltaY > 0
2808 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2809 : UIEvent_Binding::SCROLL_PAGE_UP);
2810 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2811 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2812 break;
2814 case WheelEvent_Binding::DOM_DELTA_LINE:
2815 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2816 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2817 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2818 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2819 break;
2821 case WheelEvent_Binding::DOM_DELTA_PIXEL:
2822 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2823 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2824 pixelDeltaX = RoundDown(aEvent->mDeltaX);
2825 pixelDeltaY = RoundDown(aEvent->mDeltaY);
2826 break;
2828 default:
2829 MOZ_CRASH("Invalid deltaMode value comes");
2832 // Send the legacy events in following order:
2833 // 1. Vertical scroll
2834 // 2. Vertical pixel scroll (even if #1 isn't consumed)
2835 // 3. Horizontal scroll (even if #1 and/or #2 are consumed)
2836 // 4. Horizontal pixel scroll (even if #3 isn't consumed)
2838 AutoWeakFrame targetFrame(aTargetFrame);
2840 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
2841 !aEvent->DefaultPrevented(),
2842 "If you make legacy events dispatched for default prevented wheel "
2843 "event, you need to initialize stateX and stateY");
2844 EventState stateX, stateY;
2845 if (scrollDeltaY) {
2846 SendLineScrollEvent(aTargetFrame, aEvent, stateY, scrollDeltaY,
2847 DELTA_DIRECTION_Y);
2848 if (!targetFrame.IsAlive()) {
2849 *aStatus = nsEventStatus_eConsumeNoDefault;
2850 return;
2854 if (pixelDeltaY) {
2855 SendPixelScrollEvent(aTargetFrame, aEvent, stateY, pixelDeltaY,
2856 DELTA_DIRECTION_Y);
2857 if (!targetFrame.IsAlive()) {
2858 *aStatus = nsEventStatus_eConsumeNoDefault;
2859 return;
2863 if (scrollDeltaX) {
2864 SendLineScrollEvent(aTargetFrame, aEvent, stateX, scrollDeltaX,
2865 DELTA_DIRECTION_X);
2866 if (!targetFrame.IsAlive()) {
2867 *aStatus = nsEventStatus_eConsumeNoDefault;
2868 return;
2872 if (pixelDeltaX) {
2873 SendPixelScrollEvent(aTargetFrame, aEvent, stateX, pixelDeltaX,
2874 DELTA_DIRECTION_X);
2875 if (!targetFrame.IsAlive()) {
2876 *aStatus = nsEventStatus_eConsumeNoDefault;
2877 return;
2881 if (stateY.mDefaultPrevented) {
2882 *aStatus = nsEventStatus_eConsumeNoDefault;
2883 aEvent->PreventDefault(!stateY.mDefaultPreventedByContent);
2886 if (stateX.mDefaultPrevented) {
2887 *aStatus = nsEventStatus_eConsumeNoDefault;
2888 aEvent->PreventDefault(!stateX.mDefaultPreventedByContent);
2892 void EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
2893 WidgetWheelEvent* aEvent,
2894 EventState& aState, int32_t aDelta,
2895 DeltaDirection aDeltaDirection) {
2896 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2897 if (!targetContent) {
2898 targetContent = GetFocusedElement();
2899 if (!targetContent) {
2900 return;
2904 while (targetContent->IsText()) {
2905 targetContent = targetContent->GetFlattenedTreeParent();
2908 WidgetMouseScrollEvent event(aEvent->IsTrusted(),
2909 eLegacyMouseLineOrPageScroll, aEvent->mWidget);
2910 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2911 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2912 event.mRefPoint = aEvent->mRefPoint;
2913 event.mTimeStamp = aEvent->mTimeStamp;
2914 event.mModifiers = aEvent->mModifiers;
2915 event.mButtons = aEvent->mButtons;
2916 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2917 event.mDelta = aDelta;
2918 event.mInputSource = aEvent->mInputSource;
2920 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2921 nsEventStatus status = nsEventStatus_eIgnore;
2922 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2923 &status);
2924 aState.mDefaultPrevented =
2925 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2926 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2929 void EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
2930 WidgetWheelEvent* aEvent,
2931 EventState& aState,
2932 int32_t aPixelDelta,
2933 DeltaDirection aDeltaDirection) {
2934 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2935 if (!targetContent) {
2936 targetContent = GetFocusedElement();
2937 if (!targetContent) {
2938 return;
2942 while (targetContent->IsText()) {
2943 targetContent = targetContent->GetFlattenedTreeParent();
2946 WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMousePixelScroll,
2947 aEvent->mWidget);
2948 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2949 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2950 event.mRefPoint = aEvent->mRefPoint;
2951 event.mTimeStamp = aEvent->mTimeStamp;
2952 event.mModifiers = aEvent->mModifiers;
2953 event.mButtons = aEvent->mButtons;
2954 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2955 event.mDelta = aPixelDelta;
2956 event.mInputSource = aEvent->mInputSource;
2958 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2959 nsEventStatus status = nsEventStatus_eIgnore;
2960 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2961 &status);
2962 aState.mDefaultPrevented =
2963 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2964 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2967 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2968 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent,
2969 ComputeScrollTargetOptions aOptions) {
2970 return ComputeScrollTargetAndMayAdjustWheelEvent(
2971 aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY, aEvent, aOptions);
2974 // Overload ComputeScrollTargetAndMayAdjustWheelEvent method to allow passing
2975 // "test" dx and dy when looking for which scrollbarmediators to activate when
2976 // two finger down on trackpad and before any actual motion
2977 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2978 nsIFrame* aTargetFrame, double aDirectionX, double aDirectionY,
2979 WidgetWheelEvent* aEvent, ComputeScrollTargetOptions aOptions) {
2980 bool isAutoDir = false;
2981 bool honoursRoot = false;
2982 if (MAY_BE_ADJUSTED_BY_AUTO_DIR & aOptions) {
2983 // If the scroll is respected as auto-dir, aDirection* should always be
2984 // equivalent to the event's delta vlaues(Currently, there are only one case
2985 // where aDirection*s have different values from the widget wheel event's
2986 // original delta values and the only case isn't auto-dir, see
2987 // ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets).
2988 MOZ_ASSERT(aDirectionX == aEvent->mDeltaX &&
2989 aDirectionY == aEvent->mDeltaY);
2991 WheelDeltaAdjustmentStrategy strategy =
2992 GetWheelDeltaAdjustmentStrategy(*aEvent);
2993 switch (strategy) {
2994 case WheelDeltaAdjustmentStrategy::eAutoDir:
2995 isAutoDir = true;
2996 honoursRoot = false;
2997 break;
2998 case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
2999 isAutoDir = true;
3000 honoursRoot = true;
3001 break;
3002 default:
3003 break;
3007 if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
3008 // If the user recently scrolled with the mousewheel, then they probably
3009 // want to scroll the same view as before instead of the view under the
3010 // cursor. WheelTransaction tracks the frame currently being
3011 // scrolled with the mousewheel. We consider the transaction ended when the
3012 // mouse moves more than "mousewheel.transaction.ignoremovedelay"
3013 // milliseconds after the last scroll operation, or any time the mouse moves
3014 // out of the frame, or when more than "mousewheel.transaction.timeout"
3015 // milliseconds have passed after the last operation, even if the mouse
3016 // hasn't moved.
3017 nsIFrame* lastScrollFrame = WheelTransaction::GetScrollTargetFrame();
3018 if (lastScrollFrame) {
3019 nsIScrollableFrame* scrollableFrame =
3020 lastScrollFrame->GetScrollTargetFrame();
3021 if (scrollableFrame) {
3022 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
3023 MOZ_ASSERT(frameToScroll);
3024 if (isAutoDir) {
3025 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *lastScrollFrame,
3026 honoursRoot);
3027 // Note that calling this function will not always cause the delta to
3028 // be adjusted, it only adjusts the delta when it should, because
3029 // Adjust() internally calls ShouldBeAdjusted() before making
3030 // adjustment.
3031 adjuster.Adjust();
3033 return frameToScroll;
3038 // If the event doesn't cause scroll actually, we cannot find scroll target
3039 // because we check if the event can cause scroll actually on each found
3040 // scrollable frame.
3041 if (!aDirectionX && !aDirectionY) {
3042 return nullptr;
3045 bool checkIfScrollableX;
3046 bool checkIfScrollableY;
3047 if (isAutoDir) {
3048 // Always check the frame's scrollability in both the two directions for an
3049 // auto-dir scroll. That is, for an auto-dir scroll,
3050 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS and
3051 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS should be ignored.
3052 checkIfScrollableX = true;
3053 checkIfScrollableY = true;
3054 } else {
3055 checkIfScrollableX =
3056 aDirectionX &&
3057 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
3058 checkIfScrollableY =
3059 aDirectionY &&
3060 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
3063 nsIFrame* scrollFrame = !(aOptions & START_FROM_PARENT)
3064 ? aTargetFrame
3065 : GetParentFrameToScroll(aTargetFrame);
3066 for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
3067 // Check whether the frame wants to provide us with a scrollable view.
3068 nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
3069 if (!scrollableFrame) {
3070 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
3071 if (menuPopupFrame) {
3072 return nullptr;
3074 continue;
3077 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
3078 MOZ_ASSERT(frameToScroll);
3080 if (!checkIfScrollableX && !checkIfScrollableY) {
3081 return frameToScroll;
3084 // If the frame disregards the direction the user is trying to scroll, then
3085 // it should just bubbles the scroll event up to its parental scroll frame
3087 Maybe<layers::ScrollDirection> disregardedDirection =
3088 WheelHandlingUtils::GetDisregardedWheelScrollDirection(scrollFrame);
3089 if (disregardedDirection) {
3090 switch (disregardedDirection.ref()) {
3091 case layers::ScrollDirection::eHorizontal:
3092 if (checkIfScrollableX) {
3093 continue;
3095 break;
3096 case layers::ScrollDirection::eVertical:
3097 if (checkIfScrollableY) {
3098 continue;
3100 break;
3104 layers::ScrollDirections directions =
3105 scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
3106 if ((!(directions.contains(layers::ScrollDirection::eVertical)) &&
3107 !(directions.contains(layers::ScrollDirection::eHorizontal))) ||
3108 (checkIfScrollableY && !checkIfScrollableX &&
3109 !(directions.contains(layers::ScrollDirection::eVertical))) ||
3110 (checkIfScrollableX && !checkIfScrollableY &&
3111 !(directions.contains(layers::ScrollDirection::eHorizontal)))) {
3112 continue;
3115 // Computes whether the currently checked frame is scrollable by this wheel
3116 // event.
3117 bool canScroll = false;
3118 if (isAutoDir) {
3119 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *scrollFrame, honoursRoot);
3120 if (adjuster.ShouldBeAdjusted()) {
3121 adjuster.Adjust();
3122 canScroll = true;
3123 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
3124 aDirectionY)) {
3125 canScroll = true;
3127 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
3128 aDirectionY)) {
3129 canScroll = true;
3132 if (canScroll) {
3133 return frameToScroll;
3136 // Where we are at is the block ending in a for loop.
3137 // The current frame has been checked to be unscrollable by this wheel
3138 // event, continue the loop to check its parent, if any.
3141 nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(
3142 aTargetFrame->PresShell()->GetRootFrame());
3143 aOptions =
3144 static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
3145 if (!newFrame) {
3146 return nullptr;
3148 return ComputeScrollTargetAndMayAdjustWheelEvent(newFrame, aEvent, aOptions);
3151 nsSize EventStateManager::GetScrollAmount(
3152 nsPresContext* aPresContext, WidgetWheelEvent* aEvent,
3153 nsIScrollableFrame* aScrollableFrame) {
3154 MOZ_ASSERT(aPresContext);
3155 MOZ_ASSERT(aEvent);
3157 const bool isPage = aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PAGE;
3158 if (!aScrollableFrame) {
3159 // If there is no scrollable frame, we should use root, see below.
3160 aScrollableFrame =
3161 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
3164 if (aScrollableFrame) {
3165 return isPage ? aScrollableFrame->GetPageScrollAmount()
3166 : aScrollableFrame->GetLineScrollAmount();
3169 // If there is no scrollable frame and page scrolling, use viewport size.
3170 if (isPage) {
3171 return aPresContext->GetVisibleArea().Size();
3174 // Otherwise use root frame's font metrics.
3176 // FIXME(emilio): Should this use the root element's style frame? The root
3177 // frame will always have the initial font. Then again it should never matter
3178 // for content, we should always have a root scrollable frame in html
3179 // documents.
3180 nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
3181 if (!rootFrame) {
3182 return nsSize(0, 0);
3184 RefPtr<nsFontMetrics> fm =
3185 nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame);
3186 NS_ENSURE_TRUE(fm, nsSize(0, 0));
3187 return nsSize(fm->AveCharWidth(), fm->MaxHeight());
3190 void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
3191 WidgetWheelEvent* aEvent) {
3192 MOZ_ASSERT(aScrollableFrame);
3193 MOZ_ASSERT(aEvent);
3195 nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
3196 MOZ_ASSERT(scrollFrame);
3198 AutoWeakFrame scrollFrameWeak(scrollFrame);
3199 AutoWeakFrame eventFrameWeak(mCurrentTarget);
3200 if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak,
3201 eventFrameWeak)) {
3202 return;
3205 // Default action's actual scroll amount should be computed from device
3206 // pixels.
3207 nsPresContext* pc = scrollFrame->PresContext();
3208 nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame);
3209 nsIntSize scrollAmountInDevPixels(
3210 pc->AppUnitsToDevPixels(scrollAmount.width),
3211 pc->AppUnitsToDevPixels(scrollAmount.height));
3212 nsIntPoint actualDevPixelScrollAmount =
3213 DeltaAccumulator::GetInstance()->ComputeScrollAmountForDefaultAction(
3214 aEvent, scrollAmountInDevPixels);
3216 // Don't scroll around the axis whose overflow style is hidden.
3217 ScrollStyles overflowStyle = aScrollableFrame->GetScrollStyles();
3218 if (overflowStyle.mHorizontal == StyleOverflow::Hidden) {
3219 actualDevPixelScrollAmount.x = 0;
3221 if (overflowStyle.mVertical == StyleOverflow::Hidden) {
3222 actualDevPixelScrollAmount.y = 0;
3225 ScrollSnapFlags snapFlags = ScrollSnapFlags::Disabled;
3226 mozilla::ScrollOrigin origin = mozilla::ScrollOrigin::NotSpecified;
3227 switch (aEvent->mDeltaMode) {
3228 case WheelEvent_Binding::DOM_DELTA_LINE:
3229 origin = mozilla::ScrollOrigin::MouseWheel;
3230 snapFlags = ScrollSnapFlags::IntendedDirection;
3231 break;
3232 case WheelEvent_Binding::DOM_DELTA_PAGE:
3233 origin = mozilla::ScrollOrigin::Pages;
3234 snapFlags = ScrollSnapFlags::IntendedDirection |
3235 ScrollSnapFlags::IntendedEndPosition;
3236 break;
3237 case WheelEvent_Binding::DOM_DELTA_PIXEL:
3238 origin = mozilla::ScrollOrigin::Pixels;
3239 break;
3240 default:
3241 MOZ_CRASH("Invalid deltaMode value comes");
3244 // We shouldn't scroll more one page at once except when over one page scroll
3245 // is allowed for the event.
3246 nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
3247 nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
3248 pc->AppUnitsToDevPixels(pageSize.height));
3249 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
3250 DeprecatedAbs(actualDevPixelScrollAmount.x.value) >
3251 devPixelPageSize.width) {
3252 actualDevPixelScrollAmount.x = (actualDevPixelScrollAmount.x >= 0)
3253 ? devPixelPageSize.width
3254 : -devPixelPageSize.width;
3257 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
3258 DeprecatedAbs(actualDevPixelScrollAmount.y.value) >
3259 devPixelPageSize.height) {
3260 actualDevPixelScrollAmount.y = (actualDevPixelScrollAmount.y >= 0)
3261 ? devPixelPageSize.height
3262 : -devPixelPageSize.height;
3265 bool isDeltaModePixel =
3266 (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL);
3268 ScrollMode mode;
3269 switch (aEvent->mScrollType) {
3270 case WidgetWheelEvent::SCROLL_DEFAULT:
3271 if (isDeltaModePixel) {
3272 mode = ScrollMode::Normal;
3273 } else if (aEvent->mFlags.mHandledByAPZ) {
3274 mode = ScrollMode::SmoothMsd;
3275 } else {
3276 mode = ScrollMode::Smooth;
3278 break;
3279 case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
3280 mode = ScrollMode::Instant;
3281 break;
3282 case WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY:
3283 mode = ScrollMode::Normal;
3284 break;
3285 case WidgetWheelEvent::SCROLL_SMOOTHLY:
3286 mode = ScrollMode::Smooth;
3287 break;
3288 default:
3289 MOZ_CRASH("Invalid mScrollType value comes");
3292 nsIScrollableFrame::ScrollMomentum momentum =
3293 aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
3294 : nsIScrollableFrame::NOT_MOMENTUM;
3296 nsIntPoint overflow;
3297 aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
3298 ScrollUnit::DEVICE_PIXELS, mode, &overflow, origin,
3299 momentum, snapFlags);
3301 if (!scrollFrameWeak.IsAlive()) {
3302 // If the scroll causes changing the layout, we can think that the event
3303 // has been completely consumed by the content. Then, users probably don't
3304 // want additional action.
3305 aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0;
3306 } else if (isDeltaModePixel) {
3307 aEvent->mOverflowDeltaX = overflow.x;
3308 aEvent->mOverflowDeltaY = overflow.y;
3309 } else {
3310 aEvent->mOverflowDeltaX =
3311 static_cast<double>(overflow.x) / scrollAmountInDevPixels.width;
3312 aEvent->mOverflowDeltaY =
3313 static_cast<double>(overflow.y) / scrollAmountInDevPixels.height;
3316 // If CSS overflow properties caused not to scroll, the overflowDelta* values
3317 // should be same as delta* values since they may be used as gesture event by
3318 // widget. However, if there is another scrollable element in the ancestor
3319 // along the axis, probably users don't want the operation to cause
3320 // additional action such as moving history. In such case, overflowDelta
3321 // values should stay zero.
3322 if (scrollFrameWeak.IsAlive()) {
3323 if (aEvent->mDeltaX && overflowStyle.mHorizontal == StyleOverflow::Hidden &&
3324 !ComputeScrollTargetAndMayAdjustWheelEvent(
3325 scrollFrame, aEvent,
3326 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_WITH_AUTO_DIR)) {
3327 aEvent->mOverflowDeltaX = aEvent->mDeltaX;
3329 if (aEvent->mDeltaY && overflowStyle.mVertical == StyleOverflow::Hidden &&
3330 !ComputeScrollTargetAndMayAdjustWheelEvent(
3331 scrollFrame, aEvent,
3332 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_WITH_AUTO_DIR)) {
3333 aEvent->mOverflowDeltaY = aEvent->mDeltaY;
3337 NS_ASSERTION(
3338 aEvent->mOverflowDeltaX == 0 ||
3339 (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
3340 "The sign of mOverflowDeltaX is different from the scroll direction");
3341 NS_ASSERTION(
3342 aEvent->mOverflowDeltaY == 0 ||
3343 (aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0),
3344 "The sign of mOverflowDeltaY is different from the scroll direction");
3346 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent);
3349 void EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
3350 nsIFrame* targetFrame) {
3351 NS_ASSERTION(aEvent->mMessage == eGestureNotify,
3352 "DecideGestureEvent called with a non-gesture event");
3354 /* Check the ancestor tree to decide if any frame is willing* to receive
3355 * a MozPixelScroll event. If that's the case, the current touch gesture
3356 * will be used as a pan gesture; otherwise it will be a regular
3357 * mousedown/mousemove/click event.
3359 * *willing: determine if it makes sense to pan the element using scroll
3360 * events:
3361 * - For web content: if there are any visible scrollbars on the touch point
3362 * - For XUL: if it's an scrollable element that can currently scroll in some
3363 * direction.
3365 * Note: we'll have to one-off various cases to ensure a good usable behavior
3367 WidgetGestureNotifyEvent::PanDirection panDirection =
3368 WidgetGestureNotifyEvent::ePanNone;
3369 bool displayPanFeedback = false;
3370 for (nsIFrame* current = targetFrame; current;
3371 current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
3372 // e10s - mark remote content as pannable. This is a work around since
3373 // we don't have access to remote frame scroll info here. Apz data may
3374 // assist is solving this.
3375 if (current && IsTopLevelRemoteTarget(current->GetContent())) {
3376 panDirection = WidgetGestureNotifyEvent::ePanBoth;
3377 // We don't know when we reach bounds, so just disable feedback for now.
3378 displayPanFeedback = false;
3379 break;
3382 LayoutFrameType currentFrameType = current->Type();
3384 // Scrollbars should always be draggable
3385 if (currentFrameType == LayoutFrameType::Scrollbar) {
3386 panDirection = WidgetGestureNotifyEvent::ePanNone;
3387 break;
3390 // Special check for trees
3391 if (nsTreeBodyFrame* treeFrame = do_QueryFrame(current)) {
3392 if (treeFrame->GetHorizontalOverflow()) {
3393 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3395 if (treeFrame->GetVerticalOverflow()) {
3396 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3398 break;
3401 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(current)) {
3402 layers::ScrollDirections scrollbarVisibility =
3403 scrollableFrame->GetScrollbarVisibility();
3405 // Check if we have visible scrollbars
3406 if (scrollbarVisibility.contains(layers::ScrollDirection::eVertical)) {
3407 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3408 displayPanFeedback = true;
3409 break;
3412 if (scrollbarVisibility.contains(layers::ScrollDirection::eHorizontal)) {
3413 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3414 displayPanFeedback = true;
3416 } // scrollableFrame
3417 } // ancestor chain
3418 aEvent->mDisplayPanFeedback = displayPanFeedback;
3419 aEvent->mPanDirection = panDirection;
3422 #ifdef XP_MACOSX
3423 static nsINode* GetCrossDocParentNode(nsINode* aChild) {
3424 MOZ_ASSERT(aChild, "The child is null!");
3425 MOZ_ASSERT(XRE_IsParentProcess());
3427 nsINode* parent = aChild->GetParentNode();
3428 if (parent && parent->IsContent() && aChild->IsContent()) {
3429 parent = aChild->AsContent()->GetFlattenedTreeParent();
3432 if (parent || !aChild->IsDocument()) {
3433 return parent;
3436 return aChild->AsDocument()->GetEmbedderElement();
3439 static bool NodeAllowsClickThrough(nsINode* aNode) {
3440 while (aNode) {
3441 if (aNode->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::tree)) {
3442 return false;
3444 if (aNode->IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::resizer)) {
3445 return true;
3447 aNode = GetCrossDocParentNode(aNode);
3449 return true;
3451 #endif
3453 void EventStateManager::PostHandleKeyboardEvent(
3454 WidgetKeyboardEvent* aKeyboardEvent, nsIFrame* aTargetFrame,
3455 nsEventStatus& aStatus) {
3456 if (aStatus == nsEventStatus_eConsumeNoDefault) {
3457 return;
3460 RefPtr<nsPresContext> presContext = mPresContext;
3462 if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3463 if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
3464 RefPtr<BrowserParent> remote =
3465 aTargetFrame ? BrowserParent::GetFrom(aTargetFrame->GetContent())
3466 : nullptr;
3467 if (remote) {
3468 // remote is null-checked above in order to let pre-existing event
3469 // targeting code's chrome vs. content decision override in case of
3470 // disagreement in order not to disrupt non-Fission e10s mode in case
3471 // there are still bugs in the Fission-mode code. That is, if remote
3472 // is nullptr, the pre-existing event targeting code has deemed this
3473 // event to belong to chrome rather than content.
3474 BrowserParent* preciseRemote = BrowserParent::GetFocused();
3475 if (preciseRemote) {
3476 remote = preciseRemote;
3478 // else there was a race between layout and focus tracking
3480 if (remote && !remote->IsReadyToHandleInputEvents()) {
3481 // We need to dispatch the event to the browser element again if we were
3482 // waiting for the key reply but the event wasn't sent to the content
3483 // process due to the remote browser wasn't ready.
3484 WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
3485 aKeyboardEvent->MarkAsHandledInRemoteProcess();
3486 RefPtr<Element> ownerElement = remote->GetOwnerElement();
3487 EventDispatcher::Dispatch(ownerElement, presContext, &keyEvent);
3488 if (keyEvent.DefaultPrevented()) {
3489 aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
3490 aStatus = nsEventStatus_eConsumeNoDefault;
3491 return;
3495 // The widget expects a reply for every keyboard event. If the event wasn't
3496 // dispatched to a content process (non-e10s or no content process
3497 // running), we need to short-circuit here. Otherwise, we need to wait for
3498 // the content process to handle the event.
3499 if (aKeyboardEvent->mWidget) {
3500 aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
3502 if (aKeyboardEvent->DefaultPrevented()) {
3503 aStatus = nsEventStatus_eConsumeNoDefault;
3504 return;
3508 // XXX Currently, our automated tests don't support mKeyNameIndex.
3509 // Therefore, we still need to handle this with keyCode.
3510 switch (aKeyboardEvent->mKeyCode) {
3511 case NS_VK_TAB:
3512 case NS_VK_F6:
3513 // This is to prevent keyboard scrolling while alt modifier in use.
3514 if (!aKeyboardEvent->IsAlt()) {
3515 aStatus = nsEventStatus_eConsumeNoDefault;
3517 // Handling the tab event after it was sent to content is bad,
3518 // because to the FocusManager the remote-browser looks like one
3519 // element, so we would just move the focus to the next element
3520 // in chrome, instead of handling it in content.
3521 if (aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3522 break;
3525 EnsureDocument(presContext);
3526 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3527 if (fm && mDocument) {
3528 // Shift focus forward or back depending on shift key
3529 bool isDocMove = aKeyboardEvent->IsControl() ||
3530 aKeyboardEvent->mKeyCode == NS_VK_F6;
3531 uint32_t dir =
3532 aKeyboardEvent->IsShift()
3533 ? (isDocMove ? static_cast<uint32_t>(
3534 nsIFocusManager::MOVEFOCUS_BACKWARDDOC)
3535 : static_cast<uint32_t>(
3536 nsIFocusManager::MOVEFOCUS_BACKWARD))
3537 : (isDocMove ? static_cast<uint32_t>(
3538 nsIFocusManager::MOVEFOCUS_FORWARDDOC)
3539 : static_cast<uint32_t>(
3540 nsIFocusManager::MOVEFOCUS_FORWARD));
3541 RefPtr<Element> result;
3542 fm->MoveFocus(mDocument->GetWindow(), nullptr, dir,
3543 nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
3546 return;
3547 case 0:
3548 // We handle keys with no specific keycode value below.
3549 break;
3550 default:
3551 return;
3554 switch (aKeyboardEvent->mKeyNameIndex) {
3555 case KEY_NAME_INDEX_ZoomIn:
3556 case KEY_NAME_INDEX_ZoomOut:
3557 ChangeZoom(aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn);
3558 aStatus = nsEventStatus_eConsumeNoDefault;
3559 break;
3560 default:
3561 break;
3565 static bool NeedsActiveContentChange(const WidgetMouseEvent* aMouseEvent) {
3566 // If the mouse event is a synthesized mouse event due to a touch, do
3567 // not set/clear the activation state. Element activation is handled by APZ.
3568 return !aMouseEvent ||
3569 aMouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH;
3572 nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
3573 WidgetEvent* aEvent,
3574 nsIFrame* aTargetFrame,
3575 nsEventStatus* aStatus,
3576 nsIContent* aOverrideClickTarget) {
3577 NS_ENSURE_ARG(aPresContext);
3578 NS_ENSURE_ARG_POINTER(aStatus);
3580 mCurrentTarget = aTargetFrame;
3581 mCurrentTargetContent = nullptr;
3583 HandleCrossProcessEvent(aEvent, aStatus);
3584 // NOTE: the above call may have destroyed aTargetFrame, please use
3585 // mCurrentTarget henceforth. This is to avoid using it accidentally:
3586 aTargetFrame = nullptr;
3588 // Most of the events we handle below require a frame.
3589 // Add special cases here.
3590 if (!mCurrentTarget && aEvent->mMessage != eMouseUp &&
3591 aEvent->mMessage != eMouseDown && aEvent->mMessage != eDragEnter &&
3592 aEvent->mMessage != eDragOver && aEvent->mMessage != ePointerUp &&
3593 aEvent->mMessage != ePointerCancel) {
3594 return NS_OK;
3597 // Keep the prescontext alive, we might need it after event dispatch
3598 RefPtr<nsPresContext> presContext = aPresContext;
3599 nsresult ret = NS_OK;
3601 switch (aEvent->mMessage) {
3602 case eMouseDown: {
3603 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
3604 if (mouseEvent->mButton == MouseButton::ePrimary &&
3605 !sNormalLMouseEventInProcess) {
3606 // We got a mouseup event while a mousedown event was being processed.
3607 // Make sure that the capturing content is cleared.
3608 PresShell::ReleaseCapturingContent();
3609 break;
3612 // For remote content, capture the event in the parent process at the
3613 // <xul:browser remote> element. This will ensure that subsequent
3614 // mousemove/mouseup events will continue to be dispatched to this element
3615 // and therefore forwarded to the child.
3616 if (aEvent->HasBeenPostedToRemoteProcess() &&
3617 !PresShell::GetCapturingContent()) {
3618 if (nsIContent* content =
3619 mCurrentTarget ? mCurrentTarget->GetContent() : nullptr) {
3620 PresShell::SetCapturingContent(content, CaptureFlags::None, aEvent);
3621 } else {
3622 PresShell::ReleaseCapturingContent();
3626 // If MouseEvent::PreventClickEvent() was called by chrome script,
3627 // we need to forget the clicking content and click count for the
3628 // following eMouseUp event.
3629 if (mouseEvent->mClickEventPrevented) {
3630 RefPtr<EventStateManager> esm =
3631 ESMFromContentOrThis(aOverrideClickTarget);
3632 switch (mouseEvent->mButton) {
3633 case MouseButton::ePrimary:
3634 case MouseButton::eSecondary:
3635 case MouseButton::eMiddle: {
3636 LastMouseDownInfo& mouseDownInfo =
3637 GetLastMouseDownInfo(mouseEvent->mButton);
3638 mouseDownInfo.mLastMouseDownContent = nullptr;
3639 mouseDownInfo.mClickCount = 0;
3640 mouseDownInfo.mLastMouseDownInputControlType = Nothing();
3641 break;
3644 default:
3645 break;
3649 nsCOMPtr<nsIContent> activeContent;
3650 // When content calls PreventDefault on pointerdown, we also call
3651 // PreventDefault on the subsequent mouse events to suppress default
3652 // behaviors. Normally, aStatus should be nsEventStatus_eConsumeNoDefault
3653 // when the event is DefaultPrevented but it's reset to
3654 // nsEventStatus_eIgnore in EventStateManager::PreHandleEvent. So we also
3655 // check if the event is DefaultPrevented.
3656 if (nsEventStatus_eConsumeNoDefault != *aStatus &&
3657 !aEvent->DefaultPrevented()) {
3658 nsCOMPtr<nsIContent> newFocus;
3659 bool suppressBlur = false;
3660 if (mCurrentTarget) {
3661 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
3662 activeContent = mCurrentTarget->GetContent();
3664 // In some cases, we do not want to even blur the current focused
3665 // element. Those cases are:
3666 // 1. -moz-user-focus CSS property is set to 'ignore';
3667 // 2. XUL control element has the disabled property set to 'true'.
3669 // We can't use nsIFrame::IsFocusable() because we want to blur when
3670 // we click on a visibility: none element.
3671 // We can't use nsIContent::IsFocusable() because we want to blur when
3672 // we click on a non-focusable element like a <div>.
3673 // We have to use |aEvent->mTarget| to not make sure we do not check
3674 // an anonymous node of the targeted element.
3675 suppressBlur =
3676 mCurrentTarget->StyleUI()->UserFocus() == StyleUserFocus::Ignore;
3678 if (!suppressBlur) {
3679 if (Element* element =
3680 Element::FromEventTargetOrNull(aEvent->mTarget)) {
3681 if (nsCOMPtr<nsIDOMXULControlElement> xulControl =
3682 element->AsXULControl()) {
3683 bool disabled = false;
3684 xulControl->GetDisabled(&disabled);
3685 suppressBlur = disabled;
3691 // When a root content which isn't editable but has an editable HTML
3692 // <body> element is clicked, we should redirect the focus to the
3693 // the <body> element. E.g., when an user click bottom of the editor
3694 // where is outside of the <body> element, the <body> should be focused
3695 // and the user can edit immediately after that.
3697 // NOTE: The newFocus isn't editable that also means it's not in
3698 // designMode. In designMode, all contents are not focusable.
3699 if (newFocus && !newFocus->IsEditable()) {
3700 Document* doc = newFocus->GetComposedDoc();
3701 if (doc && newFocus == doc->GetRootElement()) {
3702 nsIContent* bodyContent =
3703 nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
3704 if (bodyContent && bodyContent->GetPrimaryFrame()) {
3705 newFocus = bodyContent;
3710 // When the mouse is pressed, the default action is to focus the
3711 // target. Look for the nearest enclosing focusable frame.
3713 // TODO: Probably this should be moved to Element::PostHandleEvent.
3714 for (; newFocus; newFocus = newFocus->GetFlattenedTreeParent()) {
3715 if (!newFocus->IsElement()) {
3716 continue;
3719 nsIFrame* frame = newFocus->GetPrimaryFrame();
3720 if (!frame) {
3721 continue;
3724 // If the mousedown happened inside a popup, don't try to set focus on
3725 // one of its containing elements
3726 if (frame->IsMenuPopupFrame()) {
3727 newFocus = nullptr;
3728 break;
3731 if (frame->IsFocusable(/* aWithMouse = */ true)) {
3732 break;
3735 if (ShadowRoot* root = newFocus->GetShadowRoot()) {
3736 if (root->DelegatesFocus()) {
3737 if (Element* firstFocusable =
3738 root->GetFocusDelegate(/* aWithMouse */ true)) {
3739 newFocus = firstFocusable;
3740 break;
3746 MOZ_ASSERT_IF(newFocus, newFocus->IsElement());
3748 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3749 // if something was found to focus, focus it. Otherwise, if the
3750 // element that was clicked doesn't have -moz-user-focus: ignore,
3751 // clear the existing focus. For -moz-user-focus: ignore, the focus
3752 // is just left as is.
3753 // Another effect of mouse clicking, handled in Selection, is that
3754 // it should update the caret position to where the mouse was
3755 // clicked. Because the focus is cleared when clicking on a
3756 // non-focusable node, the next press of the tab key will cause
3757 // focus to be shifted from the caret position instead of the root.
3758 if (newFocus) {
3759 // use the mouse flag and the noscroll flag so that the content
3760 // doesn't unexpectedly scroll when clicking an element that is
3761 // only half visible
3762 uint32_t flags =
3763 nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
3764 // If this was a touch-generated event, pass that information:
3765 if (mouseEvent->mInputSource ==
3766 MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3767 flags |= nsIFocusManager::FLAG_BYTOUCH;
3769 fm->SetFocus(MOZ_KnownLive(newFocus->AsElement()), flags);
3770 } else if (!suppressBlur) {
3771 // clear the focus within the frame and then set it as the
3772 // focused frame
3773 EnsureDocument(mPresContext);
3774 if (mDocument) {
3775 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mDocument->GetWindow();
3776 #ifdef XP_MACOSX
3777 if (!activeContent || !activeContent->IsXULElement())
3778 #endif
3779 fm->ClearFocus(outerWindow);
3780 // Prevent switch frame if we're already not in the foreground tab
3781 // and we're in a content process.
3782 // TODO: If we were inactive frame in this tab, and now in
3783 // background tab, we shouldn't make the tab foreground, but
3784 // we should set focus to clicked document in the background
3785 // tab. However, nsFocusManager does not have proper method
3786 // for doing this. Therefore, we should skip setting focus
3787 // to clicked document for now.
3788 if (XRE_IsParentProcess() || IsInActiveTab(mDocument)) {
3789 fm->SetFocusedWindow(outerWindow);
3795 // The rest is left button-specific.
3796 if (mouseEvent->mButton != MouseButton::ePrimary) {
3797 break;
3800 // The nearest enclosing element goes into the :active state. If we're
3801 // not an element (so we're text or something) we need to obtain
3802 // our parent element and put it into :active instead.
3803 if (activeContent && !activeContent->IsElement()) {
3804 if (nsIContent* par = activeContent->GetFlattenedTreeParent()) {
3805 activeContent = par;
3808 } else {
3809 // if we're here, the event handler returned false, so stop
3810 // any of our own processing of a drag. Workaround for bug 43258.
3811 StopTrackingDragGesture(true);
3813 // XXX Why do we always set this is active? Active window may be changed
3814 // by a mousedown event listener.
3815 if (NeedsActiveContentChange(mouseEvent)) {
3816 SetActiveManager(this, activeContent);
3818 } break;
3819 case ePointerCancel:
3820 case ePointerUp: {
3821 WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
3822 MOZ_ASSERT(pointerEvent);
3823 // Implicitly releasing capture for given pointer. ePointerLostCapture
3824 // should be send after ePointerUp or ePointerCancel.
3825 PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
3826 PointerEventHandler::UpdateActivePointerState(pointerEvent);
3828 if (pointerEvent->mMessage == ePointerCancel ||
3829 pointerEvent->mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3830 // After pointercancel, pointer becomes invalid so we can remove
3831 // relevant helper from table. Regarding pointerup with non-hoverable
3832 // device, the pointer also becomes invalid. Hoverable (mouse/pen)
3833 // pointers are valid all the time (not only between down/up).
3834 GenerateMouseEnterExit(pointerEvent);
3835 mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
3837 break;
3839 case eMouseUp: {
3840 // We can unconditionally stop capturing because
3841 // we should never be capturing when the mouse button is up
3842 PresShell::ReleaseCapturingContent();
3844 WidgetMouseEvent* mouseUpEvent = aEvent->AsMouseEvent();
3845 if (NeedsActiveContentChange(mouseUpEvent)) {
3846 ClearGlobalActiveContent(this);
3848 if (mouseUpEvent && EventCausesClickEvents(*mouseUpEvent)) {
3849 // Make sure to dispatch the click even if there is no frame for
3850 // the current target element. This is required for Web compatibility.
3851 RefPtr<EventStateManager> esm =
3852 ESMFromContentOrThis(aOverrideClickTarget);
3853 ret =
3854 esm->PostHandleMouseUp(mouseUpEvent, aStatus, aOverrideClickTarget);
3857 if (PresShell* presShell = presContext->GetPresShell()) {
3858 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
3859 frameSelection->SetDragState(false);
3861 } break;
3862 case eWheelOperationEnd: {
3863 MOZ_ASSERT(aEvent->IsTrusted());
3864 ScrollbarsForWheel::MayInactivate();
3865 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3866 nsIScrollableFrame* scrollTarget =
3867 do_QueryFrame(ComputeScrollTargetAndMayAdjustWheelEvent(
3868 mCurrentTarget, wheelEvent,
3869 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR));
3870 // If the wheel event was handled by APZ, APZ will perform the scroll
3871 // snap.
3872 if (scrollTarget && !WheelTransaction::HandledByApz()) {
3873 scrollTarget->ScrollSnap();
3875 } break;
3876 case eWheel:
3877 case eWheelOperationStart: {
3878 MOZ_ASSERT(aEvent->IsTrusted());
3880 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
3881 ScrollbarsForWheel::Inactivate();
3882 break;
3885 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3886 MOZ_ASSERT(wheelEvent);
3888 // When APZ is enabled, the actual scroll animation might be handled by
3889 // the compositor.
3890 WheelPrefs::Action action =
3891 wheelEvent->mFlags.mHandledByAPZ
3892 ? WheelPrefs::ACTION_NONE
3893 : WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
3895 WheelDeltaAdjustmentStrategy strategy =
3896 GetWheelDeltaAdjustmentStrategy(*wheelEvent);
3897 // Adjust the delta values of the wheel event if the current default
3898 // action is to horizontalize scrolling. I.e., deltaY values are set to
3899 // deltaX and deltaY and deltaZ values are set to 0.
3900 // If horizontalized, the delta values will be restored and its overflow
3901 // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
3902 // being destroyed.
3903 WheelDeltaHorizontalizer horizontalizer(*wheelEvent);
3904 if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
3905 horizontalizer.Horizontalize();
3908 // Since ComputeScrollTargetAndMayAdjustWheelEvent() may adjust the delta
3909 // if the event is auto-dir. So we use |ESMAutoDirWheelDeltaRestorer|
3910 // here.
3911 // An instance of |ESMAutoDirWheelDeltaRestorer| is used to monitor
3912 // auto-dir adjustment which may happen during its lifetime. If the delta
3913 // values is adjusted during its lifetime, the instance will restore the
3914 // adjusted delta when it's being destrcuted.
3915 ESMAutoDirWheelDeltaRestorer restorer(*wheelEvent);
3916 nsIFrame* frameToScroll = ComputeScrollTargetAndMayAdjustWheelEvent(
3917 mCurrentTarget, wheelEvent,
3918 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR);
3920 switch (action) {
3921 case WheelPrefs::ACTION_SCROLL:
3922 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL: {
3923 // For scrolling of default action, we should honor the mouse wheel
3924 // transaction.
3926 ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget,
3927 wheelEvent);
3929 if (aEvent->mMessage != eWheel ||
3930 (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
3931 break;
3934 nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll);
3935 ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget);
3937 nsIFrame* rootScrollFrame =
3938 !mCurrentTarget
3939 ? nullptr
3940 : mCurrentTarget->PresShell()->GetRootScrollFrame();
3941 nsIScrollableFrame* rootScrollableFrame = nullptr;
3942 if (rootScrollFrame) {
3943 rootScrollableFrame = do_QueryFrame(rootScrollFrame);
3945 if (!scrollTarget || scrollTarget == rootScrollableFrame) {
3946 wheelEvent->mViewPortIsOverscrolled = true;
3948 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
3949 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
3950 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
3951 wheelEvent);
3952 if (scrollTarget) {
3953 DoScrollText(scrollTarget, wheelEvent);
3954 } else {
3955 WheelTransaction::EndTransaction();
3956 ScrollbarsForWheel::Inactivate();
3958 break;
3960 case WheelPrefs::ACTION_HISTORY: {
3961 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3962 // the direction is oblique, don't perform history back/forward.
3963 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3964 if (!intDelta) {
3965 break;
3967 DoScrollHistory(intDelta);
3968 break;
3970 case WheelPrefs::ACTION_ZOOM: {
3971 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3972 // the direction is oblique, don't perform zoom in/out.
3973 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3974 if (!intDelta) {
3975 break;
3977 DoScrollZoom(mCurrentTarget, intDelta);
3978 break;
3980 case WheelPrefs::ACTION_NONE:
3981 default:
3982 bool allDeltaOverflown = false;
3983 if (StaticPrefs::dom_event_wheel_event_groups_enabled() &&
3984 (wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0)) {
3985 if (frameToScroll) {
3986 WheelTransaction::WillHandleDefaultAction(
3987 wheelEvent, frameToScroll, mCurrentTarget);
3988 } else {
3989 WheelTransaction::EndTransaction();
3992 if (wheelEvent->mFlags.mHandledByAPZ) {
3993 if (wheelEvent->mCanTriggerSwipe) {
3994 // For events that can trigger swipes, APZ needs to know whether
3995 // scrolling is possible in the requested direction. It does this
3996 // by looking at the scroll overflow values on mCanTriggerSwipe
3997 // events after they have been processed.
3998 allDeltaOverflown = !ComputeScrollTarget(
3999 mCurrentTarget, wheelEvent, COMPUTE_DEFAULT_ACTION_TARGET);
4001 } else {
4002 // The event was processed neither by APZ nor by us, so all of the
4003 // delta values must be overflown delta values.
4004 allDeltaOverflown = true;
4007 if (!allDeltaOverflown) {
4008 break;
4010 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
4011 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
4012 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
4013 wheelEvent);
4014 wheelEvent->mViewPortIsOverscrolled = true;
4015 break;
4017 *aStatus = nsEventStatus_eConsumeNoDefault;
4018 } break;
4020 case eGestureNotify: {
4021 if (nsEventStatus_eConsumeNoDefault != *aStatus) {
4022 DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
4024 } break;
4026 case eDragEnter:
4027 case eDragOver: {
4028 NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event");
4030 // Check if the drag is occurring inside a scrollable area. If so, scroll
4031 // the area when the mouse is near the edges.
4032 if (mCurrentTarget && aEvent->mMessage == eDragOver) {
4033 nsIFrame* checkFrame = mCurrentTarget;
4034 while (checkFrame) {
4035 nsIScrollableFrame* scrollFrame = do_QueryFrame(checkFrame);
4036 // Break out so only the innermost scrollframe is scrolled.
4037 if (scrollFrame && scrollFrame->DragScroll(aEvent)) {
4038 break;
4040 checkFrame = checkFrame->GetParent();
4044 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
4045 if (!dragSession) break;
4047 // Reset the flag.
4048 dragSession->SetOnlyChromeDrop(false);
4049 if (mPresContext) {
4050 EnsureDocument(mPresContext);
4052 bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
4054 // the initial dataTransfer is the one from the dragstart event that
4055 // was set on the dragSession when the drag began.
4056 RefPtr<DataTransfer> dataTransfer;
4057 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
4059 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
4061 // collect any changes to moz cursor settings stored in the event's
4062 // data transfer.
4063 UpdateDragDataTransfer(dragEvent);
4065 // cancelling a dragenter or dragover event means that a drop should be
4066 // allowed, so update the dropEffect and the canDrop state to indicate
4067 // that a drag is allowed. If the event isn't cancelled, a drop won't be
4068 // allowed. Essentially, to allow a drop somewhere, specify the effects
4069 // using the effectAllowed and dropEffect properties in a dragenter or
4070 // dragover event and cancel the event. To not allow a drop somewhere,
4071 // don't cancel the event or set the effectAllowed or dropEffect to
4072 // "none". This way, if the event is just ignored, no drop will be
4073 // allowed.
4074 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
4075 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
4076 if (nsEventStatus_eConsumeNoDefault == *aStatus) {
4077 // If the event has initialized its mDataTransfer, use it.
4078 // Or the event has not been initialized its mDataTransfer, but
4079 // it's set before dispatch because of synthesized, but without
4080 // testing session (e.g., emulating drag from another app), use it
4081 // coming from outside.
4082 // XXX Perhaps, for the latter case, we need new API because we don't
4083 // have a chance to initialize allowed effects of the session.
4084 if (dragEvent->mDataTransfer) {
4085 // get the dataTransfer and the dropEffect that was set on it
4086 dataTransfer = dragEvent->mDataTransfer;
4087 dropEffect = dataTransfer->DropEffectInt();
4088 } else {
4089 // if dragEvent->mDataTransfer is null, it means that no attempt was
4090 // made to access the dataTransfer during the event, yet the event
4091 // was cancelled. Instead, use the initial data transfer available
4092 // from the drag session. The drop effect would not have been
4093 // initialized (which is done in DragEvent::GetDataTransfer),
4094 // so set it from the drag action. We'll still want to filter it
4095 // based on the effectAllowed below.
4096 dataTransfer = initialDataTransfer;
4098 dragSession->GetDragAction(&action);
4100 // filter the drop effect based on the action. Use UNINITIALIZED as
4101 // any effect is allowed.
4102 dropEffect = nsContentUtils::FilterDropEffect(
4103 action, nsIDragService::DRAGDROP_ACTION_UNINITIALIZED);
4106 // At this point, if the dataTransfer is null, it means that the
4107 // drag was originally started by directly calling the drag service.
4108 // Just assume that all effects are allowed.
4109 uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
4110 if (dataTransfer) {
4111 effectAllowed = dataTransfer->EffectAllowedInt();
4114 // set the drag action based on the drop effect and effect allowed.
4115 // The drop effect field on the drag transfer object specifies the
4116 // desired current drop effect. However, it cannot be used if the
4117 // effectAllowed state doesn't include that type of action. If the
4118 // dropEffect is "none", then the action will be 'none' so a drop will
4119 // not be allowed.
4120 if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED ||
4121 dropEffect & effectAllowed)
4122 action = dropEffect;
4124 if (action == nsIDragService::DRAGDROP_ACTION_NONE)
4125 dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
4127 // inform the drag session that a drop is allowed on this node.
4128 dragSession->SetDragAction(action);
4129 dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE);
4131 // For now, do this only for dragover.
4132 // XXXsmaug dragenter needs some more work.
4133 if (aEvent->mMessage == eDragOver && !isChromeDoc) {
4134 // Someone has called preventDefault(), check whether is was on
4135 // content or chrome.
4136 dragSession->SetOnlyChromeDrop(
4137 !dragEvent->mDefaultPreventedOnContent);
4139 } else if (aEvent->mMessage == eDragOver && !isChromeDoc) {
4140 // No one called preventDefault(), so handle drop only in chrome.
4141 dragSession->SetOnlyChromeDrop(true);
4143 if (ContentChild* child = ContentChild::GetSingleton()) {
4144 child->SendUpdateDropEffect(action, dropEffect);
4146 if (aEvent->HasBeenPostedToRemoteProcess()) {
4147 dragSession->SetCanDrop(true);
4148 } else if (initialDataTransfer) {
4149 // Now set the drop effect in the initial dataTransfer. This ensures
4150 // that we can get the desired drop effect in the drop event. For events
4151 // dispatched to content, the content process will take care of setting
4152 // this.
4153 initialDataTransfer->SetDropEffectInt(dropEffect);
4155 } break;
4157 case eDrop: {
4158 if (aEvent->mFlags.mIsSynthesizedForTests) {
4159 if (nsCOMPtr<nsIDragSession> dragSession =
4160 nsContentUtils::GetDragSession()) {
4161 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
4162 RefPtr<WindowContext> sourceWC;
4163 DebugOnly<nsresult> rvIgnored =
4164 dragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
4165 NS_WARNING_ASSERTION(
4166 NS_SUCCEEDED(rvIgnored),
4167 "nsIDragSession::GetSourceDocument() failed, but ignored");
4168 // If the drag source hasn't been initialized, i.e., dragstart was
4169 // consumed by the test, the test needs to dispatch "dragend" event
4170 // instead of the drag session. Therefore, it does not make sense
4171 // to set drag end point in such case (you hit assersion if you do
4172 // it).
4173 if (sourceWC) {
4174 CSSIntPoint dropPointInScreen =
4175 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
4176 .extract();
4177 dragSession->SetDragEndPointForTests(dropPointInScreen.x,
4178 dropPointInScreen.y);
4182 sLastDragOverFrame = nullptr;
4183 ClearGlobalActiveContent(this);
4184 break;
4186 case eDragExit:
4187 // make sure to fire the enter and exit_synth events after the
4188 // eDragExit event, otherwise we'll clean up too early
4189 GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
4190 if (ContentChild* child = ContentChild::GetSingleton()) {
4191 // SendUpdateDropEffect to prevent nsIDragService from waiting for
4192 // response of forwarded dragexit event.
4193 child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
4194 nsIDragService::DRAGDROP_ACTION_NONE);
4196 break;
4198 case eKeyUp:
4199 // If space key is released, we need to inactivate the element which was
4200 // activated by preceding space key down.
4201 // XXX Currently, we don't store the reason of activation. Therefore,
4202 // this may cancel what is activated by a mousedown, but it must not
4203 // cause actual problem in web apps in the wild since it must be
4204 // rare case that users release space key during a mouse click/drag.
4205 if (aEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
4206 ClearGlobalActiveContent(this);
4208 break;
4210 case eKeyPress: {
4211 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
4212 PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
4213 } break;
4215 case eMouseEnterIntoWidget:
4216 if (mCurrentTarget) {
4217 nsCOMPtr<nsIContent> targetContent;
4218 mCurrentTarget->GetContentForEvent(aEvent,
4219 getter_AddRefs(targetContent));
4220 SetContentState(targetContent, ElementState::HOVER);
4222 break;
4224 case eMouseExitFromWidget:
4225 PointerEventHandler::UpdateActivePointerState(aEvent->AsMouseEvent());
4226 break;
4228 #ifdef XP_MACOSX
4229 case eMouseActivate:
4230 if (mCurrentTarget) {
4231 nsCOMPtr<nsIContent> targetContent;
4232 mCurrentTarget->GetContentForEvent(aEvent,
4233 getter_AddRefs(targetContent));
4234 if (!NodeAllowsClickThrough(targetContent)) {
4235 *aStatus = nsEventStatus_eConsumeNoDefault;
4238 break;
4239 #endif
4241 default:
4242 break;
4245 // Reset target frame to null to avoid mistargeting after reentrant event
4246 mCurrentTarget = nullptr;
4247 mCurrentTargetContent = nullptr;
4249 return ret;
4252 BrowserParent* EventStateManager::GetCrossProcessTarget() {
4253 return IMEStateManager::GetActiveBrowserParent();
4256 bool EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent) {
4257 // Check to see if there is a focused, editable content in chrome,
4258 // in that case, do not forward IME events to content
4259 Element* focusedElement = GetFocusedElement();
4260 if (focusedElement && focusedElement->IsEditable()) {
4261 return false;
4263 return IMEStateManager::GetActiveBrowserParent() != nullptr;
4266 void EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) {
4267 RefPtr<nsPresContext> presContext = aPresContext;
4268 if (presContext) {
4269 IMEStateManager::OnDestroyPresContext(*presContext);
4272 // Bug 70855: Presentation is going away, possibly for a reframe.
4273 // Reset the hover state so that if we're recreating the presentation,
4274 // we won't have the old hover state still set in the new presentation,
4275 // as if the new presentation is resized, a new element may be hovered.
4276 ResetHoverState();
4278 mPointersEnterLeaveHelper.Clear();
4279 PointerEventHandler::NotifyDestroyPresContext(presContext);
4282 void EventStateManager::ResetHoverState() {
4283 if (mHoverContent) {
4284 SetContentState(nullptr, ElementState::HOVER);
4288 void EventStateManager::SetPresContext(nsPresContext* aPresContext) {
4289 mPresContext = aPresContext;
4292 void EventStateManager::ClearFrameRefs(nsIFrame* aFrame) {
4293 if (aFrame && aFrame == mCurrentTarget) {
4294 mCurrentTargetContent = aFrame->GetContent();
4298 struct CursorImage {
4299 gfx::IntPoint mHotspot;
4300 nsCOMPtr<imgIContainer> mContainer;
4301 ImageResolution mResolution;
4302 bool mEarlierCursorLoading = false;
4305 // Given the event that we're processing, and the computed cursor and hotspot,
4306 // determine whether the custom CSS cursor should be blocked (that is, not
4307 // honored).
4309 // We will not honor it all of the following are true:
4311 // * the size of the custom cursor is bigger than layout.cursor.block.max-size.
4312 // * the bounds of the cursor would end up outside of the viewport of the
4313 // top-level content document.
4315 // This is done in order to prevent hijacking the cursor, see bug 1445844 and
4316 // co.
4317 static bool ShouldBlockCustomCursor(nsPresContext* aPresContext,
4318 WidgetEvent* aEvent,
4319 const CursorImage& aCursor) {
4320 int32_t width = 0;
4321 int32_t height = 0;
4322 aCursor.mContainer->GetWidth(&width);
4323 aCursor.mContainer->GetHeight(&height);
4324 aCursor.mResolution.ApplyTo(width, height);
4326 int32_t maxSize = StaticPrefs::layout_cursor_block_max_size();
4328 if (width <= maxSize && height <= maxSize) {
4329 return false;
4332 auto input = DOMIntersectionObserver::ComputeInput(*aPresContext->Document(),
4333 nullptr, nullptr);
4335 if (!input.mRootFrame) {
4336 return false;
4339 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4340 aEvent, RelativeTo{input.mRootFrame});
4342 // The cursor size won't be affected by our full zoom in the parent process,
4343 // so undo that before checking the rect.
4344 float zoom = aPresContext->GetFullZoom();
4346 // Also adjust for accessibility cursor scaling factor.
4347 zoom /= LookAndFeel::GetFloat(LookAndFeel::FloatID::CursorScale, 1.0f);
4349 nsSize size(CSSPixel::ToAppUnits(width / zoom),
4350 CSSPixel::ToAppUnits(height / zoom));
4351 nsPoint hotspot(
4352 CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.x / zoom)),
4353 CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.y / zoom)));
4355 const nsRect cursorRect(point - hotspot, size);
4356 auto output = DOMIntersectionObserver::Intersect(input, cursorRect);
4357 return !output.mIntersectionRect ||
4358 !(*output.mIntersectionRect == cursorRect);
4361 static gfx::IntPoint ComputeHotspot(imgIContainer* aContainer,
4362 const Maybe<gfx::Point>& aHotspot) {
4363 MOZ_ASSERT(aContainer);
4365 // css3-ui says to use the CSS-specified hotspot if present,
4366 // otherwise use the intrinsic hotspot, otherwise use the top left
4367 // corner.
4368 if (aHotspot) {
4369 int32_t imgWidth, imgHeight;
4370 aContainer->GetWidth(&imgWidth);
4371 aContainer->GetHeight(&imgHeight);
4372 auto hotspot = gfx::IntPoint::Round(*aHotspot);
4373 return {std::max(std::min(hotspot.x.value, imgWidth - 1), 0),
4374 std::max(std::min(hotspot.y.value, imgHeight - 1), 0)};
4377 gfx::IntPoint hotspot;
4378 aContainer->GetHotspotX(&hotspot.x.value);
4379 aContainer->GetHotspotY(&hotspot.y.value);
4380 return hotspot;
4383 static CursorImage ComputeCustomCursor(nsPresContext* aPresContext,
4384 WidgetEvent* aEvent,
4385 const nsIFrame& aFrame,
4386 const nsIFrame::Cursor& aCursor) {
4387 if (aCursor.mAllowCustomCursor == nsIFrame::AllowCustomCursorImage::No) {
4388 return {};
4390 const ComputedStyle& style =
4391 aCursor.mStyle ? *aCursor.mStyle : *aFrame.Style();
4393 // If we are falling back because any cursor before us is loading, let the
4394 // consumer know.
4395 bool loading = false;
4396 for (const auto& image : style.StyleUI()->Cursor().images.AsSpan()) {
4397 MOZ_ASSERT(image.image.IsImageRequestType(),
4398 "Cursor image should only parse url() types");
4399 uint32_t status;
4400 imgRequestProxy* req = image.image.GetImageRequest();
4401 if (!req || NS_FAILED(req->GetImageStatus(&status))) {
4402 continue;
4404 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4405 loading = true;
4406 continue;
4408 if (status & imgIRequest::STATUS_ERROR) {
4409 continue;
4411 nsCOMPtr<imgIContainer> container;
4412 req->GetImage(getter_AddRefs(container));
4413 if (!container) {
4414 continue;
4416 StyleImageOrientation orientation =
4417 aFrame.StyleVisibility()->UsedImageOrientation(req);
4418 container = nsLayoutUtils::OrientImage(container, orientation);
4419 Maybe<gfx::Point> specifiedHotspot =
4420 image.has_hotspot ? Some(gfx::Point{image.hotspot_x, image.hotspot_y})
4421 : Nothing();
4422 gfx::IntPoint hotspot = ComputeHotspot(container, specifiedHotspot);
4423 CursorImage result{hotspot, std::move(container),
4424 image.image.GetResolution(style), loading};
4425 if (ShouldBlockCustomCursor(aPresContext, aEvent, result)) {
4426 continue;
4428 // This is the one we want!
4429 return result;
4431 return {{}, nullptr, {}, loading};
4434 void EventStateManager::UpdateCursor(nsPresContext* aPresContext,
4435 WidgetMouseEvent* aEvent,
4436 nsIFrame* aTargetFrame,
4437 nsEventStatus* aStatus) {
4438 if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
4439 return;
4442 auto cursor = StyleCursorKind::Default;
4443 nsCOMPtr<imgIContainer> container;
4444 ImageResolution resolution;
4445 Maybe<gfx::IntPoint> hotspot;
4447 if (mHidingCursorWhileTyping && aEvent->IsReal()) {
4448 // Any non-synthetic mouse event makes us show the cursor again.
4449 mHidingCursorWhileTyping = false;
4452 if (mHidingCursorWhileTyping) {
4453 cursor = StyleCursorKind::None;
4454 } else if (mLockCursor != kInvalidCursorKind) {
4455 // If cursor is locked just use the locked one
4456 cursor = mLockCursor;
4457 } else if (aTargetFrame) {
4458 // If not locked, look for correct cursor
4459 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4460 aEvent, RelativeTo{aTargetFrame});
4461 const nsIFrame::Cursor framecursor = aTargetFrame->GetCursor(pt);
4462 const CursorImage customCursor =
4463 ComputeCustomCursor(aPresContext, aEvent, *aTargetFrame, framecursor);
4465 // If the current cursor is from the same frame, and it is now
4466 // loading some new image for the cursor, we should wait for a
4467 // while rather than taking its fallback cursor directly.
4468 if (customCursor.mEarlierCursorLoading &&
4469 gLastCursorSourceFrame == aTargetFrame &&
4470 TimeStamp::NowLoRes() - gLastCursorUpdateTime <
4471 TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
4472 return;
4474 cursor = framecursor.mCursor;
4475 container = std::move(customCursor.mContainer);
4476 resolution = customCursor.mResolution;
4477 hotspot = Some(customCursor.mHotspot);
4480 if (aTargetFrame) {
4481 if (cursor == StyleCursorKind::Pointer && IsSelectingLink(aTargetFrame)) {
4482 cursor = aTargetFrame->GetWritingMode().IsVertical()
4483 ? StyleCursorKind::VerticalText
4484 : StyleCursorKind::Text;
4486 SetCursor(cursor, container, resolution, hotspot,
4487 aTargetFrame->GetNearestWidget(), false);
4488 gLastCursorSourceFrame = aTargetFrame;
4489 gLastCursorUpdateTime = TimeStamp::NowLoRes();
4492 if (mLockCursor != kInvalidCursorKind || StyleCursorKind::Auto != cursor) {
4493 *aStatus = nsEventStatus_eConsumeDoDefault;
4497 void EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame) {
4498 if (!aTargetFrame) {
4499 return;
4501 nsIWidget* aWidget = aTargetFrame->GetNearestWidget();
4502 if (!aWidget) {
4503 return;
4505 aWidget->ClearCachedCursor();
4508 void EventStateManager::StartHidingCursorWhileTyping(nsIWidget* aWidget) {
4509 if (mHidingCursorWhileTyping || sCursorSettingManager != this) {
4510 return;
4512 mHidingCursorWhileTyping = true;
4513 SetCursor(StyleCursorKind::None, nullptr, {}, {}, aWidget, false);
4516 nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
4517 imgIContainer* aContainer,
4518 const ImageResolution& aResolution,
4519 const Maybe<gfx::IntPoint>& aHotspot,
4520 nsIWidget* aWidget, bool aLockCursor) {
4521 EnsureDocument(mPresContext);
4522 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
4523 sCursorSettingManager = this;
4525 NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
4526 if (aLockCursor) {
4527 if (StyleCursorKind::Auto != aCursor) {
4528 mLockCursor = aCursor;
4529 } else {
4530 // If cursor style is set to auto we unlock the cursor again.
4531 mLockCursor = kInvalidCursorKind;
4534 nsCursor c;
4535 switch (aCursor) {
4536 case StyleCursorKind::Auto:
4537 case StyleCursorKind::Default:
4538 c = eCursor_standard;
4539 break;
4540 case StyleCursorKind::Pointer:
4541 c = eCursor_hyperlink;
4542 break;
4543 case StyleCursorKind::Crosshair:
4544 c = eCursor_crosshair;
4545 break;
4546 case StyleCursorKind::Move:
4547 c = eCursor_move;
4548 break;
4549 case StyleCursorKind::Text:
4550 c = eCursor_select;
4551 break;
4552 case StyleCursorKind::Wait:
4553 c = eCursor_wait;
4554 break;
4555 case StyleCursorKind::Help:
4556 c = eCursor_help;
4557 break;
4558 case StyleCursorKind::NResize:
4559 c = eCursor_n_resize;
4560 break;
4561 case StyleCursorKind::SResize:
4562 c = eCursor_s_resize;
4563 break;
4564 case StyleCursorKind::WResize:
4565 c = eCursor_w_resize;
4566 break;
4567 case StyleCursorKind::EResize:
4568 c = eCursor_e_resize;
4569 break;
4570 case StyleCursorKind::NwResize:
4571 c = eCursor_nw_resize;
4572 break;
4573 case StyleCursorKind::SeResize:
4574 c = eCursor_se_resize;
4575 break;
4576 case StyleCursorKind::NeResize:
4577 c = eCursor_ne_resize;
4578 break;
4579 case StyleCursorKind::SwResize:
4580 c = eCursor_sw_resize;
4581 break;
4582 case StyleCursorKind::Copy: // CSS3
4583 c = eCursor_copy;
4584 break;
4585 case StyleCursorKind::Alias:
4586 c = eCursor_alias;
4587 break;
4588 case StyleCursorKind::ContextMenu:
4589 c = eCursor_context_menu;
4590 break;
4591 case StyleCursorKind::Cell:
4592 c = eCursor_cell;
4593 break;
4594 case StyleCursorKind::Grab:
4595 c = eCursor_grab;
4596 break;
4597 case StyleCursorKind::Grabbing:
4598 c = eCursor_grabbing;
4599 break;
4600 case StyleCursorKind::Progress:
4601 c = eCursor_spinning;
4602 break;
4603 case StyleCursorKind::ZoomIn:
4604 c = eCursor_zoom_in;
4605 break;
4606 case StyleCursorKind::ZoomOut:
4607 c = eCursor_zoom_out;
4608 break;
4609 case StyleCursorKind::NotAllowed:
4610 c = eCursor_not_allowed;
4611 break;
4612 case StyleCursorKind::ColResize:
4613 c = eCursor_col_resize;
4614 break;
4615 case StyleCursorKind::RowResize:
4616 c = eCursor_row_resize;
4617 break;
4618 case StyleCursorKind::NoDrop:
4619 c = eCursor_no_drop;
4620 break;
4621 case StyleCursorKind::VerticalText:
4622 c = eCursor_vertical_text;
4623 break;
4624 case StyleCursorKind::AllScroll:
4625 c = eCursor_all_scroll;
4626 break;
4627 case StyleCursorKind::NeswResize:
4628 c = eCursor_nesw_resize;
4629 break;
4630 case StyleCursorKind::NwseResize:
4631 c = eCursor_nwse_resize;
4632 break;
4633 case StyleCursorKind::NsResize:
4634 c = eCursor_ns_resize;
4635 break;
4636 case StyleCursorKind::EwResize:
4637 c = eCursor_ew_resize;
4638 break;
4639 case StyleCursorKind::None:
4640 c = eCursor_none;
4641 break;
4642 default:
4643 MOZ_ASSERT_UNREACHABLE("Unknown cursor kind");
4644 c = eCursor_standard;
4645 break;
4648 uint32_t x = aHotspot ? aHotspot->x.value : 0;
4649 uint32_t y = aHotspot ? aHotspot->y.value : 0;
4650 aWidget->SetCursor(nsIWidget::Cursor{c, aContainer, x, y, aResolution});
4651 return NS_OK;
4654 class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback {
4655 public:
4656 explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
4658 MOZ_CAN_RUN_SCRIPT
4659 void HandleEvent(EventChainPostVisitor& aVisitor) override {
4660 if (aVisitor.mPresContext) {
4661 nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget);
4662 if (frame) {
4663 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
4664 &aVisitor.mEventStatus);
4669 nsCOMPtr<nsIContent> mTarget;
4672 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
4673 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4674 EventTarget* aRelatedTarget) {
4675 // This method does not support creating a mouse/pointer button change event
4676 // because of no data about the changing state.
4677 MOZ_ASSERT(aMessage != eMouseDown);
4678 MOZ_ASSERT(aMessage != eMouseUp);
4679 MOZ_ASSERT(aMessage != ePointerDown);
4680 MOZ_ASSERT(aMessage != ePointerUp);
4681 // This method is currently designed to create the following events.
4682 MOZ_ASSERT(aMessage == eMouseOver || aMessage == eMouseEnter ||
4683 aMessage == eMouseOut || aMessage == eMouseLeave ||
4684 aMessage == ePointerOver || aMessage == ePointerEnter ||
4685 aMessage == ePointerOut || aMessage == ePointerLeave ||
4686 aMessage == eMouseEnterIntoWidget ||
4687 aMessage == eMouseExitFromWidget);
4689 WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
4690 UniquePtr<WidgetMouseEvent> newEvent;
4691 if (sourcePointer) {
4692 AUTO_PROFILER_LABEL("CreateMouseOrPointerWidgetEvent", OTHER);
4694 WidgetPointerEvent* newPointerEvent = new WidgetPointerEvent(
4695 aMouseEvent->IsTrusted(), aMessage, aMouseEvent->mWidget);
4696 newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary;
4697 newPointerEvent->mWidth = sourcePointer->mWidth;
4698 newPointerEvent->mHeight = sourcePointer->mHeight;
4699 newPointerEvent->mInputSource = sourcePointer->mInputSource;
4701 newEvent = WrapUnique(newPointerEvent);
4702 } else {
4703 newEvent = MakeUnique<WidgetMouseEvent>(aMouseEvent->IsTrusted(), aMessage,
4704 aMouseEvent->mWidget,
4705 WidgetMouseEvent::eReal);
4708 // Inherit whether the event is synthesized by the test API or not.
4709 // Then, when the event is synthesized by a test API and handled in a remote
4710 // process, it won't be ignored. See PresShell::HandleEvent().
4711 newEvent->mFlags.mIsSynthesizedForTests =
4712 aMouseEvent->mFlags.mIsSynthesizedForTests;
4714 newEvent->mRelatedTarget = aRelatedTarget;
4715 newEvent->mRefPoint = aMouseEvent->mRefPoint;
4716 newEvent->mModifiers = aMouseEvent->mModifiers;
4717 if (!aMouseEvent->mFlags.mDispatchedAtLeastOnce &&
4718 aMouseEvent->InputSourceSupportsHover()) {
4719 // If we synthesize a pointer event or a mouse event from another event
4720 // which changes a button state whose input soucre supports hover state and
4721 // the source event has not been dispatched yet, we should set to the button
4722 // state of the synthesizing event to previous one.
4723 // Note that we don't need to do this if the input source does not support
4724 // hover state because a WPT check the behavior (see below) and the other
4725 // browsers pass the test even though this is inconsistent behavior.
4726 newEvent->mButton =
4727 sourcePointer ? MouseButton::eNotPressed : MouseButton::ePrimary;
4728 if (aMouseEvent->IsPressingButton()) {
4729 // If the source event has not been dispatched into the DOM yet, we
4730 // need to remove the flag which is being pressed.
4731 newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
4732 aMouseEvent->mButtons &
4733 ~MouseButtonsFlagToChange(
4734 static_cast<MouseButton>(aMouseEvent->mButton)));
4735 } else if (aMouseEvent->IsReleasingButton()) {
4736 // If the source event has not been dispatched into the DOM yet, we
4737 // need to add the flag which is being released.
4738 newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
4739 aMouseEvent->mButtons |
4740 MouseButtonsFlagToChange(
4741 static_cast<MouseButton>(aMouseEvent->mButton)));
4742 } else {
4743 // The source event does not change the buttons state so that we can
4744 // set mButtons value as-is.
4745 newEvent->mButtons = aMouseEvent->mButtons;
4747 // Adjust pressure if it does not matches with mButtons.
4748 // FIXME: We may use wrong pressure value if the source event has not been
4749 // dispatched into the DOM yet. However, fixing this requires to store the
4750 // last pressure value somewhere.
4751 if (newEvent->mButtons && aMouseEvent->mPressure == 0) {
4752 newEvent->mPressure = 0.5f;
4753 } else if (!newEvent->mButtons && aMouseEvent->mPressure != 0) {
4754 newEvent->mPressure = 0;
4755 } else {
4756 newEvent->mPressure = aMouseEvent->mPressure;
4758 } else {
4759 // If the event has already been dispatched into the tree, web apps has
4760 // already handled the button state change, so the button state of the
4761 // source event has already synced.
4762 // If the input source does not have hover state, we don't need to modify
4763 // the state because the other browsers behave so and tested by
4764 // pointerevent_attributes_nohover_pointers.html even though this is
4765 // different expectation from
4766 // pointerevent_attributes_hoverable_pointers.html, but the other browsers
4767 // pass both of them.
4768 newEvent->mButton = aMouseEvent->mButton;
4769 newEvent->mButtons = aMouseEvent->mButtons;
4770 newEvent->mPressure = aMouseEvent->mPressure;
4773 newEvent->mInputSource = aMouseEvent->mInputSource;
4774 newEvent->pointerId = aMouseEvent->pointerId;
4776 return newEvent;
4779 nsIFrame* EventStateManager::DispatchMouseOrPointerEvent(
4780 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4781 nsIContent* aTargetContent, nsIContent* aRelatedContent) {
4782 // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
4783 // "[When the mouse is locked on an element...e]vents that require the concept
4784 // of a mouse cursor must not be dispatched (for example: mouseover,
4785 // mouseout).
4786 if (PointerLockManager::IsLocked() &&
4787 (aMessage == eMouseLeave || aMessage == eMouseEnter ||
4788 aMessage == eMouseOver || aMessage == eMouseOut)) {
4789 mCurrentTargetContent = nullptr;
4790 nsCOMPtr<Element> pointerLockedElement =
4791 PointerLockManager::GetLockedElement();
4792 if (!pointerLockedElement) {
4793 NS_WARNING("Should have pointer locked element, but didn't.");
4794 return nullptr;
4796 return mPresContext->GetPrimaryFrameFor(pointerLockedElement);
4799 mCurrentTargetContent = nullptr;
4801 if (!aTargetContent) {
4802 return nullptr;
4805 nsCOMPtr<nsIContent> targetContent = aTargetContent;
4806 nsCOMPtr<nsIContent> relatedContent = aRelatedContent;
4808 UniquePtr<WidgetMouseEvent> dispatchEvent =
4809 CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, relatedContent);
4811 AutoWeakFrame previousTarget = mCurrentTarget;
4812 mCurrentTargetContent = targetContent;
4814 nsIFrame* targetFrame = nullptr;
4816 nsEventStatus status = nsEventStatus_eIgnore;
4817 ESMEventCB callback(targetContent);
4818 RefPtr<nsPresContext> presContext = mPresContext;
4819 EventDispatcher::Dispatch(targetContent, presContext, dispatchEvent.get(),
4820 nullptr, &status, &callback);
4822 if (mPresContext) {
4823 // Although the primary frame was checked in event callback, it may not be
4824 // the same object after event dispatch and handling, so refetch it.
4825 targetFrame = mPresContext->GetPrimaryFrameFor(targetContent);
4827 // If we are entering/leaving remote content, dispatch a mouse enter/exit
4828 // event to the remote frame.
4829 if (IsTopLevelRemoteTarget(targetContent)) {
4830 if (aMessage == eMouseOut) {
4831 // For remote content, send a puppet widget mouse exit event.
4832 UniquePtr<WidgetMouseEvent> remoteEvent =
4833 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
4834 relatedContent);
4835 remoteEvent->mExitFrom = Some(WidgetMouseEvent::ePuppet);
4837 // mCurrentTarget is set to the new target, so we must reset it to the
4838 // old target and then dispatch a cross-process event. (mCurrentTarget
4839 // will be set back below.) HandleCrossProcessEvent will query for the
4840 // proper target via GetEventTarget which will return mCurrentTarget.
4841 mCurrentTarget = targetFrame;
4842 HandleCrossProcessEvent(remoteEvent.get(), &status);
4843 } else if (aMessage == eMouseOver) {
4844 UniquePtr<WidgetMouseEvent> remoteEvent =
4845 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget,
4846 relatedContent);
4847 HandleCrossProcessEvent(remoteEvent.get(), &status);
4852 mCurrentTargetContent = nullptr;
4853 mCurrentTarget = previousTarget;
4855 return targetFrame;
4858 static nsIContent* FindCommonAncestor(nsIContent* aNode1, nsIContent* aNode2) {
4859 if (!aNode1 || !aNode2) {
4860 return nullptr;
4862 return nsContentUtils::GetCommonFlattenedTreeAncestor(aNode1, aNode2);
4865 class EnterLeaveDispatcher {
4866 public:
4867 EnterLeaveDispatcher(EventStateManager* aESM, nsIContent* aTarget,
4868 nsIContent* aRelatedTarget,
4869 WidgetMouseEvent* aMouseEvent,
4870 EventMessage aEventMessage)
4871 : mESM(aESM), mMouseEvent(aMouseEvent), mEventMessage(aEventMessage) {
4872 nsPIDOMWindowInner* win =
4873 aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr;
4874 if (aMouseEvent->AsPointerEvent()
4875 ? win && win->HasPointerEnterLeaveEventListeners()
4876 : win && win->HasMouseEnterLeaveEventListeners()) {
4877 mRelatedTarget =
4878 aRelatedTarget ? aRelatedTarget->FindFirstNonChromeOnlyAccessContent()
4879 : nullptr;
4880 nsINode* commonParent = FindCommonAncestor(aTarget, aRelatedTarget);
4881 nsIContent* current = aTarget;
4882 // Note, it is ok if commonParent is null!
4883 while (current && current != commonParent) {
4884 if (!current->ChromeOnlyAccess()) {
4885 mTargets.AppendObject(current);
4887 // mouseenter/leave is fired only on elements.
4888 current = current->GetFlattenedTreeParent();
4893 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
4894 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch() {
4895 if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
4896 for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
4897 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4898 MOZ_KnownLive(mTargets[i]),
4899 mRelatedTarget);
4901 } else {
4902 for (int32_t i = 0; i < mTargets.Count(); ++i) {
4903 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4904 MOZ_KnownLive(mTargets[i]),
4905 mRelatedTarget);
4910 // Nothing overwrites anything after constructor. Please remove MOZ_KnownLive
4911 // and MOZ_KNOWN_LIVE if anything marked as such becomes mutable.
4912 const RefPtr<EventStateManager> mESM;
4913 nsCOMArray<nsIContent> mTargets;
4914 MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRelatedTarget;
4915 WidgetMouseEvent* mMouseEvent;
4916 EventMessage mEventMessage;
4919 void EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
4920 nsIContent* aMovingInto) {
4921 const bool isPointer = aMouseEvent->mClass == ePointerEventClass;
4922 LogModule* const logModule =
4923 isPointer ? sPointerBoundaryLog : sMouseBoundaryLog;
4925 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
4927 // If there is no deepest "leave" event target, that means the last "over"
4928 // target has already been removed from the tree. Therefore, checking only
4929 // the "leave" event target is enough.
4930 if (!wrapper || !wrapper->GetDeepestLeaveEventTarget()) {
4931 return;
4933 // Before firing "out" and/or "leave" events, check for recursion
4934 if (wrapper->IsDispatchingOutEventOnLastOverEventTarget()) {
4935 return;
4938 MOZ_LOG(logModule, LogLevel::Info,
4939 ("NotifyMouseOut: the source event is %s (IsReal()=%s)",
4940 ToChar(aMouseEvent->mMessage),
4941 aMouseEvent->IsReal() ? "true" : "false"));
4943 // XXX If a content node is a container of remove content, it should be
4944 // replaced with them and its children should not be visible. Therefore,
4945 // if the deepest "enter" target is not the last "over" target, i.e., the
4946 // last "over" target has been removed from the DOM tree, it means that the
4947 // child/descendant was not replaced by remote content. So,
4948 // wrapper->GetOutEventTaget() may be enough here.
4949 if (RefPtr<nsFrameLoaderOwner> flo =
4950 do_QueryObject(wrapper->GetDeepestLeaveEventTarget())) {
4951 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
4952 if (nsIDocShell* docshell = bc->GetDocShell()) {
4953 if (RefPtr<nsPresContext> presContext = docshell->GetPresContext()) {
4954 EventStateManager* kidESM = presContext->EventStateManager();
4955 // Not moving into any element in this subdocument
4956 MOZ_LOG(logModule, LogLevel::Info,
4957 ("Notifying child EventStateManager (%p) of \"out\" "
4958 "event...",
4959 kidESM));
4960 kidESM->NotifyMouseOut(aMouseEvent, nullptr);
4965 // That could have caused DOM events which could wreak havoc. Reverify
4966 // things and be careful.
4967 if (!wrapper->GetDeepestLeaveEventTarget()) {
4968 return;
4971 wrapper->WillDispatchOutAndOrLeaveEvent();
4973 // Don't touch hover state if aMovingInto is non-null. Caller will update
4974 // hover state itself, and we have optimizations for hover switching between
4975 // two nearby elements both deep in the DOM tree that would be defeated by
4976 // switching the hover state to null here.
4977 if (!aMovingInto && !isPointer) {
4978 // Unset :hover
4979 SetContentState(nullptr, ElementState::HOVER);
4982 EnterLeaveDispatcher leaveDispatcher(
4983 this, wrapper->GetDeepestLeaveEventTarget(), aMovingInto, aMouseEvent,
4984 isPointer ? ePointerLeave : eMouseLeave);
4986 // "out" events hould be fired only when the deepest "leave" event target
4987 // is the last "over" event target.
4988 if (nsCOMPtr<nsIContent> outEventTarget = wrapper->GetOutEventTarget()) {
4989 MOZ_LOG(logModule, LogLevel::Info,
4990 ("Dispatching %s event to %s (%p)",
4991 isPointer ? "ePointerOut" : "eMouseOut",
4992 outEventTarget ? ToString(*outEventTarget).c_str() : "nullptr",
4993 outEventTarget.get()));
4994 DispatchMouseOrPointerEvent(aMouseEvent,
4995 isPointer ? ePointerOut : eMouseOut,
4996 outEventTarget, aMovingInto);
4999 MOZ_LOG(logModule, LogLevel::Info,
5000 ("Dispatching %s event to %s (%p) and its ancestors",
5001 isPointer ? "ePointerLeave" : "eMouseLeave",
5002 wrapper->GetDeepestLeaveEventTarget()
5003 ? ToString(*wrapper->GetDeepestLeaveEventTarget()).c_str()
5004 : "nullptr",
5005 wrapper->GetDeepestLeaveEventTarget()));
5006 leaveDispatcher.Dispatch();
5008 MOZ_LOG(logModule, LogLevel::Info,
5009 ("Dispatched \"out\" and/or \"leave\" events"));
5010 wrapper->DidDispatchOutAndOrLeaveEvent();
5013 void EventStateManager::RecomputeMouseEnterStateForRemoteFrame(
5014 Element& aElement) {
5015 if (!mMouseEnterLeaveHelper ||
5016 mMouseEnterLeaveHelper->GetDeepestLeaveEventTarget() != &aElement) {
5017 return;
5020 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
5021 remote->MouseEnterIntoWidget();
5025 void EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
5026 nsIContent* aContent) {
5027 NS_ASSERTION(aContent, "Mouse must be over something");
5029 const bool isPointer = aMouseEvent->mClass == ePointerEventClass;
5030 LogModule* const logModule =
5031 isPointer ? sPointerBoundaryLog : sMouseBoundaryLog;
5033 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
5035 // If we have next "out" event target and it's the new "over" target, we don't
5036 // need to dispatch "out" nor "enter" event.
5037 if (!wrapper || aContent == wrapper->GetOutEventTarget()) {
5038 return;
5041 // Before firing "over" and "enter" events, check for recursion
5042 if (wrapper->IsDispatchingOverEventOn(aContent)) {
5043 return;
5046 MOZ_LOG(logModule, LogLevel::Info,
5047 ("NotifyMouseOver: the source event is %s (IsReal()=%s)",
5048 ToChar(aMouseEvent->mMessage),
5049 aMouseEvent->IsReal() ? "true" : "false"));
5051 // Check to see if we're a subdocument and if so update the parent
5052 // document's ESM state to indicate that the mouse is over the
5053 // content associated with our subdocument.
5054 EnsureDocument(mPresContext);
5055 if (Document* parentDoc = mDocument->GetInProcessParentDocument()) {
5056 if (nsCOMPtr<nsIContent> docContent = mDocument->GetEmbedderElement()) {
5057 if (PresShell* parentPresShell = parentDoc->GetPresShell()) {
5058 RefPtr<EventStateManager> parentESM =
5059 parentPresShell->GetPresContext()->EventStateManager();
5060 MOZ_LOG(logModule, LogLevel::Info,
5061 ("Notifying parent EventStateManager (%p) of \"over\" "
5062 "event...",
5063 parentESM.get()));
5064 parentESM->NotifyMouseOver(aMouseEvent, docContent);
5068 // Firing the DOM event in the parent document could cause all kinds
5069 // of havoc. Reverify and take care.
5070 if (aContent == wrapper->GetOutEventTarget()) {
5071 return;
5074 // Remember the deepest leave event target as the related content for the
5075 // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it,
5076 // bug 298477.
5077 nsCOMPtr<nsIContent> deepestLeaveEventTarget =
5078 wrapper->GetDeepestLeaveEventTarget();
5080 EnterLeaveDispatcher enterDispatcher(this, aContent, deepestLeaveEventTarget,
5081 aMouseEvent,
5082 isPointer ? ePointerEnter : eMouseEnter);
5084 if (!isPointer) {
5085 SetContentState(aContent, ElementState::HOVER);
5088 NotifyMouseOut(aMouseEvent, aContent);
5090 wrapper->WillDispatchOverAndEnterEvent(aContent);
5092 // Fire mouseover
5093 // XXX If aContent has already been removed from the DOM tree, what should we
5094 // do? At least, dispatching `mouseover` on it is odd.
5095 MOZ_LOG(logModule, LogLevel::Info,
5096 ("Dispatching %s event to %s (%p)",
5097 isPointer ? "ePointerOver" : "eMoustOver",
5098 aContent ? ToString(*aContent).c_str() : "nullptr", aContent));
5099 wrapper->mLastOverFrame = DispatchMouseOrPointerEvent(
5100 aMouseEvent, isPointer ? ePointerOver : eMouseOver, aContent,
5101 deepestLeaveEventTarget);
5103 MOZ_LOG(logModule, LogLevel::Info,
5104 ("Dispatching %s event to %s (%p) and its ancestors",
5105 isPointer ? "ePointerEnter" : "eMouseEnter",
5106 aContent ? ToString(*aContent).c_str() : "nullptr", aContent));
5107 enterDispatcher.Dispatch();
5109 MOZ_LOG(logModule, LogLevel::Info,
5110 ("Dispatched \"over\" and \"enter\" events (the original \"over\" "
5111 "event target was in the document %p, and now in %p)",
5112 aContent->GetComposedDoc(), mDocument.get()));
5113 wrapper->DidDispatchOverAndEnterEvent(
5114 aContent->GetComposedDoc() == mDocument ? aContent : nullptr);
5117 // Returns the center point of the window's client area. This is
5118 // in widget coordinates, i.e. relative to the widget's top-left
5119 // corner, not in screen coordinates, the same units that UIEvent::
5120 // refpoint is in. It may not be the exact center of the window if
5121 // the platform requires rounding the coordinate.
5122 static LayoutDeviceIntPoint GetWindowClientRectCenter(nsIWidget* aWidget) {
5123 NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0));
5125 LayoutDeviceIntRect rect = aWidget->GetClientBounds();
5126 LayoutDeviceIntPoint point(rect.x + rect.width / 2, rect.y + rect.height / 2);
5127 int32_t round = aWidget->RoundsWidgetCoordinatesTo();
5128 point.x = point.x / round * round;
5129 point.y = point.y / round * round;
5130 return point - aWidget->WidgetToScreenOffset();
5133 void EventStateManager::GeneratePointerEnterExit(EventMessage aMessage,
5134 WidgetMouseEvent* aEvent) {
5135 WidgetPointerEvent pointerEvent(*aEvent);
5136 pointerEvent.mMessage = aMessage;
5137 GenerateMouseEnterExit(&pointerEvent);
5140 /* static */
5141 void EventStateManager::UpdateLastRefPointOfMouseEvent(
5142 WidgetMouseEvent* aMouseEvent) {
5143 if (aMouseEvent->mMessage != eMouseMove &&
5144 aMouseEvent->mMessage != ePointerMove) {
5145 return;
5148 // Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
5149 // Movement is calculated in UIEvent::GetMovementPoint() as:
5150 // previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
5151 if (PointerLockManager::IsLocked() && aMouseEvent->mWidget) {
5152 // The pointer is locked. If the pointer is not located at the center of
5153 // the window, dispatch a synthetic mousemove to return the pointer there.
5154 // Doing this between "real" pointer moves gives the impression that the
5155 // (locked) pointer can continue moving and won't stop at the screen
5156 // boundary. We cancel the synthetic event so that we don't end up
5157 // dispatching the centering move event to content.
5158 aMouseEvent->mLastRefPoint =
5159 GetWindowClientRectCenter(aMouseEvent->mWidget);
5161 } else if (sLastRefPoint == kInvalidRefPoint) {
5162 // We don't have a valid previous mousemove mRefPoint. This is either
5163 // the first move we've encountered, or the mouse has just re-entered
5164 // the application window. We should report (0,0) movement for this
5165 // case, so make the current and previous mRefPoints the same.
5166 aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
5167 } else {
5168 aMouseEvent->mLastRefPoint = sLastRefPoint;
5172 /* static */
5173 void EventStateManager::ResetPointerToWindowCenterWhilePointerLocked(
5174 WidgetMouseEvent* aMouseEvent) {
5175 MOZ_ASSERT(PointerLockManager::IsLocked());
5176 if ((aMouseEvent->mMessage != eMouseMove &&
5177 aMouseEvent->mMessage != ePointerMove) ||
5178 !aMouseEvent->mWidget) {
5179 return;
5182 // We generate pointermove from mousemove event, so only synthesize native
5183 // mouse move and update sSynthCenteringPoint by mousemove event.
5184 bool updateSynthCenteringPoint = aMouseEvent->mMessage == eMouseMove;
5186 // The pointer is locked. If the pointer is not located at the center of
5187 // the window, dispatch a synthetic mousemove to return the pointer there.
5188 // Doing this between "real" pointer moves gives the impression that the
5189 // (locked) pointer can continue moving and won't stop at the screen
5190 // boundary. We cancel the synthetic event so that we don't end up
5191 // dispatching the centering move event to content.
5192 LayoutDeviceIntPoint center = GetWindowClientRectCenter(aMouseEvent->mWidget);
5194 if (aMouseEvent->mRefPoint != center && updateSynthCenteringPoint) {
5195 // Mouse move doesn't finish at the center of the window. Dispatch a
5196 // synthetic native mouse event to move the pointer back to the center
5197 // of the window, to faciliate more movement. But first, record that
5198 // we've dispatched a synthetic mouse movement, so we can cancel it
5199 // in the other branch here.
5200 sSynthCenteringPoint = center;
5201 // XXX Once we fix XXX comments in SetPointerLock about this API, we could
5202 // restrict that this API works only in the automation mode or in the
5203 // pointer locked situation.
5204 aMouseEvent->mWidget->SynthesizeNativeMouseMove(
5205 center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
5206 } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
5207 // This is the "synthetic native" event we dispatched to re-center the
5208 // pointer. Cancel it so we don't expose the centering move to content.
5209 aMouseEvent->StopPropagation();
5210 // Clear sSynthCenteringPoint so we don't cancel other events
5211 // targeted at the center.
5212 if (updateSynthCenteringPoint) {
5213 sSynthCenteringPoint = kInvalidRefPoint;
5218 /* static */
5219 void EventStateManager::UpdateLastPointerPosition(
5220 WidgetMouseEvent* aMouseEvent) {
5221 if (aMouseEvent->mMessage != eMouseMove) {
5222 return;
5224 sLastRefPoint = aMouseEvent->mRefPoint;
5227 void EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) {
5228 EnsureDocument(mPresContext);
5229 if (!mDocument) return;
5231 // Hold onto old target content through the event and reset after.
5232 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
5234 switch (aMouseEvent->mMessage) {
5235 case eMouseMove:
5236 case ePointerMove:
5237 case ePointerDown:
5238 case ePointerGotCapture: {
5239 // Get the target content target (mousemove target == mouseover target)
5240 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
5241 if (!targetElement) {
5242 // We're always over the document root, even if we're only
5243 // over dead space in a page (whose frame is not associated with
5244 // any content) or in print preview dead space
5245 targetElement = mDocument->GetRootElement();
5247 if (targetElement) {
5248 NotifyMouseOver(aMouseEvent, targetElement);
5250 } break;
5251 case ePointerUp: {
5252 // Get the target content target (mousemove target == mouseover target)
5253 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
5254 if (!targetElement) {
5255 // We're always over the document root, even if we're only
5256 // over dead space in a page (whose frame is not associated with
5257 // any content) or in print preview dead space
5258 targetElement = mDocument->GetRootElement();
5260 if (targetElement) {
5261 RefPtr<OverOutElementsWrapper> helper =
5262 GetWrapperByEventID(aMouseEvent);
5263 if (helper) {
5264 helper->OverrideOverEventTarget(targetElement);
5266 NotifyMouseOut(aMouseEvent, nullptr);
5268 } break;
5269 case ePointerLeave:
5270 case ePointerCancel:
5271 case eMouseExitFromWidget: {
5272 // This is actually the window mouse exit or pointer leave event. We're
5273 // not moving into any new element.
5275 RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
5276 if (helper && helper->mLastOverFrame &&
5277 nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) !=
5278 nsContentUtils::GetTopLevelWidget(
5279 helper->mLastOverFrame->GetNearestWidget())) {
5280 // the Mouse/PointerOut event widget doesn't have same top widget with
5281 // mLastOverFrame, it's a spurious event for mLastOverFrame
5282 break;
5285 // Reset sLastRefPoint, so that we'll know not to report any
5286 // movement the next time we re-enter the window.
5287 sLastRefPoint = kInvalidRefPoint;
5289 NotifyMouseOut(aMouseEvent, nullptr);
5290 } break;
5291 default:
5292 break;
5295 // reset mCurretTargetContent to what it was
5296 mCurrentTargetContent = targetBeforeEvent;
5299 OverOutElementsWrapper* EventStateManager::GetWrapperByEventID(
5300 WidgetMouseEvent* aEvent) {
5301 WidgetPointerEvent* pointer = aEvent->AsPointerEvent();
5302 if (!pointer) {
5303 MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr);
5304 if (!mMouseEnterLeaveHelper) {
5305 mMouseEnterLeaveHelper = new OverOutElementsWrapper(
5306 OverOutElementsWrapper::BoundaryEventType::Mouse);
5308 return mMouseEnterLeaveHelper;
5310 return mPointersEnterLeaveHelper.GetOrInsertNew(
5311 pointer->pointerId, OverOutElementsWrapper::BoundaryEventType::Pointer);
5314 /* static */
5315 void EventStateManager::SetPointerLock(nsIWidget* aWidget,
5316 nsPresContext* aPresContext) {
5317 // Reset mouse wheel transaction
5318 WheelTransaction::EndTransaction();
5320 // Deal with DnD events
5321 nsCOMPtr<nsIDragService> dragService =
5322 do_GetService("@mozilla.org/widget/dragservice;1");
5324 if (PointerLockManager::IsLocked()) {
5325 MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
5326 MOZ_ASSERT(aPresContext, "Locking pointer requires a presContext");
5328 // Release all pointer capture when a pointer lock is successfully applied
5329 // on an element.
5330 PointerEventHandler::ReleaseAllPointerCapture();
5332 // Store the last known ref point so we can reposition the pointer after
5333 // unlock.
5334 sPreLockScreenPoint = LayoutDeviceIntPoint::Round(
5335 sLastScreenPoint * aPresContext->CSSToDevPixelScale());
5337 // Fire a synthetic mouse move to ensure event state is updated. We first
5338 // set the mouse to the center of the window, so that the mouse event
5339 // doesn't report any movement.
5340 // XXX Cannot we do synthesize the native mousemove in the parent process
5341 // with calling LockNativePointer below? Then, we could make this API
5342 // work only in the automation mode.
5343 sLastRefPoint = GetWindowClientRectCenter(aWidget);
5344 aWidget->SynthesizeNativeMouseMove(
5345 sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
5347 // Suppress DnD
5348 if (dragService) {
5349 dragService->Suppress();
5352 // Activate native pointer lock on platforms where it is required (Wayland)
5353 aWidget->LockNativePointer();
5354 } else {
5355 if (aWidget) {
5356 // Deactivate native pointer lock on platforms where it is required
5357 aWidget->UnlockNativePointer();
5360 // Reset SynthCenteringPoint to invalid so that next time we start
5361 // locking pointer, it has its initial value.
5362 sSynthCenteringPoint = kInvalidRefPoint;
5363 if (aWidget) {
5364 // Unlocking, so return pointer to the original position by firing a
5365 // synthetic mouse event. We first reset sLastRefPoint to its
5366 // pre-pointerlock position, so that the synthetic mouse event reports
5367 // no movement.
5368 sLastRefPoint = sPreLockScreenPoint - aWidget->WidgetToScreenOffset();
5369 // XXX Cannot we do synthesize the native mousemove in the parent process
5370 // with calling `UnlockNativePointer` above? Then, we could make this
5371 // API work only in the automation mode.
5372 aWidget->SynthesizeNativeMouseMove(sPreLockScreenPoint, nullptr);
5375 // Unsuppress DnD
5376 if (dragService) {
5377 dragService->Unsuppress();
5382 void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
5383 WidgetDragEvent* aDragEvent) {
5384 // Hold onto old target content through the event and reset after.
5385 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
5387 switch (aDragEvent->mMessage) {
5388 case eDragOver: {
5389 // when dragging from one frame to another, events are fired in the
5390 // order: dragexit, dragenter, dragleave
5391 if (sLastDragOverFrame != mCurrentTarget) {
5392 // We'll need the content, too, to check if it changed separately from
5393 // the frames.
5394 nsCOMPtr<nsIContent> lastContent;
5395 nsCOMPtr<nsIContent> targetContent;
5396 mCurrentTarget->GetContentForEvent(aDragEvent,
5397 getter_AddRefs(targetContent));
5398 if (targetContent && targetContent->IsText()) {
5399 targetContent = targetContent->GetFlattenedTreeParent();
5402 if (sLastDragOverFrame) {
5403 // The frame has changed but the content may not have. Check before
5404 // dispatching to content
5405 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5406 getter_AddRefs(lastContent));
5407 if (lastContent && lastContent->IsText()) {
5408 lastContent = lastContent->GetFlattenedTreeParent();
5411 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5412 FireDragEnterOrExit(presContext, aDragEvent, eDragExit, targetContent,
5413 lastContent, sLastDragOverFrame);
5414 nsIContent* target = sLastDragOverFrame
5415 ? sLastDragOverFrame.GetFrame()->GetContent()
5416 : nullptr;
5417 // XXXedgar, look like we need to consider fission OOP iframe, too.
5418 if (IsTopLevelRemoteTarget(target)) {
5419 // Dragging something and moving from web content to chrome only
5420 // fires dragexit and dragleave to xul:browser. We have to forward
5421 // dragexit to sLastDragOverFrame when its content is a remote
5422 // target. We don't forward dragleave since it's generated from
5423 // dragexit.
5424 WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
5425 aDragEvent->mWidget);
5426 remoteEvent.AssignDragEventData(*aDragEvent, true);
5427 remoteEvent.mFlags.mIsSynthesizedForTests =
5428 aDragEvent->mFlags.mIsSynthesizedForTests;
5429 nsEventStatus remoteStatus = nsEventStatus_eIgnore;
5430 HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
5434 AutoWeakFrame currentTraget = mCurrentTarget;
5435 FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, lastContent,
5436 targetContent, currentTraget);
5438 if (sLastDragOverFrame) {
5439 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5440 FireDragEnterOrExit(presContext, aDragEvent, eDragLeave,
5441 targetContent, lastContent, sLastDragOverFrame);
5444 sLastDragOverFrame = mCurrentTarget;
5446 } break;
5448 case eDragExit: {
5449 // This is actually the window mouse exit event.
5450 if (sLastDragOverFrame) {
5451 nsCOMPtr<nsIContent> lastContent;
5452 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5453 getter_AddRefs(lastContent));
5455 RefPtr<nsPresContext> lastDragOverFramePresContext =
5456 sLastDragOverFrame->PresContext();
5457 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent, eDragExit,
5458 nullptr, lastContent, sLastDragOverFrame);
5459 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent,
5460 eDragLeave, nullptr, lastContent,
5461 sLastDragOverFrame);
5463 sLastDragOverFrame = nullptr;
5465 } break;
5467 default:
5468 break;
5471 // reset mCurretTargetContent to what it was
5472 mCurrentTargetContent = targetBeforeEvent;
5474 // Now flush all pending notifications, for better responsiveness.
5475 FlushLayout(aPresContext);
5478 void EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
5479 WidgetDragEvent* aDragEvent,
5480 EventMessage aMessage,
5481 nsIContent* aRelatedTarget,
5482 nsIContent* aTargetContent,
5483 AutoWeakFrame& aTargetFrame) {
5484 MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
5485 aMessage == eDragEnter);
5486 nsEventStatus status = nsEventStatus_eIgnore;
5487 WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
5488 event.AssignDragEventData(*aDragEvent, false);
5489 event.mFlags.mIsSynthesizedForTests =
5490 aDragEvent->mFlags.mIsSynthesizedForTests;
5491 event.mRelatedTarget = aRelatedTarget;
5492 if (aMessage == eDragExit && !StaticPrefs::dom_event_dragexit_enabled()) {
5493 event.mFlags.mOnlyChromeDispatch = true;
5496 mCurrentTargetContent = aTargetContent;
5498 if (aTargetContent != aRelatedTarget) {
5499 // XXX This event should still go somewhere!!
5500 if (aTargetContent) {
5501 EventDispatcher::Dispatch(aTargetContent, aPresContext, &event, nullptr,
5502 &status);
5505 // adjust the drag hover if the dragenter event was cancelled or this is a
5506 // drag exit
5507 if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) {
5508 SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr,
5509 ElementState::DRAGOVER);
5512 // collect any changes to moz cursor settings stored in the event's
5513 // data transfer.
5514 UpdateDragDataTransfer(&event);
5517 // Finally dispatch the event to the frame
5518 if (aTargetFrame) {
5519 aTargetFrame->HandleEvent(aPresContext, &event, &status);
5523 void EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent) {
5524 NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!");
5525 if (!dragEvent->mDataTransfer) {
5526 return;
5529 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5531 if (dragSession) {
5532 // the initial dataTransfer is the one from the dragstart event that
5533 // was set on the dragSession when the drag began.
5534 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
5535 if (initialDataTransfer) {
5536 // retrieve the current moz cursor setting and save it.
5537 nsAutoString mozCursor;
5538 dragEvent->mDataTransfer->GetMozCursor(mozCursor);
5539 initialDataTransfer->SetMozCursor(mozCursor);
5544 nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
5545 nsEventStatus* aStatus,
5546 nsIContent* aOverrideClickTarget) {
5547 nsCOMPtr<nsIContent> mouseContent = aOverrideClickTarget;
5548 if (!mouseContent && mCurrentTarget) {
5549 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
5551 if (mouseContent && mouseContent->IsText()) {
5552 nsINode* parent = mouseContent->GetFlattenedTreeParentNode();
5553 if (parent && parent->IsContent()) {
5554 mouseContent = parent->AsContent();
5558 LastMouseDownInfo& mouseDownInfo = GetLastMouseDownInfo(aEvent->mButton);
5559 if (aEvent->mMessage == eMouseDown) {
5560 mouseDownInfo.mLastMouseDownContent =
5561 !aEvent->mClickEventPrevented ? mouseContent : nullptr;
5563 if (mouseDownInfo.mLastMouseDownContent) {
5564 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
5565 mouseDownInfo.mLastMouseDownContent)) {
5566 mouseDownInfo.mLastMouseDownInputControlType =
5567 Some(input->ControlType());
5568 } else if (mouseDownInfo.mLastMouseDownContent
5569 ->IsInNativeAnonymousSubtree()) {
5570 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
5571 mouseDownInfo.mLastMouseDownContent
5572 ->GetFlattenedTreeParent())) {
5573 mouseDownInfo.mLastMouseDownInputControlType =
5574 Some(input->ControlType());
5578 } else {
5579 aEvent->mClickTarget =
5580 !aEvent->mClickEventPrevented
5581 ? GetCommonAncestorForMouseUp(
5582 mouseContent, mouseDownInfo.mLastMouseDownContent,
5583 mouseDownInfo.mLastMouseDownInputControlType)
5584 : nullptr;
5585 if (aEvent->mClickTarget) {
5586 aEvent->mClickCount = mouseDownInfo.mClickCount;
5587 mouseDownInfo.mClickCount = 0;
5588 } else {
5589 aEvent->mClickCount = 0;
5591 mouseDownInfo.mLastMouseDownContent = nullptr;
5592 mouseDownInfo.mLastMouseDownInputControlType = Nothing();
5595 return NS_OK;
5598 // static
5599 bool EventStateManager::EventCausesClickEvents(
5600 const WidgetMouseEvent& aMouseEvent) {
5601 if (NS_WARN_IF(aMouseEvent.mMessage != eMouseUp)) {
5602 return false;
5604 // If the mouseup event is synthesized event, we don't need to dispatch
5605 // click events.
5606 if (!aMouseEvent.IsReal()) {
5607 return false;
5609 // If mouse is still over same element, clickcount will be > 1.
5610 // If it has moved it will be zero, so no click.
5611 if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
5612 return false;
5614 // If click event was explicitly prevented, we shouldn't dispatch it.
5615 if (aMouseEvent.mClickEventPrevented) {
5616 return false;
5618 // Check that the window isn't disabled before firing a click
5619 // (see bug 366544).
5620 return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
5623 nsresult EventStateManager::InitAndDispatchClickEvent(
5624 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5625 EventMessage aMessage, PresShell* aPresShell, nsIContent* aMouseUpContent,
5626 AutoWeakFrame aCurrentTarget, bool aNoContentDispatch,
5627 nsIContent* aOverrideClickTarget) {
5628 MOZ_ASSERT(aMouseUpEvent);
5629 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5630 MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget);
5632 WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage,
5633 aMouseUpEvent->mWidget, WidgetMouseEvent::eReal);
5635 event.mRefPoint = aMouseUpEvent->mRefPoint;
5636 event.mClickCount = aMouseUpEvent->mClickCount;
5637 event.mModifiers = aMouseUpEvent->mModifiers;
5638 event.mButtons = aMouseUpEvent->mButtons;
5639 event.mTimeStamp = aMouseUpEvent->mTimeStamp;
5640 event.mFlags.mOnlyChromeDispatch = aNoContentDispatch;
5641 event.mFlags.mNoContentDispatch = aNoContentDispatch;
5642 event.mButton = aMouseUpEvent->mButton;
5643 event.pointerId = aMouseUpEvent->pointerId;
5644 event.mInputSource = aMouseUpEvent->mInputSource;
5645 nsIContent* target = aMouseUpContent;
5646 nsIFrame* targetFrame = aCurrentTarget;
5647 if (aOverrideClickTarget) {
5648 target = aOverrideClickTarget;
5649 targetFrame = aOverrideClickTarget->GetPrimaryFrame();
5652 if (!target->IsInComposedDoc()) {
5653 return NS_OK;
5656 // Use local event status for each click event dispatching since it'll be
5657 // cleared by EventStateManager::PreHandleEvent(). Therefore, dispatching
5658 // an event means that previous event status will be ignored.
5659 nsEventStatus status = nsEventStatus_eIgnore;
5660 nsresult rv = aPresShell->HandleEventWithTarget(
5661 &event, targetFrame, MOZ_KnownLive(target), &status);
5663 // Copy mMultipleActionsPrevented flag from a click event to the mouseup
5664 // event only when it's set to true. It may be set to true if an editor has
5665 // already handled it. This is important to avoid two or more default
5666 // actions handled here.
5667 aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
5668 event.mFlags.mMultipleActionsPrevented;
5669 // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
5670 // overwrite it.
5671 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
5672 return rv;
5674 // If new status is nsEventStatus_eConsumeNoDefault or
5675 // nsEventStatus_eConsumeDoDefault, use it.
5676 if (status == nsEventStatus_eConsumeNoDefault ||
5677 status == nsEventStatus_eConsumeDoDefault) {
5678 *aStatus = status;
5679 return rv;
5681 // Otherwise, keep the original status.
5682 return rv;
5685 nsresult EventStateManager::PostHandleMouseUp(
5686 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5687 nsIContent* aOverrideClickTarget) {
5688 MOZ_ASSERT(aMouseUpEvent);
5689 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5690 MOZ_ASSERT(aStatus);
5692 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
5693 if (!presShell) {
5694 return NS_OK;
5697 nsCOMPtr<nsIContent> clickTarget =
5698 nsIContent::FromEventTargetOrNull(aMouseUpEvent->mClickTarget);
5699 NS_ENSURE_STATE(clickTarget);
5701 // Fire click events if the event target is still available.
5702 // Note that do not include the eMouseUp event's status since we ignore it
5703 // for compatibility with the other browsers.
5704 nsEventStatus status = nsEventStatus_eIgnore;
5705 nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, &status,
5706 clickTarget, aOverrideClickTarget);
5707 if (NS_WARN_IF(NS_FAILED(rv))) {
5708 return rv;
5711 // Do not do anything if preceding click events are consumed.
5712 // Note that Chromium dispatches "paste" event and actually pates clipboard
5713 // text into focused editor even if the preceding click events are consumed.
5714 // However, this is different from our traditional behavior and does not
5715 // conform to DOM events. If we need to keep compatibility with Chromium,
5716 // we should change it later.
5717 if (status == nsEventStatus_eConsumeNoDefault) {
5718 *aStatus = nsEventStatus_eConsumeNoDefault;
5719 return NS_OK;
5722 // Handle middle click paste if it's enabled and the mouse button is middle.
5723 if (aMouseUpEvent->mButton != MouseButton::eMiddle ||
5724 !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
5725 return NS_OK;
5727 DebugOnly<nsresult> rvIgnored =
5728 HandleMiddleClickPaste(presShell, aMouseUpEvent, &status, nullptr);
5729 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5730 "Failed to paste for a middle click");
5732 // If new status is nsEventStatus_eConsumeNoDefault or
5733 // nsEventStatus_eConsumeDoDefault, use it.
5734 if (*aStatus != nsEventStatus_eConsumeNoDefault &&
5735 (status == nsEventStatus_eConsumeNoDefault ||
5736 status == nsEventStatus_eConsumeDoDefault)) {
5737 *aStatus = status;
5740 // Don't return error even if middle mouse paste fails since we haven't
5741 // handled it here.
5742 return NS_OK;
5745 nsresult EventStateManager::DispatchClickEvents(
5746 PresShell* aPresShell, WidgetMouseEvent* aMouseUpEvent,
5747 nsEventStatus* aStatus, nsIContent* aClickTarget,
5748 nsIContent* aOverrideClickTarget) {
5749 MOZ_ASSERT(aPresShell);
5750 MOZ_ASSERT(aMouseUpEvent);
5751 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5752 MOZ_ASSERT(aStatus);
5753 MOZ_ASSERT(aClickTarget || aOverrideClickTarget);
5755 bool notDispatchToContents =
5756 (aMouseUpEvent->mButton == MouseButton::eMiddle ||
5757 aMouseUpEvent->mButton == MouseButton::eSecondary);
5759 bool fireAuxClick = notDispatchToContents;
5761 AutoWeakFrame currentTarget = aClickTarget->GetPrimaryFrame();
5762 nsresult rv = InitAndDispatchClickEvent(
5763 aMouseUpEvent, aStatus, eMouseClick, aPresShell, aClickTarget,
5764 currentTarget, notDispatchToContents, aOverrideClickTarget);
5765 if (NS_WARN_IF(NS_FAILED(rv))) {
5766 return rv;
5769 // Fire auxclick event if necessary.
5770 if (fireAuxClick && *aStatus != nsEventStatus_eConsumeNoDefault &&
5771 aClickTarget && aClickTarget->IsInComposedDoc()) {
5772 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick,
5773 aPresShell, aClickTarget, currentTarget,
5774 false, aOverrideClickTarget);
5775 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
5778 // Fire double click event if click count is 2.
5779 if (aMouseUpEvent->mClickCount == 2 && !fireAuxClick && aClickTarget &&
5780 aClickTarget->IsInComposedDoc()) {
5781 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
5782 aPresShell, aClickTarget, currentTarget,
5783 notDispatchToContents, aOverrideClickTarget);
5784 if (NS_WARN_IF(NS_FAILED(rv))) {
5785 return rv;
5789 return rv;
5792 nsresult EventStateManager::HandleMiddleClickPaste(
5793 PresShell* aPresShell, WidgetMouseEvent* aMouseEvent,
5794 nsEventStatus* aStatus, EditorBase* aEditorBase) {
5795 MOZ_ASSERT(aPresShell);
5796 MOZ_ASSERT(aMouseEvent);
5797 MOZ_ASSERT((aMouseEvent->mMessage == eMouseAuxClick &&
5798 aMouseEvent->mButton == MouseButton::eMiddle) ||
5799 EventCausesClickEvents(*aMouseEvent));
5800 MOZ_ASSERT(aStatus);
5801 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
5803 // Even if we're called twice or more for a mouse operation, we should
5804 // handle only once. Although mMultipleActionsPrevented may be set to
5805 // true by different event handler in the future, we can use it for now.
5806 if (aMouseEvent->mFlags.mMultipleActionsPrevented) {
5807 return NS_OK;
5809 aMouseEvent->mFlags.mMultipleActionsPrevented = true;
5811 RefPtr<Selection> selection;
5812 if (aEditorBase) {
5813 selection = aEditorBase->GetSelection();
5814 if (NS_WARN_IF(!selection)) {
5815 return NS_ERROR_FAILURE;
5817 } else {
5818 Document* document = aPresShell->GetDocument();
5819 if (NS_WARN_IF(!document)) {
5820 return NS_ERROR_FAILURE;
5822 selection = nsCopySupport::GetSelectionForCopy(document);
5823 if (NS_WARN_IF(!selection)) {
5824 return NS_ERROR_FAILURE;
5827 const nsRange* range = selection->GetRangeAt(0);
5828 if (range) {
5829 nsINode* target = range->GetStartContainer();
5830 if (target && target->OwnerDoc()->IsInChromeDocShell()) {
5831 // In Chrome document, limit middle-click pasting to only the editor
5832 // because it looks odd if pasting works in the focused editor when you
5833 // middle-click toolbar or something which are far from the editor.
5834 // However, as DevTools especially Web Console module assumes that paste
5835 // event will be fired when middle-click even on not editor, don't limit
5836 // it.
5837 return NS_OK;
5842 // Don't modify selection here because we've already set caret to the point
5843 // at "mousedown" event.
5845 int32_t clipboardType = nsIClipboard::kGlobalClipboard;
5846 nsCOMPtr<nsIClipboard> clipboardService =
5847 do_GetService("@mozilla.org/widget/clipboard;1");
5848 if (clipboardService && clipboardService->IsClipboardTypeSupported(
5849 nsIClipboard::kSelectionClipboard)) {
5850 clipboardType = nsIClipboard::kSelectionClipboard;
5853 // Fire ePaste event by ourselves since we need to dispatch "paste" event
5854 // even if the middle click event was consumed for compatibility with
5855 // Chromium.
5856 if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType, aPresShell,
5857 selection)) {
5858 *aStatus = nsEventStatus_eConsumeNoDefault;
5859 return NS_OK;
5862 // Although we've fired "paste" event, there is no editor to accept the
5863 // clipboard content.
5864 if (!aEditorBase) {
5865 return NS_OK;
5868 // Check if the editor is still the good target to paste.
5869 if (aEditorBase->Destroyed() || aEditorBase->IsReadonly()) {
5870 // XXX Should we consume the event when the editor is readonly and/or
5871 // disabled?
5872 return NS_OK;
5875 // The selection may have been modified during reflow. Therefore, we
5876 // should adjust event target to pass IsAcceptableInputEvent().
5877 const nsRange* range = selection->GetRangeAt(0);
5878 if (!range) {
5879 return NS_OK;
5881 WidgetMouseEvent mouseEvent(*aMouseEvent);
5882 mouseEvent.mOriginalTarget = range->GetStartContainer();
5883 if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
5884 !aEditorBase->IsAcceptableInputEvent(&mouseEvent)) {
5885 return NS_OK;
5888 // If Control key is pressed, we should paste clipboard content as
5889 // quotation. Otherwise, paste it as is.
5890 if (aMouseEvent->IsControl()) {
5891 DebugOnly<nsresult> rv = aEditorBase->PasteAsQuotationAsAction(
5892 clipboardType, EditorBase::DispatchPasteEvent::No);
5893 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
5894 } else {
5895 DebugOnly<nsresult> rv = aEditorBase->PasteAsAction(
5896 clipboardType, EditorBase::DispatchPasteEvent::No);
5897 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
5899 *aStatus = nsEventStatus_eConsumeNoDefault;
5901 return NS_OK;
5904 void EventStateManager::ConsumeInteractionData(
5905 Record<nsString, dom::InteractionData>& aInteractions) {
5906 OnTypingInteractionEnded();
5908 aInteractions.Entries().Clear();
5909 auto newEntry = aInteractions.Entries().AppendElement();
5910 newEntry->mKey = u"Typing"_ns;
5911 newEntry->mValue = gTypingInteraction;
5912 gTypingInteraction = {};
5915 nsIFrame* EventStateManager::GetEventTarget() {
5916 PresShell* presShell;
5917 if (mCurrentTarget || !mPresContext ||
5918 !(presShell = mPresContext->GetPresShell())) {
5919 return mCurrentTarget;
5922 if (mCurrentTargetContent) {
5923 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent);
5924 if (mCurrentTarget) {
5925 return mCurrentTarget;
5929 nsIFrame* frame = presShell->GetCurrentEventFrame();
5930 return (mCurrentTarget = frame);
5933 already_AddRefed<nsIContent> EventStateManager::GetEventTargetContent(
5934 WidgetEvent* aEvent) {
5935 if (aEvent && (aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) {
5936 nsCOMPtr<nsIContent> content = GetFocusedElement();
5937 return content.forget();
5940 if (mCurrentTargetContent) {
5941 nsCOMPtr<nsIContent> content = mCurrentTargetContent;
5942 return content.forget();
5945 nsCOMPtr<nsIContent> content;
5946 if (PresShell* presShell = mPresContext->GetPresShell()) {
5947 content = presShell->GetEventTargetContent(aEvent);
5950 // Some events here may set mCurrentTarget but not set the corresponding
5951 // event target in the PresShell.
5952 if (!content && mCurrentTarget) {
5953 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content));
5956 return content.forget();
5959 static Element* GetLabelTarget(nsIContent* aPossibleLabel) {
5960 mozilla::dom::HTMLLabelElement* label =
5961 mozilla::dom::HTMLLabelElement::FromNode(aPossibleLabel);
5962 if (!label) return nullptr;
5964 return label->GetLabeledElement();
5967 /* static */
5968 inline void EventStateManager::DoStateChange(Element* aElement,
5969 ElementState aState,
5970 bool aAddState) {
5971 if (aAddState) {
5972 aElement->AddStates(aState);
5973 } else {
5974 aElement->RemoveStates(aState);
5978 /* static */
5979 inline void EventStateManager::DoStateChange(nsIContent* aContent,
5980 ElementState aState,
5981 bool aStateAdded) {
5982 if (aContent->IsElement()) {
5983 DoStateChange(aContent->AsElement(), aState, aStateAdded);
5987 /* static */
5988 void EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
5989 nsIContent* aStopBefore,
5990 ElementState aState,
5991 bool aAddState) {
5992 for (; aStartNode && aStartNode != aStopBefore;
5993 aStartNode = aStartNode->GetFlattenedTreeParent()) {
5994 // We might be starting with a non-element (e.g. a text node) and
5995 // if someone is doing something weird might be ending with a
5996 // non-element too (e.g. a document fragment)
5997 if (!aStartNode->IsElement()) {
5998 continue;
6000 Element* element = aStartNode->AsElement();
6001 DoStateChange(element, aState, aAddState);
6002 Element* labelTarget = GetLabelTarget(element);
6003 if (labelTarget) {
6004 DoStateChange(labelTarget, aState, aAddState);
6008 if (aAddState) {
6009 // We might be in a situation where a node was in hover both
6010 // because it was hovered and because the label for it was
6011 // hovered, and while we stopped hovering the node the label is
6012 // still hovered. Or we might have had two nested labels for the
6013 // same node, and while one is no longer hovered the other still
6014 // is. In that situation, the label that's still hovered will be
6015 // aStopBefore or some ancestor of it, and the call we just made
6016 // to UpdateAncestorState with aAddState = false would have
6017 // removed the hover state from the node. But the node should
6018 // still be in hover state. To handle this situation we need to
6019 // keep walking up the tree and any time we find a label mark its
6020 // corresponding node as still in our state.
6021 for (; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) {
6022 if (!aStartNode->IsElement()) {
6023 continue;
6026 Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
6027 if (labelTarget && !labelTarget->State().HasState(aState)) {
6028 DoStateChange(labelTarget, aState, true);
6034 // static
6035 bool CanContentHaveActiveState(nsIContent& aContent) {
6036 // Editable content can never become active since their default actions
6037 // are disabled. Watch out for editable content in native anonymous
6038 // subtrees though, as they belong to text controls.
6039 return !aContent.IsEditable() || aContent.IsInNativeAnonymousSubtree();
6042 bool EventStateManager::SetContentState(nsIContent* aContent,
6043 ElementState aState) {
6044 MOZ_ASSERT(ManagesState(aState), "Unexpected state");
6046 nsCOMPtr<nsIContent> notifyContent1;
6047 nsCOMPtr<nsIContent> notifyContent2;
6048 bool updateAncestors;
6050 if (aState == ElementState::HOVER || aState == ElementState::ACTIVE) {
6051 // Hover and active are hierarchical
6052 updateAncestors = true;
6054 // check to see that this state is allowed by style. Check dragover too?
6055 // XXX Is this even what we want?
6056 if (mCurrentTarget &&
6057 mCurrentTarget->StyleUI()->UserInput() == StyleUserInput::None) {
6058 return false;
6061 if (aState == ElementState::ACTIVE) {
6062 if (aContent && !CanContentHaveActiveState(*aContent)) {
6063 aContent = nullptr;
6065 if (aContent != mActiveContent) {
6066 notifyContent1 = aContent;
6067 notifyContent2 = mActiveContent;
6068 mActiveContent = aContent;
6070 } else {
6071 NS_ASSERTION(aState == ElementState::HOVER, "How did that happen?");
6072 nsIContent* newHover;
6074 if (mPresContext->IsDynamic()) {
6075 newHover = aContent;
6076 } else {
6077 NS_ASSERTION(!aContent || aContent->GetComposedDoc() ==
6078 mPresContext->PresShell()->GetDocument(),
6079 "Unexpected document");
6080 nsIFrame* frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
6081 if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
6082 // The scrollbars of viewport should not ignore the hover state.
6083 // Because they are *not* the content of the web page.
6084 newHover = aContent;
6085 } else {
6086 // All contents of the web page should ignore the hover state.
6087 newHover = nullptr;
6091 if (newHover != mHoverContent) {
6092 notifyContent1 = newHover;
6093 notifyContent2 = mHoverContent;
6094 mHoverContent = newHover;
6097 } else {
6098 updateAncestors = false;
6099 if (aState == ElementState::DRAGOVER) {
6100 if (aContent != sDragOverContent) {
6101 notifyContent1 = aContent;
6102 notifyContent2 = sDragOverContent;
6103 sDragOverContent = aContent;
6105 } else if (aState == ElementState::URLTARGET) {
6106 if (aContent != mURLTargetContent) {
6107 notifyContent1 = aContent;
6108 notifyContent2 = mURLTargetContent;
6109 mURLTargetContent = aContent;
6114 // We need to keep track of which of notifyContent1 and notifyContent2 is
6115 // getting the state set and which is getting it unset. If both are
6116 // non-null, then notifyContent1 is having the state set and notifyContent2
6117 // is having it unset. But if one of them is null, we need to keep track of
6118 // the right thing for notifyContent1 explicitly.
6119 bool content1StateSet = true;
6120 if (!notifyContent1) {
6121 // This is ok because FindCommonAncestor wouldn't find anything
6122 // anyway if notifyContent1 is null.
6123 notifyContent1 = notifyContent2;
6124 notifyContent2 = nullptr;
6125 content1StateSet = false;
6128 if (notifyContent1 && mPresContext) {
6129 EnsureDocument(mPresContext);
6130 if (mDocument) {
6131 nsAutoScriptBlocker scriptBlocker;
6133 if (updateAncestors) {
6134 nsCOMPtr<nsIContent> commonAncestor =
6135 FindCommonAncestor(notifyContent1, notifyContent2);
6136 if (notifyContent2) {
6137 // It's very important to first notify the state removal and
6138 // then the state addition, because due to labels it's
6139 // possible that we're removing state from some element but
6140 // then adding it again (say because mHoverContent changed
6141 // from a control to its label).
6142 UpdateAncestorState(notifyContent2, commonAncestor, aState, false);
6144 UpdateAncestorState(notifyContent1, commonAncestor, aState,
6145 content1StateSet);
6146 } else {
6147 if (notifyContent2) {
6148 DoStateChange(notifyContent2, aState, false);
6150 DoStateChange(notifyContent1, aState, content1StateSet);
6155 return true;
6158 void EventStateManager::RemoveNodeFromChainIfNeeded(ElementState aState,
6159 nsIContent* aContentRemoved,
6160 bool aNotify) {
6161 MOZ_ASSERT(aState == ElementState::HOVER || aState == ElementState::ACTIVE);
6162 if (!aContentRemoved->IsElement() ||
6163 !aContentRemoved->AsElement()->State().HasState(aState)) {
6164 return;
6167 nsCOMPtr<nsIContent>& leaf =
6168 aState == ElementState::HOVER ? mHoverContent : mActiveContent;
6170 MOZ_ASSERT(leaf);
6171 // These two NS_ASSERTIONS below can fail for Shadow DOM sometimes, and it's
6172 // not clear how to best handle it, see
6173 // https://github.com/whatwg/html/issues/4795 and bug 1551621.
6174 NS_ASSERTION(
6175 nsContentUtils::ContentIsFlattenedTreeDescendantOf(leaf, aContentRemoved),
6176 "Flat tree and active / hover chain got out of sync");
6178 nsIContent* newLeaf = aContentRemoved->GetFlattenedTreeParent();
6179 MOZ_ASSERT(!newLeaf || newLeaf->IsElement());
6180 NS_ASSERTION(!newLeaf || newLeaf->AsElement()->State().HasState(aState),
6181 "State got out of sync because of shadow DOM");
6182 if (aNotify) {
6183 SetContentState(newLeaf, aState);
6184 } else {
6185 // We don't update the removed content's state here, since removing NAC
6186 // happens from layout and we don't really want to notify at that point or
6187 // what not.
6189 // Also, NAC is not observable and NAC being removed will go away soon.
6190 leaf = newLeaf;
6192 MOZ_ASSERT(leaf == newLeaf || (aState == ElementState::ACTIVE && !leaf &&
6193 !CanContentHaveActiveState(*newLeaf)));
6196 void EventStateManager::NativeAnonymousContentRemoved(nsIContent* aContent) {
6197 MOZ_ASSERT(aContent->IsRootOfNativeAnonymousSubtree());
6198 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, false);
6199 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, false);
6201 nsCOMPtr<nsIContent>& lastLeftMouseDownContent =
6202 mLastLeftMouseDownInfo.mLastMouseDownContent;
6203 if (lastLeftMouseDownContent &&
6204 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6205 lastLeftMouseDownContent, aContent)) {
6206 lastLeftMouseDownContent = aContent->GetFlattenedTreeParent();
6209 nsCOMPtr<nsIContent>& lastMiddleMouseDownContent =
6210 mLastMiddleMouseDownInfo.mLastMouseDownContent;
6211 if (lastMiddleMouseDownContent &&
6212 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6213 lastMiddleMouseDownContent, aContent)) {
6214 lastMiddleMouseDownContent = aContent->GetFlattenedTreeParent();
6217 nsCOMPtr<nsIContent>& lastRightMouseDownContent =
6218 mLastRightMouseDownInfo.mLastMouseDownContent;
6219 if (lastRightMouseDownContent &&
6220 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6221 lastRightMouseDownContent, aContent)) {
6222 lastRightMouseDownContent = aContent->GetFlattenedTreeParent();
6226 void EventStateManager::ContentRemoved(Document* aDocument,
6227 nsIContent* aContent) {
6229 * Anchor and area elements when focused or hovered might make the UI to show
6230 * the current link. We want to make sure that the UI gets informed when they
6231 * are actually removed from the DOM.
6233 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
6234 (aContent->AsElement()->State().HasAtLeastOneOfStates(
6235 ElementState::FOCUS | ElementState::HOVER))) {
6236 Element* element = aContent->AsElement();
6237 element->LeaveLink(element->GetPresContext(Element::eForComposedDoc));
6240 if (aContent->IsElement()) {
6241 if (RefPtr<nsPresContext> presContext = mPresContext) {
6242 IMEStateManager::OnRemoveContent(*presContext,
6243 MOZ_KnownLive(*aContent->AsElement()));
6245 WheelTransaction::OnRemoveElement(aContent);
6248 // inform the focus manager that the content is being removed. If this
6249 // content is focused, the focus will be removed without firing events.
6250 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6251 fm->ContentRemoved(aDocument, aContent);
6254 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, true);
6255 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, true);
6257 if (sDragOverContent &&
6258 sDragOverContent->OwnerDoc() == aContent->OwnerDoc() &&
6259 nsContentUtils::ContentIsFlattenedTreeDescendantOf(sDragOverContent,
6260 aContent)) {
6261 sDragOverContent = nullptr;
6264 PointerEventHandler::ReleaseIfCaptureByDescendant(aContent);
6266 if (mMouseEnterLeaveHelper) {
6267 const bool hadMouseOutTarget =
6268 mMouseEnterLeaveHelper->GetOutEventTarget() != nullptr;
6269 mMouseEnterLeaveHelper->ContentRemoved(*aContent);
6270 // If we lose the mouseout target, we need to dispatch mouseover on an
6271 // ancestor. For ensuring the chance to do it before next user input, we
6272 // need a synthetic mouse move.
6273 if (hadMouseOutTarget && !mMouseEnterLeaveHelper->GetOutEventTarget()) {
6274 if (PresShell* presShell =
6275 mPresContext ? mPresContext->GetPresShell() : nullptr) {
6276 presShell->SynthesizeMouseMove(false);
6280 for (const auto& entry : mPointersEnterLeaveHelper) {
6281 if (entry.GetData()) {
6282 entry.GetData()->ContentRemoved(*aContent);
6287 void EventStateManager::TextControlRootWillBeRemoved(
6288 TextControlElement& aTextControlElement) {
6289 if (!mGestureDownInTextControl || !mGestureDownFrameOwner ||
6290 !mGestureDownFrameOwner->IsInNativeAnonymousSubtree()) {
6291 return;
6293 // If we track gesture to start drag in aTextControlElement, we should keep
6294 // tracking it with aTextContrlElement itself for now because this may be
6295 // caused by reframing aTextControlElement which may not be intended by the
6296 // user.
6297 if (&aTextControlElement ==
6298 mGestureDownFrameOwner
6299 ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
6300 mGestureDownFrameOwner = &aTextControlElement;
6304 void EventStateManager::TextControlRootAdded(
6305 Element& aAnonymousDivElement, TextControlElement& aTextControlElement) {
6306 if (!mGestureDownInTextControl ||
6307 mGestureDownFrameOwner != &aTextControlElement) {
6308 return;
6310 // If we track gesture to start drag in aTextControlElement, but the frame
6311 // owner is the text control element itself, the anonymous nodes in it are
6312 // recreated by a reframe. If so, we should keep tracking it with the
6313 // recreated native anonymous node.
6314 mGestureDownFrameOwner =
6315 aAnonymousDivElement.GetFirstChild()
6316 ? aAnonymousDivElement.GetFirstChild()
6317 : static_cast<nsIContent*>(&aAnonymousDivElement);
6320 bool EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) {
6321 return !(aEvent->mMessage == eMouseDown &&
6322 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
6323 !sNormalLMouseEventInProcess);
6326 //-------------------------------------------
6327 // Access Key Registration
6328 //-------------------------------------------
6329 void EventStateManager::RegisterAccessKey(Element* aElement, uint32_t aKey) {
6330 if (aElement && !mAccessKeys.Contains(aElement)) {
6331 mAccessKeys.AppendObject(aElement);
6335 void EventStateManager::UnregisterAccessKey(Element* aElement, uint32_t aKey) {
6336 if (aElement) {
6337 mAccessKeys.RemoveObject(aElement);
6341 uint32_t EventStateManager::GetRegisteredAccessKey(Element* aElement) {
6342 MOZ_ASSERT(aElement);
6344 if (!mAccessKeys.Contains(aElement)) {
6345 return 0;
6348 nsAutoString accessKey;
6349 aElement->GetAttr(nsGkAtoms::accesskey, accessKey);
6350 return accessKey.First();
6353 void EventStateManager::EnsureDocument(nsPresContext* aPresContext) {
6354 if (!mDocument) mDocument = aPresContext->Document();
6357 void EventStateManager::FlushLayout(nsPresContext* aPresContext) {
6358 MOZ_ASSERT(aPresContext, "nullptr ptr");
6359 if (RefPtr<PresShell> presShell = aPresContext->GetPresShell()) {
6360 presShell->FlushPendingNotifications(FlushType::InterruptibleLayout);
6364 Element* EventStateManager::GetFocusedElement() {
6365 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6366 EnsureDocument(mPresContext);
6367 if (!fm || !mDocument) {
6368 return nullptr;
6371 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6372 return nsFocusManager::GetFocusedDescendant(
6373 mDocument->GetWindow(), nsFocusManager::eOnlyCurrentWindow,
6374 getter_AddRefs(focusedWindow));
6377 //-------------------------------------------------------
6378 // Return true if the docshell is visible
6380 bool EventStateManager::IsShellVisible(nsIDocShell* aShell) {
6381 NS_ASSERTION(aShell, "docshell is null");
6383 nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell);
6384 if (!basewin) return true;
6386 bool isVisible = true;
6387 basewin->GetVisibility(&isVisible);
6389 // We should be doing some additional checks here so that
6390 // we don't tab into hidden tabs of tabbrowser. -bryner
6392 return isVisible;
6395 nsresult EventStateManager::DoContentCommandEvent(
6396 WidgetContentCommandEvent* aEvent) {
6397 EnsureDocument(mPresContext);
6398 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
6399 nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow());
6400 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
6402 nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
6403 NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
6404 const char* cmd;
6405 switch (aEvent->mMessage) {
6406 case eContentCommandCut:
6407 cmd = "cmd_cut";
6408 break;
6409 case eContentCommandCopy:
6410 cmd = "cmd_copy";
6411 break;
6412 case eContentCommandPaste:
6413 cmd = "cmd_paste";
6414 break;
6415 case eContentCommandDelete:
6416 cmd = "cmd_delete";
6417 break;
6418 case eContentCommandUndo:
6419 cmd = "cmd_undo";
6420 break;
6421 case eContentCommandRedo:
6422 cmd = "cmd_redo";
6423 break;
6424 case eContentCommandPasteTransferable:
6425 cmd = "cmd_pasteTransferable";
6426 break;
6427 case eContentCommandLookUpDictionary:
6428 cmd = "cmd_lookUpDictionary";
6429 break;
6430 default:
6431 return NS_ERROR_NOT_IMPLEMENTED;
6433 // If user tries to do something, user must try to do it in visible window.
6434 // So, let's retrieve controller of visible window.
6435 nsCOMPtr<nsIController> controller;
6436 nsresult rv =
6437 root->GetControllerForCommand(cmd, true, getter_AddRefs(controller));
6438 NS_ENSURE_SUCCESS(rv, rv);
6439 if (!controller) {
6440 // When GetControllerForCommand succeeded but there is no controller, the
6441 // command isn't supported.
6442 aEvent->mIsEnabled = false;
6443 } else {
6444 bool canDoIt;
6445 rv = controller->IsCommandEnabled(cmd, &canDoIt);
6446 NS_ENSURE_SUCCESS(rv, rv);
6447 aEvent->mIsEnabled = canDoIt;
6448 if (canDoIt && !aEvent->mOnlyEnabledCheck) {
6449 switch (aEvent->mMessage) {
6450 case eContentCommandPasteTransferable: {
6451 BrowserParent* remote = BrowserParent::GetFocused();
6452 if (remote) {
6453 IPCTransferable ipcTransferable;
6454 nsContentUtils::TransferableToIPCTransferable(
6455 aEvent->mTransferable, &ipcTransferable, false,
6456 remote->Manager());
6457 remote->SendPasteTransferable(std::move(ipcTransferable));
6458 rv = NS_OK;
6459 } else {
6460 nsCOMPtr<nsICommandController> commandController =
6461 do_QueryInterface(controller);
6462 NS_ENSURE_STATE(commandController);
6464 RefPtr<nsCommandParams> params = new nsCommandParams();
6465 rv = params->SetISupports("transferable", aEvent->mTransferable);
6466 if (NS_WARN_IF(NS_FAILED(rv))) {
6467 return rv;
6469 rv = commandController->DoCommandWithParams(cmd, params);
6471 break;
6474 case eContentCommandLookUpDictionary: {
6475 nsCOMPtr<nsICommandController> commandController =
6476 do_QueryInterface(controller);
6477 if (NS_WARN_IF(!commandController)) {
6478 return NS_ERROR_FAILURE;
6481 RefPtr<nsCommandParams> params = new nsCommandParams();
6482 rv = params->SetInt("x", aEvent->mRefPoint.x);
6483 if (NS_WARN_IF(NS_FAILED(rv))) {
6484 return rv;
6487 rv = params->SetInt("y", aEvent->mRefPoint.y);
6488 if (NS_WARN_IF(NS_FAILED(rv))) {
6489 return rv;
6492 rv = commandController->DoCommandWithParams(cmd, params);
6493 break;
6496 default:
6497 rv = controller->DoCommand(cmd);
6498 break;
6500 NS_ENSURE_SUCCESS(rv, rv);
6503 aEvent->mSucceeded = true;
6504 return NS_OK;
6507 nsresult EventStateManager::DoContentCommandInsertTextEvent(
6508 WidgetContentCommandEvent* aEvent) {
6509 MOZ_ASSERT(aEvent);
6510 MOZ_ASSERT(aEvent->mMessage == eContentCommandInsertText);
6511 MOZ_DIAGNOSTIC_ASSERT(aEvent->mString.isSome());
6512 MOZ_DIAGNOSTIC_ASSERT(!aEvent->mString.ref().IsEmpty());
6514 aEvent->mIsEnabled = false;
6515 aEvent->mSucceeded = false;
6517 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6519 if (XRE_IsParentProcess()) {
6520 // Handle it in focused content process if there is.
6521 if (BrowserParent* remote = BrowserParent::GetFocused()) {
6522 remote->SendInsertText(aEvent->mString.ref());
6523 aEvent->mIsEnabled = true; // XXX it can be a lie...
6524 aEvent->mSucceeded = true;
6525 return NS_OK;
6529 // If there is no active editor in this process, we should treat the command
6530 // is disabled.
6531 RefPtr<EditorBase> activeEditor =
6532 nsContentUtils::GetActiveEditor(mPresContext);
6533 if (!activeEditor) {
6534 aEvent->mSucceeded = true;
6535 return NS_OK;
6538 nsresult rv = activeEditor->InsertTextAsAction(aEvent->mString.ref());
6539 aEvent->mIsEnabled = rv != NS_SUCCESS_DOM_NO_OPERATION;
6540 aEvent->mSucceeded = NS_SUCCEEDED(rv);
6541 return NS_OK;
6544 nsresult EventStateManager::DoContentCommandScrollEvent(
6545 WidgetContentCommandEvent* aEvent) {
6546 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6547 PresShell* presShell = mPresContext->GetPresShell();
6548 NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
6549 NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
6551 ScrollUnit scrollUnit;
6552 switch (aEvent->mScroll.mUnit) {
6553 case WidgetContentCommandEvent::eCmdScrollUnit_Line:
6554 scrollUnit = ScrollUnit::LINES;
6555 break;
6556 case WidgetContentCommandEvent::eCmdScrollUnit_Page:
6557 scrollUnit = ScrollUnit::PAGES;
6558 break;
6559 case WidgetContentCommandEvent::eCmdScrollUnit_Whole:
6560 scrollUnit = ScrollUnit::WHOLE;
6561 break;
6562 default:
6563 return NS_ERROR_INVALID_ARG;
6566 aEvent->mSucceeded = true;
6568 nsIScrollableFrame* sf =
6569 presShell->GetScrollableFrameToScroll(layers::EitherScrollDirection);
6570 aEvent->mIsEnabled =
6571 sf ? (aEvent->mScroll.mIsHorizontal ? WheelHandlingUtils::CanScrollOn(
6572 sf, aEvent->mScroll.mAmount, 0)
6573 : WheelHandlingUtils::CanScrollOn(
6574 sf, 0, aEvent->mScroll.mAmount))
6575 : false;
6577 if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
6578 return NS_OK;
6581 nsIntPoint pt(0, 0);
6582 if (aEvent->mScroll.mIsHorizontal) {
6583 pt.x = aEvent->mScroll.mAmount;
6584 } else {
6585 pt.y = aEvent->mScroll.mAmount;
6588 // The caller may want synchronous scrolling.
6589 sf->ScrollBy(pt, scrollUnit, ScrollMode::Instant);
6590 return NS_OK;
6593 void EventStateManager::SetActiveManager(EventStateManager* aNewESM,
6594 nsIContent* aContent) {
6595 if (sActiveESM && aNewESM != sActiveESM) {
6596 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6598 sActiveESM = aNewESM;
6599 if (sActiveESM && aContent) {
6600 sActiveESM->SetContentState(aContent, ElementState::ACTIVE);
6604 void EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer) {
6605 if (aClearer) {
6606 aClearer->SetContentState(nullptr, ElementState::ACTIVE);
6607 if (sDragOverContent) {
6608 aClearer->SetContentState(nullptr, ElementState::DRAGOVER);
6611 if (sActiveESM && aClearer != sActiveESM) {
6612 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6614 sActiveESM = nullptr;
6617 /******************************************************************/
6618 /* mozilla::EventStateManager::DeltaAccumulator */
6619 /******************************************************************/
6621 void EventStateManager::DeltaAccumulator::InitLineOrPageDelta(
6622 nsIFrame* aTargetFrame, EventStateManager* aESM, WidgetWheelEvent* aEvent) {
6623 MOZ_ASSERT(aESM);
6624 MOZ_ASSERT(aEvent);
6626 // Reset if the previous wheel event is too old.
6627 if (!mLastTime.IsNull()) {
6628 TimeDuration duration = TimeStamp::Now() - mLastTime;
6629 if (duration.ToMilliseconds() >
6630 StaticPrefs::mousewheel_transaction_timeout()) {
6631 Reset();
6634 // If we have accumulated delta, we may need to reset it.
6635 if (IsInTransaction()) {
6636 // If wheel event type is changed, reset the values.
6637 if (mHandlingDeltaMode != aEvent->mDeltaMode ||
6638 mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) {
6639 Reset();
6640 } else {
6641 // If the delta direction is changed, we should reset only the
6642 // accumulated values.
6643 if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) {
6644 mX = mPendingScrollAmountX = 0.0;
6646 if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) {
6647 mY = mPendingScrollAmountY = 0.0;
6652 mHandlingDeltaMode = aEvent->mDeltaMode;
6653 mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta;
6656 nsIFrame* frame = aESM->ComputeScrollTarget(aTargetFrame, aEvent,
6657 COMPUTE_DEFAULT_ACTION_TARGET);
6658 nsPresContext* pc =
6659 frame ? frame->PresContext() : aTargetFrame->PresContext();
6660 nsIScrollableFrame* scrollTarget = do_QueryFrame(frame);
6661 aEvent->mScrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget);
6664 // If it's handling neither a device that does not provide line or page deltas
6665 // nor delta values multiplied by prefs, we must not modify lineOrPageDelta
6666 // values.
6667 // TODO(emilio): Does this care about overridden scroll speed?
6668 if (!mIsNoLineOrPageDeltaDevice &&
6669 !EventStateManager::WheelPrefs::GetInstance()
6670 ->NeedToComputeLineOrPageDelta(aEvent)) {
6671 // Set the delta values to mX and mY. They would be used when above block
6672 // resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction
6673 // is changed.
6674 // NOTE: We shouldn't accumulate the delta values, it might could cause
6675 // overflow even though it's not a realistic situation.
6676 if (aEvent->mDeltaX) {
6677 mX = aEvent->mDeltaX;
6679 if (aEvent->mDeltaY) {
6680 mY = aEvent->mDeltaY;
6682 mLastTime = TimeStamp::Now();
6683 return;
6686 mX += aEvent->mDeltaX;
6687 mY += aEvent->mDeltaY;
6689 if (mHandlingDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6690 // Records pixel delta values and init mLineOrPageDeltaX and
6691 // mLineOrPageDeltaY for wheel events which are caused by pixel only
6692 // devices. Ignore mouse wheel transaction for computing this. The
6693 // lineOrPageDelta values will be used by dispatching legacy
6694 // eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling
6695 // of default action. The transaction should be used only for the default
6696 // action.
6697 auto scrollAmountInCSSPixels =
6698 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
6700 aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width;
6701 aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height;
6703 mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width;
6704 mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height;
6705 } else {
6706 aEvent->mLineOrPageDeltaX = RoundDown(mX);
6707 aEvent->mLineOrPageDeltaY = RoundDown(mY);
6708 mX -= aEvent->mLineOrPageDeltaX;
6709 mY -= aEvent->mLineOrPageDeltaY;
6712 mLastTime = TimeStamp::Now();
6715 void EventStateManager::DeltaAccumulator::Reset() {
6716 mX = mY = 0.0;
6717 mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
6718 mHandlingDeltaMode = UINT32_MAX;
6719 mIsNoLineOrPageDeltaDevice = false;
6722 nsIntPoint
6723 EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
6724 WidgetWheelEvent* aEvent, const nsIntSize& aScrollAmountInDevPixels) {
6725 MOZ_ASSERT(aEvent);
6727 DeltaValues acceleratedDelta = WheelTransaction::AccelerateWheelDelta(aEvent);
6729 nsIntPoint result(0, 0);
6730 if (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6731 mPendingScrollAmountX += acceleratedDelta.deltaX;
6732 mPendingScrollAmountY += acceleratedDelta.deltaY;
6733 } else {
6734 mPendingScrollAmountX +=
6735 aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
6736 mPendingScrollAmountY +=
6737 aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
6739 result.x = RoundDown(mPendingScrollAmountX);
6740 result.y = RoundDown(mPendingScrollAmountY);
6741 mPendingScrollAmountX -= result.x;
6742 mPendingScrollAmountY -= result.y;
6744 return result;
6747 /******************************************************************/
6748 /* mozilla::EventStateManager::WheelPrefs */
6749 /******************************************************************/
6751 // static
6752 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::GetInstance() {
6753 if (!sInstance) {
6754 sInstance = new WheelPrefs();
6756 return sInstance;
6759 // static
6760 void EventStateManager::WheelPrefs::Shutdown() {
6761 delete sInstance;
6762 sInstance = nullptr;
6765 // static
6766 void EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName,
6767 void* aClosure) {
6768 // forget all prefs, it's not problem for performance.
6769 sInstance->Reset();
6770 DeltaAccumulator::GetInstance()->Reset();
6773 EventStateManager::WheelPrefs::WheelPrefs() {
6774 Reset();
6775 Preferences::RegisterPrefixCallback(OnPrefChanged, "mousewheel.");
6778 EventStateManager::WheelPrefs::~WheelPrefs() {
6779 Preferences::UnregisterPrefixCallback(OnPrefChanged, "mousewheel.");
6782 void EventStateManager::WheelPrefs::Reset() { memset(mInit, 0, sizeof(mInit)); }
6784 EventStateManager::WheelPrefs::Index EventStateManager::WheelPrefs::GetIndexFor(
6785 const WidgetWheelEvent* aEvent) {
6786 if (!aEvent) {
6787 return INDEX_DEFAULT;
6790 Modifiers modifiers = (aEvent->mModifiers & (MODIFIER_ALT | MODIFIER_CONTROL |
6791 MODIFIER_META | MODIFIER_SHIFT));
6793 switch (modifiers) {
6794 case MODIFIER_ALT:
6795 return INDEX_ALT;
6796 case MODIFIER_CONTROL:
6797 return INDEX_CONTROL;
6798 case MODIFIER_META:
6799 return INDEX_META;
6800 case MODIFIER_SHIFT:
6801 return INDEX_SHIFT;
6802 default:
6803 // If two or more modifier keys are pressed, we should use default
6804 // settings.
6805 return INDEX_DEFAULT;
6809 void EventStateManager::WheelPrefs::GetBasePrefName(
6810 EventStateManager::WheelPrefs::Index aIndex, nsACString& aBasePrefName) {
6811 aBasePrefName.AssignLiteral("mousewheel.");
6812 switch (aIndex) {
6813 case INDEX_ALT:
6814 aBasePrefName.AppendLiteral("with_alt.");
6815 break;
6816 case INDEX_CONTROL:
6817 aBasePrefName.AppendLiteral("with_control.");
6818 break;
6819 case INDEX_META:
6820 aBasePrefName.AppendLiteral("with_meta.");
6821 break;
6822 case INDEX_SHIFT:
6823 aBasePrefName.AppendLiteral("with_shift.");
6824 break;
6825 case INDEX_DEFAULT:
6826 default:
6827 aBasePrefName.AppendLiteral("default.");
6828 break;
6832 void EventStateManager::WheelPrefs::Init(
6833 EventStateManager::WheelPrefs::Index aIndex) {
6834 if (mInit[aIndex]) {
6835 return;
6837 mInit[aIndex] = true;
6839 nsAutoCString basePrefName;
6840 GetBasePrefName(aIndex, basePrefName);
6842 nsAutoCString prefNameX(basePrefName);
6843 prefNameX.AppendLiteral("delta_multiplier_x");
6844 mMultiplierX[aIndex] =
6845 static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100;
6847 nsAutoCString prefNameY(basePrefName);
6848 prefNameY.AppendLiteral("delta_multiplier_y");
6849 mMultiplierY[aIndex] =
6850 static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100;
6852 nsAutoCString prefNameZ(basePrefName);
6853 prefNameZ.AppendLiteral("delta_multiplier_z");
6854 mMultiplierZ[aIndex] =
6855 static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
6857 nsAutoCString prefNameAction(basePrefName);
6858 prefNameAction.AppendLiteral("action");
6859 int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
6860 if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
6861 NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
6862 action = ACTION_SCROLL;
6864 mActions[aIndex] = static_cast<Action>(action);
6866 // Compute action values overridden by .override_x pref.
6867 // At present, override is possible only for the x-direction
6868 // because this pref is introduced mainly for tilt wheels.
6869 // Note that ACTION_HORIZONTALIZED_SCROLL isn't a valid value for this pref
6870 // because it affects only to deltaY.
6871 prefNameAction.AppendLiteral(".override_x");
6872 int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1);
6873 if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST) ||
6874 actionOverrideX == ACTION_HORIZONTALIZED_SCROLL) {
6875 NS_WARNING("Unsupported action override pref value, didn't override.");
6876 actionOverrideX = -1;
6878 mOverriddenActionsX[aIndex] = (actionOverrideX == -1)
6879 ? static_cast<Action>(action)
6880 : static_cast<Action>(actionOverrideX);
6883 void EventStateManager::WheelPrefs::GetMultiplierForDeltaXAndY(
6884 const WidgetWheelEvent* aEvent, Index aIndex, double* aMultiplierForDeltaX,
6885 double* aMultiplierForDeltaY) {
6886 *aMultiplierForDeltaX = mMultiplierX[aIndex];
6887 *aMultiplierForDeltaY = mMultiplierY[aIndex];
6888 // If the event has been horizontalized(I.e. treated as a horizontal wheel
6889 // scroll for a vertical wheel scroll), then we should swap mMultiplierX and
6890 // mMultiplierY. By doing this, multipliers will still apply to the delta
6891 // values they origianlly corresponded to.
6892 if (aEvent->mDeltaValuesHorizontalizedForDefaultHandler &&
6893 ComputeActionFor(aEvent) == ACTION_HORIZONTALIZED_SCROLL) {
6894 std::swap(*aMultiplierForDeltaX, *aMultiplierForDeltaY);
6898 void EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(
6899 WidgetWheelEvent* aEvent) {
6900 if (aEvent->mCustomizedByUserPrefs) {
6901 return;
6904 Index index = GetIndexFor(aEvent);
6905 Init(index);
6907 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6908 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6909 &multiplierForDeltaY);
6910 aEvent->mDeltaX *= multiplierForDeltaX;
6911 aEvent->mDeltaY *= multiplierForDeltaY;
6912 aEvent->mDeltaZ *= mMultiplierZ[index];
6914 // If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute
6915 // value, we should use lineOrPageDelta values which were set by widget.
6916 // Otherwise, we need to compute them from accumulated delta values.
6917 if (!NeedToComputeLineOrPageDelta(aEvent)) {
6918 aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(multiplierForDeltaX);
6919 aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(multiplierForDeltaY);
6920 } else {
6921 aEvent->mLineOrPageDeltaX = 0;
6922 aEvent->mLineOrPageDeltaY = 0;
6925 aEvent->mCustomizedByUserPrefs =
6926 ((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) ||
6927 (mMultiplierZ[index] != 1.0));
6930 void EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta(
6931 WidgetWheelEvent* aEvent) {
6932 Index index = GetIndexFor(aEvent);
6933 Init(index);
6935 // XXX If the multiplier pref value is negative, the scroll direction was
6936 // changed and caused to scroll different direction. In such case,
6937 // this method reverts the sign of overflowDelta. Does it make widget
6938 // happy? Although, widget can know the pref applied delta values by
6939 // referrencing the deltaX and deltaY of the event.
6941 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6942 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6943 &multiplierForDeltaY);
6944 if (multiplierForDeltaX) {
6945 aEvent->mOverflowDeltaX /= multiplierForDeltaX;
6947 if (multiplierForDeltaY) {
6948 aEvent->mOverflowDeltaY /= multiplierForDeltaY;
6952 EventStateManager::WheelPrefs::Action
6953 EventStateManager::WheelPrefs::ComputeActionFor(
6954 const WidgetWheelEvent* aEvent) {
6955 Index index = GetIndexFor(aEvent);
6956 Init(index);
6958 bool deltaXPreferred = (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
6959 Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
6960 Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
6961 if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL ||
6962 actions[index] == ACTION_HORIZONTALIZED_SCROLL) {
6963 return actions[index];
6966 // Momentum events shouldn't run special actions.
6967 if (aEvent->mIsMomentum) {
6968 // Use the default action. Note that user might kill the wheel scrolling.
6969 Init(INDEX_DEFAULT);
6970 if (actions[INDEX_DEFAULT] == ACTION_SCROLL ||
6971 actions[INDEX_DEFAULT] == ACTION_HORIZONTALIZED_SCROLL) {
6972 return actions[INDEX_DEFAULT];
6974 return ACTION_NONE;
6977 return actions[index];
6980 bool EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta(
6981 const WidgetWheelEvent* aEvent) {
6982 Index index = GetIndexFor(aEvent);
6983 Init(index);
6985 return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
6986 (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
6989 void EventStateManager::WheelPrefs::GetUserPrefsForEvent(
6990 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
6991 double* aOutMultiplierY) {
6992 Index index = GetIndexFor(aEvent);
6993 Init(index);
6995 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6996 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6997 &multiplierForDeltaY);
6998 *aOutMultiplierX = multiplierForDeltaX;
6999 *aOutMultiplierY = multiplierForDeltaY;
7002 // static
7003 Maybe<layers::APZWheelAction> EventStateManager::APZWheelActionFor(
7004 const WidgetWheelEvent* aEvent) {
7005 if (aEvent->mMessage != eWheel) {
7006 return Nothing();
7008 WheelPrefs::Action action =
7009 WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
7010 switch (action) {
7011 case WheelPrefs::ACTION_SCROLL:
7012 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
7013 return Some(layers::APZWheelAction::Scroll);
7014 case WheelPrefs::ACTION_PINCH_ZOOM:
7015 return Some(layers::APZWheelAction::PinchZoom);
7016 default:
7017 return Nothing();
7021 // static
7022 WheelDeltaAdjustmentStrategy EventStateManager::GetWheelDeltaAdjustmentStrategy(
7023 const WidgetWheelEvent& aEvent) {
7024 if (aEvent.mMessage != eWheel) {
7025 return WheelDeltaAdjustmentStrategy::eNone;
7027 switch (WheelPrefs::GetInstance()->ComputeActionFor(&aEvent)) {
7028 case WheelPrefs::ACTION_SCROLL:
7029 if (StaticPrefs::mousewheel_autodir_enabled() && 0 == aEvent.mDeltaZ) {
7030 if (StaticPrefs::mousewheel_autodir_honourroot()) {
7031 return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour;
7033 return WheelDeltaAdjustmentStrategy::eAutoDir;
7035 return WheelDeltaAdjustmentStrategy::eNone;
7036 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
7037 return WheelDeltaAdjustmentStrategy::eHorizontalize;
7038 default:
7039 break;
7041 return WheelDeltaAdjustmentStrategy::eNone;
7044 void EventStateManager::GetUserPrefsForWheelEvent(
7045 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
7046 double* aOutMultiplierY) {
7047 WheelPrefs::GetInstance()->GetUserPrefsForEvent(aEvent, aOutMultiplierX,
7048 aOutMultiplierY);
7051 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
7052 const WidgetWheelEvent* aEvent) {
7053 Index index = GetIndexFor(aEvent);
7054 Init(index);
7055 return Abs(mMultiplierX[index]) >=
7056 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
7059 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
7060 const WidgetWheelEvent* aEvent) {
7061 Index index = GetIndexFor(aEvent);
7062 Init(index);
7063 return Abs(mMultiplierY[index]) >=
7064 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
7067 } // namespace mozilla