Bug 1826136 [wpt PR 39338] - Update wpt metadata, a=testonly
[gecko.git] / dom / events / EventStateManager.cpp
blob1f6454f7dac5df46c7cd35416bec7bed71e81fea
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/MiscEvents.h"
18 #include "mozilla/MathAlgorithms.h"
19 #include "mozilla/MouseEvents.h"
20 #include "mozilla/PointerLockManager.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/ScrollTypes.h"
24 #include "mozilla/TextComposition.h"
25 #include "mozilla/TextControlElement.h"
26 #include "mozilla/TextEditor.h"
27 #include "mozilla/TextEvents.h"
28 #include "mozilla/TouchEvents.h"
29 #include "mozilla/Telemetry.h"
30 #include "mozilla/UniquePtr.h"
31 #include "mozilla/dom/BrowserBridgeChild.h"
32 #include "mozilla/dom/BrowsingContext.h"
33 #include "mozilla/dom/CanonicalBrowsingContext.h"
34 #include "mozilla/dom/ContentChild.h"
35 #include "mozilla/dom/DOMIntersectionObserver.h"
36 #include "mozilla/dom/DragEvent.h"
37 #include "mozilla/dom/Event.h"
38 #include "mozilla/dom/FrameLoaderBinding.h"
39 #include "mozilla/dom/HTMLLabelElement.h"
40 #include "mozilla/dom/MouseEventBinding.h"
41 #include "mozilla/dom/BrowserChild.h"
42 #include "mozilla/dom/PointerEventHandler.h"
43 #include "mozilla/dom/UIEvent.h"
44 #include "mozilla/dom/UIEventBinding.h"
45 #include "mozilla/dom/UserActivation.h"
46 #include "mozilla/dom/WheelEventBinding.h"
47 #include "mozilla/glean/GleanMetrics.h"
48 #include "mozilla/StaticPrefs_accessibility.h"
49 #include "mozilla/StaticPrefs_browser.h"
50 #include "mozilla/StaticPrefs_dom.h"
51 #include "mozilla/StaticPrefs_layout.h"
52 #include "mozilla/StaticPrefs_mousewheel.h"
53 #include "mozilla/StaticPrefs_plugin.h"
54 #include "mozilla/StaticPrefs_ui.h"
55 #include "mozilla/StaticPrefs_zoom.h"
57 #include "ContentEventHandler.h"
58 #include "IMEContentObserver.h"
59 #include "WheelHandlingHelper.h"
60 #include "RemoteDragStartData.h"
62 #include "nsCommandParams.h"
63 #include "nsCOMPtr.h"
64 #include "nsCopySupport.h"
65 #include "nsFocusManager.h"
66 #include "nsGenericHTMLElement.h"
67 #include "nsIClipboard.h"
68 #include "nsIContent.h"
69 #include "nsIContentInlines.h"
70 #include "mozilla/dom/Document.h"
71 #include "nsICookieJarSettings.h"
72 #include "nsIFrame.h"
73 #include "nsFrameLoaderOwner.h"
74 #include "nsIWidget.h"
75 #include "nsLiteralString.h"
76 #include "nsPresContext.h"
77 #include "nsGkAtoms.h"
78 #include "nsIFormControl.h"
79 #include "nsComboboxControlFrame.h"
80 #include "nsIScrollableFrame.h"
81 #include "nsIDOMXULControlElement.h"
82 #include "nsNameSpaceManager.h"
83 #include "nsIBaseWindow.h"
84 #include "nsFrameSelection.h"
85 #include "nsPIDOMWindow.h"
86 #include "nsPIWindowRoot.h"
87 #include "nsIWebNavigation.h"
88 #include "nsIContentViewer.h"
89 #include "nsFrameManager.h"
90 #include "nsIBrowserChild.h"
91 #include "nsMenuPopupFrame.h"
93 #include "nsIObserverService.h"
94 #include "nsIDocShell.h"
96 #include "nsSubDocumentFrame.h"
97 #include "nsLayoutUtils.h"
98 #include "nsIInterfaceRequestorUtils.h"
99 #include "nsUnicharUtils.h"
100 #include "nsContentUtils.h"
102 #include "imgIContainer.h"
103 #include "nsIProperties.h"
104 #include "nsISupportsPrimitives.h"
106 #include "nsServiceManagerUtils.h"
107 #include "nsITimer.h"
108 #include "nsFontMetrics.h"
109 #include "nsIDragService.h"
110 #include "nsIDragSession.h"
111 #include "mozilla/dom/DataTransfer.h"
112 #include "nsContentAreaDragDrop.h"
113 #include "nsTreeBodyFrame.h"
114 #include "nsIController.h"
115 #include "mozilla/Services.h"
116 #include "mozilla/dom/ContentParent.h"
117 #include "mozilla/dom/Record.h"
118 #include "mozilla/dom/Selection.h"
120 #include "mozilla/Preferences.h"
121 #include "mozilla/LookAndFeel.h"
122 #include "mozilla/ProfilerLabels.h"
123 #include "Units.h"
125 #ifdef XP_MACOSX
126 # import <ApplicationServices/ApplicationServices.h>
127 #endif
129 namespace mozilla {
131 using namespace dom;
133 static const LayoutDeviceIntPoint kInvalidRefPoint =
134 LayoutDeviceIntPoint(-1, -1);
136 static uint32_t gMouseOrKeyboardEventCounter = 0;
137 static nsITimer* gUserInteractionTimer = nullptr;
138 static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
140 static const double kCursorLoadingTimeout = 1000; // ms
141 static AutoWeakFrame gLastCursorSourceFrame;
142 static TimeStamp gLastCursorUpdateTime;
143 static TimeStamp gTypingStartTime;
144 static TimeStamp gTypingEndTime;
145 static int32_t gTypingInteractionKeyPresses = 0;
146 static dom::InteractionData gTypingInteraction = {};
148 static inline int32_t RoundDown(double aDouble) {
149 return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble))
150 : static_cast<int32_t>(ceil(aDouble));
153 static bool IsSelectingLink(nsIFrame* aTargetFrame) {
154 if (!aTargetFrame) {
155 return false;
157 const nsFrameSelection* frameSel = aTargetFrame->GetConstFrameSelection();
158 if (!frameSel || !frameSel->GetDragState()) {
159 return false;
162 if (!nsContentUtils::GetClosestLinkInFlatTree(aTargetFrame->GetContent())) {
163 return false;
165 return true;
168 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
169 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
170 EventTarget* aRelatedTarget);
172 /******************************************************************/
173 /* mozilla::UITimerCallback */
174 /******************************************************************/
176 class UITimerCallback final : public nsITimerCallback, public nsINamed {
177 public:
178 UITimerCallback() : mPreviousCount(0) {}
179 NS_DECL_ISUPPORTS
180 NS_DECL_NSITIMERCALLBACK
181 NS_DECL_NSINAMED
182 private:
183 ~UITimerCallback() = default;
184 uint32_t mPreviousCount;
187 NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)
189 // If aTimer is nullptr, this method always sends "user-interaction-inactive"
190 // notification.
191 NS_IMETHODIMP
192 UITimerCallback::Notify(nsITimer* aTimer) {
193 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
194 if (!obs) return NS_ERROR_FAILURE;
195 if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
196 gMouseOrKeyboardEventCounter = 0;
197 obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
198 if (gUserInteractionTimer) {
199 gUserInteractionTimer->Cancel();
200 NS_RELEASE(gUserInteractionTimer);
202 } else {
203 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
204 EventStateManager::UpdateUserActivityTimer();
206 if (XRE_IsParentProcess()) {
207 hal::BatteryInformation batteryInfo;
208 hal::GetCurrentBatteryInformation(&batteryInfo);
209 glean::power_battery::percentage_when_user_active.AccumulateSamples(
210 {uint64_t(batteryInfo.level() * 100)});
213 mPreviousCount = gMouseOrKeyboardEventCounter;
214 return NS_OK;
217 NS_IMETHODIMP
218 UITimerCallback::GetName(nsACString& aName) {
219 aName.AssignLiteral("UITimerCallback_timer");
220 return NS_OK;
223 /******************************************************************/
224 /* mozilla::OverOutElementsWrapper */
225 /******************************************************************/
227 OverOutElementsWrapper::OverOutElementsWrapper() : mLastOverFrame(nullptr) {}
229 OverOutElementsWrapper::~OverOutElementsWrapper() = default;
231 NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, mLastOverElement,
232 mFirstOverEventElement, mFirstOutEventElement)
233 NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
234 NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)
236 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
237 NS_INTERFACE_MAP_ENTRY(nsISupports)
238 NS_INTERFACE_MAP_END
240 /******************************************************************/
241 /* mozilla::EventStateManager */
242 /******************************************************************/
244 static uint32_t sESMInstanceCount = 0;
246 bool EventStateManager::sNormalLMouseEventInProcess = false;
247 int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
248 EventStateManager* EventStateManager::sActiveESM = nullptr;
249 Document* EventStateManager::sMouseOverDocument = nullptr;
250 AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
251 LayoutDeviceIntPoint EventStateManager::sPreLockPoint =
252 LayoutDeviceIntPoint(0, 0);
253 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
254 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
255 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
256 CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
257 nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
259 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::sInstance =
260 nullptr;
261 EventStateManager::DeltaAccumulator*
262 EventStateManager::DeltaAccumulator::sInstance = nullptr;
264 constexpr const StyleCursorKind kInvalidCursorKind =
265 static_cast<StyleCursorKind>(255);
267 EventStateManager::EventStateManager()
268 : mLockCursor(kInvalidCursorKind),
269 mLastFrameConsumedSetCursor(false),
270 mCurrentTarget(nullptr),
271 // init d&d gesture state machine variables
272 mGestureDownPoint(0, 0),
273 mGestureModifiers(0),
274 mGestureDownButtons(0),
275 mPresContext(nullptr),
276 mLClickCount(0),
277 mMClickCount(0),
278 mRClickCount(0),
279 mShouldAlwaysUseLineDeltas(false),
280 mShouldAlwaysUseLineDeltasInitialized(false),
281 mGestureDownInTextControl(false),
282 mInTouchDrag(false),
283 m_haveShutdown(false) {
284 if (sESMInstanceCount == 0) {
285 gUserInteractionTimerCallback = new UITimerCallback();
286 if (gUserInteractionTimerCallback) NS_ADDREF(gUserInteractionTimerCallback);
287 UpdateUserActivityTimer();
289 ++sESMInstanceCount;
292 nsresult EventStateManager::UpdateUserActivityTimer() {
293 if (!gUserInteractionTimerCallback) return NS_OK;
295 if (!gUserInteractionTimer) {
296 gUserInteractionTimer = NS_NewTimer().take();
299 if (gUserInteractionTimer) {
300 gUserInteractionTimer->InitWithCallback(
301 gUserInteractionTimerCallback,
302 StaticPrefs::dom_events_user_interaction_interval(),
303 nsITimer::TYPE_ONE_SHOT);
305 return NS_OK;
308 nsresult EventStateManager::Init() {
309 nsCOMPtr<nsIObserverService> observerService =
310 mozilla::services::GetObserverService();
311 if (!observerService) return NS_ERROR_FAILURE;
313 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
315 return NS_OK;
318 bool EventStateManager::ShouldAlwaysUseLineDeltas() {
319 if (MOZ_UNLIKELY(!mShouldAlwaysUseLineDeltasInitialized)) {
320 mShouldAlwaysUseLineDeltasInitialized = true;
321 mShouldAlwaysUseLineDeltas =
322 !StaticPrefs::dom_event_wheel_deltaMode_lines_disabled();
323 if (!mShouldAlwaysUseLineDeltas && mDocument) {
324 if (nsIPrincipal* principal =
325 mDocument->GetPrincipalForPrefBasedHacks()) {
326 mShouldAlwaysUseLineDeltas = principal->IsURIInPrefList(
327 "dom.event.wheel-deltaMode-lines.always-enabled");
331 return mShouldAlwaysUseLineDeltas;
334 EventStateManager::~EventStateManager() {
335 ReleaseCurrentIMEContentObserver();
337 if (sActiveESM == this) {
338 sActiveESM = nullptr;
341 if (StaticPrefs::ui_click_hold_context_menus()) {
342 KillClickHoldTimer();
345 if (mDocument == sMouseOverDocument) {
346 sMouseOverDocument = nullptr;
349 --sESMInstanceCount;
350 if (sESMInstanceCount == 0) {
351 WheelTransaction::Shutdown();
352 if (gUserInteractionTimerCallback) {
353 gUserInteractionTimerCallback->Notify(nullptr);
354 NS_RELEASE(gUserInteractionTimerCallback);
356 if (gUserInteractionTimer) {
357 gUserInteractionTimer->Cancel();
358 NS_RELEASE(gUserInteractionTimer);
360 WheelPrefs::Shutdown();
361 DeltaAccumulator::Shutdown();
364 if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
365 sDragOverContent = nullptr;
368 if (!m_haveShutdown) {
369 Shutdown();
371 // Don't remove from Observer service in Shutdown because Shutdown also
372 // gets called from xpcom shutdown observer. And we don't want to remove
373 // from the service in that case.
375 nsCOMPtr<nsIObserverService> observerService =
376 mozilla::services::GetObserverService();
377 if (observerService) {
378 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
383 nsresult EventStateManager::Shutdown() {
384 m_haveShutdown = true;
385 return NS_OK;
388 NS_IMETHODIMP
389 EventStateManager::Observe(nsISupports* aSubject, const char* aTopic,
390 const char16_t* someData) {
391 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
392 Shutdown();
395 return NS_OK;
398 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
399 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
400 NS_INTERFACE_MAP_ENTRY(nsIObserver)
401 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
402 NS_INTERFACE_MAP_END
404 NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
405 NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)
407 NS_IMPL_CYCLE_COLLECTION_WEAK(EventStateManager, mCurrentTargetContent,
408 mGestureDownContent, mGestureDownFrameOwner,
409 mLastLeftMouseDownContent,
410 mLastMiddleMouseDownContent,
411 mLastRightMouseDownContent, mActiveContent,
412 mHoverContent, mURLTargetContent,
413 mMouseEnterLeaveHelper, mPointersEnterLeaveHelper,
414 mDocument, mIMEContentObserver, mAccessKeys)
416 void EventStateManager::ReleaseCurrentIMEContentObserver() {
417 if (mIMEContentObserver) {
418 mIMEContentObserver->DisconnectFromEventStateManager();
420 mIMEContentObserver = nullptr;
423 void EventStateManager::OnStartToObserveContent(
424 IMEContentObserver* aIMEContentObserver) {
425 if (mIMEContentObserver == aIMEContentObserver) {
426 return;
428 ReleaseCurrentIMEContentObserver();
429 mIMEContentObserver = aIMEContentObserver;
432 void EventStateManager::OnStopObservingContent(
433 IMEContentObserver* aIMEContentObserver) {
434 aIMEContentObserver->DisconnectFromEventStateManager();
435 NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
436 mIMEContentObserver = nullptr;
439 void EventStateManager::TryToFlushPendingNotificationsToIME() {
440 if (mIMEContentObserver) {
441 mIMEContentObserver->TryToFlushPendingNotifications(true);
445 static bool IsMessageMouseUserActivity(EventMessage aMessage) {
446 return aMessage == eMouseMove || aMessage == eMouseUp ||
447 aMessage == eMouseDown || aMessage == eMouseAuxClick ||
448 aMessage == eMouseDoubleClick || aMessage == eMouseClick ||
449 aMessage == eMouseActivate || aMessage == eMouseLongTap;
452 static bool IsMessageGamepadUserActivity(EventMessage aMessage) {
453 return aMessage == eGamepadButtonDown || aMessage == eGamepadButtonUp ||
454 aMessage == eGamepadAxisMove;
457 // static
458 bool EventStateManager::IsKeyboardEventUserActivity(WidgetEvent* aEvent) {
459 // We ignore things that shouldn't cause popups, but also things that look
460 // like shortcut presses. In some obscure cases these may actually be
461 // website input, but any meaningful website will have other input anyway,
462 // and we can't very well tell whether shortcut input was supposed to be
463 // directed at chrome or the document.
465 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
466 // Access keys should be treated as page interaction.
467 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
468 return true;
470 if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
471 keyEvent->IsMeta() || keyEvent->IsOS() || keyEvent->IsAlt()) {
472 return false;
474 // Deal with function keys:
475 switch (keyEvent->mKeyNameIndex) {
476 case KEY_NAME_INDEX_F1:
477 case KEY_NAME_INDEX_F2:
478 case KEY_NAME_INDEX_F3:
479 case KEY_NAME_INDEX_F4:
480 case KEY_NAME_INDEX_F5:
481 case KEY_NAME_INDEX_F6:
482 case KEY_NAME_INDEX_F7:
483 case KEY_NAME_INDEX_F8:
484 case KEY_NAME_INDEX_F9:
485 case KEY_NAME_INDEX_F10:
486 case KEY_NAME_INDEX_F11:
487 case KEY_NAME_INDEX_F12:
488 case KEY_NAME_INDEX_F13:
489 case KEY_NAME_INDEX_F14:
490 case KEY_NAME_INDEX_F15:
491 case KEY_NAME_INDEX_F16:
492 case KEY_NAME_INDEX_F17:
493 case KEY_NAME_INDEX_F18:
494 case KEY_NAME_INDEX_F19:
495 case KEY_NAME_INDEX_F20:
496 case KEY_NAME_INDEX_F21:
497 case KEY_NAME_INDEX_F22:
498 case KEY_NAME_INDEX_F23:
499 case KEY_NAME_INDEX_F24:
500 return false;
501 default:
502 return true;
506 static void OnTypingInteractionEnded() {
507 // We don't consider a single keystroke to be typing.
508 if (gTypingInteractionKeyPresses > 1) {
509 gTypingInteraction.mInteractionCount += gTypingInteractionKeyPresses;
510 gTypingInteraction.mInteractionTimeInMilliseconds += static_cast<uint32_t>(
511 std::ceil((gTypingEndTime - gTypingStartTime).ToMilliseconds()));
514 gTypingInteractionKeyPresses = 0;
515 gTypingStartTime = TimeStamp();
516 gTypingEndTime = TimeStamp();
519 static void HandleKeyUpInteraction(WidgetKeyboardEvent* aKeyEvent) {
520 if (EventStateManager::IsKeyboardEventUserActivity(aKeyEvent)) {
521 TimeStamp now = TimeStamp::Now();
522 if (gTypingEndTime.IsNull()) {
523 gTypingEndTime = now;
525 TimeDuration delay = now - gTypingEndTime;
526 // Has it been too long since the last keystroke to be considered typing?
527 if (gTypingInteractionKeyPresses > 0 &&
528 delay >
529 TimeDuration::FromMilliseconds(
530 StaticPrefs::browser_places_interactions_typing_timeout_ms())) {
531 OnTypingInteractionEnded();
533 gTypingInteractionKeyPresses++;
534 if (gTypingStartTime.IsNull()) {
535 gTypingStartTime = now;
537 gTypingEndTime = now;
541 nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
542 WidgetEvent* aEvent,
543 nsIFrame* aTargetFrame,
544 nsIContent* aTargetContent,
545 nsEventStatus* aStatus,
546 nsIContent* aOverrideClickTarget) {
547 NS_ENSURE_ARG_POINTER(aStatus);
548 NS_ENSURE_ARG(aPresContext);
549 if (!aEvent) {
550 NS_ERROR("aEvent is null. This should never happen.");
551 return NS_ERROR_NULL_POINTER;
554 NS_WARNING_ASSERTION(
555 !aTargetFrame || !aTargetFrame->GetContent() ||
556 aTargetFrame->GetContent() == aTargetContent ||
557 aTargetFrame->GetContent()->GetFlattenedTreeParent() ==
558 aTargetContent ||
559 aTargetFrame->IsGeneratedContentFrame(),
560 "aTargetFrame should be related with aTargetContent");
561 #if DEBUG
562 if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
563 nsCOMPtr<nsIContent> targetContent;
564 aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
565 MOZ_ASSERT(aTargetContent == targetContent,
566 "Unexpected target for generated content frame!");
568 #endif
570 mCurrentTarget = aTargetFrame;
571 mCurrentTargetContent = nullptr;
573 // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
574 // a page when user is not active doesn't change the state to active.
575 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
576 if (aEvent->IsTrusted() &&
577 ((mouseEvent && mouseEvent->IsReal() &&
578 IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
579 aEvent->mClass == eWheelEventClass ||
580 aEvent->mClass == ePointerEventClass ||
581 aEvent->mClass == eTouchEventClass ||
582 aEvent->mClass == eKeyboardEventClass ||
583 (aEvent->mClass == eDragEventClass && aEvent->mMessage == eDrop) ||
584 IsMessageGamepadUserActivity(aEvent->mMessage))) {
585 if (gMouseOrKeyboardEventCounter == 0) {
586 nsCOMPtr<nsIObserverService> obs =
587 mozilla::services::GetObserverService();
588 if (obs) {
589 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
590 UpdateUserActivityTimer();
593 ++gMouseOrKeyboardEventCounter;
595 nsCOMPtr<nsINode> node = aTargetContent;
596 if (node &&
597 ((aEvent->mMessage == eKeyUp && IsKeyboardEventUserActivity(aEvent)) ||
598 aEvent->mMessage == eMouseUp || aEvent->mMessage == eWheel ||
599 aEvent->mMessage == eTouchEnd || aEvent->mMessage == ePointerUp ||
600 aEvent->mMessage == eDrop)) {
601 Document* doc = node->OwnerDoc();
602 while (doc) {
603 doc->SetUserHasInteracted();
604 doc = nsContentUtils::IsChildOfSameType(doc)
605 ? doc->GetInProcessParentDocument()
606 : nullptr;
611 WheelTransaction::OnEvent(aEvent);
613 // Focus events don't necessarily need a frame.
614 if (!mCurrentTarget && !aTargetContent) {
615 NS_ERROR("mCurrentTarget and aTargetContent are null");
616 return NS_ERROR_NULL_POINTER;
618 #ifdef DEBUG
619 if (aEvent->HasDragEventMessage() && PointerLockManager::IsLocked()) {
620 NS_ASSERTION(PointerLockManager::IsLocked(),
621 "Pointer is locked. Drag events should be suppressed when "
622 "the pointer is locked.");
624 #endif
625 // Store last known screenPoint and clientPoint so pointer lock
626 // can use these values as constants.
627 if (aEvent->IsTrusted() &&
628 ((mouseEvent && mouseEvent->IsReal()) ||
629 aEvent->mClass == eWheelEventClass) &&
630 !PointerLockManager::IsLocked()) {
631 // XXX Probably doesn't matter much, but storing these in CSS pixels instead
632 // of device pixels means behavior can be a bit odd if you zoom while
633 // pointer-locked.
634 sLastScreenPoint =
635 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
636 .extract();
637 sLastClientPoint = Event::GetClientCoords(
638 aPresContext, aEvent, aEvent->mRefPoint, CSSIntPoint(0, 0));
641 *aStatus = nsEventStatus_eIgnore;
643 if (aEvent->mClass == eQueryContentEventClass) {
644 HandleQueryContentEvent(aEvent->AsQueryContentEvent());
645 return NS_OK;
648 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
649 if (touchEvent && mInTouchDrag) {
650 if (touchEvent->mMessage == eTouchMove) {
651 GenerateDragGesture(aPresContext, touchEvent);
652 } else {
653 mInTouchDrag = false;
654 StopTrackingDragGesture(true);
658 switch (aEvent->mMessage) {
659 case eContextMenu:
660 if (PointerLockManager::IsLocked()) {
661 return NS_ERROR_DOM_INVALID_STATE_ERR;
663 break;
664 case eMouseTouchDrag:
665 mInTouchDrag = true;
666 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
667 break;
668 case eMouseDown: {
669 switch (mouseEvent->mButton) {
670 case MouseButton::ePrimary:
671 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
672 mLClickCount = mouseEvent->mClickCount;
673 SetClickCount(mouseEvent, aStatus);
674 sNormalLMouseEventInProcess = true;
675 break;
676 case MouseButton::eMiddle:
677 mMClickCount = mouseEvent->mClickCount;
678 SetClickCount(mouseEvent, aStatus);
679 break;
680 case MouseButton::eSecondary:
681 mRClickCount = mouseEvent->mClickCount;
682 SetClickCount(mouseEvent, aStatus);
683 break;
685 NotifyTargetUserActivation(aEvent, aTargetContent);
686 break;
688 case eMouseUp: {
689 switch (mouseEvent->mButton) {
690 case MouseButton::ePrimary:
691 if (StaticPrefs::ui_click_hold_context_menus()) {
692 KillClickHoldTimer();
694 mInTouchDrag = false;
695 StopTrackingDragGesture(true);
696 sNormalLMouseEventInProcess = false;
697 // then fall through...
698 [[fallthrough]];
699 case MouseButton::eSecondary:
700 case MouseButton::eMiddle:
701 RefPtr<EventStateManager> esm =
702 ESMFromContentOrThis(aOverrideClickTarget);
703 esm->SetClickCount(mouseEvent, aStatus, aOverrideClickTarget);
704 break;
706 break;
708 case eMouseEnterIntoWidget:
709 PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
710 // In some cases on e10s eMouseEnterIntoWidget
711 // event was sent twice into child process of content.
712 // (From specific widget code (sending is not permanent) and
713 // from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
714 // IsCrossProcessForwardingStopped() helps to suppress sending accidental
715 // event from widget code.
716 aEvent->StopCrossProcessForwarding();
717 break;
718 case eMouseExitFromWidget:
719 // If this is a remote frame, we receive eMouseExitFromWidget from the
720 // parent the mouse exits our content. Since the parent may update the
721 // cursor while the mouse is outside our frame, and since PuppetWidget
722 // caches the current cursor internally, re-entering our content (say from
723 // over a window edge) wont update the cursor if the cached value and the
724 // current cursor match. So when the mouse exits a remote frame, clear the
725 // cached widget cursor so a proper update will occur when the mouse
726 // re-enters.
727 if (XRE_IsContentProcess()) {
728 ClearCachedWidgetCursor(mCurrentTarget);
731 // IsCrossProcessForwardingStopped() helps to suppress double event
732 // sending into process of content. For more information see comment
733 // above, at eMouseEnterIntoWidget case.
734 aEvent->StopCrossProcessForwarding();
736 // If the event is not a top-level window or puppet widget exit, then it's
737 // not really an exit --- we may have traversed widget boundaries but
738 // we're still in our toplevel window or puppet widget.
739 if (mouseEvent->mExitFrom.value() !=
740 WidgetMouseEvent::ePlatformTopLevel &&
741 mouseEvent->mExitFrom.value() != WidgetMouseEvent::ePuppet) {
742 // Treat it as a synthetic move so we don't generate spurious
743 // "exit" or "move" events. Any necessary "out" or "over" events
744 // will be generated by GenerateMouseEnterExit
745 mouseEvent->mMessage = eMouseMove;
746 mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
747 // then fall through...
748 } else {
749 MOZ_ASSERT_IF(XRE_IsParentProcess(),
750 mouseEvent->mExitFrom.value() ==
751 WidgetMouseEvent::ePlatformTopLevel);
752 MOZ_ASSERT_IF(XRE_IsContentProcess(), mouseEvent->mExitFrom.value() ==
753 WidgetMouseEvent::ePuppet);
754 // We should synthetize corresponding pointer events
755 GeneratePointerEnterExit(ePointerLeave, mouseEvent);
756 GenerateMouseEnterExit(mouseEvent);
757 // This is really an exit and should stop here
758 aEvent->mMessage = eVoidEvent;
759 break;
761 [[fallthrough]];
762 case eMouseMove:
763 case ePointerDown:
764 if (aEvent->mMessage == ePointerDown) {
765 PointerEventHandler::UpdateActivePointerState(mouseEvent,
766 aTargetContent);
767 PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
768 if (mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
769 NotifyTargetUserActivation(aEvent, aTargetContent);
772 [[fallthrough]];
773 case ePointerMove: {
774 if (!mInTouchDrag &&
775 PointerEventHandler::IsDragAndDropEnabled(*mouseEvent)) {
776 GenerateDragGesture(aPresContext, mouseEvent);
778 // on the Mac, GenerateDragGesture() may not return until the drag
779 // has completed and so |aTargetFrame| may have been deleted (moving
780 // a bookmark, for example). If this is the case, however, we know
781 // that ClearFrameRefs() has been called and it cleared out
782 // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
783 // into UpdateCursor().
784 UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus);
786 UpdateLastRefPointOfMouseEvent(mouseEvent);
787 if (PointerLockManager::IsLocked()) {
788 ResetPointerToWindowCenterWhilePointerLocked(mouseEvent);
790 UpdateLastPointerPosition(mouseEvent);
792 GenerateMouseEnterExit(mouseEvent);
793 // Flush pending layout changes, so that later mouse move events
794 // will go to the right nodes.
795 FlushLayout(aPresContext);
796 break;
798 case ePointerGotCapture:
799 GenerateMouseEnterExit(mouseEvent);
800 break;
801 case eDragStart:
802 if (StaticPrefs::ui_click_hold_context_menus()) {
803 // an external drag gesture event came in, not generated internally
804 // by Gecko. Make sure we get rid of the click-hold timer.
805 KillClickHoldTimer();
807 break;
808 case eDragOver: {
809 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
810 MOZ_ASSERT(dragEvent);
811 if (dragEvent->mFlags.mIsSynthesizedForTests) {
812 dragEvent->InitDropEffectForTests();
814 // Send the enter/exit events before eDrop.
815 GenerateDragDropEnterExit(aPresContext, dragEvent);
816 break;
818 case eDrop:
819 if (aEvent->mFlags.mIsSynthesizedForTests) {
820 MOZ_ASSERT(aEvent->AsDragEvent());
821 aEvent->AsDragEvent()->InitDropEffectForTests();
823 break;
825 case eKeyPress: {
826 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
827 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
828 keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
829 // If the eKeyPress event will be sent to a remote process, this
830 // process needs to wait reply from the remote process for checking if
831 // preceding eKeyDown event is consumed. If preceding eKeyDown event
832 // is consumed in the remote process, BrowserChild won't send the event
833 // back to this process. So, only when this process receives a reply
834 // eKeyPress event in BrowserParent, we should handle accesskey in this
835 // process.
836 if (IsTopLevelRemoteTarget(GetFocusedElement())) {
837 // However, if there is no accesskey target for the key combination,
838 // we don't need to wait reply from the remote process. Otherwise,
839 // Mark the event as waiting reply from remote process and stop
840 // propagation in this process.
841 if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
842 keyEvent->StopPropagation();
843 keyEvent->MarkAsWaitingReplyFromRemoteProcess();
846 // If the event target is in this process, we can handle accesskey now
847 // since if preceding eKeyDown event was consumed, eKeyPress event
848 // won't be dispatched by widget. So, coming eKeyPress event means
849 // that the preceding eKeyDown event wasn't consumed in this case.
850 else {
851 AutoTArray<uint32_t, 10> accessCharCodes;
852 keyEvent->GetAccessKeyCandidates(accessCharCodes);
854 if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
855 *aStatus = nsEventStatus_eConsumeNoDefault;
860 // then fall through...
861 [[fallthrough]];
862 case eKeyDown:
863 if (aEvent->mMessage == eKeyDown) {
864 NotifyTargetUserActivation(aEvent, aTargetContent);
866 [[fallthrough]];
867 case eKeyUp: {
868 Element* element = GetFocusedElement();
869 if (element) {
870 mCurrentTargetContent = element;
873 // NOTE: Don't refer TextComposition::IsComposing() since UI Events
874 // defines that KeyboardEvent.isComposing is true when it's
875 // dispatched after compositionstart and compositionend.
876 // TextComposition::IsComposing() is false even before
877 // compositionend if there is no composing string.
878 // And also don't expose other document's composition state.
879 // A native IME context is typically shared by multiple documents.
880 // So, don't use GetTextCompositionFor(nsIWidget*) here.
881 RefPtr<TextComposition> composition =
882 IMEStateManager::GetTextCompositionFor(aPresContext);
883 aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
885 // Widget may need to perform default action for specific keyboard
886 // event if it's not consumed. In this case, widget has already marked
887 // the event as "waiting reply from remote process". However, we need
888 // to reset it if the target (focused content) isn't in a remote process
889 // because PresShell needs to check if it's marked as so before
890 // dispatching events into the DOM tree.
891 if (aEvent->IsWaitingReplyFromRemoteProcess() &&
892 !aEvent->PropagationStopped() && !IsTopLevelRemoteTarget(element)) {
893 aEvent->ResetWaitingReplyFromRemoteProcessState();
895 } break;
896 case eWheel:
897 case eWheelOperationStart:
898 case eWheelOperationEnd: {
899 NS_ASSERTION(aEvent->IsTrusted(),
900 "Untrusted wheel event shouldn't be here");
901 using DeltaModeCheckingState = WidgetWheelEvent::DeltaModeCheckingState;
903 if (Element* element = GetFocusedElement()) {
904 mCurrentTargetContent = element;
907 if (aEvent->mMessage != eWheel) {
908 break;
911 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
912 WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);
914 // If we won't dispatch a DOM event for this event, nothing to do anymore.
915 if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
916 break;
919 if (StaticPrefs::dom_event_wheel_deltaMode_lines_always_disabled()) {
920 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unchecked;
921 } else if (ShouldAlwaysUseLineDeltas()) {
922 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Checked;
923 } else {
924 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unknown;
927 // Init lineOrPageDelta values for line scroll events for some devices
928 // on some platforms which might dispatch wheel events which don't
929 // have lineOrPageDelta values. And also, if delta values are
930 // customized by prefs, this recomputes them.
931 DeltaAccumulator::GetInstance()->InitLineOrPageDelta(aTargetFrame, this,
932 wheelEvent);
933 } break;
934 case eSetSelection: {
935 RefPtr<Element> focuedElement = GetFocusedElement();
936 IMEStateManager::HandleSelectionEvent(aPresContext, focuedElement,
937 aEvent->AsSelectionEvent());
938 break;
940 case eContentCommandCut:
941 case eContentCommandCopy:
942 case eContentCommandPaste:
943 case eContentCommandDelete:
944 case eContentCommandUndo:
945 case eContentCommandRedo:
946 case eContentCommandPasteTransferable:
947 case eContentCommandLookUpDictionary:
948 DoContentCommandEvent(aEvent->AsContentCommandEvent());
949 break;
950 case eContentCommandInsertText:
951 DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
952 break;
953 case eContentCommandScroll:
954 DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
955 break;
956 case eCompositionStart:
957 if (aEvent->IsTrusted()) {
958 // If the event is trusted event, set the selected text to data of
959 // composition event.
960 WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
961 WidgetQueryContentEvent querySelectedTextEvent(
962 true, eQuerySelectedText, compositionEvent->mWidget);
963 HandleQueryContentEvent(&querySelectedTextEvent);
964 if (querySelectedTextEvent.FoundSelection()) {
965 compositionEvent->mData = querySelectedTextEvent.mReply->DataRef();
967 NS_ASSERTION(querySelectedTextEvent.Succeeded(),
968 "Failed to get selected text");
970 break;
971 case eTouchStart:
972 SetGestureDownPoint(aEvent->AsTouchEvent());
973 break;
974 case eTouchEnd:
975 NotifyTargetUserActivation(aEvent, aTargetContent);
976 break;
977 default:
978 break;
980 return NS_OK;
983 void EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
984 nsIContent* aTargetContent) {
985 if (!aEvent->IsTrusted()) {
986 return;
989 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
990 if (mouseEvent && !mouseEvent->IsReal()) {
991 return;
994 nsCOMPtr<nsINode> node = aTargetContent;
995 if (!node) {
996 return;
999 Document* doc = node->OwnerDoc();
1000 if (!doc) {
1001 return;
1004 // Don't gesture activate for key events for keys which are likely
1005 // to be interaction with the browser, OS.
1006 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
1007 if (keyEvent && !keyEvent->CanUserGestureActivateTarget()) {
1008 return;
1011 // Touch gestures that end outside the drag target were touches that turned
1012 // into scroll/pan/swipe actions. We don't want to gesture activate on such
1013 // actions, we want to only gesture activate on touches that are taps.
1014 // That is, touches that end in roughly the same place that they started.
1015 if (aEvent->mMessage == eTouchEnd && aEvent->AsTouchEvent() &&
1016 IsEventOutsideDragThreshold(aEvent->AsTouchEvent())) {
1017 return;
1020 // Do not treat the click on scrollbar as a user interaction with the web
1021 // content.
1022 if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
1023 (aEvent->mMessage == eMouseDown || aEvent->mMessage == ePointerDown) &&
1024 aTargetContent->IsInNativeAnonymousSubtree()) {
1025 nsIContent* current = aTargetContent;
1026 do {
1027 nsIContent* root = current->GetClosestNativeAnonymousSubtreeRoot();
1028 if (!root) {
1029 break;
1031 if (root->IsXULElement(nsGkAtoms::scrollbar)) {
1032 return;
1034 current = root->GetParent();
1035 } while (current);
1038 MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
1039 aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
1040 doc->NotifyUserGestureActivation();
1043 already_AddRefed<EventStateManager> EventStateManager::ESMFromContentOrThis(
1044 nsIContent* aContent) {
1045 if (aContent) {
1046 PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
1047 if (presShell) {
1048 nsPresContext* prescontext = presShell->GetPresContext();
1049 if (prescontext) {
1050 RefPtr<EventStateManager> esm = prescontext->EventStateManager();
1051 if (esm) {
1052 return esm.forget();
1058 RefPtr<EventStateManager> esm = this;
1059 return esm.forget();
1062 void EventStateManager::HandleQueryContentEvent(
1063 WidgetQueryContentEvent* aEvent) {
1064 switch (aEvent->mMessage) {
1065 case eQuerySelectedText:
1066 case eQueryTextContent:
1067 case eQueryCaretRect:
1068 case eQueryTextRect:
1069 case eQueryEditorRect:
1070 if (!IsTargetCrossProcess(aEvent)) {
1071 break;
1073 // Will not be handled locally, remote the event
1074 GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
1075 return;
1076 // Following events have not been supported in e10s mode yet.
1077 case eQueryContentState:
1078 case eQuerySelectionAsTransferable:
1079 case eQueryCharacterAtPoint:
1080 case eQueryDOMWidgetHittest:
1081 case eQueryTextRectArray:
1082 break;
1083 default:
1084 return;
1087 // If there is an IMEContentObserver, we need to handle QueryContentEvent
1088 // with it.
1089 if (mIMEContentObserver) {
1090 RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
1091 contentObserver->HandleQueryContentEvent(aEvent);
1092 return;
1095 ContentEventHandler handler(mPresContext);
1096 handler.HandleQueryContentEvent(aEvent);
1099 static AccessKeyType GetAccessKeyTypeFor(nsISupports* aDocShell) {
1100 nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
1101 if (!treeItem) {
1102 return AccessKeyType::eNone;
1105 switch (treeItem->ItemType()) {
1106 case nsIDocShellTreeItem::typeChrome:
1107 return AccessKeyType::eChrome;
1108 case nsIDocShellTreeItem::typeContent:
1109 return AccessKeyType::eContent;
1110 default:
1111 return AccessKeyType::eNone;
1115 static bool IsAccessKeyTarget(Element* aElement, nsAString& aKey) {
1116 // Use GetAttr because we want Unicode case=insensitive matching
1117 // XXXbz shouldn't this be case-sensitive, per spec?
1118 nsString contentKey;
1119 if (!aElement ||
1120 !aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, contentKey) ||
1121 !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator)) {
1122 return false;
1125 if (!aElement->IsXULElement()) {
1126 return true;
1129 // For XUL we do visibility checks.
1130 nsIFrame* frame = aElement->GetPrimaryFrame();
1131 if (!frame) {
1132 return false;
1135 if (frame->IsFocusable()) {
1136 return true;
1139 if (!frame->IsVisibleConsideringAncestors()) {
1140 return false;
1143 // XUL controls can be activated.
1144 nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl();
1145 if (control) {
1146 return true;
1149 // XUL label elements are never focusable, so we need to check for them
1150 // explicitly before giving up.
1151 if (aElement->IsXULElement(nsGkAtoms::label)) {
1152 return true;
1155 return false;
1158 bool EventStateManager::CheckIfEventMatchesAccessKey(
1159 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext) {
1160 AutoTArray<uint32_t, 10> accessCharCodes;
1161 aEvent->GetAccessKeyCandidates(accessCharCodes);
1162 return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, accessCharCodes,
1163 nullptr, eAccessKeyProcessingNormal,
1164 false);
1167 bool EventStateManager::LookForAccessKeyAndExecute(
1168 nsTArray<uint32_t>& aAccessCharCodes, bool aIsTrustedEvent, bool aIsRepeat,
1169 bool aExecute) {
1170 int32_t count, start = -1;
1171 if (Element* focusedElement = GetFocusedElement()) {
1172 start = mAccessKeys.IndexOf(focusedElement);
1173 if (start == -1 && focusedElement->IsInNativeAnonymousSubtree()) {
1174 start = mAccessKeys.IndexOf(Element::FromNodeOrNull(
1175 focusedElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()));
1178 RefPtr<Element> element;
1179 int32_t length = mAccessKeys.Count();
1180 for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
1181 uint32_t ch = aAccessCharCodes[i];
1182 nsAutoString accessKey;
1183 AppendUCS4ToUTF16(ch, accessKey);
1184 for (count = 1; count <= length; ++count) {
1185 // mAccessKeys always stores Element instances.
1186 MOZ_DIAGNOSTIC_ASSERT(length == mAccessKeys.Count());
1187 element = mAccessKeys[(start + count) % length];
1188 if (IsAccessKeyTarget(element, accessKey)) {
1189 if (!aExecute) {
1190 return true;
1192 Document* doc = element->OwnerDoc();
1193 const bool shouldActivate = [&] {
1194 if (!StaticPrefs::accessibility_accesskeycausesactivation()) {
1195 return false;
1197 if (aIsRepeat && nsContentUtils::IsChromeDoc(doc)) {
1198 return false;
1201 // XXXedgar, Bug 1700646, maybe we could use other data structure to
1202 // make searching target with same accesskey easier, and current setup
1203 // could not ensure we cycle the target with tree order.
1204 int32_t j = 0;
1205 while (++j < length) {
1206 Element* el = mAccessKeys[(start + count + j) % length];
1207 if (IsAccessKeyTarget(el, accessKey)) {
1208 return false;
1211 return true;
1212 }();
1214 // TODO(bug 1641171): This shouldn't be needed if we considered the
1215 // accesskey combination properly.
1216 if (aIsTrustedEvent) {
1217 doc->NotifyUserGestureActivation();
1220 auto result =
1221 element->PerformAccesskey(shouldActivate, aIsTrustedEvent);
1222 if (result.isOk()) {
1223 if (result.unwrap() && aIsTrustedEvent) {
1224 // If this is a child process, inform the parent that we want the
1225 // focus, but pass false since we don't want to change the window
1226 // order.
1227 nsIDocShell* docShell = mPresContext->GetDocShell();
1228 nsCOMPtr<nsIBrowserChild> child =
1229 docShell ? docShell->GetBrowserChild() : nullptr;
1230 if (child) {
1231 child->SendRequestFocus(false, CallerType::System);
1234 return true;
1239 return false;
1242 // static
1243 void EventStateManager::GetAccessKeyLabelPrefix(Element* aElement,
1244 nsAString& aPrefix) {
1245 aPrefix.Truncate();
1246 nsAutoString separator, modifierText;
1247 nsContentUtils::GetModifierSeparatorText(separator);
1249 AccessKeyType accessKeyType =
1250 GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
1251 if (accessKeyType == AccessKeyType::eNone) {
1252 return;
1254 Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
1255 if (modifiers == MODIFIER_NONE) {
1256 return;
1259 if (modifiers & MODIFIER_CONTROL) {
1260 nsContentUtils::GetControlText(modifierText);
1261 aPrefix.Append(modifierText + separator);
1263 if (modifiers & MODIFIER_META) {
1264 nsContentUtils::GetMetaText(modifierText);
1265 aPrefix.Append(modifierText + separator);
1267 if (modifiers & MODIFIER_OS) {
1268 nsContentUtils::GetOSText(modifierText);
1269 aPrefix.Append(modifierText + separator);
1271 if (modifiers & MODIFIER_ALT) {
1272 nsContentUtils::GetAltText(modifierText);
1273 aPrefix.Append(modifierText + separator);
1275 if (modifiers & MODIFIER_SHIFT) {
1276 nsContentUtils::GetShiftText(modifierText);
1277 aPrefix.Append(modifierText + separator);
1281 struct MOZ_STACK_CLASS AccessKeyInfo {
1282 WidgetKeyboardEvent* event;
1283 nsTArray<uint32_t>& charCodes;
1285 AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes)
1286 : event(aEvent), charCodes(aCharCodes) {}
1289 bool EventStateManager::WalkESMTreeToHandleAccessKey(
1290 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext,
1291 nsTArray<uint32_t>& aAccessCharCodes, nsIDocShellTreeItem* aBubbledFrom,
1292 ProcessingAccessKeyState aAccessKeyState, bool aExecute) {
1293 EnsureDocument(mPresContext);
1294 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
1295 if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
1296 return false;
1298 AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
1299 if (accessKeyType == AccessKeyType::eNone) {
1300 return false;
1302 // Alt or other accesskey modifier is down, we may need to do an accesskey.
1303 if (mAccessKeys.Count() > 0 &&
1304 aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
1305 // Someone registered an accesskey. Find and activate it.
1306 if (LookForAccessKeyAndExecute(aAccessCharCodes, aEvent->IsTrusted(),
1307 aEvent->mIsRepeat, aExecute)) {
1308 return true;
1312 int32_t childCount;
1313 docShell->GetInProcessChildCount(&childCount);
1314 for (int32_t counter = 0; counter < childCount; counter++) {
1315 // Not processing the child which bubbles up the handling
1316 nsCOMPtr<nsIDocShellTreeItem> subShellItem;
1317 docShell->GetInProcessChildAt(counter, getter_AddRefs(subShellItem));
1318 if (aAccessKeyState == eAccessKeyProcessingUp &&
1319 subShellItem == aBubbledFrom) {
1320 continue;
1323 nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
1324 if (subDS && IsShellVisible(subDS)) {
1325 // Guarantee subPresShell lifetime while we're handling access key
1326 // since somebody may assume that it won't be deleted before the
1327 // corresponding nsPresContext and EventStateManager.
1328 RefPtr<PresShell> subPresShell = subDS->GetPresShell();
1330 // Docshells need not have a presshell (eg. display:none
1331 // iframes, docshells in transition between documents, etc).
1332 if (!subPresShell) {
1333 // Oh, well. Just move on to the next child
1334 continue;
1337 RefPtr<nsPresContext> subPresContext = subPresShell->GetPresContext();
1339 RefPtr<EventStateManager> esm =
1340 static_cast<EventStateManager*>(subPresContext->EventStateManager());
1342 if (esm && esm->WalkESMTreeToHandleAccessKey(
1343 aEvent, subPresContext, aAccessCharCodes, nullptr,
1344 eAccessKeyProcessingDown, aExecute)) {
1345 return true;
1348 } // if end . checking all sub docshell ends here.
1350 // bubble up the process to the parent docshell if necessary
1351 if (eAccessKeyProcessingDown != aAccessKeyState) {
1352 nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
1353 docShell->GetInProcessParent(getter_AddRefs(parentShellItem));
1354 nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
1355 if (parentDS) {
1356 // Guarantee parentPresShell lifetime while we're handling access key
1357 // since somebody may assume that it won't be deleted before the
1358 // corresponding nsPresContext and EventStateManager.
1359 RefPtr<PresShell> parentPresShell = parentDS->GetPresShell();
1360 NS_ASSERTION(parentPresShell,
1361 "Our PresShell exists but the parent's does not?");
1363 RefPtr<nsPresContext> parentPresContext =
1364 parentPresShell->GetPresContext();
1365 NS_ASSERTION(parentPresContext, "PresShell without PresContext");
1367 RefPtr<EventStateManager> esm = static_cast<EventStateManager*>(
1368 parentPresContext->EventStateManager());
1369 if (esm && esm->WalkESMTreeToHandleAccessKey(
1370 aEvent, parentPresContext, aAccessCharCodes, docShell,
1371 eAccessKeyProcessingDown, aExecute)) {
1372 return true;
1375 } // if end. bubble up process
1377 // If the content access key modifier is pressed, try remote children
1378 if (aExecute &&
1379 aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
1380 mDocument && mDocument->GetWindow()) {
1381 // If the focus is currently on a node with a BrowserParent, the key event
1382 // should've gotten forwarded to the child process and HandleAccessKey
1383 // called from there.
1384 if (BrowserParent::GetFrom(GetFocusedElement())) {
1385 // If access key may be only in remote contents, this method won't handle
1386 // access key synchronously. In this case, only reply event should reach
1387 // here.
1388 MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
1389 !aEvent->IsWaitingReplyFromRemoteProcess());
1391 // If focus is somewhere else, then we need to check the remote children.
1392 // However, if the event has already been handled in a remote process,
1393 // then, focus is moved from the remote process after posting the event.
1394 // In such case, we shouldn't retry to handle access keys in remote
1395 // processes.
1396 else if (!aEvent->IsHandledInRemoteProcess()) {
1397 AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
1398 nsContentUtils::CallOnAllRemoteChildren(
1399 mDocument->GetWindow(),
1400 [&accessKeyInfo](BrowserParent* aBrowserParent) -> CallState {
1401 // Only forward accesskeys for the active tab.
1402 if (aBrowserParent->GetDocShellIsActive()) {
1403 // Even if there is no target for the accesskey in this process,
1404 // the event may match with a content accesskey. If so, the
1405 // keyboard event should be handled with reply event for
1406 // preventing double action. (e.g., Alt+Shift+F on Windows may
1407 // focus a content in remote and open "File" menu.)
1408 accessKeyInfo.event->StopPropagation();
1409 accessKeyInfo.event->MarkAsWaitingReplyFromRemoteProcess();
1410 aBrowserParent->HandleAccessKey(*accessKeyInfo.event,
1411 accessKeyInfo.charCodes);
1412 return CallState::Stop;
1415 return CallState::Continue;
1420 return false;
1421 } // end of HandleAccessKey
1423 static BrowserParent* GetBrowserParentAncestor(BrowserParent* aBrowserParent) {
1424 MOZ_ASSERT(aBrowserParent);
1426 BrowserBridgeParent* bbp = aBrowserParent->GetBrowserBridgeParent();
1427 if (!bbp) {
1428 return nullptr;
1431 return bbp->Manager();
1434 static void DispatchCrossProcessMouseExitEvents(WidgetMouseEvent* aMouseEvent,
1435 BrowserParent* aRemoteTarget,
1436 BrowserParent* aStopAncestor,
1437 bool aIsReallyExit) {
1438 MOZ_ASSERT(aMouseEvent);
1439 MOZ_ASSERT(aRemoteTarget);
1440 MOZ_ASSERT(aRemoteTarget != aStopAncestor);
1441 MOZ_ASSERT_IF(aStopAncestor, nsContentUtils::GetCommonBrowserParentAncestor(
1442 aRemoteTarget, aStopAncestor));
1444 while (aRemoteTarget != aStopAncestor) {
1445 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1446 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
1447 aMouseEvent->mRelatedTarget);
1448 mouseExitEvent->mExitFrom =
1449 Some(aIsReallyExit ? WidgetMouseEvent::ePuppet
1450 : WidgetMouseEvent::ePuppetParentToPuppetChild);
1451 aRemoteTarget->SendRealMouseEvent(*mouseExitEvent);
1453 aRemoteTarget = GetBrowserParentAncestor(aRemoteTarget);
1457 void EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
1458 BrowserParent* aRemoteTarget,
1459 nsEventStatus* aStatus) {
1460 MOZ_ASSERT(aEvent);
1461 MOZ_ASSERT(aRemoteTarget);
1462 MOZ_ASSERT(aStatus);
1464 BrowserParent* remote = aRemoteTarget;
1466 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1467 bool isContextMenuKey = mouseEvent && mouseEvent->IsContextMenuKeyEvent();
1468 if (aEvent->mClass == eKeyboardEventClass || isContextMenuKey) {
1469 // APZ attaches a LayersId to hit-testable events, for keyboard events,
1470 // we use focus.
1471 BrowserParent* preciseRemote = BrowserParent::GetFocused();
1472 if (preciseRemote) {
1473 remote = preciseRemote;
1475 // else there is a race between layout and focus tracking,
1476 // so fall back to delivering the event to the topmost child process.
1477 } else if (aEvent->mLayersId.IsValid()) {
1478 BrowserParent* preciseRemote =
1479 BrowserParent::GetBrowserParentFromLayersId(aEvent->mLayersId);
1480 if (preciseRemote) {
1481 remote = preciseRemote;
1483 // else there is a race between APZ and the LayersId to BrowserParent
1484 // mapping, so fall back to delivering the event to the topmost child
1485 // process.
1488 switch (aEvent->mClass) {
1489 case eMouseEventClass: {
1490 BrowserParent* oldRemote = BrowserParent::GetLastMouseRemoteTarget();
1492 // If this is a eMouseExitFromWidget event, need to redirect the event to
1493 // the last remote and and notify all its ancestors about the exit, if
1494 // any.
1495 if (mouseEvent->mMessage == eMouseExitFromWidget) {
1496 MOZ_ASSERT(mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet);
1497 MOZ_ASSERT(mouseEvent->mReason == WidgetMouseEvent::eReal);
1498 MOZ_ASSERT(!mouseEvent->mLayersId.IsValid());
1499 MOZ_ASSERT(remote->GetBrowserHost());
1501 if (oldRemote && oldRemote != remote) {
1502 Unused << NS_WARN_IF(nsContentUtils::GetCommonBrowserParentAncestor(
1503 remote, oldRemote) != remote);
1504 remote = oldRemote;
1507 DispatchCrossProcessMouseExitEvents(mouseEvent, remote, nullptr, true);
1508 return;
1511 if (BrowserParent* pointerLockedRemote =
1512 PointerLockManager::GetLockedRemoteTarget()) {
1513 remote = pointerLockedRemote;
1514 } else if (BrowserParent* pointerCapturedRemote =
1515 PointerEventHandler::GetPointerCapturingRemoteTarget(
1516 mouseEvent->pointerId)) {
1517 remote = pointerCapturedRemote;
1518 } else if (BrowserParent* capturingRemote =
1519 PresShell::GetCapturingRemoteTarget()) {
1520 remote = capturingRemote;
1523 // If a mouse is over a remote target A, and then moves to
1524 // remote target B, we'd deliver the event directly to remote target B
1525 // after the moving, A would never get notified that the mouse left.
1526 // So we generate a exit event to notify A after the move.
1527 // XXXedgar, if the synthesized mouse events could deliver to the correct
1528 // process directly (see
1529 // https://bugzilla.mozilla.org/show_bug.cgi?id=1549355), we probably
1530 // don't need to check mReason then.
1531 if (mouseEvent->mReason == WidgetMouseEvent::eReal &&
1532 remote != oldRemote) {
1533 MOZ_ASSERT(mouseEvent->mMessage != eMouseExitFromWidget);
1534 if (oldRemote) {
1535 BrowserParent* commonAncestor =
1536 nsContentUtils::GetCommonBrowserParentAncestor(remote, oldRemote);
1537 if (commonAncestor == oldRemote) {
1538 // Mouse moves to the inner OOP frame, it is not a really exit.
1539 DispatchCrossProcessMouseExitEvents(
1540 mouseEvent, GetBrowserParentAncestor(remote),
1541 GetBrowserParentAncestor(commonAncestor), false);
1542 } else if (commonAncestor == remote) {
1543 // Mouse moves to the outer OOP frame, it is a really exit.
1544 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1545 commonAncestor, true);
1546 } else {
1547 // Mouse moves to OOP frame in other subtree, it is a really exit,
1548 // need to notify all its ancestors before common ancestor about the
1549 // exit.
1550 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1551 commonAncestor, true);
1552 if (commonAncestor) {
1553 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1554 CreateMouseOrPointerWidgetEvent(mouseEvent,
1555 eMouseExitFromWidget,
1556 mouseEvent->mRelatedTarget);
1557 mouseExitEvent->mExitFrom =
1558 Some(WidgetMouseEvent::ePuppetParentToPuppetChild);
1559 commonAncestor->SendRealMouseEvent(*mouseExitEvent);
1564 if (mouseEvent->mMessage != eMouseExitFromWidget &&
1565 mouseEvent->mMessage != eMouseEnterIntoWidget) {
1566 // This is to make cursor would be updated correctly.
1567 remote->MouseEnterIntoWidget();
1571 remote->SendRealMouseEvent(*mouseEvent);
1572 return;
1574 case eKeyboardEventClass: {
1575 auto* keyboardEvent = aEvent->AsKeyboardEvent();
1576 if (aEvent->mMessage == eKeyUp) {
1577 HandleKeyUpInteraction(keyboardEvent);
1579 remote->SendRealKeyEvent(*keyboardEvent);
1580 return;
1582 case eWheelEventClass: {
1583 if (BrowserParent* pointerLockedRemote =
1584 PointerLockManager::GetLockedRemoteTarget()) {
1585 remote = pointerLockedRemote;
1587 remote->SendMouseWheelEvent(*aEvent->AsWheelEvent());
1588 return;
1590 case eTouchEventClass: {
1591 // Let the child process synthesize a mouse event if needed, and
1592 // ensure we don't synthesize one in this process.
1593 *aStatus = nsEventStatus_eConsumeNoDefault;
1594 remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
1595 return;
1597 case eDragEventClass: {
1598 RefPtr<BrowserParent> browserParent = remote;
1599 browserParent->Manager()->MaybeInvokeDragSession(browserParent);
1601 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1602 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
1603 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
1604 nsCOMPtr<nsIPrincipal> principal;
1605 nsCOMPtr<nsIContentSecurityPolicy> csp;
1607 if (dragSession) {
1608 dragSession->DragEventDispatchedToChildProcess();
1609 dragSession->GetDragAction(&action);
1610 dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
1611 dragSession->GetCsp(getter_AddRefs(csp));
1612 RefPtr<DataTransfer> initialDataTransfer =
1613 dragSession->GetDataTransfer();
1614 if (initialDataTransfer) {
1615 dropEffect = initialDataTransfer->DropEffectInt();
1619 browserParent->SendRealDragEvent(*aEvent->AsDragEvent(), action,
1620 dropEffect, principal, csp);
1621 return;
1623 default: {
1624 MOZ_CRASH("Attempt to send non-whitelisted event?");
1629 bool EventStateManager::IsRemoteTarget(nsIContent* target) {
1630 return BrowserParent::GetFrom(target) || BrowserBridgeChild::GetFrom(target);
1633 bool EventStateManager::IsTopLevelRemoteTarget(nsIContent* target) {
1634 return !!BrowserParent::GetFrom(target);
1637 bool EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
1638 nsEventStatus* aStatus) {
1639 if (!aEvent->CanBeSentToRemoteProcess()) {
1640 return false;
1643 MOZ_ASSERT(!aEvent->HasBeenPostedToRemoteProcess(),
1644 "Why do we need to post same event to remote processes again?");
1646 // Collect the remote event targets we're going to forward this
1647 // event to.
1649 // NB: the elements of |remoteTargets| must be unique, for correctness.
1650 AutoTArray<RefPtr<BrowserParent>, 1> remoteTargets;
1651 if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) {
1652 // If this event only has one target, and it's remote, add it to
1653 // the array.
1654 nsIFrame* frame = aEvent->mMessage == eDragExit
1655 ? sLastDragOverFrame.GetFrame()
1656 : GetEventTarget();
1657 nsIContent* target = frame ? frame->GetContent() : nullptr;
1658 if (BrowserParent* remoteTarget = BrowserParent::GetFrom(target)) {
1659 remoteTargets.AppendElement(remoteTarget);
1661 } else {
1662 // This is a touch event with possibly multiple touch points.
1663 // Each touch point may have its own target. So iterate through
1664 // all of them and collect the unique set of targets for event
1665 // forwarding.
1667 // This loop is similar to the one used in
1668 // PresShell::DispatchTouchEvent().
1669 const WidgetTouchEvent::TouchArray& touches =
1670 aEvent->AsTouchEvent()->mTouches;
1671 for (uint32_t i = 0; i < touches.Length(); ++i) {
1672 Touch* touch = touches[i];
1673 // NB: the |mChanged| check is an optimization, subprocesses can
1674 // compute this for themselves. If the touch hasn't changed, we
1675 // may be able to avoid forwarding the event entirely (which is
1676 // not free).
1677 if (!touch || !touch->mChanged) {
1678 continue;
1680 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
1681 if (!targetPtr) {
1682 continue;
1684 nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr);
1685 BrowserParent* remoteTarget = BrowserParent::GetFrom(target);
1686 if (remoteTarget && !remoteTargets.Contains(remoteTarget)) {
1687 remoteTargets.AppendElement(remoteTarget);
1692 if (remoteTargets.Length() == 0) {
1693 return false;
1696 // Dispatch the event to the remote target.
1697 for (uint32_t i = 0; i < remoteTargets.Length(); ++i) {
1698 DispatchCrossProcessEvent(aEvent, remoteTargets[i], aStatus);
1700 return aEvent->HasBeenPostedToRemoteProcess();
1704 // CreateClickHoldTimer
1706 // Fire off a timer for determining if the user wants click-hold. This timer
1707 // is a one-shot that will be cancelled when the user moves enough to fire
1708 // a drag.
1710 void EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext,
1711 nsIFrame* inDownFrame,
1712 WidgetGUIEvent* inMouseDownEvent) {
1713 if (!inMouseDownEvent->IsTrusted() ||
1714 IsTopLevelRemoteTarget(mGestureDownContent) ||
1715 PointerLockManager::IsLocked()) {
1716 return;
1719 // just to be anal (er, safe)
1720 if (mClickHoldTimer) {
1721 mClickHoldTimer->Cancel();
1722 mClickHoldTimer = nullptr;
1725 // if content clicked on has a popup, don't even start the timer
1726 // since we'll end up conflicting and both will show.
1727 if (mGestureDownContent &&
1728 nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None,
1729 nsGkAtoms::popup)) {
1730 return;
1733 int32_t clickHoldDelay = StaticPrefs::ui_click_hold_context_menus_delay();
1734 NS_NewTimerWithFuncCallback(
1735 getter_AddRefs(mClickHoldTimer), sClickHoldCallback, this, clickHoldDelay,
1736 nsITimer::TYPE_ONE_SHOT, "EventStateManager::CreateClickHoldTimer");
1737 } // CreateClickHoldTimer
1740 // KillClickHoldTimer
1742 // Stop the timer that would show the context menu dead in its tracks
1744 void EventStateManager::KillClickHoldTimer() {
1745 if (mClickHoldTimer) {
1746 mClickHoldTimer->Cancel();
1747 mClickHoldTimer = nullptr;
1752 // sClickHoldCallback
1754 // This fires after the mouse has been down for a certain length of time.
1756 void EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM) {
1757 RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM);
1758 if (self) {
1759 self->FireContextClick();
1762 // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling
1763 // ClosePopup();
1765 } // sAutoHideCallback
1768 // FireContextClick
1770 // If we're this far, our timer has fired, which means the mouse has been down
1771 // for a certain period of time and has not moved enough to generate a
1772 // dragGesture. We can be certain the user wants a context-click at this stage,
1773 // so generate a dom event and fire it in.
1775 // After the event fires, check if PreventDefault() has been set on the event
1776 // which means that someone either ate the event or put up a context menu. This
1777 // is our cue to stop tracking the drag gesture. If we always did this,
1778 // draggable items w/out a context menu wouldn't be draggable after a certain
1779 // length of time, which is _not_ what we want.
1781 void EventStateManager::FireContextClick() {
1782 if (!mGestureDownContent || !mPresContext || PointerLockManager::IsLocked()) {
1783 return;
1786 #ifdef XP_MACOSX
1787 // Hack to ensure that we don't show a context menu when the user
1788 // let go of the mouse after a long cpu-hogging operation prevented
1789 // us from handling any OS events. See bug 117589.
1790 if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState,
1791 kCGMouseButtonLeft))
1792 return;
1793 #endif
1795 nsEventStatus status = nsEventStatus_eIgnore;
1797 // Dispatch to the DOM. We have to fake out the ESM and tell it that the
1798 // current target frame is actually where the mouseDown occurred, otherwise it
1799 // will use the frame the mouse is currently over which may or may not be
1800 // the same. (Note: saari and I have decided that we don't have to reset
1801 // |mCurrentTarget| when we're through because no one else is doing anything
1802 // more with this event and it will get reset on the very next event to the
1803 // correct frame).
1804 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent);
1805 // make sure the widget sticks around
1806 nsCOMPtr<nsIWidget> targetWidget;
1807 if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) {
1808 NS_ASSERTION(
1809 mPresContext == mCurrentTarget->PresContext(),
1810 "a prescontext returned a primary frame that didn't belong to it?");
1812 // before dispatching, check that we're not on something that
1813 // doesn't get a context menu
1814 bool allowedToDispatch = true;
1816 if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar,
1817 nsGkAtoms::scrollbarbutton,
1818 nsGkAtoms::button)) {
1819 allowedToDispatch = false;
1820 } else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) {
1821 // a <toolbarbutton> that has the container attribute set
1822 // will already have its own dropdown.
1823 if (nsContentUtils::HasNonEmptyAttr(
1824 mGestureDownContent, kNameSpaceID_None, nsGkAtoms::container)) {
1825 allowedToDispatch = false;
1826 } else {
1827 // If the toolbar button has an open menu, don't attempt to open
1828 // a second menu
1829 if (mGestureDownContent->IsElement() &&
1830 mGestureDownContent->AsElement()->AttrValueIs(
1831 kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true,
1832 eCaseMatters)) {
1833 allowedToDispatch = false;
1836 } else if (mGestureDownContent->IsHTMLElement()) {
1837 nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent));
1839 if (formCtrl) {
1840 allowedToDispatch =
1841 formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
1842 formCtrl->ControlType() == FormControlType::InputFile;
1843 } else if (mGestureDownContent->IsAnyOfHTMLElements(
1844 nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {
1845 allowedToDispatch = false;
1849 if (allowedToDispatch) {
1850 // init the event while mCurrentTarget is still good
1851 WidgetMouseEvent event(true, eContextMenu, targetWidget,
1852 WidgetMouseEvent::eReal);
1853 event.mClickCount = 1;
1854 FillInEventFromGestureDown(&event);
1856 // stop selection tracking, we're in control now
1857 if (mCurrentTarget) {
1858 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
1860 if (frameSel && frameSel->GetDragState()) {
1861 // note that this can cause selection changed events to fire if we're
1862 // in a text field, which will null out mCurrentTarget
1863 frameSel->SetDragState(false);
1867 AutoHandlingUserInputStatePusher userInpStatePusher(true, &event);
1869 // dispatch to DOM
1870 RefPtr<nsIContent> gestureDownContent = mGestureDownContent;
1871 RefPtr<nsPresContext> presContext = mPresContext;
1872 EventDispatcher::Dispatch(gestureDownContent, presContext, &event,
1873 nullptr, &status);
1875 // We don't need to dispatch to frame handling because no frames
1876 // watch eContextMenu except for nsMenuFrame and that's only for
1877 // dismissal. That's just as well since we don't really know
1878 // which frame to send it to.
1882 // now check if the event has been handled. If so, stop tracking a drag
1883 if (status == nsEventStatus_eConsumeNoDefault) {
1884 StopTrackingDragGesture(true);
1887 KillClickHoldTimer();
1889 } // FireContextClick
1892 // BeginTrackingDragGesture
1894 // Record that the mouse has gone down and that we should move to TRACKING state
1895 // of d&d gesture tracker.
1897 // We also use this to track click-hold context menus. When the mouse goes down,
1898 // fire off a short timer. If the timer goes off and we have yet to fire the
1899 // drag gesture (ie, the mouse hasn't moved a certain distance), then we can
1900 // assume the user wants a click-hold, so fire a context-click event. We only
1901 // want to cancel the drag gesture if the context-click event is handled.
1903 void EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext,
1904 WidgetMouseEvent* inDownEvent,
1905 nsIFrame* inDownFrame) {
1906 if (!inDownEvent->mWidget) {
1907 return;
1910 // Note that |inDownEvent| could be either a mouse down event or a
1911 // synthesized mouse move event.
1912 SetGestureDownPoint(inDownEvent);
1914 if (inDownFrame) {
1915 inDownFrame->GetContentForEvent(inDownEvent,
1916 getter_AddRefs(mGestureDownContent));
1918 mGestureDownFrameOwner = inDownFrame->GetContent();
1919 if (!mGestureDownFrameOwner) {
1920 mGestureDownFrameOwner = mGestureDownContent;
1923 mGestureModifiers = inDownEvent->mModifiers;
1924 mGestureDownButtons = inDownEvent->mButtons;
1926 if (inDownEvent->mMessage != eMouseTouchDrag &&
1927 StaticPrefs::ui_click_hold_context_menus()) {
1928 // fire off a timer to track click-hold
1929 CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
1933 void EventStateManager::SetGestureDownPoint(WidgetGUIEvent* aEvent) {
1934 mGestureDownPoint =
1935 GetEventRefPoint(aEvent) + aEvent->mWidget->WidgetToScreenOffset();
1938 LayoutDeviceIntPoint EventStateManager::GetEventRefPoint(
1939 WidgetEvent* aEvent) const {
1940 auto touchEvent = aEvent->AsTouchEvent();
1941 return (touchEvent && !touchEvent->mTouches.IsEmpty())
1942 ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
1943 : aEvent->mRefPoint;
1946 void EventStateManager::BeginTrackingRemoteDragGesture(
1947 nsIContent* aContent, RemoteDragStartData* aDragStartData) {
1948 mGestureDownContent = aContent;
1949 mGestureDownFrameOwner = aContent;
1950 mGestureDownInTextControl =
1951 aContent && aContent->IsInNativeAnonymousSubtree() &&
1952 TextControlElement::FromNodeOrNull(
1953 aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost());
1954 mGestureDownDragStartData = aDragStartData;
1958 // StopTrackingDragGesture
1960 // Record that the mouse has gone back up so that we should leave the TRACKING
1961 // state of d&d gesture tracker and return to the START state.
1963 void EventStateManager::StopTrackingDragGesture(bool aClearInChildProcesses) {
1964 mGestureDownContent = nullptr;
1965 mGestureDownFrameOwner = nullptr;
1966 mGestureDownInTextControl = false;
1967 mGestureDownDragStartData = nullptr;
1969 // If a content process starts a drag but the mouse is released before the
1970 // parent starts the actual drag, the content process will think a drag is
1971 // still happening. Inform any child processes with active drags that the drag
1972 // should be stopped.
1973 if (aClearInChildProcesses) {
1974 nsCOMPtr<nsIDragService> dragService =
1975 do_GetService("@mozilla.org/widget/dragservice;1");
1976 if (dragService) {
1977 nsCOMPtr<nsIDragSession> dragSession;
1978 dragService->GetCurrentSession(getter_AddRefs(dragSession));
1979 if (!dragSession) {
1980 // Only notify if there isn't a drag session active.
1981 dragService->RemoveAllChildProcesses();
1987 void EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent) {
1988 NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
1989 "Incorrect widget in event");
1991 // Set the coordinates in the new event to the coordinates of
1992 // the old event, adjusted for the fact that the widget might be
1993 // different
1994 aEvent->mRefPoint =
1995 mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
1996 aEvent->mModifiers = mGestureModifiers;
1997 aEvent->mButtons = mGestureDownButtons;
2000 void EventStateManager::MaybeFirePointerCancel(WidgetInputEvent* aEvent) {
2001 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
2002 AutoWeakFrame targetFrame = mCurrentTarget;
2004 if (!presShell || !targetFrame) {
2005 return;
2008 nsCOMPtr<nsIContent> content;
2009 targetFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
2010 if (!content) {
2011 return;
2014 nsEventStatus status = nsEventStatus_eIgnore;
2016 if (WidgetMouseEvent* aMouseEvent = aEvent->AsMouseEvent()) {
2017 WidgetPointerEvent event(*aMouseEvent);
2018 PointerEventHandler::InitPointerEventFromMouse(&event, aMouseEvent,
2019 ePointerCancel);
2021 event.convertToPointer = false;
2022 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2023 } else if (WidgetTouchEvent* aTouchEvent = aEvent->AsTouchEvent()) {
2024 WidgetPointerEvent event(aTouchEvent->IsTrusted(), ePointerCancel,
2025 aTouchEvent->mWidget);
2027 PointerEventHandler::InitPointerEventFromTouch(
2028 event, *aTouchEvent, *aTouchEvent->mTouches[0], true);
2030 event.convertToPointer = false;
2031 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2032 } else {
2033 MOZ_ASSERT(false);
2036 // HandleEventWithTarget clears out mCurrentTarget, which may be used in the
2037 // caller GenerateDragGesture. We have to restore mCurrentTarget.
2038 mCurrentTarget = targetFrame;
2041 bool EventStateManager::IsEventOutsideDragThreshold(
2042 WidgetInputEvent* aEvent) const {
2043 static int32_t sPixelThresholdX = 0;
2044 static int32_t sPixelThresholdY = 0;
2046 if (!sPixelThresholdX) {
2047 sPixelThresholdX =
2048 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdX, 0);
2049 sPixelThresholdY =
2050 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdY, 0);
2051 if (sPixelThresholdX <= 0) {
2052 sPixelThresholdX = 5;
2054 if (sPixelThresholdY <= 0) {
2055 sPixelThresholdY = 5;
2059 LayoutDeviceIntPoint pt =
2060 aEvent->mWidget->WidgetToScreenOffset() + GetEventRefPoint(aEvent);
2061 LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
2062 return Abs(distance.x) > sPixelThresholdX ||
2063 Abs(distance.y) > sPixelThresholdY;
2067 // GenerateDragGesture
2069 // If we're in the TRACKING state of the d&d gesture tracker, check the current
2070 // position of the mouse in relation to the old one. If we've moved a sufficient
2071 // amount from the mouse down, then fire off a drag gesture event.
2072 void EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
2073 WidgetInputEvent* aEvent) {
2074 NS_ASSERTION(aPresContext, "This shouldn't happen.");
2075 if (!IsTrackingDragGesture()) {
2076 return;
2079 AutoWeakFrame targetFrameBefore = mCurrentTarget;
2080 auto autoRestore = MakeScopeExit([&] { mCurrentTarget = targetFrameBefore; });
2081 mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
2083 if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
2084 StopTrackingDragGesture(true);
2085 return;
2088 // Check if selection is tracking drag gestures, if so
2089 // don't interfere!
2090 if (mCurrentTarget) {
2091 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
2092 if (frameSel && frameSel->GetDragState()) {
2093 StopTrackingDragGesture(true);
2094 return;
2098 // If non-native code is capturing the mouse don't start a drag.
2099 if (PresShell::IsMouseCapturePreventingDrag()) {
2100 StopTrackingDragGesture(true);
2101 return;
2104 if (!IsEventOutsideDragThreshold(aEvent)) {
2105 // To keep the old behavior, flush layout even if we don't start dnd.
2106 FlushLayout(aPresContext);
2107 return;
2110 if (StaticPrefs::ui_click_hold_context_menus()) {
2111 // stop the click-hold before we fire off the drag gesture, in case
2112 // it takes a long time
2113 KillClickHoldTimer();
2116 nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell();
2117 if (!docshell) {
2118 return;
2121 nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow();
2122 if (!window) return;
2124 RefPtr<DataTransfer> dataTransfer =
2125 new DataTransfer(window, eDragStart, false, -1);
2126 auto protectDataTransfer = MakeScopeExit([&] {
2127 if (dataTransfer) {
2128 dataTransfer->Disconnect();
2132 RefPtr<Selection> selection;
2133 RefPtr<RemoteDragStartData> remoteDragStartData;
2134 nsCOMPtr<nsIContent> eventContent, targetContent;
2135 nsCOMPtr<nsIPrincipal> principal;
2136 nsCOMPtr<nsIContentSecurityPolicy> csp;
2137 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2138 bool allowEmptyDataTransfer = false;
2139 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
2140 if (eventContent) {
2141 // If the content is a text node in a password field, we shouldn't
2142 // allow to drag its raw text. Note that we've supported drag from
2143 // password fields but dragging data was masked text. So, it doesn't
2144 // make sense anyway.
2145 if (eventContent->IsText() && eventContent->HasFlag(NS_MAYBE_MASKED)) {
2146 // However, it makes sense to allow to drag selected password text
2147 // when copying selected password is allowed because users may want
2148 // to use drag and drop rather than copy and paste when web apps
2149 // request to input password twice for conforming new password but
2150 // they used password generator.
2151 TextEditor* textEditor =
2152 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
2153 eventContent);
2154 if (!textEditor || !textEditor->IsCopyToClipboardAllowed()) {
2155 StopTrackingDragGesture(true);
2156 return;
2159 DetermineDragTargetAndDefaultData(
2160 window, eventContent, dataTransfer, &allowEmptyDataTransfer,
2161 getter_AddRefs(selection), getter_AddRefs(remoteDragStartData),
2162 getter_AddRefs(targetContent), getter_AddRefs(principal),
2163 getter_AddRefs(csp), getter_AddRefs(cookieJarSettings));
2166 // Stop tracking the drag gesture now. This should stop us from
2167 // reentering GenerateDragGesture inside DOM event processing.
2168 // Pass false to avoid clearing the child process state since a real
2169 // drag should be starting.
2170 StopTrackingDragGesture(false);
2172 if (!targetContent) return;
2174 // Use our targetContent, now that we've determined it, as the
2175 // parent object of the DataTransfer.
2176 nsCOMPtr<nsIContent> parentContent =
2177 targetContent->FindFirstNonChromeOnlyAccessContent();
2178 dataTransfer->SetParentObject(parentContent);
2180 sLastDragOverFrame = nullptr;
2181 nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
2183 // get the widget from the target frame
2184 WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
2185 startEvent.mFlags.mIsSynthesizedForTests =
2186 aEvent->mFlags.mIsSynthesizedForTests;
2187 FillInEventFromGestureDown(&startEvent);
2189 startEvent.mDataTransfer = dataTransfer;
2190 if (aEvent->AsMouseEvent()) {
2191 startEvent.mInputSource = aEvent->AsMouseEvent()->mInputSource;
2192 } else if (aEvent->AsTouchEvent()) {
2193 startEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
2194 } else {
2195 MOZ_ASSERT(false);
2198 // Dispatch to the DOM. By setting mCurrentTarget we are faking
2199 // out the ESM and telling it that the current target frame is
2200 // actually where the mouseDown occurred, otherwise it will use
2201 // the frame the mouse is currently over which may or may not be
2202 // the same.
2204 // Hold onto old target content through the event and reset after.
2205 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
2207 // Set the current target to the content for the mouse down
2208 mCurrentTargetContent = targetContent;
2210 // Dispatch the dragstart event to the DOM.
2211 nsEventStatus status = nsEventStatus_eIgnore;
2212 EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent, nullptr,
2213 &status);
2215 WidgetDragEvent* event = &startEvent;
2217 nsCOMPtr<nsIObserverService> observerService =
2218 mozilla::services::GetObserverService();
2219 // Emit observer event to allow addons to modify the DataTransfer
2220 // object.
2221 if (observerService) {
2222 observerService->NotifyObservers(dataTransfer, "on-datatransfer-available",
2223 nullptr);
2226 if (status != nsEventStatus_eConsumeNoDefault) {
2227 bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
2228 allowEmptyDataTransfer, targetContent,
2229 selection, remoteDragStartData,
2230 principal, csp, cookieJarSettings);
2231 if (dragStarted) {
2232 sActiveESM = nullptr;
2233 MaybeFirePointerCancel(aEvent);
2234 aEvent->StopPropagation();
2238 // Reset mCurretTargetContent to what it was
2239 mCurrentTargetContent = targetBeforeEvent;
2241 // Now flush all pending notifications, for better responsiveness
2242 // while dragging.
2243 FlushLayout(aPresContext);
2244 } // GenerateDragGesture
2246 void EventStateManager::DetermineDragTargetAndDefaultData(
2247 nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget,
2248 DataTransfer* aDataTransfer, bool* aAllowEmptyDataTransfer,
2249 Selection** aSelection, RemoteDragStartData** aRemoteDragStartData,
2250 nsIContent** aTargetNode, nsIPrincipal** aPrincipal,
2251 nsIContentSecurityPolicy** aCsp,
2252 nsICookieJarSettings** aCookieJarSettings) {
2253 *aTargetNode = nullptr;
2254 *aAllowEmptyDataTransfer = false;
2255 nsCOMPtr<nsIContent> dragDataNode;
2257 nsIContent* editingElement = aSelectionTarget->IsEditable()
2258 ? aSelectionTarget->GetEditingHost()
2259 : nullptr;
2261 // In chrome, only allow dragging inside editable areas.
2262 bool isChromeContext = !aWindow->GetBrowsingContext()->IsContent();
2263 if (isChromeContext && !editingElement) {
2264 if (mGestureDownDragStartData) {
2265 // A child process started a drag so use any data it assigned for the dnd
2266 // session.
2267 mGestureDownDragStartData->AddInitialDnDDataTo(aDataTransfer, aPrincipal,
2268 aCsp, aCookieJarSettings);
2269 mGestureDownDragStartData.forget(aRemoteDragStartData);
2270 *aAllowEmptyDataTransfer = true;
2272 } else {
2273 mGestureDownDragStartData = nullptr;
2275 // GetDragData determines if a selection, link or image in the content
2276 // should be dragged, and places the data associated with the drag in the
2277 // data transfer.
2278 // mGestureDownContent is the node where the mousedown event for the drag
2279 // occurred, and aSelectionTarget is the node to use when a selection is
2280 // used
2281 bool canDrag;
2282 bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
2283 nsresult rv = nsContentAreaDragDrop::GetDragData(
2284 aWindow, mGestureDownContent, aSelectionTarget, wasAlt, aDataTransfer,
2285 &canDrag, aSelection, getter_AddRefs(dragDataNode), aPrincipal, aCsp,
2286 aCookieJarSettings);
2287 if (NS_FAILED(rv) || !canDrag) {
2288 return;
2292 // if GetDragData returned a node, use that as the node being dragged.
2293 // Otherwise, if a selection is being dragged, use the node within the
2294 // selection that was dragged. Otherwise, just use the mousedown target.
2295 nsIContent* dragContent = mGestureDownContent;
2296 if (dragDataNode)
2297 dragContent = dragDataNode;
2298 else if (*aSelection)
2299 dragContent = aSelectionTarget;
2301 nsIContent* originalDragContent = dragContent;
2303 // If a selection isn't being dragged, look for an ancestor with the
2304 // draggable property set. If one is found, use that as the target of the
2305 // drag instead of the node that was clicked on. If a draggable node wasn't
2306 // found, just use the clicked node.
2307 if (!*aSelection) {
2308 while (dragContent) {
2309 if (auto htmlElement = nsGenericHTMLElement::FromNode(dragContent)) {
2310 if (htmlElement->Draggable()) {
2311 // We let draggable elements to trigger dnd even if there is no data
2312 // in the DataTransfer.
2313 *aAllowEmptyDataTransfer = true;
2314 break;
2316 } else {
2317 if (dragContent->IsXULElement()) {
2318 // All XUL elements are draggable, so if a XUL element is
2319 // encountered, stop looking for draggable nodes and just use the
2320 // original clicked node instead.
2321 // XXXndeakin
2322 // In the future, we will want to improve this so that XUL has a
2323 // better way to specify whether something is draggable than just
2324 // on/off.
2325 dragContent = mGestureDownContent;
2326 break;
2328 // otherwise, it's not an HTML or XUL element, so just keep looking
2330 dragContent = dragContent->GetFlattenedTreeParent();
2334 // if no node in the hierarchy was found to drag, but the GetDragData method
2335 // returned a node, use that returned node. Otherwise, nothing is draggable.
2336 if (!dragContent && dragDataNode) dragContent = dragDataNode;
2338 if (dragContent) {
2339 // if an ancestor node was used instead, clear the drag data
2340 // XXXndeakin rework this a bit. Find a way to just not call GetDragData if
2341 // we don't need to.
2342 if (dragContent != originalDragContent) aDataTransfer->ClearAll();
2343 *aTargetNode = dragContent;
2344 NS_ADDREF(*aTargetNode);
2348 bool EventStateManager::DoDefaultDragStart(
2349 nsPresContext* aPresContext, WidgetDragEvent* aDragEvent,
2350 DataTransfer* aDataTransfer, bool aAllowEmptyDataTransfer,
2351 nsIContent* aDragTarget, Selection* aSelection,
2352 RemoteDragStartData* aDragStartData, nsIPrincipal* aPrincipal,
2353 nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings) {
2354 nsCOMPtr<nsIDragService> dragService =
2355 do_GetService("@mozilla.org/widget/dragservice;1");
2356 if (!dragService) return false;
2358 // Default handling for the dragstart event.
2360 // First, check if a drag session already exists. This means that the drag
2361 // service was called directly within a draggesture handler. In this case,
2362 // don't do anything more, as it is assumed that the handler is managing
2363 // drag and drop manually. Make sure to return true to indicate that a drag
2364 // began. However, if we're handling drag session for synthesized events,
2365 // we need to initialize some information of the session. Therefore, we
2366 // need to keep going for synthesized case.
2367 nsCOMPtr<nsIDragSession> dragSession;
2368 dragService->GetCurrentSession(getter_AddRefs(dragSession));
2369 if (dragSession && !dragSession->IsSynthesizedForTests()) {
2370 return true;
2373 // No drag session is currently active, so check if a handler added
2374 // any items to be dragged. If not, there isn't anything to drag.
2375 uint32_t count = 0;
2376 if (aDataTransfer) {
2377 count = aDataTransfer->MozItemCount();
2379 if (!aAllowEmptyDataTransfer && !count) {
2380 return false;
2383 // Get the target being dragged, which may not be the same as the
2384 // target of the mouse event. If one wasn't set in the
2385 // aDataTransfer during the event handler, just use the original
2386 // target instead.
2387 nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget();
2388 if (!dragTarget) {
2389 dragTarget = aDragTarget;
2390 if (!dragTarget) {
2391 return false;
2395 // check which drag effect should initially be used. If the effect was not
2396 // set, just use all actions, otherwise Windows won't allow a drop.
2397 uint32_t action = aDataTransfer->EffectAllowedInt();
2398 if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
2399 action = nsIDragService::DRAGDROP_ACTION_COPY |
2400 nsIDragService::DRAGDROP_ACTION_MOVE |
2401 nsIDragService::DRAGDROP_ACTION_LINK;
2404 // get any custom drag image that was set
2405 int32_t imageX, imageY;
2406 RefPtr<Element> dragImage = aDataTransfer->GetDragImage(&imageX, &imageY);
2408 nsCOMPtr<nsIArray> transArray = aDataTransfer->GetTransferables(dragTarget);
2409 if (!transArray) {
2410 return false;
2413 RefPtr<DataTransfer> dataTransfer;
2414 if (!dragSession) {
2415 // After this function returns, the DataTransfer will be cleared so it
2416 // appears empty to content. We need to pass a DataTransfer into the Drag
2417 // Session, so we need to make a copy.
2418 aDataTransfer->Clone(aDragTarget, eDrop, aDataTransfer->MozUserCancelled(),
2419 false, getter_AddRefs(dataTransfer));
2421 // Copy over the drop effect, as Clone doesn't copy it for us.
2422 dataTransfer->SetDropEffectInt(aDataTransfer->DropEffectInt());
2423 } else {
2424 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
2425 MOZ_ASSERT(aDragEvent->mFlags.mIsSynthesizedForTests);
2426 // If we're initializing synthesized drag session, we should use given
2427 // DataTransfer as is because it'll be used with following drag events
2428 // in any tests, therefore it should be set to nsIDragSession.dataTransfer
2429 // because it and DragEvent.dataTransfer should be same instance.
2430 dataTransfer = aDataTransfer;
2433 // XXXndeakin don't really want to create a new drag DOM event
2434 // here, but we need something to pass to the InvokeDragSession
2435 // methods.
2436 RefPtr<DragEvent> event =
2437 NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
2439 // Use InvokeDragSessionWithSelection if a selection is being dragged,
2440 // such that the image can be generated from the selected text. However,
2441 // use InvokeDragSessionWithImage if a custom image was set or something
2442 // other than a selection is being dragged.
2443 if (!dragImage && aSelection) {
2444 dragService->InvokeDragSessionWithSelection(aSelection, aPrincipal, aCsp,
2445 aCookieJarSettings, transArray,
2446 action, event, dataTransfer);
2447 } else if (aDragStartData) {
2448 MOZ_ASSERT(XRE_IsParentProcess());
2449 dragService->InvokeDragSessionWithRemoteImage(
2450 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2451 aDragStartData, event, dataTransfer);
2452 } else {
2453 dragService->InvokeDragSessionWithImage(
2454 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2455 dragImage, imageX, imageY, event, dataTransfer);
2458 return true;
2461 void EventStateManager::ChangeZoom(bool aIncrease) {
2462 // Send the zoom change to the top level browser so it will be handled by the
2463 // front end in the same way as other zoom actions.
2464 nsIDocShell* docShell = mDocument->GetDocShell();
2465 if (!docShell) {
2466 return;
2469 BrowsingContext* bc = docShell->GetBrowsingContext();
2470 if (!bc) {
2471 return;
2474 if (XRE_IsParentProcess()) {
2475 bc->Canonical()->DispatchWheelZoomChange(aIncrease);
2476 } else if (BrowserChild* child = BrowserChild::GetFrom(docShell)) {
2477 child->SendWheelZoomChange(aIncrease);
2481 void EventStateManager::DoScrollHistory(int32_t direction) {
2482 nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
2483 if (pcContainer) {
2484 nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer));
2485 if (webNav) {
2486 // positive direction to go back one step, nonpositive to go forward
2487 // This is doing user-initiated history traversal, hence we want
2488 // to require that history entries we navigate to have user interaction.
2489 if (direction > 0)
2490 webNav->GoBack(StaticPrefs::browser_navigation_requireUserInteraction(),
2491 true);
2492 else
2493 webNav->GoForward(
2494 StaticPrefs::browser_navigation_requireUserInteraction(), true);
2499 void EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
2500 int32_t adjustment) {
2501 // Exclude content in chrome docshells.
2502 nsIContent* content = aTargetFrame->GetContent();
2503 if (content && !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) {
2504 // Positive adjustment to decrease zoom, negative to increase
2505 const bool increase = adjustment <= 0;
2506 EnsureDocument(mPresContext);
2507 ChangeZoom(increase);
2511 static nsIFrame* GetParentFrameToScroll(nsIFrame* aFrame) {
2512 if (!aFrame) return nullptr;
2514 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2515 nsLayoutUtils::IsReallyFixedPos(aFrame))
2516 return aFrame->PresContext()->GetPresShell()->GetRootScrollFrame();
2518 return aFrame->GetParent();
2521 void EventStateManager::DispatchLegacyMouseScrollEvents(
2522 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent, nsEventStatus* aStatus) {
2523 MOZ_ASSERT(aEvent);
2524 MOZ_ASSERT(aStatus);
2526 if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) {
2527 return;
2530 // Ignore mouse wheel transaction for computing legacy mouse wheel
2531 // events' delta value.
2532 // DOM event's delta vales are computed from CSS pixels.
2533 auto scrollAmountInCSSPixels =
2534 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
2536 // XXX We don't deal with fractional amount in legacy event, though the
2537 // default action handler (DoScrollText()) deals with it.
2538 // If we implemented such strict computation, we would need additional
2539 // accumulated delta values. It would made the code more complicated.
2540 // And also it would computes different delta values from older version.
2541 // It doesn't make sense to implement such code for legacy events and
2542 // rare cases.
2543 int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
2544 switch (aEvent->mDeltaMode) {
2545 case WheelEvent_Binding::DOM_DELTA_PAGE:
2546 scrollDeltaX = !aEvent->mLineOrPageDeltaX
2548 : (aEvent->mLineOrPageDeltaX > 0
2549 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2550 : UIEvent_Binding::SCROLL_PAGE_UP);
2551 scrollDeltaY = !aEvent->mLineOrPageDeltaY
2553 : (aEvent->mLineOrPageDeltaY > 0
2554 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2555 : UIEvent_Binding::SCROLL_PAGE_UP);
2556 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2557 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2558 break;
2560 case WheelEvent_Binding::DOM_DELTA_LINE:
2561 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2562 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2563 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2564 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2565 break;
2567 case WheelEvent_Binding::DOM_DELTA_PIXEL:
2568 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2569 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2570 pixelDeltaX = RoundDown(aEvent->mDeltaX);
2571 pixelDeltaY = RoundDown(aEvent->mDeltaY);
2572 break;
2574 default:
2575 MOZ_CRASH("Invalid deltaMode value comes");
2578 // Send the legacy events in following order:
2579 // 1. Vertical scroll
2580 // 2. Vertical pixel scroll (even if #1 isn't consumed)
2581 // 3. Horizontal scroll (even if #1 and/or #2 are consumed)
2582 // 4. Horizontal pixel scroll (even if #3 isn't consumed)
2584 AutoWeakFrame targetFrame(aTargetFrame);
2586 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
2587 !aEvent->DefaultPrevented(),
2588 "If you make legacy events dispatched for default prevented wheel "
2589 "event, you need to initialize stateX and stateY");
2590 EventState stateX, stateY;
2591 if (scrollDeltaY) {
2592 SendLineScrollEvent(aTargetFrame, aEvent, stateY, scrollDeltaY,
2593 DELTA_DIRECTION_Y);
2594 if (!targetFrame.IsAlive()) {
2595 *aStatus = nsEventStatus_eConsumeNoDefault;
2596 return;
2600 if (pixelDeltaY) {
2601 SendPixelScrollEvent(aTargetFrame, aEvent, stateY, pixelDeltaY,
2602 DELTA_DIRECTION_Y);
2603 if (!targetFrame.IsAlive()) {
2604 *aStatus = nsEventStatus_eConsumeNoDefault;
2605 return;
2609 if (scrollDeltaX) {
2610 SendLineScrollEvent(aTargetFrame, aEvent, stateX, scrollDeltaX,
2611 DELTA_DIRECTION_X);
2612 if (!targetFrame.IsAlive()) {
2613 *aStatus = nsEventStatus_eConsumeNoDefault;
2614 return;
2618 if (pixelDeltaX) {
2619 SendPixelScrollEvent(aTargetFrame, aEvent, stateX, pixelDeltaX,
2620 DELTA_DIRECTION_X);
2621 if (!targetFrame.IsAlive()) {
2622 *aStatus = nsEventStatus_eConsumeNoDefault;
2623 return;
2627 if (stateY.mDefaultPrevented) {
2628 *aStatus = nsEventStatus_eConsumeNoDefault;
2629 aEvent->PreventDefault(!stateY.mDefaultPreventedByContent);
2632 if (stateX.mDefaultPrevented) {
2633 *aStatus = nsEventStatus_eConsumeNoDefault;
2634 aEvent->PreventDefault(!stateX.mDefaultPreventedByContent);
2638 void EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
2639 WidgetWheelEvent* aEvent,
2640 EventState& aState, int32_t aDelta,
2641 DeltaDirection aDeltaDirection) {
2642 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2643 if (!targetContent) {
2644 targetContent = GetFocusedElement();
2645 if (!targetContent) {
2646 return;
2650 while (targetContent->IsText()) {
2651 targetContent = targetContent->GetFlattenedTreeParent();
2654 WidgetMouseScrollEvent event(aEvent->IsTrusted(),
2655 eLegacyMouseLineOrPageScroll, aEvent->mWidget);
2656 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2657 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2658 event.mRefPoint = aEvent->mRefPoint;
2659 event.mTimeStamp = aEvent->mTimeStamp;
2660 event.mModifiers = aEvent->mModifiers;
2661 event.mButtons = aEvent->mButtons;
2662 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2663 event.mDelta = aDelta;
2664 event.mInputSource = aEvent->mInputSource;
2666 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2667 nsEventStatus status = nsEventStatus_eIgnore;
2668 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2669 &status);
2670 aState.mDefaultPrevented =
2671 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2672 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2675 void EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
2676 WidgetWheelEvent* aEvent,
2677 EventState& aState,
2678 int32_t aPixelDelta,
2679 DeltaDirection aDeltaDirection) {
2680 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2681 if (!targetContent) {
2682 targetContent = GetFocusedElement();
2683 if (!targetContent) {
2684 return;
2688 while (targetContent->IsText()) {
2689 targetContent = targetContent->GetFlattenedTreeParent();
2692 WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMousePixelScroll,
2693 aEvent->mWidget);
2694 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2695 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2696 event.mRefPoint = aEvent->mRefPoint;
2697 event.mTimeStamp = aEvent->mTimeStamp;
2698 event.mModifiers = aEvent->mModifiers;
2699 event.mButtons = aEvent->mButtons;
2700 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2701 event.mDelta = aPixelDelta;
2702 event.mInputSource = aEvent->mInputSource;
2704 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2705 nsEventStatus status = nsEventStatus_eIgnore;
2706 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2707 &status);
2708 aState.mDefaultPrevented =
2709 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2710 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2713 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2714 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent,
2715 ComputeScrollTargetOptions aOptions) {
2716 return ComputeScrollTargetAndMayAdjustWheelEvent(
2717 aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY, aEvent, aOptions);
2720 // Overload ComputeScrollTargetAndMayAdjustWheelEvent method to allow passing
2721 // "test" dx and dy when looking for which scrollbarmediators to activate when
2722 // two finger down on trackpad and before any actual motion
2723 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2724 nsIFrame* aTargetFrame, double aDirectionX, double aDirectionY,
2725 WidgetWheelEvent* aEvent, ComputeScrollTargetOptions aOptions) {
2726 bool isAutoDir = false;
2727 bool honoursRoot = false;
2728 if (MAY_BE_ADJUSTED_BY_AUTO_DIR & aOptions) {
2729 // If the scroll is respected as auto-dir, aDirection* should always be
2730 // equivalent to the event's delta vlaues(Currently, there are only one case
2731 // where aDirection*s have different values from the widget wheel event's
2732 // original delta values and the only case isn't auto-dir, see
2733 // ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets).
2734 MOZ_ASSERT(aDirectionX == aEvent->mDeltaX &&
2735 aDirectionY == aEvent->mDeltaY);
2737 WheelDeltaAdjustmentStrategy strategy =
2738 GetWheelDeltaAdjustmentStrategy(*aEvent);
2739 switch (strategy) {
2740 case WheelDeltaAdjustmentStrategy::eAutoDir:
2741 isAutoDir = true;
2742 honoursRoot = false;
2743 break;
2744 case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
2745 isAutoDir = true;
2746 honoursRoot = true;
2747 break;
2748 default:
2749 break;
2753 if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
2754 // If the user recently scrolled with the mousewheel, then they probably
2755 // want to scroll the same view as before instead of the view under the
2756 // cursor. WheelTransaction tracks the frame currently being
2757 // scrolled with the mousewheel. We consider the transaction ended when the
2758 // mouse moves more than "mousewheel.transaction.ignoremovedelay"
2759 // milliseconds after the last scroll operation, or any time the mouse moves
2760 // out of the frame, or when more than "mousewheel.transaction.timeout"
2761 // milliseconds have passed after the last operation, even if the mouse
2762 // hasn't moved.
2763 nsIFrame* lastScrollFrame = WheelTransaction::GetScrollTargetFrame();
2764 if (lastScrollFrame) {
2765 nsIScrollableFrame* scrollableFrame =
2766 lastScrollFrame->GetScrollTargetFrame();
2767 if (scrollableFrame) {
2768 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
2769 MOZ_ASSERT(frameToScroll);
2770 if (isAutoDir) {
2771 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *lastScrollFrame,
2772 honoursRoot);
2773 // Note that calling this function will not always cause the delta to
2774 // be adjusted, it only adjusts the delta when it should, because
2775 // Adjust() internally calls ShouldBeAdjusted() before making
2776 // adjustment.
2777 adjuster.Adjust();
2779 return frameToScroll;
2784 // If the event doesn't cause scroll actually, we cannot find scroll target
2785 // because we check if the event can cause scroll actually on each found
2786 // scrollable frame.
2787 if (!aDirectionX && !aDirectionY) {
2788 return nullptr;
2791 bool checkIfScrollableX;
2792 bool checkIfScrollableY;
2793 if (isAutoDir) {
2794 // Always check the frame's scrollability in both the two directions for an
2795 // auto-dir scroll. That is, for an auto-dir scroll,
2796 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS and
2797 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS should be ignored.
2798 checkIfScrollableX = true;
2799 checkIfScrollableY = true;
2800 } else {
2801 checkIfScrollableX =
2802 aDirectionX &&
2803 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
2804 checkIfScrollableY =
2805 aDirectionY &&
2806 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
2809 nsIFrame* scrollFrame = !(aOptions & START_FROM_PARENT)
2810 ? aTargetFrame
2811 : GetParentFrameToScroll(aTargetFrame);
2812 for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
2813 // Check whether the frame wants to provide us with a scrollable view.
2814 nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
2815 if (!scrollableFrame) {
2816 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
2817 if (menuPopupFrame) {
2818 return nullptr;
2820 continue;
2823 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
2824 MOZ_ASSERT(frameToScroll);
2826 if (!checkIfScrollableX && !checkIfScrollableY) {
2827 return frameToScroll;
2830 // If the frame disregards the direction the user is trying to scroll, then
2831 // it should just bubbles the scroll event up to its parental scroll frame
2833 Maybe<layers::ScrollDirection> disregardedDirection =
2834 WheelHandlingUtils::GetDisregardedWheelScrollDirection(scrollFrame);
2835 if (disregardedDirection) {
2836 switch (disregardedDirection.ref()) {
2837 case layers::ScrollDirection::eHorizontal:
2838 if (checkIfScrollableX) {
2839 continue;
2841 break;
2842 case layers::ScrollDirection::eVertical:
2843 if (checkIfScrollableY) {
2844 continue;
2846 break;
2850 layers::ScrollDirections directions =
2851 scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
2852 if ((!(directions.contains(layers::ScrollDirection::eVertical)) &&
2853 !(directions.contains(layers::ScrollDirection::eHorizontal))) ||
2854 (checkIfScrollableY && !checkIfScrollableX &&
2855 !(directions.contains(layers::ScrollDirection::eVertical))) ||
2856 (checkIfScrollableX && !checkIfScrollableY &&
2857 !(directions.contains(layers::ScrollDirection::eHorizontal)))) {
2858 continue;
2861 // Computes whether the currently checked frame is scrollable by this wheel
2862 // event.
2863 bool canScroll = false;
2864 if (isAutoDir) {
2865 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *scrollFrame, honoursRoot);
2866 if (adjuster.ShouldBeAdjusted()) {
2867 adjuster.Adjust();
2868 canScroll = true;
2869 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
2870 aDirectionY)) {
2871 canScroll = true;
2873 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
2874 aDirectionY)) {
2875 canScroll = true;
2878 if (canScroll) {
2879 return frameToScroll;
2882 // Where we are at is the block ending in a for loop.
2883 // The current frame has been checked to be unscrollable by this wheel
2884 // event, continue the loop to check its parent, if any.
2887 nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(
2888 aTargetFrame->PresShell()->GetRootFrame());
2889 aOptions =
2890 static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
2891 if (!newFrame) {
2892 return nullptr;
2894 return ComputeScrollTargetAndMayAdjustWheelEvent(newFrame, aEvent, aOptions);
2897 nsSize EventStateManager::GetScrollAmount(
2898 nsPresContext* aPresContext, WidgetWheelEvent* aEvent,
2899 nsIScrollableFrame* aScrollableFrame) {
2900 MOZ_ASSERT(aPresContext);
2901 MOZ_ASSERT(aEvent);
2903 const bool isPage = aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PAGE;
2904 if (!aScrollableFrame) {
2905 // If there is no scrollable frame, we should use root, see below.
2906 aScrollableFrame =
2907 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
2910 if (aScrollableFrame) {
2911 return isPage ? aScrollableFrame->GetPageScrollAmount()
2912 : aScrollableFrame->GetLineScrollAmount();
2915 // If there is no scrollable frame and page scrolling, use viewport size.
2916 if (isPage) {
2917 return aPresContext->GetVisibleArea().Size();
2920 // Otherwise use root frame's font metrics.
2922 // FIXME(emilio): Should this use the root element's style frame? The root
2923 // frame will always have the initial font. Then again it should never matter
2924 // for content, we should always have a root scrollable frame in html
2925 // documents.
2926 nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
2927 if (!rootFrame) {
2928 return nsSize(0, 0);
2930 RefPtr<nsFontMetrics> fm =
2931 nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame);
2932 NS_ENSURE_TRUE(fm, nsSize(0, 0));
2933 return nsSize(fm->AveCharWidth(), fm->MaxHeight());
2936 void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
2937 WidgetWheelEvent* aEvent) {
2938 MOZ_ASSERT(aScrollableFrame);
2939 MOZ_ASSERT(aEvent);
2941 nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
2942 MOZ_ASSERT(scrollFrame);
2944 AutoWeakFrame scrollFrameWeak(scrollFrame);
2945 AutoWeakFrame eventFrameWeak(mCurrentTarget);
2946 if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak,
2947 eventFrameWeak)) {
2948 return;
2951 // Default action's actual scroll amount should be computed from device
2952 // pixels.
2953 nsPresContext* pc = scrollFrame->PresContext();
2954 nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame);
2955 nsIntSize scrollAmountInDevPixels(
2956 pc->AppUnitsToDevPixels(scrollAmount.width),
2957 pc->AppUnitsToDevPixels(scrollAmount.height));
2958 nsIntPoint actualDevPixelScrollAmount =
2959 DeltaAccumulator::GetInstance()->ComputeScrollAmountForDefaultAction(
2960 aEvent, scrollAmountInDevPixels);
2962 // Don't scroll around the axis whose overflow style is hidden.
2963 ScrollStyles overflowStyle = aScrollableFrame->GetScrollStyles();
2964 if (overflowStyle.mHorizontal == StyleOverflow::Hidden) {
2965 actualDevPixelScrollAmount.x = 0;
2967 if (overflowStyle.mVertical == StyleOverflow::Hidden) {
2968 actualDevPixelScrollAmount.y = 0;
2971 ScrollSnapFlags snapFlags = ScrollSnapFlags::Disabled;
2972 mozilla::ScrollOrigin origin = mozilla::ScrollOrigin::NotSpecified;
2973 switch (aEvent->mDeltaMode) {
2974 case WheelEvent_Binding::DOM_DELTA_LINE:
2975 origin = mozilla::ScrollOrigin::MouseWheel;
2976 snapFlags = ScrollSnapFlags::IntendedDirection;
2977 break;
2978 case WheelEvent_Binding::DOM_DELTA_PAGE:
2979 origin = mozilla::ScrollOrigin::Pages;
2980 snapFlags = ScrollSnapFlags::IntendedDirection |
2981 ScrollSnapFlags::IntendedEndPosition;
2982 break;
2983 case WheelEvent_Binding::DOM_DELTA_PIXEL:
2984 origin = mozilla::ScrollOrigin::Pixels;
2985 break;
2986 default:
2987 MOZ_CRASH("Invalid deltaMode value comes");
2990 // We shouldn't scroll more one page at once except when over one page scroll
2991 // is allowed for the event.
2992 nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
2993 nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
2994 pc->AppUnitsToDevPixels(pageSize.height));
2995 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
2996 DeprecatedAbs(actualDevPixelScrollAmount.x.value) >
2997 devPixelPageSize.width) {
2998 actualDevPixelScrollAmount.x = (actualDevPixelScrollAmount.x >= 0)
2999 ? devPixelPageSize.width
3000 : -devPixelPageSize.width;
3003 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
3004 DeprecatedAbs(actualDevPixelScrollAmount.y.value) >
3005 devPixelPageSize.height) {
3006 actualDevPixelScrollAmount.y = (actualDevPixelScrollAmount.y >= 0)
3007 ? devPixelPageSize.height
3008 : -devPixelPageSize.height;
3011 bool isDeltaModePixel =
3012 (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL);
3014 ScrollMode mode;
3015 switch (aEvent->mScrollType) {
3016 case WidgetWheelEvent::SCROLL_DEFAULT:
3017 if (isDeltaModePixel) {
3018 mode = ScrollMode::Normal;
3019 } else if (aEvent->mFlags.mHandledByAPZ) {
3020 mode = ScrollMode::SmoothMsd;
3021 } else {
3022 mode = ScrollMode::Smooth;
3024 break;
3025 case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
3026 mode = ScrollMode::Instant;
3027 break;
3028 case WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY:
3029 mode = ScrollMode::Normal;
3030 break;
3031 case WidgetWheelEvent::SCROLL_SMOOTHLY:
3032 mode = ScrollMode::Smooth;
3033 break;
3034 default:
3035 MOZ_CRASH("Invalid mScrollType value comes");
3038 nsIScrollableFrame::ScrollMomentum momentum =
3039 aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
3040 : nsIScrollableFrame::NOT_MOMENTUM;
3042 nsIntPoint overflow;
3043 aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
3044 ScrollUnit::DEVICE_PIXELS, mode, &overflow, origin,
3045 momentum, snapFlags);
3047 if (!scrollFrameWeak.IsAlive()) {
3048 // If the scroll causes changing the layout, we can think that the event
3049 // has been completely consumed by the content. Then, users probably don't
3050 // want additional action.
3051 aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0;
3052 } else if (isDeltaModePixel) {
3053 aEvent->mOverflowDeltaX = overflow.x;
3054 aEvent->mOverflowDeltaY = overflow.y;
3055 } else {
3056 aEvent->mOverflowDeltaX =
3057 static_cast<double>(overflow.x) / scrollAmountInDevPixels.width;
3058 aEvent->mOverflowDeltaY =
3059 static_cast<double>(overflow.y) / scrollAmountInDevPixels.height;
3062 // If CSS overflow properties caused not to scroll, the overflowDelta* values
3063 // should be same as delta* values since they may be used as gesture event by
3064 // widget. However, if there is another scrollable element in the ancestor
3065 // along the axis, probably users don't want the operation to cause
3066 // additional action such as moving history. In such case, overflowDelta
3067 // values should stay zero.
3068 if (scrollFrameWeak.IsAlive()) {
3069 if (aEvent->mDeltaX && overflowStyle.mHorizontal == StyleOverflow::Hidden &&
3070 !ComputeScrollTargetAndMayAdjustWheelEvent(
3071 scrollFrame, aEvent,
3072 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_WITH_AUTO_DIR)) {
3073 aEvent->mOverflowDeltaX = aEvent->mDeltaX;
3075 if (aEvent->mDeltaY && overflowStyle.mVertical == StyleOverflow::Hidden &&
3076 !ComputeScrollTargetAndMayAdjustWheelEvent(
3077 scrollFrame, aEvent,
3078 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_WITH_AUTO_DIR)) {
3079 aEvent->mOverflowDeltaY = aEvent->mDeltaY;
3083 NS_ASSERTION(
3084 aEvent->mOverflowDeltaX == 0 ||
3085 (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
3086 "The sign of mOverflowDeltaX is different from the scroll direction");
3087 NS_ASSERTION(
3088 aEvent->mOverflowDeltaY == 0 ||
3089 (aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0),
3090 "The sign of mOverflowDeltaY is different from the scroll direction");
3092 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent);
3095 void EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
3096 nsIFrame* targetFrame) {
3097 NS_ASSERTION(aEvent->mMessage == eGestureNotify,
3098 "DecideGestureEvent called with a non-gesture event");
3100 /* Check the ancestor tree to decide if any frame is willing* to receive
3101 * a MozPixelScroll event. If that's the case, the current touch gesture
3102 * will be used as a pan gesture; otherwise it will be a regular
3103 * mousedown/mousemove/click event.
3105 * *willing: determine if it makes sense to pan the element using scroll
3106 * events:
3107 * - For web content: if there are any visible scrollbars on the touch point
3108 * - For XUL: if it's an scrollable element that can currently scroll in some
3109 * direction.
3111 * Note: we'll have to one-off various cases to ensure a good usable behavior
3113 WidgetGestureNotifyEvent::PanDirection panDirection =
3114 WidgetGestureNotifyEvent::ePanNone;
3115 bool displayPanFeedback = false;
3116 for (nsIFrame* current = targetFrame; current;
3117 current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
3118 // e10s - mark remote content as pannable. This is a work around since
3119 // we don't have access to remote frame scroll info here. Apz data may
3120 // assist is solving this.
3121 if (current && IsTopLevelRemoteTarget(current->GetContent())) {
3122 panDirection = WidgetGestureNotifyEvent::ePanBoth;
3123 // We don't know when we reach bounds, so just disable feedback for now.
3124 displayPanFeedback = false;
3125 break;
3128 LayoutFrameType currentFrameType = current->Type();
3130 // Scrollbars should always be draggable
3131 if (currentFrameType == LayoutFrameType::Scrollbar) {
3132 panDirection = WidgetGestureNotifyEvent::ePanNone;
3133 break;
3136 // Special check for trees
3137 if (nsTreeBodyFrame* treeFrame = do_QueryFrame(current)) {
3138 if (treeFrame->GetHorizontalOverflow()) {
3139 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3141 if (treeFrame->GetVerticalOverflow()) {
3142 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3144 break;
3147 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(current)) {
3148 layers::ScrollDirections scrollbarVisibility =
3149 scrollableFrame->GetScrollbarVisibility();
3151 // Check if we have visible scrollbars
3152 if (scrollbarVisibility.contains(layers::ScrollDirection::eVertical)) {
3153 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3154 displayPanFeedback = true;
3155 break;
3158 if (scrollbarVisibility.contains(layers::ScrollDirection::eHorizontal)) {
3159 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3160 displayPanFeedback = true;
3162 } // scrollableFrame
3163 } // ancestor chain
3164 aEvent->mDisplayPanFeedback = displayPanFeedback;
3165 aEvent->mPanDirection = panDirection;
3168 #ifdef XP_MACOSX
3169 static nsINode* GetCrossDocParentNode(nsINode* aChild) {
3170 MOZ_ASSERT(aChild, "The child is null!");
3171 MOZ_ASSERT(XRE_IsParentProcess());
3173 nsINode* parent = aChild->GetParentNode();
3174 if (parent && parent->IsContent() && aChild->IsContent()) {
3175 parent = aChild->AsContent()->GetFlattenedTreeParent();
3178 if (parent || !aChild->IsDocument()) {
3179 return parent;
3182 return aChild->AsDocument()->GetEmbedderElement();
3185 static bool NodeAllowsClickThrough(nsINode* aNode) {
3186 while (aNode) {
3187 if (aNode->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::tree)) {
3188 return false;
3190 if (aNode->IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::resizer)) {
3191 return true;
3193 aNode = GetCrossDocParentNode(aNode);
3195 return true;
3197 #endif
3199 void EventStateManager::PostHandleKeyboardEvent(
3200 WidgetKeyboardEvent* aKeyboardEvent, nsIFrame* aTargetFrame,
3201 nsEventStatus& aStatus) {
3202 if (aStatus == nsEventStatus_eConsumeNoDefault) {
3203 return;
3206 RefPtr<nsPresContext> presContext = mPresContext;
3208 if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3209 if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
3210 RefPtr<BrowserParent> remote =
3211 aTargetFrame ? BrowserParent::GetFrom(aTargetFrame->GetContent())
3212 : nullptr;
3213 if (remote) {
3214 // remote is null-checked above in order to let pre-existing event
3215 // targeting code's chrome vs. content decision override in case of
3216 // disagreement in order not to disrupt non-Fission e10s mode in case
3217 // there are still bugs in the Fission-mode code. That is, if remote
3218 // is nullptr, the pre-existing event targeting code has deemed this
3219 // event to belong to chrome rather than content.
3220 BrowserParent* preciseRemote = BrowserParent::GetFocused();
3221 if (preciseRemote) {
3222 remote = preciseRemote;
3224 // else there was a race between layout and focus tracking
3226 if (remote && !remote->IsReadyToHandleInputEvents()) {
3227 // We need to dispatch the event to the browser element again if we were
3228 // waiting for the key reply but the event wasn't sent to the content
3229 // process due to the remote browser wasn't ready.
3230 WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
3231 aKeyboardEvent->MarkAsHandledInRemoteProcess();
3232 RefPtr<Element> ownerElement = remote->GetOwnerElement();
3233 EventDispatcher::Dispatch(ownerElement, presContext, &keyEvent);
3234 if (keyEvent.DefaultPrevented()) {
3235 aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
3236 aStatus = nsEventStatus_eConsumeNoDefault;
3237 return;
3241 // The widget expects a reply for every keyboard event. If the event wasn't
3242 // dispatched to a content process (non-e10s or no content process
3243 // running), we need to short-circuit here. Otherwise, we need to wait for
3244 // the content process to handle the event.
3245 if (aKeyboardEvent->mWidget) {
3246 aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
3248 if (aKeyboardEvent->DefaultPrevented()) {
3249 aStatus = nsEventStatus_eConsumeNoDefault;
3250 return;
3254 // XXX Currently, our automated tests don't support mKeyNameIndex.
3255 // Therefore, we still need to handle this with keyCode.
3256 switch (aKeyboardEvent->mKeyCode) {
3257 case NS_VK_TAB:
3258 case NS_VK_F6:
3259 // This is to prevent keyboard scrolling while alt modifier in use.
3260 if (!aKeyboardEvent->IsAlt()) {
3261 aStatus = nsEventStatus_eConsumeNoDefault;
3263 // Handling the tab event after it was sent to content is bad,
3264 // because to the FocusManager the remote-browser looks like one
3265 // element, so we would just move the focus to the next element
3266 // in chrome, instead of handling it in content.
3267 if (aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3268 break;
3271 EnsureDocument(presContext);
3272 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3273 if (fm && mDocument) {
3274 // Shift focus forward or back depending on shift key
3275 bool isDocMove = aKeyboardEvent->IsControl() ||
3276 aKeyboardEvent->mKeyCode == NS_VK_F6;
3277 uint32_t dir =
3278 aKeyboardEvent->IsShift()
3279 ? (isDocMove ? static_cast<uint32_t>(
3280 nsIFocusManager::MOVEFOCUS_BACKWARDDOC)
3281 : static_cast<uint32_t>(
3282 nsIFocusManager::MOVEFOCUS_BACKWARD))
3283 : (isDocMove ? static_cast<uint32_t>(
3284 nsIFocusManager::MOVEFOCUS_FORWARDDOC)
3285 : static_cast<uint32_t>(
3286 nsIFocusManager::MOVEFOCUS_FORWARD));
3287 RefPtr<Element> result;
3288 fm->MoveFocus(mDocument->GetWindow(), nullptr, dir,
3289 nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
3292 return;
3293 case 0:
3294 // We handle keys with no specific keycode value below.
3295 break;
3296 default:
3297 return;
3300 switch (aKeyboardEvent->mKeyNameIndex) {
3301 case KEY_NAME_INDEX_ZoomIn:
3302 case KEY_NAME_INDEX_ZoomOut:
3303 ChangeZoom(aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn);
3304 aStatus = nsEventStatus_eConsumeNoDefault;
3305 break;
3306 default:
3307 break;
3311 nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
3312 WidgetEvent* aEvent,
3313 nsIFrame* aTargetFrame,
3314 nsEventStatus* aStatus,
3315 nsIContent* aOverrideClickTarget) {
3316 NS_ENSURE_ARG(aPresContext);
3317 NS_ENSURE_ARG_POINTER(aStatus);
3319 mCurrentTarget = aTargetFrame;
3320 mCurrentTargetContent = nullptr;
3322 HandleCrossProcessEvent(aEvent, aStatus);
3323 // NOTE: the above call may have destroyed aTargetFrame, please use
3324 // mCurrentTarget henceforth. This is to avoid using it accidentally:
3325 aTargetFrame = nullptr;
3327 // Most of the events we handle below require a frame.
3328 // Add special cases here.
3329 if (!mCurrentTarget && aEvent->mMessage != eMouseUp &&
3330 aEvent->mMessage != eMouseDown && aEvent->mMessage != eDragEnter &&
3331 aEvent->mMessage != eDragOver && aEvent->mMessage != ePointerUp &&
3332 aEvent->mMessage != ePointerCancel) {
3333 return NS_OK;
3336 // Keep the prescontext alive, we might need it after event dispatch
3337 RefPtr<nsPresContext> presContext = aPresContext;
3338 nsresult ret = NS_OK;
3340 switch (aEvent->mMessage) {
3341 case eMouseDown: {
3342 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
3343 if (mouseEvent->mButton == MouseButton::ePrimary &&
3344 !sNormalLMouseEventInProcess) {
3345 // We got a mouseup event while a mousedown event was being processed.
3346 // Make sure that the capturing content is cleared.
3347 PresShell::ReleaseCapturingContent();
3348 break;
3351 // For remote content, capture the event in the parent process at the
3352 // <xul:browser remote> element. This will ensure that subsequent
3353 // mousemove/mouseup events will continue to be dispatched to this element
3354 // and therefore forwarded to the child.
3355 if (aEvent->HasBeenPostedToRemoteProcess() &&
3356 !PresShell::GetCapturingContent()) {
3357 if (nsIContent* content =
3358 mCurrentTarget ? mCurrentTarget->GetContent() : nullptr) {
3359 PresShell::SetCapturingContent(content, CaptureFlags::None, aEvent);
3360 } else {
3361 PresShell::ReleaseCapturingContent();
3365 // If MouseEvent::PreventClickEvent() was called by chrome script,
3366 // we need to forget the clicking content and click count for the
3367 // following eMouseUp event.
3368 if (mouseEvent->mClickEventPrevented) {
3369 RefPtr<EventStateManager> esm =
3370 ESMFromContentOrThis(aOverrideClickTarget);
3371 switch (mouseEvent->mButton) {
3372 case MouseButton::ePrimary:
3373 esm->mLastLeftMouseDownContent = nullptr;
3374 esm->mLClickCount = 0;
3375 break;
3376 case MouseButton::eSecondary:
3377 esm->mLastMiddleMouseDownContent = nullptr;
3378 esm->mMClickCount = 0;
3379 break;
3380 case MouseButton::eMiddle:
3381 esm->mLastRightMouseDownContent = nullptr;
3382 esm->mRClickCount = 0;
3383 break;
3384 default:
3385 break;
3389 nsCOMPtr<nsIContent> activeContent;
3390 // When content calls PreventDefault on pointerdown, we also call
3391 // PreventDefault on the subsequent mouse events to suppress default
3392 // behaviors. Normally, aStatus should be nsEventStatus_eConsumeNoDefault
3393 // when the event is DefaultPrevented but it's reset to
3394 // nsEventStatus_eIgnore in EventStateManager::PreHandleEvent. So we also
3395 // check if the event is DefaultPrevented.
3396 if (nsEventStatus_eConsumeNoDefault != *aStatus &&
3397 !aEvent->DefaultPrevented()) {
3398 nsCOMPtr<nsIContent> newFocus;
3399 bool suppressBlur = false;
3400 if (mCurrentTarget) {
3401 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
3402 activeContent = mCurrentTarget->GetContent();
3404 // In some cases, we do not want to even blur the current focused
3405 // element. Those cases are:
3406 // 1. -moz-user-focus CSS property is set to 'ignore';
3407 // 2. XUL control element has the disabled property set to 'true'.
3409 // We can't use nsIFrame::IsFocusable() because we want to blur when
3410 // we click on a visibility: none element.
3411 // We can't use nsIContent::IsFocusable() because we want to blur when
3412 // we click on a non-focusable element like a <div>.
3413 // We have to use |aEvent->mTarget| to not make sure we do not check
3414 // an anonymous node of the targeted element.
3415 suppressBlur =
3416 mCurrentTarget->StyleUI()->UserFocus() == StyleUserFocus::Ignore;
3418 if (!suppressBlur) {
3419 if (Element* element =
3420 Element::FromEventTargetOrNull(aEvent->mTarget)) {
3421 if (nsCOMPtr<nsIDOMXULControlElement> xulControl =
3422 element->AsXULControl()) {
3423 bool disabled = false;
3424 xulControl->GetDisabled(&disabled);
3425 suppressBlur = disabled;
3431 // When a root content which isn't editable but has an editable HTML
3432 // <body> element is clicked, we should redirect the focus to the
3433 // the <body> element. E.g., when an user click bottom of the editor
3434 // where is outside of the <body> element, the <body> should be focused
3435 // and the user can edit immediately after that.
3437 // NOTE: The newFocus isn't editable that also means it's not in
3438 // designMode. In designMode, all contents are not focusable.
3439 if (newFocus && !newFocus->IsEditable()) {
3440 Document* doc = newFocus->GetComposedDoc();
3441 if (doc && newFocus == doc->GetRootElement()) {
3442 nsIContent* bodyContent =
3443 nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
3444 if (bodyContent && bodyContent->GetPrimaryFrame()) {
3445 newFocus = bodyContent;
3450 // When the mouse is pressed, the default action is to focus the
3451 // target. Look for the nearest enclosing focusable frame.
3453 // TODO: Probably this should be moved to Element::PostHandleEvent.
3454 for (; newFocus; newFocus = newFocus->GetFlattenedTreeParent()) {
3455 if (!newFocus->IsElement()) {
3456 continue;
3459 nsIFrame* frame = newFocus->GetPrimaryFrame();
3460 if (!frame) {
3461 continue;
3464 // If the mousedown happened inside a popup, don't try to set focus on
3465 // one of its containing elements
3466 if (frame->IsMenuPopupFrame()) {
3467 newFocus = nullptr;
3468 break;
3471 if (frame->IsFocusable(/* aWithMouse = */ true)) {
3472 break;
3475 if (ShadowRoot* root = newFocus->GetShadowRoot()) {
3476 if (root->DelegatesFocus()) {
3477 if (Element* firstFocusable =
3478 root->GetFocusDelegate(/* aWithMouse */ true)) {
3479 newFocus = firstFocusable;
3480 break;
3486 MOZ_ASSERT_IF(newFocus, newFocus->IsElement());
3488 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3489 // if something was found to focus, focus it. Otherwise, if the
3490 // element that was clicked doesn't have -moz-user-focus: ignore,
3491 // clear the existing focus. For -moz-user-focus: ignore, the focus
3492 // is just left as is.
3493 // Another effect of mouse clicking, handled in Selection, is that
3494 // it should update the caret position to where the mouse was
3495 // clicked. Because the focus is cleared when clicking on a
3496 // non-focusable node, the next press of the tab key will cause
3497 // focus to be shifted from the caret position instead of the root.
3498 if (newFocus) {
3499 // use the mouse flag and the noscroll flag so that the content
3500 // doesn't unexpectedly scroll when clicking an element that is
3501 // only half visible
3502 uint32_t flags =
3503 nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
3504 // If this was a touch-generated event, pass that information:
3505 if (mouseEvent->mInputSource ==
3506 MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3507 flags |= nsIFocusManager::FLAG_BYTOUCH;
3509 fm->SetFocus(MOZ_KnownLive(newFocus->AsElement()), flags);
3510 } else if (!suppressBlur) {
3511 // clear the focus within the frame and then set it as the
3512 // focused frame
3513 EnsureDocument(mPresContext);
3514 if (mDocument) {
3515 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mDocument->GetWindow();
3516 #ifdef XP_MACOSX
3517 if (!activeContent || !activeContent->IsXULElement())
3518 #endif
3519 fm->ClearFocus(outerWindow);
3520 // Prevent switch frame if we're already not in the foreground tab
3521 // and we're in a content process.
3522 // TODO: If we were inactive frame in this tab, and now in
3523 // background tab, we shouldn't make the tab foreground, but
3524 // we should set focus to clicked document in the background
3525 // tab. However, nsFocusManager does not have proper method
3526 // for doing this. Therefore, we should skip setting focus
3527 // to clicked document for now.
3528 if (XRE_IsParentProcess() || IsInActiveTab(mDocument)) {
3529 fm->SetFocusedWindow(outerWindow);
3535 // The rest is left button-specific.
3536 if (mouseEvent->mButton != MouseButton::ePrimary) {
3537 break;
3540 // The nearest enclosing element goes into the :active state. If we're
3541 // not an element (so we're text or something) we need to obtain
3542 // our parent element and put it into :active instead.
3543 if (activeContent && !activeContent->IsElement()) {
3544 if (nsIContent* par = activeContent->GetFlattenedTreeParent()) {
3545 activeContent = par;
3548 } else {
3549 // if we're here, the event handler returned false, so stop
3550 // any of our own processing of a drag. Workaround for bug 43258.
3551 StopTrackingDragGesture(true);
3553 // XXX Why do we always set this is active? Active window may be changed
3554 // by a mousedown event listener.
3555 SetActiveManager(this, activeContent);
3556 } break;
3557 case ePointerCancel:
3558 case ePointerUp: {
3559 WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
3560 MOZ_ASSERT(pointerEvent);
3561 // Implicitly releasing capture for given pointer. ePointerLostCapture
3562 // should be send after ePointerUp or ePointerCancel.
3563 PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
3564 PointerEventHandler::UpdateActivePointerState(pointerEvent);
3566 if (pointerEvent->mMessage == ePointerCancel ||
3567 pointerEvent->mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3568 // After pointercancel, pointer becomes invalid so we can remove
3569 // relevant helper from table. Regarding pointerup with non-hoverable
3570 // device, the pointer also becomes invalid. Hoverable (mouse/pen)
3571 // pointers are valid all the time (not only between down/up).
3572 GenerateMouseEnterExit(pointerEvent);
3573 mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
3575 break;
3577 case eMouseUp: {
3578 // We can unconditionally stop capturing because
3579 // we should never be capturing when the mouse button is up
3580 PresShell::ReleaseCapturingContent();
3582 ClearGlobalActiveContent(this);
3583 WidgetMouseEvent* mouseUpEvent = aEvent->AsMouseEvent();
3584 if (mouseUpEvent && EventCausesClickEvents(*mouseUpEvent)) {
3585 // Make sure to dispatch the click even if there is no frame for
3586 // the current target element. This is required for Web compatibility.
3587 RefPtr<EventStateManager> esm =
3588 ESMFromContentOrThis(aOverrideClickTarget);
3589 ret =
3590 esm->PostHandleMouseUp(mouseUpEvent, aStatus, aOverrideClickTarget);
3593 if (PresShell* presShell = presContext->GetPresShell()) {
3594 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
3595 frameSelection->SetDragState(false);
3597 } break;
3598 case eWheelOperationEnd: {
3599 MOZ_ASSERT(aEvent->IsTrusted());
3600 ScrollbarsForWheel::MayInactivate();
3601 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3602 nsIScrollableFrame* scrollTarget =
3603 do_QueryFrame(ComputeScrollTargetAndMayAdjustWheelEvent(
3604 mCurrentTarget, wheelEvent,
3605 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR));
3606 if (scrollTarget) {
3607 scrollTarget->ScrollSnap();
3609 } break;
3610 case eWheel:
3611 case eWheelOperationStart: {
3612 MOZ_ASSERT(aEvent->IsTrusted());
3614 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
3615 ScrollbarsForWheel::Inactivate();
3616 break;
3619 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3620 MOZ_ASSERT(wheelEvent);
3622 // When APZ is enabled, the actual scroll animation might be handled by
3623 // the compositor.
3624 WheelPrefs::Action action =
3625 wheelEvent->mFlags.mHandledByAPZ
3626 ? WheelPrefs::ACTION_NONE
3627 : WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
3629 WheelDeltaAdjustmentStrategy strategy =
3630 GetWheelDeltaAdjustmentStrategy(*wheelEvent);
3631 // Adjust the delta values of the wheel event if the current default
3632 // action is to horizontalize scrolling. I.e., deltaY values are set to
3633 // deltaX and deltaY and deltaZ values are set to 0.
3634 // If horizontalized, the delta values will be restored and its overflow
3635 // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
3636 // being destroyed.
3637 WheelDeltaHorizontalizer horizontalizer(*wheelEvent);
3638 if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
3639 horizontalizer.Horizontalize();
3642 // Since ComputeScrollTargetAndMayAdjustWheelEvent() may adjust the delta
3643 // if the event is auto-dir. So we use |ESMAutoDirWheelDeltaRestorer|
3644 // here.
3645 // An instance of |ESMAutoDirWheelDeltaRestorer| is used to monitor
3646 // auto-dir adjustment which may happen during its lifetime. If the delta
3647 // values is adjusted during its lifetime, the instance will restore the
3648 // adjusted delta when it's being destrcuted.
3649 ESMAutoDirWheelDeltaRestorer restorer(*wheelEvent);
3650 nsIFrame* frameToScroll = ComputeScrollTargetAndMayAdjustWheelEvent(
3651 mCurrentTarget, wheelEvent,
3652 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR);
3654 switch (action) {
3655 case WheelPrefs::ACTION_SCROLL:
3656 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL: {
3657 // For scrolling of default action, we should honor the mouse wheel
3658 // transaction.
3660 ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget,
3661 wheelEvent);
3663 if (aEvent->mMessage != eWheel ||
3664 (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
3665 break;
3668 nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll);
3669 ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget);
3671 nsIFrame* rootScrollFrame =
3672 !mCurrentTarget
3673 ? nullptr
3674 : mCurrentTarget->PresShell()->GetRootScrollFrame();
3675 nsIScrollableFrame* rootScrollableFrame = nullptr;
3676 if (rootScrollFrame) {
3677 rootScrollableFrame = do_QueryFrame(rootScrollFrame);
3679 if (!scrollTarget || scrollTarget == rootScrollableFrame) {
3680 wheelEvent->mViewPortIsOverscrolled = true;
3682 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
3683 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
3684 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
3685 wheelEvent);
3686 if (scrollTarget) {
3687 DoScrollText(scrollTarget, wheelEvent);
3688 } else {
3689 WheelTransaction::EndTransaction();
3690 ScrollbarsForWheel::Inactivate();
3692 break;
3694 case WheelPrefs::ACTION_HISTORY: {
3695 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3696 // the direction is oblique, don't perform history back/forward.
3697 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3698 if (!intDelta) {
3699 break;
3701 DoScrollHistory(intDelta);
3702 break;
3704 case WheelPrefs::ACTION_ZOOM: {
3705 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3706 // the direction is oblique, don't perform zoom in/out.
3707 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3708 if (!intDelta) {
3709 break;
3711 DoScrollZoom(mCurrentTarget, intDelta);
3712 break;
3714 case WheelPrefs::ACTION_NONE:
3715 default:
3716 bool allDeltaOverflown = false;
3717 if (wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0) {
3718 if (frameToScroll) {
3719 WheelTransaction::WillHandleDefaultAction(
3720 wheelEvent, frameToScroll, mCurrentTarget);
3721 } else {
3722 WheelTransaction::EndTransaction();
3725 if (wheelEvent->mFlags.mHandledByAPZ) {
3726 if (wheelEvent->mCanTriggerSwipe) {
3727 // For events that can trigger swipes, APZ needs to know whether
3728 // scrolling is possible in the requested direction. It does this
3729 // by looking at the scroll overflow values on mCanTriggerSwipe
3730 // events after they have been processed.
3731 allDeltaOverflown = !ComputeScrollTarget(
3732 mCurrentTarget, wheelEvent, COMPUTE_DEFAULT_ACTION_TARGET);
3734 } else {
3735 // The event was processed neither by APZ nor by us, so all of the
3736 // delta values must be overflown delta values.
3737 allDeltaOverflown = true;
3740 if (!allDeltaOverflown) {
3741 break;
3743 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
3744 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
3745 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
3746 wheelEvent);
3747 wheelEvent->mViewPortIsOverscrolled = true;
3748 break;
3750 *aStatus = nsEventStatus_eConsumeNoDefault;
3751 } break;
3753 case eGestureNotify: {
3754 if (nsEventStatus_eConsumeNoDefault != *aStatus) {
3755 DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
3757 } break;
3759 case eDragEnter:
3760 case eDragOver: {
3761 NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event");
3763 // Check if the drag is occurring inside a scrollable area. If so, scroll
3764 // the area when the mouse is near the edges.
3765 if (mCurrentTarget && aEvent->mMessage == eDragOver) {
3766 nsIFrame* checkFrame = mCurrentTarget;
3767 while (checkFrame) {
3768 nsIScrollableFrame* scrollFrame = do_QueryFrame(checkFrame);
3769 // Break out so only the innermost scrollframe is scrolled.
3770 if (scrollFrame && scrollFrame->DragScroll(aEvent)) {
3771 break;
3773 checkFrame = checkFrame->GetParent();
3777 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
3778 if (!dragSession) break;
3780 // Reset the flag.
3781 dragSession->SetOnlyChromeDrop(false);
3782 if (mPresContext) {
3783 EnsureDocument(mPresContext);
3785 bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
3787 // the initial dataTransfer is the one from the dragstart event that
3788 // was set on the dragSession when the drag began.
3789 RefPtr<DataTransfer> dataTransfer;
3790 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
3792 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
3794 // collect any changes to moz cursor settings stored in the event's
3795 // data transfer.
3796 UpdateDragDataTransfer(dragEvent);
3798 // cancelling a dragenter or dragover event means that a drop should be
3799 // allowed, so update the dropEffect and the canDrop state to indicate
3800 // that a drag is allowed. If the event isn't cancelled, a drop won't be
3801 // allowed. Essentially, to allow a drop somewhere, specify the effects
3802 // using the effectAllowed and dropEffect properties in a dragenter or
3803 // dragover event and cancel the event. To not allow a drop somewhere,
3804 // don't cancel the event or set the effectAllowed or dropEffect to
3805 // "none". This way, if the event is just ignored, no drop will be
3806 // allowed.
3807 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
3808 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
3809 if (nsEventStatus_eConsumeNoDefault == *aStatus) {
3810 // If the event has initialized its mDataTransfer, use it.
3811 // Or the event has not been initialized its mDataTransfer, but
3812 // it's set before dispatch because of synthesized, but without
3813 // testing session (e.g., emulating drag from another app), use it
3814 // coming from outside.
3815 // XXX Perhaps, for the latter case, we need new API because we don't
3816 // have a chance to initialize allowed effects of the session.
3817 if (dragEvent->mDataTransfer) {
3818 // get the dataTransfer and the dropEffect that was set on it
3819 dataTransfer = dragEvent->mDataTransfer;
3820 dropEffect = dataTransfer->DropEffectInt();
3821 } else {
3822 // if dragEvent->mDataTransfer is null, it means that no attempt was
3823 // made to access the dataTransfer during the event, yet the event
3824 // was cancelled. Instead, use the initial data transfer available
3825 // from the drag session. The drop effect would not have been
3826 // initialized (which is done in DragEvent::GetDataTransfer),
3827 // so set it from the drag action. We'll still want to filter it
3828 // based on the effectAllowed below.
3829 dataTransfer = initialDataTransfer;
3831 dragSession->GetDragAction(&action);
3833 // filter the drop effect based on the action. Use UNINITIALIZED as
3834 // any effect is allowed.
3835 dropEffect = nsContentUtils::FilterDropEffect(
3836 action, nsIDragService::DRAGDROP_ACTION_UNINITIALIZED);
3839 // At this point, if the dataTransfer is null, it means that the
3840 // drag was originally started by directly calling the drag service.
3841 // Just assume that all effects are allowed.
3842 uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
3843 if (dataTransfer) {
3844 effectAllowed = dataTransfer->EffectAllowedInt();
3847 // set the drag action based on the drop effect and effect allowed.
3848 // The drop effect field on the drag transfer object specifies the
3849 // desired current drop effect. However, it cannot be used if the
3850 // effectAllowed state doesn't include that type of action. If the
3851 // dropEffect is "none", then the action will be 'none' so a drop will
3852 // not be allowed.
3853 if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED ||
3854 dropEffect & effectAllowed)
3855 action = dropEffect;
3857 if (action == nsIDragService::DRAGDROP_ACTION_NONE)
3858 dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
3860 // inform the drag session that a drop is allowed on this node.
3861 dragSession->SetDragAction(action);
3862 dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE);
3864 // For now, do this only for dragover.
3865 // XXXsmaug dragenter needs some more work.
3866 if (aEvent->mMessage == eDragOver && !isChromeDoc) {
3867 // Someone has called preventDefault(), check whether is was on
3868 // content or chrome.
3869 dragSession->SetOnlyChromeDrop(
3870 !dragEvent->mDefaultPreventedOnContent);
3872 } else if (aEvent->mMessage == eDragOver && !isChromeDoc) {
3873 // No one called preventDefault(), so handle drop only in chrome.
3874 dragSession->SetOnlyChromeDrop(true);
3876 if (ContentChild* child = ContentChild::GetSingleton()) {
3877 child->SendUpdateDropEffect(action, dropEffect);
3879 if (aEvent->HasBeenPostedToRemoteProcess()) {
3880 dragSession->SetCanDrop(true);
3881 } else if (initialDataTransfer) {
3882 // Now set the drop effect in the initial dataTransfer. This ensures
3883 // that we can get the desired drop effect in the drop event. For events
3884 // dispatched to content, the content process will take care of setting
3885 // this.
3886 initialDataTransfer->SetDropEffectInt(dropEffect);
3888 } break;
3890 case eDrop: {
3891 if (aEvent->mFlags.mIsSynthesizedForTests) {
3892 if (nsCOMPtr<nsIDragSession> dragSession =
3893 nsContentUtils::GetDragSession()) {
3894 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
3895 RefPtr<WindowContext> sourceWC;
3896 DebugOnly<nsresult> rvIgnored =
3897 dragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
3898 NS_WARNING_ASSERTION(
3899 NS_SUCCEEDED(rvIgnored),
3900 "nsIDragSession::GetSourceDocument() failed, but ignored");
3901 // If the drag source hasn't been initialized, i.e., dragstart was
3902 // consumed by the test, the test needs to dispatch "dragend" event
3903 // instead of the drag session. Therefore, it does not make sense
3904 // to set drag end point in such case (you hit assersion if you do
3905 // it).
3906 if (sourceWC) {
3907 CSSIntPoint dropPointInScreen =
3908 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
3909 .extract();
3910 dragSession->SetDragEndPointForTests(dropPointInScreen.x,
3911 dropPointInScreen.y);
3915 sLastDragOverFrame = nullptr;
3916 ClearGlobalActiveContent(this);
3917 break;
3919 case eDragExit:
3920 // make sure to fire the enter and exit_synth events after the
3921 // eDragExit event, otherwise we'll clean up too early
3922 GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
3923 if (ContentChild* child = ContentChild::GetSingleton()) {
3924 // SendUpdateDropEffect to prevent nsIDragService from waiting for
3925 // response of forwarded dragexit event.
3926 child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
3927 nsIDragService::DRAGDROP_ACTION_NONE);
3929 break;
3931 case eKeyUp:
3932 // If space key is released, we need to inactivate the element which was
3933 // activated by preceding space key down.
3934 // XXX Currently, we don't store the reason of activation. Therefore,
3935 // this may cancel what is activated by a mousedown, but it must not
3936 // cause actual problem in web apps in the wild since it must be
3937 // rare case that users release space key during a mouse click/drag.
3938 if (aEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
3939 ClearGlobalActiveContent(this);
3941 break;
3943 case eKeyPress: {
3944 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
3945 PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
3946 } break;
3948 case eMouseEnterIntoWidget:
3949 if (mCurrentTarget) {
3950 nsCOMPtr<nsIContent> targetContent;
3951 mCurrentTarget->GetContentForEvent(aEvent,
3952 getter_AddRefs(targetContent));
3953 SetContentState(targetContent, ElementState::HOVER);
3955 break;
3957 case eMouseExitFromWidget:
3958 PointerEventHandler::UpdateActivePointerState(aEvent->AsMouseEvent());
3959 break;
3961 #ifdef XP_MACOSX
3962 case eMouseActivate:
3963 if (mCurrentTarget) {
3964 nsCOMPtr<nsIContent> targetContent;
3965 mCurrentTarget->GetContentForEvent(aEvent,
3966 getter_AddRefs(targetContent));
3967 if (!NodeAllowsClickThrough(targetContent)) {
3968 *aStatus = nsEventStatus_eConsumeNoDefault;
3971 break;
3972 #endif
3974 default:
3975 break;
3978 // Reset target frame to null to avoid mistargeting after reentrant event
3979 mCurrentTarget = nullptr;
3980 mCurrentTargetContent = nullptr;
3982 return ret;
3985 BrowserParent* EventStateManager::GetCrossProcessTarget() {
3986 return IMEStateManager::GetActiveBrowserParent();
3989 bool EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent) {
3990 // Check to see if there is a focused, editable content in chrome,
3991 // in that case, do not forward IME events to content
3992 Element* focusedElement = GetFocusedElement();
3993 if (focusedElement && focusedElement->IsEditable()) {
3994 return false;
3996 return IMEStateManager::GetActiveBrowserParent() != nullptr;
3999 void EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) {
4000 RefPtr<nsPresContext> presContext = aPresContext;
4001 if (presContext) {
4002 IMEStateManager::OnDestroyPresContext(*presContext);
4005 // Bug 70855: Presentation is going away, possibly for a reframe.
4006 // Reset the hover state so that if we're recreating the presentation,
4007 // we won't have the old hover state still set in the new presentation,
4008 // as if the new presentation is resized, a new element may be hovered.
4009 ResetHoverState();
4011 mPointersEnterLeaveHelper.Clear();
4012 PointerEventHandler::NotifyDestroyPresContext(presContext);
4015 void EventStateManager::ResetHoverState() {
4016 if (mHoverContent) {
4017 SetContentState(nullptr, ElementState::HOVER);
4021 void EventStateManager::SetPresContext(nsPresContext* aPresContext) {
4022 mPresContext = aPresContext;
4025 void EventStateManager::ClearFrameRefs(nsIFrame* aFrame) {
4026 if (aFrame && aFrame == mCurrentTarget) {
4027 mCurrentTargetContent = aFrame->GetContent();
4031 struct CursorImage {
4032 gfx::IntPoint mHotspot;
4033 nsCOMPtr<imgIContainer> mContainer;
4034 ImageResolution mResolution;
4035 bool mEarlierCursorLoading = false;
4038 // Given the event that we're processing, and the computed cursor and hotspot,
4039 // determine whether the custom CSS cursor should be blocked (that is, not
4040 // honored).
4042 // We will not honor it all of the following are true:
4044 // * layout.cursor.block.enabled is true.
4045 // * the size of the custom cursor is bigger than layout.cursor.block.max-size.
4046 // * the bounds of the cursor would end up outside of the viewport of the
4047 // top-level content document.
4049 // This is done in order to prevent hijacking the cursor, see bug 1445844 and
4050 // co.
4051 static bool ShouldBlockCustomCursor(nsPresContext* aPresContext,
4052 WidgetEvent* aEvent,
4053 const CursorImage& aCursor) {
4054 if (!StaticPrefs::layout_cursor_block_enabled()) {
4055 return false;
4058 int32_t width = 0;
4059 int32_t height = 0;
4060 aCursor.mContainer->GetWidth(&width);
4061 aCursor.mContainer->GetHeight(&height);
4062 aCursor.mResolution.ApplyTo(width, height);
4064 int32_t maxSize = StaticPrefs::layout_cursor_block_max_size();
4066 if (width <= maxSize && height <= maxSize) {
4067 return false;
4070 auto input = DOMIntersectionObserver::ComputeInput(*aPresContext->Document(),
4071 nullptr, nullptr);
4073 if (!input.mRootFrame) {
4074 return false;
4077 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4078 aEvent, RelativeTo{input.mRootFrame});
4080 // The cursor size won't be affected by our full zoom in the parent process,
4081 // so undo that before checking the rect.
4082 float zoom = aPresContext->GetFullZoom();
4084 // Also adjust for accessibility cursor scaling factor.
4085 zoom /= LookAndFeel::GetFloat(LookAndFeel::FloatID::CursorScale, 1.0f);
4087 nsSize size(CSSPixel::ToAppUnits(width / zoom),
4088 CSSPixel::ToAppUnits(height / zoom));
4089 nsPoint hotspot(CSSPixel::ToAppUnits(aCursor.mHotspot.x / zoom),
4090 CSSPixel::ToAppUnits(aCursor.mHotspot.y / zoom));
4092 const nsRect cursorRect(point - hotspot, size);
4093 auto output = DOMIntersectionObserver::Intersect(input, cursorRect);
4094 return !output.mIntersectionRect ||
4095 !(*output.mIntersectionRect == cursorRect);
4098 static gfx::IntPoint ComputeHotspot(imgIContainer* aContainer,
4099 const Maybe<gfx::Point>& aHotspot) {
4100 MOZ_ASSERT(aContainer);
4102 // css3-ui says to use the CSS-specified hotspot if present,
4103 // otherwise use the intrinsic hotspot, otherwise use the top left
4104 // corner.
4105 if (aHotspot) {
4106 int32_t imgWidth, imgHeight;
4107 aContainer->GetWidth(&imgWidth);
4108 aContainer->GetHeight(&imgHeight);
4109 auto hotspot = gfx::IntPoint::Round(*aHotspot);
4110 return {std::max(std::min(hotspot.x.value, imgWidth - 1), 0),
4111 std::max(std::min(hotspot.y.value, imgHeight - 1), 0)};
4114 gfx::IntPoint hotspot;
4115 aContainer->GetHotspotX(&hotspot.x.value);
4116 aContainer->GetHotspotY(&hotspot.y.value);
4117 return hotspot;
4120 static CursorImage ComputeCustomCursor(nsPresContext* aPresContext,
4121 WidgetEvent* aEvent,
4122 const nsIFrame& aFrame,
4123 const nsIFrame::Cursor& aCursor) {
4124 if (aCursor.mAllowCustomCursor == nsIFrame::AllowCustomCursorImage::No) {
4125 return {};
4127 const ComputedStyle& style =
4128 aCursor.mStyle ? *aCursor.mStyle : *aFrame.Style();
4130 // If we are falling back because any cursor before us is loading, let the
4131 // consumer know.
4132 bool loading = false;
4133 for (const auto& image : style.StyleUI()->Cursor().images.AsSpan()) {
4134 MOZ_ASSERT(image.image.IsImageRequestType(),
4135 "Cursor image should only parse url() types");
4136 uint32_t status;
4137 imgRequestProxy* req = image.image.GetImageRequest();
4138 if (!req || NS_FAILED(req->GetImageStatus(&status))) {
4139 continue;
4141 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4142 loading = true;
4143 continue;
4145 if (status & imgIRequest::STATUS_ERROR) {
4146 continue;
4148 nsCOMPtr<imgIContainer> container;
4149 req->GetImage(getter_AddRefs(container));
4150 if (!container) {
4151 continue;
4153 StyleImageOrientation orientation =
4154 aFrame.StyleVisibility()->UsedImageOrientation(req);
4155 container = nsLayoutUtils::OrientImage(container, orientation);
4156 Maybe<gfx::Point> specifiedHotspot =
4157 image.has_hotspot ? Some(gfx::Point{image.hotspot_x, image.hotspot_y})
4158 : Nothing();
4159 gfx::IntPoint hotspot = ComputeHotspot(container, specifiedHotspot);
4160 CursorImage result{hotspot, std::move(container),
4161 image.image.GetResolution(), loading};
4162 if (ShouldBlockCustomCursor(aPresContext, aEvent, result)) {
4163 continue;
4165 // This is the one we want!
4166 return result;
4168 return {{}, nullptr, {}, loading};
4171 void EventStateManager::UpdateCursor(nsPresContext* aPresContext,
4172 WidgetEvent* aEvent,
4173 nsIFrame* aTargetFrame,
4174 nsEventStatus* aStatus) {
4175 if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
4176 return;
4179 auto cursor = StyleCursorKind::Default;
4180 nsCOMPtr<imgIContainer> container;
4181 ImageResolution resolution;
4182 Maybe<gfx::IntPoint> hotspot;
4184 // If cursor is locked just use the locked one
4185 if (mLockCursor != kInvalidCursorKind) {
4186 cursor = mLockCursor;
4188 // If not locked, look for correct cursor
4189 else if (aTargetFrame) {
4190 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4191 aEvent, RelativeTo{aTargetFrame});
4192 Maybe<nsIFrame::Cursor> framecursor = aTargetFrame->GetCursor(pt);
4193 // Avoid setting cursor when the mouse is over a windowless plugin.
4194 if (!framecursor) {
4195 if (XRE_IsContentProcess()) {
4196 mLastFrameConsumedSetCursor = true;
4198 return;
4200 // Make sure cursors get reset after the mouse leaves a
4201 // windowless plugin frame.
4202 if (mLastFrameConsumedSetCursor) {
4203 ClearCachedWidgetCursor(aTargetFrame);
4204 mLastFrameConsumedSetCursor = false;
4207 const CursorImage customCursor =
4208 ComputeCustomCursor(aPresContext, aEvent, *aTargetFrame, *framecursor);
4210 // If the current cursor is from the same frame, and it is now
4211 // loading some new image for the cursor, we should wait for a
4212 // while rather than taking its fallback cursor directly.
4213 if (customCursor.mEarlierCursorLoading &&
4214 gLastCursorSourceFrame == aTargetFrame &&
4215 TimeStamp::NowLoRes() - gLastCursorUpdateTime <
4216 TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
4217 return;
4219 cursor = framecursor->mCursor;
4220 container = std::move(customCursor.mContainer);
4221 resolution = customCursor.mResolution;
4222 hotspot = Some(customCursor.mHotspot);
4225 if (StaticPrefs::ui_use_activity_cursor()) {
4226 // Check whether or not to show the busy cursor
4227 nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell());
4228 if (!docShell) return;
4229 auto busyFlags = docShell->GetBusyFlags();
4231 // Show busy cursor everywhere before page loads
4232 // and just replace the arrow cursor after page starts loading
4233 if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY &&
4234 (cursor == StyleCursorKind::Auto ||
4235 cursor == StyleCursorKind::Default)) {
4236 cursor = StyleCursorKind::Progress;
4237 container = nullptr;
4241 if (aTargetFrame) {
4242 if (cursor == StyleCursorKind::Pointer && IsSelectingLink(aTargetFrame)) {
4243 cursor = aTargetFrame->GetWritingMode().IsVertical()
4244 ? StyleCursorKind::VerticalText
4245 : StyleCursorKind::Text;
4247 SetCursor(cursor, container, resolution, hotspot,
4248 aTargetFrame->GetNearestWidget(), false);
4249 gLastCursorSourceFrame = aTargetFrame;
4250 gLastCursorUpdateTime = TimeStamp::NowLoRes();
4253 if (mLockCursor != kInvalidCursorKind || StyleCursorKind::Auto != cursor) {
4254 *aStatus = nsEventStatus_eConsumeDoDefault;
4258 void EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame) {
4259 if (!aTargetFrame) {
4260 return;
4262 nsIWidget* aWidget = aTargetFrame->GetNearestWidget();
4263 if (!aWidget) {
4264 return;
4266 aWidget->ClearCachedCursor();
4269 nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
4270 imgIContainer* aContainer,
4271 const ImageResolution& aResolution,
4272 const Maybe<gfx::IntPoint>& aHotspot,
4273 nsIWidget* aWidget, bool aLockCursor) {
4274 EnsureDocument(mPresContext);
4275 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
4276 sMouseOverDocument = mDocument.get();
4278 NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
4279 if (aLockCursor) {
4280 if (StyleCursorKind::Auto != aCursor) {
4281 mLockCursor = aCursor;
4282 } else {
4283 // If cursor style is set to auto we unlock the cursor again.
4284 mLockCursor = kInvalidCursorKind;
4287 nsCursor c;
4288 switch (aCursor) {
4289 case StyleCursorKind::Auto:
4290 case StyleCursorKind::Default:
4291 c = eCursor_standard;
4292 break;
4293 case StyleCursorKind::Pointer:
4294 c = eCursor_hyperlink;
4295 break;
4296 case StyleCursorKind::Crosshair:
4297 c = eCursor_crosshair;
4298 break;
4299 case StyleCursorKind::Move:
4300 c = eCursor_move;
4301 break;
4302 case StyleCursorKind::Text:
4303 c = eCursor_select;
4304 break;
4305 case StyleCursorKind::Wait:
4306 c = eCursor_wait;
4307 break;
4308 case StyleCursorKind::Help:
4309 c = eCursor_help;
4310 break;
4311 case StyleCursorKind::NResize:
4312 c = eCursor_n_resize;
4313 break;
4314 case StyleCursorKind::SResize:
4315 c = eCursor_s_resize;
4316 break;
4317 case StyleCursorKind::WResize:
4318 c = eCursor_w_resize;
4319 break;
4320 case StyleCursorKind::EResize:
4321 c = eCursor_e_resize;
4322 break;
4323 case StyleCursorKind::NwResize:
4324 c = eCursor_nw_resize;
4325 break;
4326 case StyleCursorKind::SeResize:
4327 c = eCursor_se_resize;
4328 break;
4329 case StyleCursorKind::NeResize:
4330 c = eCursor_ne_resize;
4331 break;
4332 case StyleCursorKind::SwResize:
4333 c = eCursor_sw_resize;
4334 break;
4335 case StyleCursorKind::Copy: // CSS3
4336 c = eCursor_copy;
4337 break;
4338 case StyleCursorKind::Alias:
4339 c = eCursor_alias;
4340 break;
4341 case StyleCursorKind::ContextMenu:
4342 c = eCursor_context_menu;
4343 break;
4344 case StyleCursorKind::Cell:
4345 c = eCursor_cell;
4346 break;
4347 case StyleCursorKind::Grab:
4348 c = eCursor_grab;
4349 break;
4350 case StyleCursorKind::Grabbing:
4351 c = eCursor_grabbing;
4352 break;
4353 case StyleCursorKind::Progress:
4354 c = eCursor_spinning;
4355 break;
4356 case StyleCursorKind::ZoomIn:
4357 c = eCursor_zoom_in;
4358 break;
4359 case StyleCursorKind::ZoomOut:
4360 c = eCursor_zoom_out;
4361 break;
4362 case StyleCursorKind::NotAllowed:
4363 c = eCursor_not_allowed;
4364 break;
4365 case StyleCursorKind::ColResize:
4366 c = eCursor_col_resize;
4367 break;
4368 case StyleCursorKind::RowResize:
4369 c = eCursor_row_resize;
4370 break;
4371 case StyleCursorKind::NoDrop:
4372 c = eCursor_no_drop;
4373 break;
4374 case StyleCursorKind::VerticalText:
4375 c = eCursor_vertical_text;
4376 break;
4377 case StyleCursorKind::AllScroll:
4378 c = eCursor_all_scroll;
4379 break;
4380 case StyleCursorKind::NeswResize:
4381 c = eCursor_nesw_resize;
4382 break;
4383 case StyleCursorKind::NwseResize:
4384 c = eCursor_nwse_resize;
4385 break;
4386 case StyleCursorKind::NsResize:
4387 c = eCursor_ns_resize;
4388 break;
4389 case StyleCursorKind::EwResize:
4390 c = eCursor_ew_resize;
4391 break;
4392 case StyleCursorKind::None:
4393 c = eCursor_none;
4394 break;
4395 default:
4396 MOZ_ASSERT_UNREACHABLE("Unknown cursor kind");
4397 c = eCursor_standard;
4398 break;
4401 uint32_t x = aHotspot ? aHotspot->x.value : 0;
4402 uint32_t y = aHotspot ? aHotspot->y.value : 0;
4403 aWidget->SetCursor(nsIWidget::Cursor{c, aContainer, x, y, aResolution});
4404 return NS_OK;
4407 class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback {
4408 public:
4409 explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
4411 MOZ_CAN_RUN_SCRIPT
4412 void HandleEvent(EventChainPostVisitor& aVisitor) override {
4413 if (aVisitor.mPresContext) {
4414 nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget);
4415 if (frame) {
4416 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
4417 &aVisitor.mEventStatus);
4422 nsCOMPtr<nsIContent> mTarget;
4425 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
4426 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4427 EventTarget* aRelatedTarget) {
4428 WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
4429 UniquePtr<WidgetMouseEvent> newEvent;
4430 if (sourcePointer) {
4431 AUTO_PROFILER_LABEL("CreateMouseOrPointerWidgetEvent", OTHER);
4433 WidgetPointerEvent* newPointerEvent = new WidgetPointerEvent(
4434 aMouseEvent->IsTrusted(), aMessage, aMouseEvent->mWidget);
4435 newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary;
4436 newPointerEvent->mWidth = sourcePointer->mWidth;
4437 newPointerEvent->mHeight = sourcePointer->mHeight;
4438 newPointerEvent->mInputSource = sourcePointer->mInputSource;
4440 newEvent = WrapUnique(newPointerEvent);
4441 } else {
4442 newEvent = MakeUnique<WidgetMouseEvent>(aMouseEvent->IsTrusted(), aMessage,
4443 aMouseEvent->mWidget,
4444 WidgetMouseEvent::eReal);
4446 newEvent->mRelatedTarget = aRelatedTarget;
4447 newEvent->mRefPoint = aMouseEvent->mRefPoint;
4448 newEvent->mModifiers = aMouseEvent->mModifiers;
4449 newEvent->mButton = aMouseEvent->mButton;
4450 newEvent->mButtons = aMouseEvent->mButtons;
4451 newEvent->mPressure = aMouseEvent->mPressure;
4452 newEvent->mInputSource = aMouseEvent->mInputSource;
4453 newEvent->pointerId = aMouseEvent->pointerId;
4455 return newEvent;
4458 nsIFrame* EventStateManager::DispatchMouseOrPointerEvent(
4459 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4460 nsIContent* aTargetContent, nsIContent* aRelatedContent) {
4461 // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
4462 // "[When the mouse is locked on an element...e]vents that require the concept
4463 // of a mouse cursor must not be dispatched (for example: mouseover,
4464 // mouseout).
4465 if (PointerLockManager::IsLocked() &&
4466 (aMessage == eMouseLeave || aMessage == eMouseEnter ||
4467 aMessage == eMouseOver || aMessage == eMouseOut)) {
4468 mCurrentTargetContent = nullptr;
4469 nsCOMPtr<Element> pointerLockedElement =
4470 PointerLockManager::GetLockedElement();
4471 if (!pointerLockedElement) {
4472 NS_WARNING("Should have pointer locked element, but didn't.");
4473 return nullptr;
4475 return mPresContext->GetPrimaryFrameFor(pointerLockedElement);
4478 mCurrentTargetContent = nullptr;
4480 if (!aTargetContent) {
4481 return nullptr;
4484 nsCOMPtr<nsIContent> targetContent = aTargetContent;
4485 nsCOMPtr<nsIContent> relatedContent = aRelatedContent;
4487 UniquePtr<WidgetMouseEvent> dispatchEvent =
4488 CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, relatedContent);
4490 AutoWeakFrame previousTarget = mCurrentTarget;
4491 mCurrentTargetContent = targetContent;
4493 nsIFrame* targetFrame = nullptr;
4495 nsEventStatus status = nsEventStatus_eIgnore;
4496 ESMEventCB callback(targetContent);
4497 RefPtr<nsPresContext> presContext = mPresContext;
4498 EventDispatcher::Dispatch(targetContent, presContext, dispatchEvent.get(),
4499 nullptr, &status, &callback);
4501 if (mPresContext) {
4502 // Although the primary frame was checked in event callback, it may not be
4503 // the same object after event dispatch and handling, so refetch it.
4504 targetFrame = mPresContext->GetPrimaryFrameFor(targetContent);
4506 // If we are entering/leaving remote content, dispatch a mouse enter/exit
4507 // event to the remote frame.
4508 if (IsTopLevelRemoteTarget(targetContent)) {
4509 if (aMessage == eMouseOut) {
4510 // For remote content, send a puppet widget mouse exit event.
4511 UniquePtr<WidgetMouseEvent> remoteEvent =
4512 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
4513 relatedContent);
4514 remoteEvent->mExitFrom = Some(WidgetMouseEvent::ePuppet);
4516 // mCurrentTarget is set to the new target, so we must reset it to the
4517 // old target and then dispatch a cross-process event. (mCurrentTarget
4518 // will be set back below.) HandleCrossProcessEvent will query for the
4519 // proper target via GetEventTarget which will return mCurrentTarget.
4520 mCurrentTarget = targetFrame;
4521 HandleCrossProcessEvent(remoteEvent.get(), &status);
4522 } else if (aMessage == eMouseOver) {
4523 UniquePtr<WidgetMouseEvent> remoteEvent =
4524 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget,
4525 relatedContent);
4526 HandleCrossProcessEvent(remoteEvent.get(), &status);
4531 mCurrentTargetContent = nullptr;
4532 mCurrentTarget = previousTarget;
4534 return targetFrame;
4537 static nsIContent* FindCommonAncestor(nsIContent* aNode1, nsIContent* aNode2) {
4538 if (!aNode1 || !aNode2) {
4539 return nullptr;
4541 return nsContentUtils::GetCommonFlattenedTreeAncestor(aNode1, aNode2);
4544 class EnterLeaveDispatcher {
4545 public:
4546 EnterLeaveDispatcher(EventStateManager* aESM, nsIContent* aTarget,
4547 nsIContent* aRelatedTarget,
4548 WidgetMouseEvent* aMouseEvent,
4549 EventMessage aEventMessage)
4550 : mESM(aESM), mMouseEvent(aMouseEvent), mEventMessage(aEventMessage) {
4551 nsPIDOMWindowInner* win =
4552 aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr;
4553 if (aMouseEvent->AsPointerEvent()
4554 ? win && win->HasPointerEnterLeaveEventListeners()
4555 : win && win->HasMouseEnterLeaveEventListeners()) {
4556 mRelatedTarget =
4557 aRelatedTarget ? aRelatedTarget->FindFirstNonChromeOnlyAccessContent()
4558 : nullptr;
4559 nsINode* commonParent = FindCommonAncestor(aTarget, aRelatedTarget);
4560 nsIContent* current = aTarget;
4561 // Note, it is ok if commonParent is null!
4562 while (current && current != commonParent) {
4563 if (!current->ChromeOnlyAccess()) {
4564 mTargets.AppendObject(current);
4566 // mouseenter/leave is fired only on elements.
4567 current = current->GetFlattenedTreeParent();
4572 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
4573 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch() {
4574 if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
4575 for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
4576 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4577 MOZ_KnownLive(mTargets[i]),
4578 mRelatedTarget);
4580 } else {
4581 for (int32_t i = 0; i < mTargets.Count(); ++i) {
4582 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4583 MOZ_KnownLive(mTargets[i]),
4584 mRelatedTarget);
4589 // Nothing overwrites anything after constructor. Please remove MOZ_KnownLive
4590 // and MOZ_KNOWN_LIVE if anything marked as such becomes mutable.
4591 const RefPtr<EventStateManager> mESM;
4592 nsCOMArray<nsIContent> mTargets;
4593 MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRelatedTarget;
4594 WidgetMouseEvent* mMouseEvent;
4595 EventMessage mEventMessage;
4598 void EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
4599 nsIContent* aMovingInto) {
4600 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
4602 if (!wrapper || !wrapper->mLastOverElement) {
4603 return;
4605 // Before firing mouseout, check for recursion
4606 if (wrapper->mLastOverElement == wrapper->mFirstOutEventElement) {
4607 return;
4610 if (RefPtr<nsFrameLoaderOwner> flo =
4611 do_QueryObject(wrapper->mLastOverElement)) {
4612 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
4613 if (nsIDocShell* docshell = bc->GetDocShell()) {
4614 if (RefPtr<nsPresContext> presContext = docshell->GetPresContext()) {
4615 EventStateManager* kidESM = presContext->EventStateManager();
4616 // Not moving into any element in this subdocument
4617 kidESM->NotifyMouseOut(aMouseEvent, nullptr);
4622 // That could have caused DOM events which could wreak havoc. Reverify
4623 // things and be careful.
4624 if (!wrapper->mLastOverElement) {
4625 return;
4628 // Store the first mouseOut event we fire and don't refire mouseOut
4629 // to that element while the first mouseOut is still ongoing.
4630 wrapper->mFirstOutEventElement = wrapper->mLastOverElement;
4632 // Don't touch hover state if aMovingInto is non-null. Caller will update
4633 // hover state itself, and we have optimizations for hover switching between
4634 // two nearby elements both deep in the DOM tree that would be defeated by
4635 // switching the hover state to null here.
4636 bool isPointer = aMouseEvent->mClass == ePointerEventClass;
4637 if (!aMovingInto && !isPointer) {
4638 // Unset :hover
4639 SetContentState(nullptr, ElementState::HOVER);
4642 EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement,
4643 aMovingInto, aMouseEvent,
4644 isPointer ? ePointerLeave : eMouseLeave);
4646 // Fire mouseout
4647 nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
4648 DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut,
4649 lastOverElement, aMovingInto);
4650 leaveDispatcher.Dispatch();
4652 wrapper->mLastOverFrame = nullptr;
4653 wrapper->mLastOverElement = nullptr;
4655 // Turn recursion protection back off
4656 wrapper->mFirstOutEventElement = nullptr;
4659 void EventStateManager::RecomputeMouseEnterStateForRemoteFrame(
4660 Element& aElement) {
4661 if (!mMouseEnterLeaveHelper ||
4662 mMouseEnterLeaveHelper->mLastOverElement != &aElement) {
4663 return;
4666 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
4667 remote->MouseEnterIntoWidget();
4671 void EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
4672 nsIContent* aContent) {
4673 NS_ASSERTION(aContent, "Mouse must be over something");
4675 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
4677 if (!wrapper || wrapper->mLastOverElement == aContent) return;
4679 // Before firing mouseover, check for recursion
4680 if (aContent == wrapper->mFirstOverEventElement) return;
4682 // Check to see if we're a subdocument and if so update the parent
4683 // document's ESM state to indicate that the mouse is over the
4684 // content associated with our subdocument.
4685 EnsureDocument(mPresContext);
4686 if (Document* parentDoc = mDocument->GetInProcessParentDocument()) {
4687 if (nsCOMPtr<nsIContent> docContent = mDocument->GetEmbedderElement()) {
4688 if (PresShell* parentPresShell = parentDoc->GetPresShell()) {
4689 RefPtr<EventStateManager> parentESM =
4690 parentPresShell->GetPresContext()->EventStateManager();
4691 parentESM->NotifyMouseOver(aMouseEvent, docContent);
4695 // Firing the DOM event in the parent document could cause all kinds
4696 // of havoc. Reverify and take care.
4697 if (wrapper->mLastOverElement == aContent) return;
4699 // Remember mLastOverElement as the related content for the
4700 // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it,
4701 // bug 298477.
4702 nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
4704 bool isPointer = aMouseEvent->mClass == ePointerEventClass;
4706 EnterLeaveDispatcher enterDispatcher(this, aContent, lastOverElement,
4707 aMouseEvent,
4708 isPointer ? ePointerEnter : eMouseEnter);
4710 if (!isPointer) {
4711 SetContentState(aContent, ElementState::HOVER);
4714 NotifyMouseOut(aMouseEvent, aContent);
4716 // Store the first mouseOver event we fire and don't refire mouseOver
4717 // to that element while the first mouseOver is still ongoing.
4718 wrapper->mFirstOverEventElement = aContent;
4720 // Fire mouseover
4721 wrapper->mLastOverFrame = DispatchMouseOrPointerEvent(
4722 aMouseEvent, isPointer ? ePointerOver : eMouseOver, aContent,
4723 lastOverElement);
4724 enterDispatcher.Dispatch();
4725 wrapper->mLastOverElement = aContent;
4727 // Turn recursion protection back off
4728 wrapper->mFirstOverEventElement = nullptr;
4731 // Returns the center point of the window's client area. This is
4732 // in widget coordinates, i.e. relative to the widget's top-left
4733 // corner, not in screen coordinates, the same units that UIEvent::
4734 // refpoint is in. It may not be the exact center of the window if
4735 // the platform requires rounding the coordinate.
4736 static LayoutDeviceIntPoint GetWindowClientRectCenter(nsIWidget* aWidget) {
4737 NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0));
4739 LayoutDeviceIntRect rect = aWidget->GetClientBounds();
4740 LayoutDeviceIntPoint point(rect.x + rect.width / 2, rect.y + rect.height / 2);
4741 int32_t round = aWidget->RoundsWidgetCoordinatesTo();
4742 point.x = point.x / round * round;
4743 point.y = point.y / round * round;
4744 return point - aWidget->WidgetToScreenOffset();
4747 void EventStateManager::GeneratePointerEnterExit(EventMessage aMessage,
4748 WidgetMouseEvent* aEvent) {
4749 WidgetPointerEvent pointerEvent(*aEvent);
4750 pointerEvent.mMessage = aMessage;
4751 GenerateMouseEnterExit(&pointerEvent);
4754 /* static */
4755 void EventStateManager::UpdateLastRefPointOfMouseEvent(
4756 WidgetMouseEvent* aMouseEvent) {
4757 if (aMouseEvent->mMessage != eMouseMove &&
4758 aMouseEvent->mMessage != ePointerMove) {
4759 return;
4762 // Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
4763 // Movement is calculated in UIEvent::GetMovementPoint() as:
4764 // previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
4765 if (PointerLockManager::IsLocked() && aMouseEvent->mWidget) {
4766 // The pointer is locked. If the pointer is not located at the center of
4767 // the window, dispatch a synthetic mousemove to return the pointer there.
4768 // Doing this between "real" pointer moves gives the impression that the
4769 // (locked) pointer can continue moving and won't stop at the screen
4770 // boundary. We cancel the synthetic event so that we don't end up
4771 // dispatching the centering move event to content.
4772 aMouseEvent->mLastRefPoint =
4773 GetWindowClientRectCenter(aMouseEvent->mWidget);
4775 } else if (sLastRefPoint == kInvalidRefPoint) {
4776 // We don't have a valid previous mousemove mRefPoint. This is either
4777 // the first move we've encountered, or the mouse has just re-entered
4778 // the application window. We should report (0,0) movement for this
4779 // case, so make the current and previous mRefPoints the same.
4780 aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
4781 } else {
4782 aMouseEvent->mLastRefPoint = sLastRefPoint;
4786 /* static */
4787 void EventStateManager::ResetPointerToWindowCenterWhilePointerLocked(
4788 WidgetMouseEvent* aMouseEvent) {
4789 MOZ_ASSERT(PointerLockManager::IsLocked());
4790 if ((aMouseEvent->mMessage != eMouseMove &&
4791 aMouseEvent->mMessage != ePointerMove) ||
4792 !aMouseEvent->mWidget) {
4793 return;
4796 // We generate pointermove from mousemove event, so only synthesize native
4797 // mouse move and update sSynthCenteringPoint by mousemove event.
4798 bool updateSynthCenteringPoint = aMouseEvent->mMessage == eMouseMove;
4800 // The pointer is locked. If the pointer is not located at the center of
4801 // the window, dispatch a synthetic mousemove to return the pointer there.
4802 // Doing this between "real" pointer moves gives the impression that the
4803 // (locked) pointer can continue moving and won't stop at the screen
4804 // boundary. We cancel the synthetic event so that we don't end up
4805 // dispatching the centering move event to content.
4806 LayoutDeviceIntPoint center = GetWindowClientRectCenter(aMouseEvent->mWidget);
4808 if (aMouseEvent->mRefPoint != center && updateSynthCenteringPoint) {
4809 // Mouse move doesn't finish at the center of the window. Dispatch a
4810 // synthetic native mouse event to move the pointer back to the center
4811 // of the window, to faciliate more movement. But first, record that
4812 // we've dispatched a synthetic mouse movement, so we can cancel it
4813 // in the other branch here.
4814 sSynthCenteringPoint = center;
4815 // XXX Once we fix XXX comments in SetPointerLock about this API, we could
4816 // restrict that this API works only in the automation mode or in the
4817 // pointer locked situation.
4818 aMouseEvent->mWidget->SynthesizeNativeMouseMove(
4819 center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
4820 } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
4821 // This is the "synthetic native" event we dispatched to re-center the
4822 // pointer. Cancel it so we don't expose the centering move to content.
4823 aMouseEvent->StopPropagation();
4824 // Clear sSynthCenteringPoint so we don't cancel other events
4825 // targeted at the center.
4826 if (updateSynthCenteringPoint) {
4827 sSynthCenteringPoint = kInvalidRefPoint;
4832 /* static */
4833 void EventStateManager::UpdateLastPointerPosition(
4834 WidgetMouseEvent* aMouseEvent) {
4835 if (aMouseEvent->mMessage != eMouseMove) {
4836 return;
4838 sLastRefPoint = aMouseEvent->mRefPoint;
4841 void EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) {
4842 EnsureDocument(mPresContext);
4843 if (!mDocument) return;
4845 // Hold onto old target content through the event and reset after.
4846 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
4848 switch (aMouseEvent->mMessage) {
4849 case eMouseMove:
4850 case ePointerMove:
4851 case ePointerDown:
4852 case ePointerGotCapture: {
4853 // Get the target content target (mousemove target == mouseover target)
4854 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
4855 if (!targetElement) {
4856 // We're always over the document root, even if we're only
4857 // over dead space in a page (whose frame is not associated with
4858 // any content) or in print preview dead space
4859 targetElement = mDocument->GetRootElement();
4861 if (targetElement) {
4862 NotifyMouseOver(aMouseEvent, targetElement);
4864 } break;
4865 case ePointerUp: {
4866 // Get the target content target (mousemove target == mouseover target)
4867 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
4868 if (!targetElement) {
4869 // We're always over the document root, even if we're only
4870 // over dead space in a page (whose frame is not associated with
4871 // any content) or in print preview dead space
4872 targetElement = mDocument->GetRootElement();
4874 if (targetElement) {
4875 RefPtr<OverOutElementsWrapper> helper =
4876 GetWrapperByEventID(aMouseEvent);
4877 if (helper) {
4878 helper->mLastOverElement = targetElement;
4880 NotifyMouseOut(aMouseEvent, nullptr);
4882 } break;
4883 case ePointerLeave:
4884 case ePointerCancel:
4885 case eMouseExitFromWidget: {
4886 // This is actually the window mouse exit or pointer leave event. We're
4887 // not moving into any new element.
4889 RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
4890 if (helper && helper->mLastOverFrame &&
4891 nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) !=
4892 nsContentUtils::GetTopLevelWidget(
4893 helper->mLastOverFrame->GetNearestWidget())) {
4894 // the Mouse/PointerOut event widget doesn't have same top widget with
4895 // mLastOverFrame, it's a spurious event for mLastOverFrame
4896 break;
4899 // Reset sLastRefPoint, so that we'll know not to report any
4900 // movement the next time we re-enter the window.
4901 sLastRefPoint = kInvalidRefPoint;
4903 NotifyMouseOut(aMouseEvent, nullptr);
4904 } break;
4905 default:
4906 break;
4909 // reset mCurretTargetContent to what it was
4910 mCurrentTargetContent = targetBeforeEvent;
4913 OverOutElementsWrapper* EventStateManager::GetWrapperByEventID(
4914 WidgetMouseEvent* aEvent) {
4915 WidgetPointerEvent* pointer = aEvent->AsPointerEvent();
4916 if (!pointer) {
4917 MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr);
4918 if (!mMouseEnterLeaveHelper) {
4919 mMouseEnterLeaveHelper = new OverOutElementsWrapper();
4921 return mMouseEnterLeaveHelper;
4923 return mPointersEnterLeaveHelper.GetOrInsertNew(pointer->pointerId);
4926 /* static */
4927 void EventStateManager::SetPointerLock(nsIWidget* aWidget,
4928 nsIContent* aElement) {
4929 // Reset mouse wheel transaction
4930 WheelTransaction::EndTransaction();
4932 // Deal with DnD events
4933 nsCOMPtr<nsIDragService> dragService =
4934 do_GetService("@mozilla.org/widget/dragservice;1");
4936 if (PointerLockManager::IsLocked()) {
4937 MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
4939 // Release all pointer capture when a pointer lock is successfully applied
4940 // on an element.
4941 PointerEventHandler::ReleaseAllPointerCapture();
4943 // Store the last known ref point so we can reposition the pointer after
4944 // unlock.
4945 sPreLockPoint = sLastRefPoint;
4947 // Fire a synthetic mouse move to ensure event state is updated. We first
4948 // set the mouse to the center of the window, so that the mouse event
4949 // doesn't report any movement.
4950 // XXX Cannot we do synthesize the native mousemove in the parent process
4951 // with calling LockNativePointer below? Then, we could make this API
4952 // work only in the automation mode.
4953 sLastRefPoint = GetWindowClientRectCenter(aWidget);
4954 aWidget->SynthesizeNativeMouseMove(
4955 sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
4957 // Suppress DnD
4958 if (dragService) {
4959 dragService->Suppress();
4962 // Activate native pointer lock on platforms where it is required (Wayland)
4963 aWidget->LockNativePointer();
4964 } else {
4965 if (aWidget) {
4966 // Deactivate native pointer lock on platforms where it is required
4967 aWidget->UnlockNativePointer();
4970 // Unlocking, so return pointer to the original position by firing a
4971 // synthetic mouse event. We first reset sLastRefPoint to its
4972 // pre-pointerlock position, so that the synthetic mouse event reports
4973 // no movement.
4974 sLastRefPoint = sPreLockPoint;
4975 // Reset SynthCenteringPoint to invalid so that next time we start
4976 // locking pointer, it has its initial value.
4977 sSynthCenteringPoint = kInvalidRefPoint;
4978 if (aWidget) {
4979 // XXX Cannot we do synthesize the native mousemove in the parent process
4980 // with calling `UnlockNativePointer` above? Then, we could make this
4981 // API work only in the automation mode.
4982 aWidget->SynthesizeNativeMouseMove(
4983 sPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr);
4986 // Unsuppress DnD
4987 if (dragService) {
4988 dragService->Unsuppress();
4993 void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
4994 WidgetDragEvent* aDragEvent) {
4995 // Hold onto old target content through the event and reset after.
4996 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
4998 switch (aDragEvent->mMessage) {
4999 case eDragOver: {
5000 // when dragging from one frame to another, events are fired in the
5001 // order: dragexit, dragenter, dragleave
5002 if (sLastDragOverFrame != mCurrentTarget) {
5003 // We'll need the content, too, to check if it changed separately from
5004 // the frames.
5005 nsCOMPtr<nsIContent> lastContent;
5006 nsCOMPtr<nsIContent> targetContent;
5007 mCurrentTarget->GetContentForEvent(aDragEvent,
5008 getter_AddRefs(targetContent));
5009 if (targetContent && targetContent->IsText()) {
5010 targetContent = targetContent->GetFlattenedTreeParent();
5013 if (sLastDragOverFrame) {
5014 // The frame has changed but the content may not have. Check before
5015 // dispatching to content
5016 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5017 getter_AddRefs(lastContent));
5018 if (lastContent && lastContent->IsText()) {
5019 lastContent = lastContent->GetFlattenedTreeParent();
5022 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5023 FireDragEnterOrExit(presContext, aDragEvent, eDragExit, targetContent,
5024 lastContent, sLastDragOverFrame);
5025 nsIContent* target = sLastDragOverFrame
5026 ? sLastDragOverFrame.GetFrame()->GetContent()
5027 : nullptr;
5028 // XXXedgar, look like we need to consider fission OOP iframe, too.
5029 if (IsTopLevelRemoteTarget(target)) {
5030 // Dragging something and moving from web content to chrome only
5031 // fires dragexit and dragleave to xul:browser. We have to forward
5032 // dragexit to sLastDragOverFrame when its content is a remote
5033 // target. We don't forward dragleave since it's generated from
5034 // dragexit.
5035 WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
5036 aDragEvent->mWidget);
5037 remoteEvent.AssignDragEventData(*aDragEvent, true);
5038 remoteEvent.mFlags.mIsSynthesizedForTests =
5039 aDragEvent->mFlags.mIsSynthesizedForTests;
5040 nsEventStatus remoteStatus = nsEventStatus_eIgnore;
5041 HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
5045 AutoWeakFrame currentTraget = mCurrentTarget;
5046 FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, lastContent,
5047 targetContent, currentTraget);
5049 if (sLastDragOverFrame) {
5050 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5051 FireDragEnterOrExit(presContext, aDragEvent, eDragLeave,
5052 targetContent, lastContent, sLastDragOverFrame);
5055 sLastDragOverFrame = mCurrentTarget;
5057 } break;
5059 case eDragExit: {
5060 // This is actually the window mouse exit event.
5061 if (sLastDragOverFrame) {
5062 nsCOMPtr<nsIContent> lastContent;
5063 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5064 getter_AddRefs(lastContent));
5066 RefPtr<nsPresContext> lastDragOverFramePresContext =
5067 sLastDragOverFrame->PresContext();
5068 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent, eDragExit,
5069 nullptr, lastContent, sLastDragOverFrame);
5070 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent,
5071 eDragLeave, nullptr, lastContent,
5072 sLastDragOverFrame);
5074 sLastDragOverFrame = nullptr;
5076 } break;
5078 default:
5079 break;
5082 // reset mCurretTargetContent to what it was
5083 mCurrentTargetContent = targetBeforeEvent;
5085 // Now flush all pending notifications, for better responsiveness.
5086 FlushLayout(aPresContext);
5089 void EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
5090 WidgetDragEvent* aDragEvent,
5091 EventMessage aMessage,
5092 nsIContent* aRelatedTarget,
5093 nsIContent* aTargetContent,
5094 AutoWeakFrame& aTargetFrame) {
5095 MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
5096 aMessage == eDragEnter);
5097 nsEventStatus status = nsEventStatus_eIgnore;
5098 WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
5099 event.AssignDragEventData(*aDragEvent, false);
5100 event.mFlags.mIsSynthesizedForTests =
5101 aDragEvent->mFlags.mIsSynthesizedForTests;
5102 event.mRelatedTarget = aRelatedTarget;
5103 if (aMessage == eDragExit && !StaticPrefs::dom_event_dragexit_enabled()) {
5104 event.mFlags.mOnlyChromeDispatch = true;
5107 mCurrentTargetContent = aTargetContent;
5109 if (aTargetContent != aRelatedTarget) {
5110 // XXX This event should still go somewhere!!
5111 if (aTargetContent) {
5112 EventDispatcher::Dispatch(aTargetContent, aPresContext, &event, nullptr,
5113 &status);
5116 // adjust the drag hover if the dragenter event was cancelled or this is a
5117 // drag exit
5118 if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) {
5119 SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr,
5120 ElementState::DRAGOVER);
5123 // collect any changes to moz cursor settings stored in the event's
5124 // data transfer.
5125 UpdateDragDataTransfer(&event);
5128 // Finally dispatch the event to the frame
5129 if (aTargetFrame) {
5130 aTargetFrame->HandleEvent(aPresContext, &event, &status);
5134 void EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent) {
5135 NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!");
5136 if (!dragEvent->mDataTransfer) {
5137 return;
5140 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5142 if (dragSession) {
5143 // the initial dataTransfer is the one from the dragstart event that
5144 // was set on the dragSession when the drag began.
5145 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
5146 if (initialDataTransfer) {
5147 // retrieve the current moz cursor setting and save it.
5148 nsAutoString mozCursor;
5149 dragEvent->mDataTransfer->GetMozCursor(mozCursor);
5150 initialDataTransfer->SetMozCursor(mozCursor);
5155 nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
5156 nsEventStatus* aStatus,
5157 nsIContent* aOverrideClickTarget) {
5158 nsCOMPtr<nsIContent> mouseContent = aOverrideClickTarget;
5159 if (!mouseContent && mCurrentTarget) {
5160 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
5162 if (mouseContent && mouseContent->IsText()) {
5163 nsINode* parent = mouseContent->GetFlattenedTreeParentNode();
5164 if (parent && parent->IsContent()) {
5165 mouseContent = parent->AsContent();
5169 switch (aEvent->mButton) {
5170 case MouseButton::ePrimary:
5171 if (aEvent->mMessage == eMouseDown) {
5172 mLastLeftMouseDownContent =
5173 !aEvent->mClickEventPrevented ? mouseContent : nullptr;
5174 } else if (aEvent->mMessage == eMouseUp) {
5175 aEvent->mClickTarget =
5176 !aEvent->mClickEventPrevented
5177 ? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
5178 mouseContent, mLastLeftMouseDownContent)
5179 : nullptr;
5180 if (aEvent->mClickTarget) {
5181 aEvent->mClickCount = mLClickCount;
5182 mLClickCount = 0;
5183 } else {
5184 aEvent->mClickCount = 0;
5186 mLastLeftMouseDownContent = nullptr;
5188 break;
5190 case MouseButton::eMiddle:
5191 if (aEvent->mMessage == eMouseDown) {
5192 mLastMiddleMouseDownContent =
5193 !aEvent->mClickEventPrevented ? mouseContent : nullptr;
5194 } else if (aEvent->mMessage == eMouseUp) {
5195 aEvent->mClickTarget =
5196 !aEvent->mClickEventPrevented
5197 ? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
5198 mouseContent, mLastMiddleMouseDownContent)
5199 : nullptr;
5200 if (aEvent->mClickTarget) {
5201 aEvent->mClickCount = mMClickCount;
5202 mMClickCount = 0;
5203 } else {
5204 aEvent->mClickCount = 0;
5206 mLastMiddleMouseDownContent = nullptr;
5208 break;
5210 case MouseButton::eSecondary:
5211 if (aEvent->mMessage == eMouseDown) {
5212 mLastRightMouseDownContent =
5213 !aEvent->mClickEventPrevented ? mouseContent : nullptr;
5214 } else if (aEvent->mMessage == eMouseUp) {
5215 aEvent->mClickTarget =
5216 !aEvent->mClickEventPrevented
5217 ? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
5218 mouseContent, mLastRightMouseDownContent)
5219 : nullptr;
5220 if (aEvent->mClickTarget) {
5221 aEvent->mClickCount = mRClickCount;
5222 mRClickCount = 0;
5223 } else {
5224 aEvent->mClickCount = 0;
5226 mLastRightMouseDownContent = nullptr;
5228 break;
5231 return NS_OK;
5234 // static
5235 bool EventStateManager::EventCausesClickEvents(
5236 const WidgetMouseEvent& aMouseEvent) {
5237 if (NS_WARN_IF(aMouseEvent.mMessage != eMouseUp)) {
5238 return false;
5240 // If the mouseup event is synthesized event, we don't need to dispatch
5241 // click events.
5242 if (!aMouseEvent.IsReal()) {
5243 return false;
5245 // If mouse is still over same element, clickcount will be > 1.
5246 // If it has moved it will be zero, so no click.
5247 if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
5248 return false;
5250 // If click event was explicitly prevented, we shouldn't dispatch it.
5251 if (aMouseEvent.mClickEventPrevented) {
5252 return false;
5254 // Check that the window isn't disabled before firing a click
5255 // (see bug 366544).
5256 return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
5259 nsresult EventStateManager::InitAndDispatchClickEvent(
5260 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5261 EventMessage aMessage, PresShell* aPresShell, nsIContent* aMouseUpContent,
5262 AutoWeakFrame aCurrentTarget, bool aNoContentDispatch,
5263 nsIContent* aOverrideClickTarget) {
5264 MOZ_ASSERT(aMouseUpEvent);
5265 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5266 MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget);
5268 WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage,
5269 aMouseUpEvent->mWidget, WidgetMouseEvent::eReal);
5271 event.mRefPoint = aMouseUpEvent->mRefPoint;
5272 event.mClickCount = aMouseUpEvent->mClickCount;
5273 event.mModifiers = aMouseUpEvent->mModifiers;
5274 event.mButtons = aMouseUpEvent->mButtons;
5275 event.mTimeStamp = aMouseUpEvent->mTimeStamp;
5276 event.mFlags.mOnlyChromeDispatch =
5277 aNoContentDispatch && !aMouseUpEvent->mUseLegacyNonPrimaryDispatch;
5278 event.mFlags.mNoContentDispatch = aNoContentDispatch;
5279 event.mButton = aMouseUpEvent->mButton;
5280 event.pointerId = aMouseUpEvent->pointerId;
5281 event.mInputSource = aMouseUpEvent->mInputSource;
5282 nsIContent* target = aMouseUpContent;
5283 nsIFrame* targetFrame = aCurrentTarget;
5284 if (aOverrideClickTarget) {
5285 target = aOverrideClickTarget;
5286 targetFrame = aOverrideClickTarget->GetPrimaryFrame();
5289 if (!target->IsInComposedDoc()) {
5290 return NS_OK;
5293 // Use local event status for each click event dispatching since it'll be
5294 // cleared by EventStateManager::PreHandleEvent(). Therefore, dispatching
5295 // an event means that previous event status will be ignored.
5296 nsEventStatus status = nsEventStatus_eIgnore;
5297 nsresult rv = aPresShell->HandleEventWithTarget(
5298 &event, targetFrame, MOZ_KnownLive(target), &status);
5300 // Copy mMultipleActionsPrevented flag from a click event to the mouseup
5301 // event only when it's set to true. It may be set to true if an editor has
5302 // already handled it. This is important to avoid two or more default
5303 // actions handled here.
5304 aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
5305 event.mFlags.mMultipleActionsPrevented;
5306 // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
5307 // overwrite it.
5308 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
5309 return rv;
5311 // If new status is nsEventStatus_eConsumeNoDefault or
5312 // nsEventStatus_eConsumeDoDefault, use it.
5313 if (status == nsEventStatus_eConsumeNoDefault ||
5314 status == nsEventStatus_eConsumeDoDefault) {
5315 *aStatus = status;
5316 return rv;
5318 // Otherwise, keep the original status.
5319 return rv;
5322 nsresult EventStateManager::PostHandleMouseUp(
5323 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5324 nsIContent* aOverrideClickTarget) {
5325 MOZ_ASSERT(aMouseUpEvent);
5326 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5327 MOZ_ASSERT(aStatus);
5329 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
5330 if (!presShell) {
5331 return NS_OK;
5334 nsCOMPtr<nsIContent> clickTarget =
5335 nsIContent::FromEventTargetOrNull(aMouseUpEvent->mClickTarget);
5336 NS_ENSURE_STATE(clickTarget);
5338 // Fire click events if the event target is still available.
5339 // Note that do not include the eMouseUp event's status since we ignore it
5340 // for compatibility with the other browsers.
5341 nsEventStatus status = nsEventStatus_eIgnore;
5342 nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, &status,
5343 clickTarget, aOverrideClickTarget);
5344 if (NS_WARN_IF(NS_FAILED(rv))) {
5345 return rv;
5348 // Do not do anything if preceding click events are consumed.
5349 // Note that Chromium dispatches "paste" event and actually pates clipboard
5350 // text into focused editor even if the preceding click events are consumed.
5351 // However, this is different from our traditional behavior and does not
5352 // conform to DOM events. If we need to keep compatibility with Chromium,
5353 // we should change it later.
5354 if (status == nsEventStatus_eConsumeNoDefault) {
5355 *aStatus = nsEventStatus_eConsumeNoDefault;
5356 return NS_OK;
5359 // Handle middle click paste if it's enabled and the mouse button is middle.
5360 if (aMouseUpEvent->mButton != MouseButton::eMiddle ||
5361 !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
5362 return NS_OK;
5364 DebugOnly<nsresult> rvIgnored =
5365 HandleMiddleClickPaste(presShell, aMouseUpEvent, &status, nullptr);
5366 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5367 "Failed to paste for a middle click");
5369 // If new status is nsEventStatus_eConsumeNoDefault or
5370 // nsEventStatus_eConsumeDoDefault, use it.
5371 if (*aStatus != nsEventStatus_eConsumeNoDefault &&
5372 (status == nsEventStatus_eConsumeNoDefault ||
5373 status == nsEventStatus_eConsumeDoDefault)) {
5374 *aStatus = status;
5377 // Don't return error even if middle mouse paste fails since we haven't
5378 // handled it here.
5379 return NS_OK;
5382 nsresult EventStateManager::DispatchClickEvents(
5383 PresShell* aPresShell, WidgetMouseEvent* aMouseUpEvent,
5384 nsEventStatus* aStatus, nsIContent* aClickTarget,
5385 nsIContent* aOverrideClickTarget) {
5386 MOZ_ASSERT(aPresShell);
5387 MOZ_ASSERT(aMouseUpEvent);
5388 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5389 MOZ_ASSERT(aStatus);
5390 MOZ_ASSERT(aClickTarget || aOverrideClickTarget);
5392 bool notDispatchToContents =
5393 (aMouseUpEvent->mButton == MouseButton::eMiddle ||
5394 aMouseUpEvent->mButton == MouseButton::eSecondary);
5396 bool fireAuxClick = notDispatchToContents;
5398 AutoWeakFrame currentTarget = aClickTarget->GetPrimaryFrame();
5399 nsresult rv = InitAndDispatchClickEvent(
5400 aMouseUpEvent, aStatus, eMouseClick, aPresShell, aClickTarget,
5401 currentTarget, notDispatchToContents, aOverrideClickTarget);
5402 if (NS_WARN_IF(NS_FAILED(rv))) {
5403 return rv;
5406 // Fire auxclick event if necessary.
5407 if (fireAuxClick && *aStatus != nsEventStatus_eConsumeNoDefault &&
5408 aClickTarget && aClickTarget->IsInComposedDoc()) {
5409 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick,
5410 aPresShell, aClickTarget, currentTarget,
5411 false, aOverrideClickTarget);
5412 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
5415 // Fire double click event if click count is 2.
5416 if (aMouseUpEvent->mClickCount == 2 && !fireAuxClick && aClickTarget &&
5417 aClickTarget->IsInComposedDoc()) {
5418 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
5419 aPresShell, aClickTarget, currentTarget,
5420 notDispatchToContents, aOverrideClickTarget);
5421 if (NS_WARN_IF(NS_FAILED(rv))) {
5422 return rv;
5426 return rv;
5429 nsresult EventStateManager::HandleMiddleClickPaste(
5430 PresShell* aPresShell, WidgetMouseEvent* aMouseEvent,
5431 nsEventStatus* aStatus, EditorBase* aEditorBase) {
5432 MOZ_ASSERT(aPresShell);
5433 MOZ_ASSERT(aMouseEvent);
5434 MOZ_ASSERT((aMouseEvent->mMessage == eMouseAuxClick &&
5435 aMouseEvent->mButton == MouseButton::eMiddle) ||
5436 EventCausesClickEvents(*aMouseEvent));
5437 MOZ_ASSERT(aStatus);
5438 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
5440 // Even if we're called twice or more for a mouse operation, we should
5441 // handle only once. Although mMultipleActionsPrevented may be set to
5442 // true by different event handler in the future, we can use it for now.
5443 if (aMouseEvent->mFlags.mMultipleActionsPrevented) {
5444 return NS_OK;
5446 aMouseEvent->mFlags.mMultipleActionsPrevented = true;
5448 RefPtr<Selection> selection;
5449 if (aEditorBase) {
5450 selection = aEditorBase->GetSelection();
5451 if (NS_WARN_IF(!selection)) {
5452 return NS_ERROR_FAILURE;
5454 } else {
5455 Document* document = aPresShell->GetDocument();
5456 if (NS_WARN_IF(!document)) {
5457 return NS_ERROR_FAILURE;
5459 selection = nsCopySupport::GetSelectionForCopy(document);
5460 if (NS_WARN_IF(!selection)) {
5461 return NS_ERROR_FAILURE;
5465 // Don't modify selection here because we've already set caret to the point
5466 // at "mousedown" event.
5468 int32_t clipboardType = nsIClipboard::kGlobalClipboard;
5469 nsCOMPtr<nsIClipboard> clipboardService =
5470 do_GetService("@mozilla.org/widget/clipboard;1");
5471 if (clipboardService && clipboardService->IsClipboardTypeSupported(
5472 nsIClipboard::kSelectionClipboard)) {
5473 clipboardType = nsIClipboard::kSelectionClipboard;
5476 // Fire ePaste event by ourselves since we need to dispatch "paste" event
5477 // even if the middle click event was consumed for compatibility with
5478 // Chromium.
5479 if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType, aPresShell,
5480 selection)) {
5481 *aStatus = nsEventStatus_eConsumeNoDefault;
5482 return NS_OK;
5485 // Although we've fired "paste" event, there is no editor to accept the
5486 // clipboard content.
5487 if (!aEditorBase) {
5488 return NS_OK;
5491 // Check if the editor is still the good target to paste.
5492 if (aEditorBase->Destroyed() || aEditorBase->IsReadonly()) {
5493 // XXX Should we consume the event when the editor is readonly and/or
5494 // disabled?
5495 return NS_OK;
5498 // The selection may have been modified during reflow. Therefore, we
5499 // should adjust event target to pass IsAcceptableInputEvent().
5500 const nsRange* range = selection->GetRangeAt(0);
5501 if (!range) {
5502 return NS_OK;
5504 WidgetMouseEvent mouseEvent(*aMouseEvent);
5505 mouseEvent.mOriginalTarget = range->GetStartContainer();
5506 if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
5507 !aEditorBase->IsAcceptableInputEvent(&mouseEvent)) {
5508 return NS_OK;
5511 // If Control key is pressed, we should paste clipboard content as
5512 // quotation. Otherwise, paste it as is.
5513 if (aMouseEvent->IsControl()) {
5514 DebugOnly<nsresult> rv =
5515 aEditorBase->PasteAsQuotationAsAction(clipboardType, false);
5516 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
5517 } else {
5518 DebugOnly<nsresult> rv = aEditorBase->PasteAsAction(clipboardType, false);
5519 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
5521 *aStatus = nsEventStatus_eConsumeNoDefault;
5523 return NS_OK;
5526 void EventStateManager::ConsumeInteractionData(
5527 Record<nsString, dom::InteractionData>& aInteractions) {
5528 OnTypingInteractionEnded();
5530 aInteractions.Entries().Clear();
5531 auto newEntry = aInteractions.Entries().AppendElement();
5532 newEntry->mKey = u"Typing"_ns;
5533 newEntry->mValue = gTypingInteraction;
5534 gTypingInteraction = {};
5537 nsIFrame* EventStateManager::GetEventTarget() {
5538 PresShell* presShell;
5539 if (mCurrentTarget || !mPresContext ||
5540 !(presShell = mPresContext->GetPresShell())) {
5541 return mCurrentTarget;
5544 if (mCurrentTargetContent) {
5545 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent);
5546 if (mCurrentTarget) {
5547 return mCurrentTarget;
5551 nsIFrame* frame = presShell->GetCurrentEventFrame();
5552 return (mCurrentTarget = frame);
5555 already_AddRefed<nsIContent> EventStateManager::GetEventTargetContent(
5556 WidgetEvent* aEvent) {
5557 if (aEvent && (aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) {
5558 nsCOMPtr<nsIContent> content = GetFocusedElement();
5559 return content.forget();
5562 if (mCurrentTargetContent) {
5563 nsCOMPtr<nsIContent> content = mCurrentTargetContent;
5564 return content.forget();
5567 nsCOMPtr<nsIContent> content;
5568 if (PresShell* presShell = mPresContext->GetPresShell()) {
5569 content = presShell->GetEventTargetContent(aEvent);
5572 // Some events here may set mCurrentTarget but not set the corresponding
5573 // event target in the PresShell.
5574 if (!content && mCurrentTarget) {
5575 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content));
5578 return content.forget();
5581 static Element* GetLabelTarget(nsIContent* aPossibleLabel) {
5582 mozilla::dom::HTMLLabelElement* label =
5583 mozilla::dom::HTMLLabelElement::FromNode(aPossibleLabel);
5584 if (!label) return nullptr;
5586 return label->GetLabeledElement();
5589 /* static */
5590 inline void EventStateManager::DoStateChange(Element* aElement,
5591 ElementState aState,
5592 bool aAddState) {
5593 if (aAddState) {
5594 aElement->AddStates(aState);
5595 } else {
5596 aElement->RemoveStates(aState);
5600 /* static */
5601 inline void EventStateManager::DoStateChange(nsIContent* aContent,
5602 ElementState aState,
5603 bool aStateAdded) {
5604 if (aContent->IsElement()) {
5605 DoStateChange(aContent->AsElement(), aState, aStateAdded);
5609 /* static */
5610 void EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
5611 nsIContent* aStopBefore,
5612 ElementState aState,
5613 bool aAddState) {
5614 for (; aStartNode && aStartNode != aStopBefore;
5615 aStartNode = aStartNode->GetFlattenedTreeParent()) {
5616 // We might be starting with a non-element (e.g. a text node) and
5617 // if someone is doing something weird might be ending with a
5618 // non-element too (e.g. a document fragment)
5619 if (!aStartNode->IsElement()) {
5620 continue;
5622 Element* element = aStartNode->AsElement();
5623 DoStateChange(element, aState, aAddState);
5624 Element* labelTarget = GetLabelTarget(element);
5625 if (labelTarget) {
5626 DoStateChange(labelTarget, aState, aAddState);
5630 if (aAddState) {
5631 // We might be in a situation where a node was in hover both
5632 // because it was hovered and because the label for it was
5633 // hovered, and while we stopped hovering the node the label is
5634 // still hovered. Or we might have had two nested labels for the
5635 // same node, and while one is no longer hovered the other still
5636 // is. In that situation, the label that's still hovered will be
5637 // aStopBefore or some ancestor of it, and the call we just made
5638 // to UpdateAncestorState with aAddState = false would have
5639 // removed the hover state from the node. But the node should
5640 // still be in hover state. To handle this situation we need to
5641 // keep walking up the tree and any time we find a label mark its
5642 // corresponding node as still in our state.
5643 for (; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) {
5644 if (!aStartNode->IsElement()) {
5645 continue;
5648 Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
5649 if (labelTarget && !labelTarget->State().HasState(aState)) {
5650 DoStateChange(labelTarget, aState, true);
5656 // static
5657 bool CanContentHaveActiveState(nsIContent& aContent) {
5658 // Editable content can never become active since their default actions
5659 // are disabled. Watch out for editable content in native anonymous
5660 // subtrees though, as they belong to text controls.
5661 return !aContent.IsEditable() || aContent.IsInNativeAnonymousSubtree();
5664 bool EventStateManager::SetContentState(nsIContent* aContent,
5665 ElementState aState) {
5666 MOZ_ASSERT(ManagesState(aState), "Unexpected state");
5668 nsCOMPtr<nsIContent> notifyContent1;
5669 nsCOMPtr<nsIContent> notifyContent2;
5670 bool updateAncestors;
5672 if (aState == ElementState::HOVER || aState == ElementState::ACTIVE) {
5673 // Hover and active are hierarchical
5674 updateAncestors = true;
5676 // check to see that this state is allowed by style. Check dragover too?
5677 // XXX Is this even what we want?
5678 if (mCurrentTarget &&
5679 mCurrentTarget->StyleUI()->UserInput() == StyleUserInput::None) {
5680 return false;
5683 if (aState == ElementState::ACTIVE) {
5684 if (aContent && !CanContentHaveActiveState(*aContent)) {
5685 aContent = nullptr;
5687 if (aContent != mActiveContent) {
5688 notifyContent1 = aContent;
5689 notifyContent2 = mActiveContent;
5690 mActiveContent = aContent;
5692 } else {
5693 NS_ASSERTION(aState == ElementState::HOVER, "How did that happen?");
5694 nsIContent* newHover;
5696 if (mPresContext->IsDynamic()) {
5697 newHover = aContent;
5698 } else {
5699 NS_ASSERTION(!aContent || aContent->GetComposedDoc() ==
5700 mPresContext->PresShell()->GetDocument(),
5701 "Unexpected document");
5702 nsIFrame* frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
5703 if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
5704 // The scrollbars of viewport should not ignore the hover state.
5705 // Because they are *not* the content of the web page.
5706 newHover = aContent;
5707 } else {
5708 // All contents of the web page should ignore the hover state.
5709 newHover = nullptr;
5713 if (newHover != mHoverContent) {
5714 notifyContent1 = newHover;
5715 notifyContent2 = mHoverContent;
5716 mHoverContent = newHover;
5719 } else {
5720 updateAncestors = false;
5721 if (aState == ElementState::DRAGOVER) {
5722 if (aContent != sDragOverContent) {
5723 notifyContent1 = aContent;
5724 notifyContent2 = sDragOverContent;
5725 sDragOverContent = aContent;
5727 } else if (aState == ElementState::URLTARGET) {
5728 if (aContent != mURLTargetContent) {
5729 notifyContent1 = aContent;
5730 notifyContent2 = mURLTargetContent;
5731 mURLTargetContent = aContent;
5736 // We need to keep track of which of notifyContent1 and notifyContent2 is
5737 // getting the state set and which is getting it unset. If both are
5738 // non-null, then notifyContent1 is having the state set and notifyContent2
5739 // is having it unset. But if one of them is null, we need to keep track of
5740 // the right thing for notifyContent1 explicitly.
5741 bool content1StateSet = true;
5742 if (!notifyContent1) {
5743 // This is ok because FindCommonAncestor wouldn't find anything
5744 // anyway if notifyContent1 is null.
5745 notifyContent1 = notifyContent2;
5746 notifyContent2 = nullptr;
5747 content1StateSet = false;
5750 if (notifyContent1 && mPresContext) {
5751 EnsureDocument(mPresContext);
5752 if (mDocument) {
5753 nsAutoScriptBlocker scriptBlocker;
5755 if (updateAncestors) {
5756 nsCOMPtr<nsIContent> commonAncestor =
5757 FindCommonAncestor(notifyContent1, notifyContent2);
5758 if (notifyContent2) {
5759 // It's very important to first notify the state removal and
5760 // then the state addition, because due to labels it's
5761 // possible that we're removing state from some element but
5762 // then adding it again (say because mHoverContent changed
5763 // from a control to its label).
5764 UpdateAncestorState(notifyContent2, commonAncestor, aState, false);
5766 UpdateAncestorState(notifyContent1, commonAncestor, aState,
5767 content1StateSet);
5768 } else {
5769 if (notifyContent2) {
5770 DoStateChange(notifyContent2, aState, false);
5772 DoStateChange(notifyContent1, aState, content1StateSet);
5777 return true;
5780 void EventStateManager::ResetLastOverForContent(
5781 const uint32_t& aIdx, const RefPtr<OverOutElementsWrapper>& aElemWrapper,
5782 nsIContent* aContent) {
5783 if (aElemWrapper && aElemWrapper->mLastOverElement &&
5784 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
5785 aElemWrapper->mLastOverElement, aContent)) {
5786 aElemWrapper->mLastOverElement = nullptr;
5790 void EventStateManager::RemoveNodeFromChainIfNeeded(ElementState aState,
5791 nsIContent* aContentRemoved,
5792 bool aNotify) {
5793 MOZ_ASSERT(aState == ElementState::HOVER || aState == ElementState::ACTIVE);
5794 if (!aContentRemoved->IsElement() ||
5795 !aContentRemoved->AsElement()->State().HasState(aState)) {
5796 return;
5799 nsCOMPtr<nsIContent>& leaf =
5800 aState == ElementState::HOVER ? mHoverContent : mActiveContent;
5802 MOZ_ASSERT(leaf);
5803 // These two NS_ASSERTIONS below can fail for Shadow DOM sometimes, and it's
5804 // not clear how to best handle it, see
5805 // https://github.com/whatwg/html/issues/4795 and bug 1551621.
5806 NS_ASSERTION(
5807 nsContentUtils::ContentIsFlattenedTreeDescendantOf(leaf, aContentRemoved),
5808 "Flat tree and active / hover chain got out of sync");
5810 nsIContent* newLeaf = aContentRemoved->GetFlattenedTreeParent();
5811 MOZ_ASSERT(!newLeaf || newLeaf->IsElement());
5812 NS_ASSERTION(!newLeaf || newLeaf->AsElement()->State().HasState(aState),
5813 "State got out of sync because of shadow DOM");
5814 if (aNotify) {
5815 SetContentState(newLeaf, aState);
5816 } else {
5817 // We don't update the removed content's state here, since removing NAC
5818 // happens from layout and we don't really want to notify at that point or
5819 // what not.
5821 // Also, NAC is not observable and NAC being removed will go away soon.
5822 leaf = newLeaf;
5824 MOZ_ASSERT(leaf == newLeaf || (aState == ElementState::ACTIVE && !leaf &&
5825 !CanContentHaveActiveState(*newLeaf)));
5828 void EventStateManager::NativeAnonymousContentRemoved(nsIContent* aContent) {
5829 MOZ_ASSERT(aContent->IsRootOfNativeAnonymousSubtree());
5830 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, false);
5831 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, false);
5833 if (mLastLeftMouseDownContent &&
5834 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
5835 mLastLeftMouseDownContent, aContent)) {
5836 mLastLeftMouseDownContent = aContent->GetFlattenedTreeParent();
5839 if (mLastMiddleMouseDownContent &&
5840 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
5841 mLastMiddleMouseDownContent, aContent)) {
5842 mLastMiddleMouseDownContent = aContent->GetFlattenedTreeParent();
5845 if (mLastRightMouseDownContent &&
5846 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
5847 mLastRightMouseDownContent, aContent)) {
5848 mLastRightMouseDownContent = aContent->GetFlattenedTreeParent();
5852 void EventStateManager::ContentRemoved(Document* aDocument,
5853 nsIContent* aContent) {
5855 * Anchor and area elements when focused or hovered might make the UI to show
5856 * the current link. We want to make sure that the UI gets informed when they
5857 * are actually removed from the DOM.
5859 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
5860 (aContent->AsElement()->State().HasAtLeastOneOfStates(
5861 ElementState::FOCUS | ElementState::HOVER))) {
5862 Element* element = aContent->AsElement();
5863 element->LeaveLink(element->GetPresContext(Element::eForComposedDoc));
5866 if (aContent->IsElement()) {
5867 if (RefPtr<nsPresContext> presContext = mPresContext) {
5868 IMEStateManager::OnRemoveContent(*presContext,
5869 MOZ_KnownLive(*aContent->AsElement()));
5871 WheelTransaction::OnRemoveElement(aContent);
5874 // inform the focus manager that the content is being removed. If this
5875 // content is focused, the focus will be removed without firing events.
5876 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
5877 fm->ContentRemoved(aDocument, aContent);
5880 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, true);
5881 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, true);
5883 if (sDragOverContent &&
5884 sDragOverContent->OwnerDoc() == aContent->OwnerDoc() &&
5885 nsContentUtils::ContentIsFlattenedTreeDescendantOf(sDragOverContent,
5886 aContent)) {
5887 sDragOverContent = nullptr;
5890 PointerEventHandler::ReleaseIfCaptureByDescendant(aContent);
5892 // See bug 292146 for why we want to null this out
5893 ResetLastOverForContent(0, mMouseEnterLeaveHelper, aContent);
5894 for (const auto& entry : mPointersEnterLeaveHelper) {
5895 ResetLastOverForContent(entry.GetKey(), entry.GetData(), aContent);
5899 void EventStateManager::TextControlRootWillBeRemoved(
5900 TextControlElement& aTextControlElement) {
5901 if (!mGestureDownInTextControl || !mGestureDownFrameOwner ||
5902 !mGestureDownFrameOwner->IsInNativeAnonymousSubtree()) {
5903 return;
5905 // If we track gesture to start drag in aTextControlElement, we should keep
5906 // tracking it with aTextContrlElement itself for now because this may be
5907 // caused by reframing aTextControlElement which may not be intended by the
5908 // user.
5909 if (&aTextControlElement ==
5910 mGestureDownFrameOwner
5911 ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
5912 mGestureDownFrameOwner = &aTextControlElement;
5916 void EventStateManager::TextControlRootAdded(
5917 Element& aAnonymousDivElement, TextControlElement& aTextControlElement) {
5918 if (!mGestureDownInTextControl ||
5919 mGestureDownFrameOwner != &aTextControlElement) {
5920 return;
5922 // If we track gesture to start drag in aTextControlElement, but the frame
5923 // owner is the text control element itself, the anonymous nodes in it are
5924 // recreated by a reframe. If so, we should keep tracking it with the
5925 // recreated native anonymous node.
5926 mGestureDownFrameOwner =
5927 aAnonymousDivElement.GetFirstChild()
5928 ? aAnonymousDivElement.GetFirstChild()
5929 : static_cast<nsIContent*>(&aAnonymousDivElement);
5932 bool EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) {
5933 return !(aEvent->mMessage == eMouseDown &&
5934 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
5935 !sNormalLMouseEventInProcess);
5938 //-------------------------------------------
5939 // Access Key Registration
5940 //-------------------------------------------
5941 void EventStateManager::RegisterAccessKey(Element* aElement, uint32_t aKey) {
5942 if (aElement && !mAccessKeys.Contains(aElement)) {
5943 mAccessKeys.AppendObject(aElement);
5947 void EventStateManager::UnregisterAccessKey(Element* aElement, uint32_t aKey) {
5948 if (aElement) {
5949 mAccessKeys.RemoveObject(aElement);
5953 uint32_t EventStateManager::GetRegisteredAccessKey(Element* aElement) {
5954 MOZ_ASSERT(aElement);
5956 if (!mAccessKeys.Contains(aElement)) {
5957 return 0;
5960 nsAutoString accessKey;
5961 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
5962 return accessKey.First();
5965 void EventStateManager::EnsureDocument(nsPresContext* aPresContext) {
5966 if (!mDocument) mDocument = aPresContext->Document();
5969 void EventStateManager::FlushLayout(nsPresContext* aPresContext) {
5970 MOZ_ASSERT(aPresContext, "nullptr ptr");
5971 if (RefPtr<PresShell> presShell = aPresContext->GetPresShell()) {
5972 presShell->FlushPendingNotifications(FlushType::InterruptibleLayout);
5976 Element* EventStateManager::GetFocusedElement() {
5977 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5978 EnsureDocument(mPresContext);
5979 if (!fm || !mDocument) {
5980 return nullptr;
5983 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
5984 return nsFocusManager::GetFocusedDescendant(
5985 mDocument->GetWindow(), nsFocusManager::eOnlyCurrentWindow,
5986 getter_AddRefs(focusedWindow));
5989 //-------------------------------------------------------
5990 // Return true if the docshell is visible
5992 bool EventStateManager::IsShellVisible(nsIDocShell* aShell) {
5993 NS_ASSERTION(aShell, "docshell is null");
5995 nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell);
5996 if (!basewin) return true;
5998 bool isVisible = true;
5999 basewin->GetVisibility(&isVisible);
6001 // We should be doing some additional checks here so that
6002 // we don't tab into hidden tabs of tabbrowser. -bryner
6004 return isVisible;
6007 nsresult EventStateManager::DoContentCommandEvent(
6008 WidgetContentCommandEvent* aEvent) {
6009 EnsureDocument(mPresContext);
6010 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
6011 nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow());
6012 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
6014 nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
6015 NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
6016 const char* cmd;
6017 switch (aEvent->mMessage) {
6018 case eContentCommandCut:
6019 cmd = "cmd_cut";
6020 break;
6021 case eContentCommandCopy:
6022 cmd = "cmd_copy";
6023 break;
6024 case eContentCommandPaste:
6025 cmd = "cmd_paste";
6026 break;
6027 case eContentCommandDelete:
6028 cmd = "cmd_delete";
6029 break;
6030 case eContentCommandUndo:
6031 cmd = "cmd_undo";
6032 break;
6033 case eContentCommandRedo:
6034 cmd = "cmd_redo";
6035 break;
6036 case eContentCommandPasteTransferable:
6037 cmd = "cmd_pasteTransferable";
6038 break;
6039 case eContentCommandLookUpDictionary:
6040 cmd = "cmd_lookUpDictionary";
6041 break;
6042 default:
6043 return NS_ERROR_NOT_IMPLEMENTED;
6045 // If user tries to do something, user must try to do it in visible window.
6046 // So, let's retrieve controller of visible window.
6047 nsCOMPtr<nsIController> controller;
6048 nsresult rv =
6049 root->GetControllerForCommand(cmd, true, getter_AddRefs(controller));
6050 NS_ENSURE_SUCCESS(rv, rv);
6051 if (!controller) {
6052 // When GetControllerForCommand succeeded but there is no controller, the
6053 // command isn't supported.
6054 aEvent->mIsEnabled = false;
6055 } else {
6056 bool canDoIt;
6057 rv = controller->IsCommandEnabled(cmd, &canDoIt);
6058 NS_ENSURE_SUCCESS(rv, rv);
6059 aEvent->mIsEnabled = canDoIt;
6060 if (canDoIt && !aEvent->mOnlyEnabledCheck) {
6061 switch (aEvent->mMessage) {
6062 case eContentCommandPasteTransferable: {
6063 BrowserParent* remote = BrowserParent::GetFocused();
6064 if (remote) {
6065 nsCOMPtr<nsITransferable> transferable = aEvent->mTransferable;
6066 IPCDataTransfer ipcDataTransfer;
6067 nsContentUtils::TransferableToIPCTransferable(
6068 transferable, &ipcDataTransfer, false, remote->Manager());
6069 bool isPrivateData = transferable->GetIsPrivateData();
6070 nsCOMPtr<nsIPrincipal> requestingPrincipal =
6071 transferable->GetRequestingPrincipal();
6072 nsContentPolicyType contentPolicyType =
6073 transferable->GetContentPolicyType();
6074 remote->SendPasteTransferable(std::move(ipcDataTransfer),
6075 isPrivateData, requestingPrincipal,
6076 contentPolicyType);
6077 rv = NS_OK;
6078 } else {
6079 nsCOMPtr<nsICommandController> commandController =
6080 do_QueryInterface(controller);
6081 NS_ENSURE_STATE(commandController);
6083 RefPtr<nsCommandParams> params = new nsCommandParams();
6084 rv = params->SetISupports("transferable", aEvent->mTransferable);
6085 if (NS_WARN_IF(NS_FAILED(rv))) {
6086 return rv;
6088 rv = commandController->DoCommandWithParams(cmd, params);
6090 break;
6093 case eContentCommandLookUpDictionary: {
6094 nsCOMPtr<nsICommandController> commandController =
6095 do_QueryInterface(controller);
6096 if (NS_WARN_IF(!commandController)) {
6097 return NS_ERROR_FAILURE;
6100 RefPtr<nsCommandParams> params = new nsCommandParams();
6101 rv = params->SetInt("x", aEvent->mRefPoint.x);
6102 if (NS_WARN_IF(NS_FAILED(rv))) {
6103 return rv;
6106 rv = params->SetInt("y", aEvent->mRefPoint.y);
6107 if (NS_WARN_IF(NS_FAILED(rv))) {
6108 return rv;
6111 rv = commandController->DoCommandWithParams(cmd, params);
6112 break;
6115 default:
6116 rv = controller->DoCommand(cmd);
6117 break;
6119 NS_ENSURE_SUCCESS(rv, rv);
6122 aEvent->mSucceeded = true;
6123 return NS_OK;
6126 nsresult EventStateManager::DoContentCommandInsertTextEvent(
6127 WidgetContentCommandEvent* aEvent) {
6128 MOZ_ASSERT(aEvent);
6129 MOZ_ASSERT(aEvent->mMessage == eContentCommandInsertText);
6130 MOZ_DIAGNOSTIC_ASSERT(aEvent->mString.isSome());
6131 MOZ_DIAGNOSTIC_ASSERT(!aEvent->mString.ref().IsEmpty());
6133 aEvent->mIsEnabled = false;
6134 aEvent->mSucceeded = false;
6136 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6138 if (XRE_IsParentProcess()) {
6139 // Handle it in focused content process if there is.
6140 if (BrowserParent* remote = BrowserParent::GetFocused()) {
6141 remote->SendInsertText(aEvent->mString.ref());
6142 aEvent->mIsEnabled = true; // XXX it can be a lie...
6143 aEvent->mSucceeded = true;
6144 return NS_OK;
6148 // If there is no active editor in this process, we should treat the command
6149 // is disabled.
6150 RefPtr<EditorBase> activeEditor =
6151 nsContentUtils::GetActiveEditor(mPresContext);
6152 if (!activeEditor) {
6153 aEvent->mSucceeded = true;
6154 return NS_OK;
6157 nsresult rv = activeEditor->InsertTextAsAction(aEvent->mString.ref());
6158 aEvent->mIsEnabled = rv != NS_SUCCESS_DOM_NO_OPERATION;
6159 aEvent->mSucceeded = NS_SUCCEEDED(rv);
6160 return NS_OK;
6163 nsresult EventStateManager::DoContentCommandScrollEvent(
6164 WidgetContentCommandEvent* aEvent) {
6165 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6166 PresShell* presShell = mPresContext->GetPresShell();
6167 NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
6168 NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
6170 ScrollUnit scrollUnit;
6171 switch (aEvent->mScroll.mUnit) {
6172 case WidgetContentCommandEvent::eCmdScrollUnit_Line:
6173 scrollUnit = ScrollUnit::LINES;
6174 break;
6175 case WidgetContentCommandEvent::eCmdScrollUnit_Page:
6176 scrollUnit = ScrollUnit::PAGES;
6177 break;
6178 case WidgetContentCommandEvent::eCmdScrollUnit_Whole:
6179 scrollUnit = ScrollUnit::WHOLE;
6180 break;
6181 default:
6182 return NS_ERROR_INVALID_ARG;
6185 aEvent->mSucceeded = true;
6187 nsIScrollableFrame* sf =
6188 presShell->GetScrollableFrameToScroll(layers::EitherScrollDirection);
6189 aEvent->mIsEnabled =
6190 sf ? (aEvent->mScroll.mIsHorizontal ? WheelHandlingUtils::CanScrollOn(
6191 sf, aEvent->mScroll.mAmount, 0)
6192 : WheelHandlingUtils::CanScrollOn(
6193 sf, 0, aEvent->mScroll.mAmount))
6194 : false;
6196 if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
6197 return NS_OK;
6200 nsIntPoint pt(0, 0);
6201 if (aEvent->mScroll.mIsHorizontal) {
6202 pt.x = aEvent->mScroll.mAmount;
6203 } else {
6204 pt.y = aEvent->mScroll.mAmount;
6207 // The caller may want synchronous scrolling.
6208 sf->ScrollBy(pt, scrollUnit, ScrollMode::Instant);
6209 return NS_OK;
6212 void EventStateManager::SetActiveManager(EventStateManager* aNewESM,
6213 nsIContent* aContent) {
6214 if (sActiveESM && aNewESM != sActiveESM) {
6215 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6217 sActiveESM = aNewESM;
6218 if (sActiveESM && aContent) {
6219 sActiveESM->SetContentState(aContent, ElementState::ACTIVE);
6223 void EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer) {
6224 if (aClearer) {
6225 aClearer->SetContentState(nullptr, ElementState::ACTIVE);
6226 if (sDragOverContent) {
6227 aClearer->SetContentState(nullptr, ElementState::DRAGOVER);
6230 if (sActiveESM && aClearer != sActiveESM) {
6231 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6233 sActiveESM = nullptr;
6236 /******************************************************************/
6237 /* mozilla::EventStateManager::DeltaAccumulator */
6238 /******************************************************************/
6240 void EventStateManager::DeltaAccumulator::InitLineOrPageDelta(
6241 nsIFrame* aTargetFrame, EventStateManager* aESM, WidgetWheelEvent* aEvent) {
6242 MOZ_ASSERT(aESM);
6243 MOZ_ASSERT(aEvent);
6245 // Reset if the previous wheel event is too old.
6246 if (!mLastTime.IsNull()) {
6247 TimeDuration duration = TimeStamp::Now() - mLastTime;
6248 if (duration.ToMilliseconds() >
6249 StaticPrefs::mousewheel_transaction_timeout()) {
6250 Reset();
6253 // If we have accumulated delta, we may need to reset it.
6254 if (IsInTransaction()) {
6255 // If wheel event type is changed, reset the values.
6256 if (mHandlingDeltaMode != aEvent->mDeltaMode ||
6257 mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) {
6258 Reset();
6259 } else {
6260 // If the delta direction is changed, we should reset only the
6261 // accumulated values.
6262 if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) {
6263 mX = mPendingScrollAmountX = 0.0;
6265 if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) {
6266 mY = mPendingScrollAmountY = 0.0;
6271 mHandlingDeltaMode = aEvent->mDeltaMode;
6272 mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta;
6275 nsIFrame* frame = aESM->ComputeScrollTarget(aTargetFrame, aEvent,
6276 COMPUTE_DEFAULT_ACTION_TARGET);
6277 nsPresContext* pc =
6278 frame ? frame->PresContext() : aTargetFrame->PresContext();
6279 nsIScrollableFrame* scrollTarget = do_QueryFrame(frame);
6280 aEvent->mScrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget);
6283 // If it's handling neither a device that does not provide line or page deltas
6284 // nor delta values multiplied by prefs, we must not modify lineOrPageDelta
6285 // values.
6286 // TODO(emilio): Does this care about overridden scroll speed?
6287 if (!mIsNoLineOrPageDeltaDevice &&
6288 !EventStateManager::WheelPrefs::GetInstance()
6289 ->NeedToComputeLineOrPageDelta(aEvent)) {
6290 // Set the delta values to mX and mY. They would be used when above block
6291 // resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction
6292 // is changed.
6293 // NOTE: We shouldn't accumulate the delta values, it might could cause
6294 // overflow even though it's not a realistic situation.
6295 if (aEvent->mDeltaX) {
6296 mX = aEvent->mDeltaX;
6298 if (aEvent->mDeltaY) {
6299 mY = aEvent->mDeltaY;
6301 mLastTime = TimeStamp::Now();
6302 return;
6305 mX += aEvent->mDeltaX;
6306 mY += aEvent->mDeltaY;
6308 if (mHandlingDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6309 // Records pixel delta values and init mLineOrPageDeltaX and
6310 // mLineOrPageDeltaY for wheel events which are caused by pixel only
6311 // devices. Ignore mouse wheel transaction for computing this. The
6312 // lineOrPageDelta values will be used by dispatching legacy
6313 // eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling
6314 // of default action. The transaction should be used only for the default
6315 // action.
6316 auto scrollAmountInCSSPixels =
6317 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
6319 aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width;
6320 aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height;
6322 mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width;
6323 mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height;
6324 } else {
6325 aEvent->mLineOrPageDeltaX = RoundDown(mX);
6326 aEvent->mLineOrPageDeltaY = RoundDown(mY);
6327 mX -= aEvent->mLineOrPageDeltaX;
6328 mY -= aEvent->mLineOrPageDeltaY;
6331 mLastTime = TimeStamp::Now();
6334 void EventStateManager::DeltaAccumulator::Reset() {
6335 mX = mY = 0.0;
6336 mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
6337 mHandlingDeltaMode = UINT32_MAX;
6338 mIsNoLineOrPageDeltaDevice = false;
6341 nsIntPoint
6342 EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
6343 WidgetWheelEvent* aEvent, const nsIntSize& aScrollAmountInDevPixels) {
6344 MOZ_ASSERT(aEvent);
6346 DeltaValues acceleratedDelta = WheelTransaction::AccelerateWheelDelta(aEvent);
6348 nsIntPoint result(0, 0);
6349 if (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6350 mPendingScrollAmountX += acceleratedDelta.deltaX;
6351 mPendingScrollAmountY += acceleratedDelta.deltaY;
6352 } else {
6353 mPendingScrollAmountX +=
6354 aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
6355 mPendingScrollAmountY +=
6356 aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
6358 result.x = RoundDown(mPendingScrollAmountX);
6359 result.y = RoundDown(mPendingScrollAmountY);
6360 mPendingScrollAmountX -= result.x;
6361 mPendingScrollAmountY -= result.y;
6363 return result;
6366 /******************************************************************/
6367 /* mozilla::EventStateManager::WheelPrefs */
6368 /******************************************************************/
6370 // static
6371 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::GetInstance() {
6372 if (!sInstance) {
6373 sInstance = new WheelPrefs();
6375 return sInstance;
6378 // static
6379 void EventStateManager::WheelPrefs::Shutdown() {
6380 delete sInstance;
6381 sInstance = nullptr;
6384 // static
6385 void EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName,
6386 void* aClosure) {
6387 // forget all prefs, it's not problem for performance.
6388 sInstance->Reset();
6389 DeltaAccumulator::GetInstance()->Reset();
6392 EventStateManager::WheelPrefs::WheelPrefs() {
6393 Reset();
6394 Preferences::RegisterPrefixCallback(OnPrefChanged, "mousewheel.");
6397 EventStateManager::WheelPrefs::~WheelPrefs() {
6398 Preferences::UnregisterPrefixCallback(OnPrefChanged, "mousewheel.");
6401 void EventStateManager::WheelPrefs::Reset() { memset(mInit, 0, sizeof(mInit)); }
6403 EventStateManager::WheelPrefs::Index EventStateManager::WheelPrefs::GetIndexFor(
6404 const WidgetWheelEvent* aEvent) {
6405 if (!aEvent) {
6406 return INDEX_DEFAULT;
6409 Modifiers modifiers =
6410 (aEvent->mModifiers & (MODIFIER_ALT | MODIFIER_CONTROL | MODIFIER_META |
6411 MODIFIER_SHIFT | MODIFIER_OS));
6413 switch (modifiers) {
6414 case MODIFIER_ALT:
6415 return INDEX_ALT;
6416 case MODIFIER_CONTROL:
6417 return INDEX_CONTROL;
6418 case MODIFIER_META:
6419 return INDEX_META;
6420 case MODIFIER_SHIFT:
6421 return INDEX_SHIFT;
6422 case MODIFIER_OS:
6423 return INDEX_OS;
6424 default:
6425 // If two or more modifier keys are pressed, we should use default
6426 // settings.
6427 return INDEX_DEFAULT;
6431 void EventStateManager::WheelPrefs::GetBasePrefName(
6432 EventStateManager::WheelPrefs::Index aIndex, nsACString& aBasePrefName) {
6433 aBasePrefName.AssignLiteral("mousewheel.");
6434 switch (aIndex) {
6435 case INDEX_ALT:
6436 aBasePrefName.AppendLiteral("with_alt.");
6437 break;
6438 case INDEX_CONTROL:
6439 aBasePrefName.AppendLiteral("with_control.");
6440 break;
6441 case INDEX_META:
6442 aBasePrefName.AppendLiteral("with_meta.");
6443 break;
6444 case INDEX_SHIFT:
6445 aBasePrefName.AppendLiteral("with_shift.");
6446 break;
6447 case INDEX_OS:
6448 aBasePrefName.AppendLiteral("with_win.");
6449 break;
6450 case INDEX_DEFAULT:
6451 default:
6452 aBasePrefName.AppendLiteral("default.");
6453 break;
6457 void EventStateManager::WheelPrefs::Init(
6458 EventStateManager::WheelPrefs::Index aIndex) {
6459 if (mInit[aIndex]) {
6460 return;
6462 mInit[aIndex] = true;
6464 nsAutoCString basePrefName;
6465 GetBasePrefName(aIndex, basePrefName);
6467 nsAutoCString prefNameX(basePrefName);
6468 prefNameX.AppendLiteral("delta_multiplier_x");
6469 mMultiplierX[aIndex] =
6470 static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100;
6472 nsAutoCString prefNameY(basePrefName);
6473 prefNameY.AppendLiteral("delta_multiplier_y");
6474 mMultiplierY[aIndex] =
6475 static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100;
6477 nsAutoCString prefNameZ(basePrefName);
6478 prefNameZ.AppendLiteral("delta_multiplier_z");
6479 mMultiplierZ[aIndex] =
6480 static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
6482 nsAutoCString prefNameAction(basePrefName);
6483 prefNameAction.AppendLiteral("action");
6484 int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
6485 if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
6486 NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
6487 action = ACTION_SCROLL;
6489 mActions[aIndex] = static_cast<Action>(action);
6491 // Compute action values overridden by .override_x pref.
6492 // At present, override is possible only for the x-direction
6493 // because this pref is introduced mainly for tilt wheels.
6494 // Note that ACTION_HORIZONTALIZED_SCROLL isn't a valid value for this pref
6495 // because it affects only to deltaY.
6496 prefNameAction.AppendLiteral(".override_x");
6497 int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1);
6498 if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST) ||
6499 actionOverrideX == ACTION_HORIZONTALIZED_SCROLL) {
6500 NS_WARNING("Unsupported action override pref value, didn't override.");
6501 actionOverrideX = -1;
6503 mOverriddenActionsX[aIndex] = (actionOverrideX == -1)
6504 ? static_cast<Action>(action)
6505 : static_cast<Action>(actionOverrideX);
6508 void EventStateManager::WheelPrefs::GetMultiplierForDeltaXAndY(
6509 const WidgetWheelEvent* aEvent, Index aIndex, double* aMultiplierForDeltaX,
6510 double* aMultiplierForDeltaY) {
6511 *aMultiplierForDeltaX = mMultiplierX[aIndex];
6512 *aMultiplierForDeltaY = mMultiplierY[aIndex];
6513 // If the event has been horizontalized(I.e. treated as a horizontal wheel
6514 // scroll for a vertical wheel scroll), then we should swap mMultiplierX and
6515 // mMultiplierY. By doing this, multipliers will still apply to the delta
6516 // values they origianlly corresponded to.
6517 if (aEvent->mDeltaValuesHorizontalizedForDefaultHandler &&
6518 ComputeActionFor(aEvent) == ACTION_HORIZONTALIZED_SCROLL) {
6519 std::swap(*aMultiplierForDeltaX, *aMultiplierForDeltaY);
6523 void EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(
6524 WidgetWheelEvent* aEvent) {
6525 if (aEvent->mCustomizedByUserPrefs) {
6526 return;
6529 Index index = GetIndexFor(aEvent);
6530 Init(index);
6532 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6533 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6534 &multiplierForDeltaY);
6535 aEvent->mDeltaX *= multiplierForDeltaX;
6536 aEvent->mDeltaY *= multiplierForDeltaY;
6537 aEvent->mDeltaZ *= mMultiplierZ[index];
6539 // If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute
6540 // value, we should use lineOrPageDelta values which were set by widget.
6541 // Otherwise, we need to compute them from accumulated delta values.
6542 if (!NeedToComputeLineOrPageDelta(aEvent)) {
6543 aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(multiplierForDeltaX);
6544 aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(multiplierForDeltaY);
6545 } else {
6546 aEvent->mLineOrPageDeltaX = 0;
6547 aEvent->mLineOrPageDeltaY = 0;
6550 aEvent->mCustomizedByUserPrefs =
6551 ((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) ||
6552 (mMultiplierZ[index] != 1.0));
6555 void EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta(
6556 WidgetWheelEvent* aEvent) {
6557 Index index = GetIndexFor(aEvent);
6558 Init(index);
6560 // XXX If the multiplier pref value is negative, the scroll direction was
6561 // changed and caused to scroll different direction. In such case,
6562 // this method reverts the sign of overflowDelta. Does it make widget
6563 // happy? Although, widget can know the pref applied delta values by
6564 // referrencing the deltaX and deltaY of the event.
6566 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6567 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6568 &multiplierForDeltaY);
6569 if (multiplierForDeltaX) {
6570 aEvent->mOverflowDeltaX /= multiplierForDeltaX;
6572 if (multiplierForDeltaY) {
6573 aEvent->mOverflowDeltaY /= multiplierForDeltaY;
6577 EventStateManager::WheelPrefs::Action
6578 EventStateManager::WheelPrefs::ComputeActionFor(
6579 const WidgetWheelEvent* aEvent) {
6580 Index index = GetIndexFor(aEvent);
6581 Init(index);
6583 bool deltaXPreferred = (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
6584 Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
6585 Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
6586 if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL ||
6587 actions[index] == ACTION_HORIZONTALIZED_SCROLL) {
6588 return actions[index];
6591 // Momentum events shouldn't run special actions.
6592 if (aEvent->mIsMomentum) {
6593 // Use the default action. Note that user might kill the wheel scrolling.
6594 Init(INDEX_DEFAULT);
6595 if (actions[INDEX_DEFAULT] == ACTION_SCROLL ||
6596 actions[INDEX_DEFAULT] == ACTION_HORIZONTALIZED_SCROLL) {
6597 return actions[INDEX_DEFAULT];
6599 return ACTION_NONE;
6602 return actions[index];
6605 bool EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta(
6606 const WidgetWheelEvent* aEvent) {
6607 Index index = GetIndexFor(aEvent);
6608 Init(index);
6610 return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
6611 (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
6614 void EventStateManager::WheelPrefs::GetUserPrefsForEvent(
6615 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
6616 double* aOutMultiplierY) {
6617 Index index = GetIndexFor(aEvent);
6618 Init(index);
6620 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6621 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6622 &multiplierForDeltaY);
6623 *aOutMultiplierX = multiplierForDeltaX;
6624 *aOutMultiplierY = multiplierForDeltaY;
6627 // static
6628 Maybe<layers::APZWheelAction> EventStateManager::APZWheelActionFor(
6629 const WidgetWheelEvent* aEvent) {
6630 if (aEvent->mMessage != eWheel) {
6631 return Nothing();
6633 WheelPrefs::Action action =
6634 WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
6635 switch (action) {
6636 case WheelPrefs::ACTION_SCROLL:
6637 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
6638 return Some(layers::APZWheelAction::Scroll);
6639 case WheelPrefs::ACTION_PINCH_ZOOM:
6640 return Some(layers::APZWheelAction::PinchZoom);
6641 default:
6642 return Nothing();
6646 // static
6647 WheelDeltaAdjustmentStrategy EventStateManager::GetWheelDeltaAdjustmentStrategy(
6648 const WidgetWheelEvent& aEvent) {
6649 if (aEvent.mMessage != eWheel) {
6650 return WheelDeltaAdjustmentStrategy::eNone;
6652 switch (WheelPrefs::GetInstance()->ComputeActionFor(&aEvent)) {
6653 case WheelPrefs::ACTION_SCROLL:
6654 if (StaticPrefs::mousewheel_autodir_enabled() && 0 == aEvent.mDeltaZ) {
6655 if (StaticPrefs::mousewheel_autodir_honourroot()) {
6656 return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour;
6658 return WheelDeltaAdjustmentStrategy::eAutoDir;
6660 return WheelDeltaAdjustmentStrategy::eNone;
6661 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
6662 return WheelDeltaAdjustmentStrategy::eHorizontalize;
6663 default:
6664 break;
6666 return WheelDeltaAdjustmentStrategy::eNone;
6669 void EventStateManager::GetUserPrefsForWheelEvent(
6670 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
6671 double* aOutMultiplierY) {
6672 WheelPrefs::GetInstance()->GetUserPrefsForEvent(aEvent, aOutMultiplierX,
6673 aOutMultiplierY);
6676 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
6677 const WidgetWheelEvent* aEvent) {
6678 Index index = GetIndexFor(aEvent);
6679 Init(index);
6680 return Abs(mMultiplierX[index]) >=
6681 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
6684 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
6685 const WidgetWheelEvent* aEvent) {
6686 Index index = GetIndexFor(aEvent);
6687 Init(index);
6688 return Abs(mMultiplierY[index]) >=
6689 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
6692 } // namespace mozilla