Bug 1866894 - Update failing subtest for content-visibility-auto-resize.html. r=fredw
[gecko.git] / dom / events / EventStateManager.cpp
blob2631583fade011b9801612a9a4c3546da9bb0756
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "EventStateManager.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EditorBase.h"
12 #include "mozilla/EventDispatcher.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/Hal.h"
15 #include "mozilla/HTMLEditor.h"
16 #include "mozilla/IMEStateManager.h"
17 #include "mozilla/Likely.h"
18 #include "mozilla/MiscEvents.h"
19 #include "mozilla/MathAlgorithms.h"
20 #include "mozilla/MouseEvents.h"
21 #include "mozilla/PointerLockManager.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/ScrollTypes.h"
25 #include "mozilla/TextComposition.h"
26 #include "mozilla/TextControlElement.h"
27 #include "mozilla/TextEditor.h"
28 #include "mozilla/TextEvents.h"
29 #include "mozilla/TouchEvents.h"
30 #include "mozilla/Telemetry.h"
31 #include "mozilla/UniquePtr.h"
32 #include "mozilla/dom/BrowserBridgeChild.h"
33 #include "mozilla/dom/BrowsingContext.h"
34 #include "mozilla/dom/CanonicalBrowsingContext.h"
35 #include "mozilla/dom/ContentChild.h"
36 #include "mozilla/dom/DOMIntersectionObserver.h"
37 #include "mozilla/dom/DragEvent.h"
38 #include "mozilla/dom/Event.h"
39 #include "mozilla/dom/FrameLoaderBinding.h"
40 #include "mozilla/dom/HTMLLabelElement.h"
41 #include "mozilla/dom/HTMLInputElement.h"
42 #include "mozilla/dom/MouseEventBinding.h"
43 #include "mozilla/dom/BrowserChild.h"
44 #include "mozilla/dom/PointerEventHandler.h"
45 #include "mozilla/dom/UIEvent.h"
46 #include "mozilla/dom/UIEventBinding.h"
47 #include "mozilla/dom/UserActivation.h"
48 #include "mozilla/dom/WheelEventBinding.h"
49 #include "mozilla/glean/GleanMetrics.h"
50 #include "mozilla/StaticPrefs_accessibility.h"
51 #include "mozilla/StaticPrefs_browser.h"
52 #include "mozilla/StaticPrefs_dom.h"
53 #include "mozilla/StaticPrefs_layout.h"
54 #include "mozilla/StaticPrefs_mousewheel.h"
55 #include "mozilla/StaticPrefs_ui.h"
56 #include "mozilla/StaticPrefs_zoom.h"
58 #include "ContentEventHandler.h"
59 #include "IMEContentObserver.h"
60 #include "WheelHandlingHelper.h"
61 #include "RemoteDragStartData.h"
63 #include "nsCommandParams.h"
64 #include "nsCOMPtr.h"
65 #include "nsCopySupport.h"
66 #include "nsFocusManager.h"
67 #include "nsGenericHTMLElement.h"
68 #include "nsIClipboard.h"
69 #include "nsIContent.h"
70 #include "nsIContentInlines.h"
71 #include "mozilla/dom/Document.h"
72 #include "nsICookieJarSettings.h"
73 #include "nsIFrame.h"
74 #include "nsFrameLoaderOwner.h"
75 #include "nsIWidget.h"
76 #include "nsLiteralString.h"
77 #include "nsPresContext.h"
78 #include "nsTArray.h"
79 #include "nsGkAtoms.h"
80 #include "nsIFormControl.h"
81 #include "nsComboboxControlFrame.h"
82 #include "nsIScrollableFrame.h"
83 #include "nsIDOMXULControlElement.h"
84 #include "nsNameSpaceManager.h"
85 #include "nsIBaseWindow.h"
86 #include "nsFrameSelection.h"
87 #include "nsPIDOMWindow.h"
88 #include "nsPIWindowRoot.h"
89 #include "nsIWebNavigation.h"
90 #include "nsIDocumentViewer.h"
91 #include "nsFrameManager.h"
92 #include "nsIBrowserChild.h"
93 #include "nsMenuPopupFrame.h"
95 #include "nsIObserverService.h"
96 #include "nsIDocShell.h"
98 #include "nsSubDocumentFrame.h"
99 #include "nsLayoutUtils.h"
100 #include "nsIInterfaceRequestorUtils.h"
101 #include "nsUnicharUtils.h"
102 #include "nsContentUtils.h"
104 #include "imgIContainer.h"
105 #include "nsIProperties.h"
106 #include "nsISupportsPrimitives.h"
108 #include "nsServiceManagerUtils.h"
109 #include "nsITimer.h"
110 #include "nsFontMetrics.h"
111 #include "nsIDragService.h"
112 #include "nsIDragSession.h"
113 #include "mozilla/dom/DataTransfer.h"
114 #include "nsContentAreaDragDrop.h"
115 #include "nsTreeBodyFrame.h"
116 #include "nsIController.h"
117 #include "mozilla/Services.h"
118 #include "mozilla/dom/ContentParent.h"
119 #include "mozilla/dom/Record.h"
120 #include "mozilla/dom/Selection.h"
122 #include "mozilla/Preferences.h"
123 #include "mozilla/LookAndFeel.h"
124 #include "mozilla/ProfilerLabels.h"
125 #include "Units.h"
127 #ifdef XP_MACOSX
128 # import <ApplicationServices/ApplicationServices.h>
129 #endif
131 namespace mozilla {
133 using namespace dom;
135 static const LayoutDeviceIntPoint kInvalidRefPoint =
136 LayoutDeviceIntPoint(-1, -1);
138 static uint32_t gMouseOrKeyboardEventCounter = 0;
139 static nsITimer* gUserInteractionTimer = nullptr;
140 static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
142 static const double kCursorLoadingTimeout = 1000; // ms
143 static AutoWeakFrame gLastCursorSourceFrame;
144 static TimeStamp gLastCursorUpdateTime;
145 static TimeStamp gTypingStartTime;
146 static TimeStamp gTypingEndTime;
147 static int32_t gTypingInteractionKeyPresses = 0;
148 static dom::InteractionData gTypingInteraction = {};
150 static inline int32_t RoundDown(double aDouble) {
151 return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble))
152 : static_cast<int32_t>(ceil(aDouble));
155 static bool IsSelectingLink(nsIFrame* aTargetFrame) {
156 if (!aTargetFrame) {
157 return false;
159 const nsFrameSelection* frameSel = aTargetFrame->GetConstFrameSelection();
160 if (!frameSel || !frameSel->GetDragState()) {
161 return false;
164 if (!nsContentUtils::GetClosestLinkInFlatTree(aTargetFrame->GetContent())) {
165 return false;
167 return true;
170 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
171 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
172 EventTarget* aRelatedTarget);
175 * Returns the common ancestor for mouseup purpose, given the
176 * current mouseup target and the previous mousedown target.
178 static nsINode* GetCommonAncestorForMouseUp(
179 nsINode* aCurrentMouseUpTarget, nsINode* aLastMouseDownTarget,
180 Maybe<FormControlType>& aLastMouseDownInputControlType) {
181 if (!aCurrentMouseUpTarget || !aLastMouseDownTarget) {
182 return nullptr;
185 if (aCurrentMouseUpTarget == aLastMouseDownTarget) {
186 return aCurrentMouseUpTarget;
189 // Build the chain of parents
190 AutoTArray<nsINode*, 30> parents1;
191 do {
192 parents1.AppendElement(aCurrentMouseUpTarget);
193 aCurrentMouseUpTarget = aCurrentMouseUpTarget->GetFlattenedTreeParentNode();
194 } while (aCurrentMouseUpTarget);
196 AutoTArray<nsINode*, 30> parents2;
197 do {
198 parents2.AppendElement(aLastMouseDownTarget);
199 if (aLastMouseDownTarget == parents1.LastElement()) {
200 break;
202 aLastMouseDownTarget = aLastMouseDownTarget->GetFlattenedTreeParentNode();
203 } while (aLastMouseDownTarget);
205 // Find where the parent chain differs
206 uint32_t pos1 = parents1.Length();
207 uint32_t pos2 = parents2.Length();
208 nsINode* parent = nullptr;
209 for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
210 nsINode* child1 = parents1.ElementAt(--pos1);
211 nsINode* child2 = parents2.ElementAt(--pos2);
212 if (child1 != child2) {
213 break;
216 // If the input control type is different between mouseup and mousedown,
217 // this is not a valid click.
218 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(child1)) {
219 if (aLastMouseDownInputControlType.isSome() &&
220 aLastMouseDownInputControlType.ref() != input->ControlType()) {
221 break;
224 parent = child1;
227 return parent;
230 /******************************************************************/
231 /* mozilla::UITimerCallback */
232 /******************************************************************/
234 class UITimerCallback final : public nsITimerCallback, public nsINamed {
235 public:
236 UITimerCallback() : mPreviousCount(0) {}
237 NS_DECL_ISUPPORTS
238 NS_DECL_NSITIMERCALLBACK
239 NS_DECL_NSINAMED
240 private:
241 ~UITimerCallback() = default;
242 uint32_t mPreviousCount;
245 NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)
247 // If aTimer is nullptr, this method always sends "user-interaction-inactive"
248 // notification.
249 NS_IMETHODIMP
250 UITimerCallback::Notify(nsITimer* aTimer) {
251 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
252 if (!obs) return NS_ERROR_FAILURE;
253 if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
254 gMouseOrKeyboardEventCounter = 0;
255 obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
256 if (gUserInteractionTimer) {
257 gUserInteractionTimer->Cancel();
258 NS_RELEASE(gUserInteractionTimer);
260 } else {
261 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
262 EventStateManager::UpdateUserActivityTimer();
264 if (XRE_IsParentProcess()) {
265 hal::BatteryInformation batteryInfo;
266 hal::GetCurrentBatteryInformation(&batteryInfo);
267 glean::power_battery::percentage_when_user_active.AccumulateSamples(
268 {uint64_t(batteryInfo.level() * 100)});
271 mPreviousCount = gMouseOrKeyboardEventCounter;
272 return NS_OK;
275 NS_IMETHODIMP
276 UITimerCallback::GetName(nsACString& aName) {
277 aName.AssignLiteral("UITimerCallback_timer");
278 return NS_OK;
281 /******************************************************************/
282 /* mozilla::OverOutElementsWrapper */
283 /******************************************************************/
285 OverOutElementsWrapper::OverOutElementsWrapper() : mLastOverFrame(nullptr) {}
287 OverOutElementsWrapper::~OverOutElementsWrapper() = default;
289 NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, mLastOverElement,
290 mFirstOverEventElement, mFirstOutEventElement)
291 NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
292 NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)
294 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
295 NS_INTERFACE_MAP_ENTRY(nsISupports)
296 NS_INTERFACE_MAP_END
298 /******************************************************************/
299 /* mozilla::EventStateManager */
300 /******************************************************************/
302 static uint32_t sESMInstanceCount = 0;
304 bool EventStateManager::sNormalLMouseEventInProcess = false;
305 int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
306 EventStateManager* EventStateManager::sActiveESM = nullptr;
307 EventStateManager* EventStateManager::sCursorSettingManager = nullptr;
308 AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
309 LayoutDeviceIntPoint EventStateManager::sPreLockPoint =
310 LayoutDeviceIntPoint(0, 0);
311 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
312 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
313 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
314 CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
315 nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
317 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::sInstance =
318 nullptr;
319 EventStateManager::DeltaAccumulator*
320 EventStateManager::DeltaAccumulator::sInstance = nullptr;
322 constexpr const StyleCursorKind kInvalidCursorKind =
323 static_cast<StyleCursorKind>(255);
325 EventStateManager::EventStateManager()
326 : mLockCursor(kInvalidCursorKind),
327 mCurrentTarget(nullptr),
328 // init d&d gesture state machine variables
329 mGestureDownPoint(0, 0),
330 mGestureModifiers(0),
331 mGestureDownButtons(0),
332 mPresContext(nullptr),
333 mShouldAlwaysUseLineDeltas(false),
334 mShouldAlwaysUseLineDeltasInitialized(false),
335 mGestureDownInTextControl(false),
336 mInTouchDrag(false),
337 m_haveShutdown(false) {
338 if (sESMInstanceCount == 0) {
339 gUserInteractionTimerCallback = new UITimerCallback();
340 if (gUserInteractionTimerCallback) NS_ADDREF(gUserInteractionTimerCallback);
341 UpdateUserActivityTimer();
343 ++sESMInstanceCount;
346 nsresult EventStateManager::UpdateUserActivityTimer() {
347 if (!gUserInteractionTimerCallback) return NS_OK;
349 if (!gUserInteractionTimer) {
350 gUserInteractionTimer = NS_NewTimer().take();
353 if (gUserInteractionTimer) {
354 gUserInteractionTimer->InitWithCallback(
355 gUserInteractionTimerCallback,
356 StaticPrefs::dom_events_user_interaction_interval(),
357 nsITimer::TYPE_ONE_SHOT);
359 return NS_OK;
362 nsresult EventStateManager::Init() {
363 nsCOMPtr<nsIObserverService> observerService =
364 mozilla::services::GetObserverService();
365 if (!observerService) return NS_ERROR_FAILURE;
367 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
369 return NS_OK;
372 bool EventStateManager::ShouldAlwaysUseLineDeltas() {
373 if (MOZ_UNLIKELY(!mShouldAlwaysUseLineDeltasInitialized)) {
374 mShouldAlwaysUseLineDeltasInitialized = true;
375 mShouldAlwaysUseLineDeltas =
376 !StaticPrefs::dom_event_wheel_deltaMode_lines_disabled();
377 if (!mShouldAlwaysUseLineDeltas && mDocument) {
378 if (nsIPrincipal* principal =
379 mDocument->GetPrincipalForPrefBasedHacks()) {
380 mShouldAlwaysUseLineDeltas = principal->IsURIInPrefList(
381 "dom.event.wheel-deltaMode-lines.always-enabled");
385 return mShouldAlwaysUseLineDeltas;
388 EventStateManager::~EventStateManager() {
389 ReleaseCurrentIMEContentObserver();
391 if (sActiveESM == this) {
392 sActiveESM = nullptr;
395 if (StaticPrefs::ui_click_hold_context_menus()) {
396 KillClickHoldTimer();
399 if (sCursorSettingManager == this) {
400 sCursorSettingManager = nullptr;
403 --sESMInstanceCount;
404 if (sESMInstanceCount == 0) {
405 WheelTransaction::Shutdown();
406 if (gUserInteractionTimerCallback) {
407 gUserInteractionTimerCallback->Notify(nullptr);
408 NS_RELEASE(gUserInteractionTimerCallback);
410 if (gUserInteractionTimer) {
411 gUserInteractionTimer->Cancel();
412 NS_RELEASE(gUserInteractionTimer);
414 WheelPrefs::Shutdown();
415 DeltaAccumulator::Shutdown();
418 if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
419 sDragOverContent = nullptr;
422 if (!m_haveShutdown) {
423 Shutdown();
425 // Don't remove from Observer service in Shutdown because Shutdown also
426 // gets called from xpcom shutdown observer. And we don't want to remove
427 // from the service in that case.
429 nsCOMPtr<nsIObserverService> observerService =
430 mozilla::services::GetObserverService();
431 if (observerService) {
432 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
437 nsresult EventStateManager::Shutdown() {
438 m_haveShutdown = true;
439 return NS_OK;
442 NS_IMETHODIMP
443 EventStateManager::Observe(nsISupports* aSubject, const char* aTopic,
444 const char16_t* someData) {
445 if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
446 Shutdown();
449 return NS_OK;
452 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
453 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
454 NS_INTERFACE_MAP_ENTRY(nsIObserver)
455 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
456 NS_INTERFACE_MAP_END
458 NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
459 NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)
461 NS_IMPL_CYCLE_COLLECTION_WEAK(EventStateManager, mCurrentTargetContent,
462 mGestureDownContent, mGestureDownFrameOwner,
463 mLastLeftMouseDownInfo.mLastMouseDownContent,
464 mLastMiddleMouseDownInfo.mLastMouseDownContent,
465 mLastRightMouseDownInfo.mLastMouseDownContent,
466 mActiveContent, mHoverContent, mURLTargetContent,
467 mPopoverPointerDownTarget, mMouseEnterLeaveHelper,
468 mPointersEnterLeaveHelper, mDocument,
469 mIMEContentObserver, mAccessKeys)
471 void EventStateManager::ReleaseCurrentIMEContentObserver() {
472 if (mIMEContentObserver) {
473 mIMEContentObserver->DisconnectFromEventStateManager();
475 mIMEContentObserver = nullptr;
478 void EventStateManager::OnStartToObserveContent(
479 IMEContentObserver* aIMEContentObserver) {
480 if (mIMEContentObserver == aIMEContentObserver) {
481 return;
483 ReleaseCurrentIMEContentObserver();
484 mIMEContentObserver = aIMEContentObserver;
487 void EventStateManager::OnStopObservingContent(
488 IMEContentObserver* aIMEContentObserver) {
489 aIMEContentObserver->DisconnectFromEventStateManager();
490 NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
491 mIMEContentObserver = nullptr;
494 void EventStateManager::TryToFlushPendingNotificationsToIME() {
495 if (mIMEContentObserver) {
496 mIMEContentObserver->TryToFlushPendingNotifications(true);
500 static bool IsMessageMouseUserActivity(EventMessage aMessage) {
501 return aMessage == eMouseMove || aMessage == eMouseUp ||
502 aMessage == eMouseDown || aMessage == eMouseAuxClick ||
503 aMessage == eMouseDoubleClick || aMessage == eMouseClick ||
504 aMessage == eMouseActivate || aMessage == eMouseLongTap;
507 static bool IsMessageGamepadUserActivity(EventMessage aMessage) {
508 return aMessage == eGamepadButtonDown || aMessage == eGamepadButtonUp ||
509 aMessage == eGamepadAxisMove;
512 // static
513 bool EventStateManager::IsKeyboardEventUserActivity(WidgetEvent* aEvent) {
514 // We ignore things that shouldn't cause popups, but also things that look
515 // like shortcut presses. In some obscure cases these may actually be
516 // website input, but any meaningful website will have other input anyway,
517 // and we can't very well tell whether shortcut input was supposed to be
518 // directed at chrome or the document.
520 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
521 // Access keys should be treated as page interaction.
522 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
523 return true;
525 if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
526 keyEvent->IsMeta() || keyEvent->IsAlt()) {
527 return false;
529 // Deal with function keys:
530 switch (keyEvent->mKeyNameIndex) {
531 case KEY_NAME_INDEX_F1:
532 case KEY_NAME_INDEX_F2:
533 case KEY_NAME_INDEX_F3:
534 case KEY_NAME_INDEX_F4:
535 case KEY_NAME_INDEX_F5:
536 case KEY_NAME_INDEX_F6:
537 case KEY_NAME_INDEX_F7:
538 case KEY_NAME_INDEX_F8:
539 case KEY_NAME_INDEX_F9:
540 case KEY_NAME_INDEX_F10:
541 case KEY_NAME_INDEX_F11:
542 case KEY_NAME_INDEX_F12:
543 case KEY_NAME_INDEX_F13:
544 case KEY_NAME_INDEX_F14:
545 case KEY_NAME_INDEX_F15:
546 case KEY_NAME_INDEX_F16:
547 case KEY_NAME_INDEX_F17:
548 case KEY_NAME_INDEX_F18:
549 case KEY_NAME_INDEX_F19:
550 case KEY_NAME_INDEX_F20:
551 case KEY_NAME_INDEX_F21:
552 case KEY_NAME_INDEX_F22:
553 case KEY_NAME_INDEX_F23:
554 case KEY_NAME_INDEX_F24:
555 return false;
556 default:
557 return true;
561 static void OnTypingInteractionEnded() {
562 // We don't consider a single keystroke to be typing.
563 if (gTypingInteractionKeyPresses > 1) {
564 gTypingInteraction.mInteractionCount += gTypingInteractionKeyPresses;
565 gTypingInteraction.mInteractionTimeInMilliseconds += static_cast<uint32_t>(
566 std::ceil((gTypingEndTime - gTypingStartTime).ToMilliseconds()));
569 gTypingInteractionKeyPresses = 0;
570 gTypingStartTime = TimeStamp();
571 gTypingEndTime = TimeStamp();
574 static void HandleKeyUpInteraction(WidgetKeyboardEvent* aKeyEvent) {
575 if (EventStateManager::IsKeyboardEventUserActivity(aKeyEvent)) {
576 TimeStamp now = TimeStamp::Now();
577 if (gTypingEndTime.IsNull()) {
578 gTypingEndTime = now;
580 TimeDuration delay = now - gTypingEndTime;
581 // Has it been too long since the last keystroke to be considered typing?
582 if (gTypingInteractionKeyPresses > 0 &&
583 delay >
584 TimeDuration::FromMilliseconds(
585 StaticPrefs::browser_places_interactions_typing_timeout_ms())) {
586 OnTypingInteractionEnded();
588 gTypingInteractionKeyPresses++;
589 if (gTypingStartTime.IsNull()) {
590 gTypingStartTime = now;
592 gTypingEndTime = now;
596 nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
597 WidgetEvent* aEvent,
598 nsIFrame* aTargetFrame,
599 nsIContent* aTargetContent,
600 nsEventStatus* aStatus,
601 nsIContent* aOverrideClickTarget) {
602 NS_ENSURE_ARG_POINTER(aStatus);
603 NS_ENSURE_ARG(aPresContext);
604 if (!aEvent) {
605 NS_ERROR("aEvent is null. This should never happen.");
606 return NS_ERROR_NULL_POINTER;
609 NS_WARNING_ASSERTION(
610 !aTargetFrame || !aTargetFrame->GetContent() ||
611 aTargetFrame->GetContent() == aTargetContent ||
612 aTargetFrame->GetContent()->GetFlattenedTreeParent() ==
613 aTargetContent ||
614 aTargetFrame->IsGeneratedContentFrame(),
615 "aTargetFrame should be related with aTargetContent");
616 #if DEBUG
617 if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
618 nsCOMPtr<nsIContent> targetContent;
619 aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
620 MOZ_ASSERT(aTargetContent == targetContent,
621 "Unexpected target for generated content frame!");
623 #endif
625 mCurrentTarget = aTargetFrame;
626 mCurrentTargetContent = nullptr;
628 // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
629 // a page when user is not active doesn't change the state to active.
630 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
631 if (aEvent->IsTrusted() &&
632 ((mouseEvent && mouseEvent->IsReal() &&
633 IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
634 aEvent->mClass == eWheelEventClass ||
635 aEvent->mClass == ePointerEventClass ||
636 aEvent->mClass == eTouchEventClass ||
637 aEvent->mClass == eKeyboardEventClass ||
638 (aEvent->mClass == eDragEventClass && aEvent->mMessage == eDrop) ||
639 IsMessageGamepadUserActivity(aEvent->mMessage))) {
640 if (gMouseOrKeyboardEventCounter == 0) {
641 nsCOMPtr<nsIObserverService> obs =
642 mozilla::services::GetObserverService();
643 if (obs) {
644 obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
645 UpdateUserActivityTimer();
648 ++gMouseOrKeyboardEventCounter;
650 nsCOMPtr<nsINode> node = aTargetContent;
651 if (node &&
652 ((aEvent->mMessage == eKeyUp && IsKeyboardEventUserActivity(aEvent)) ||
653 aEvent->mMessage == eMouseUp || aEvent->mMessage == eWheel ||
654 aEvent->mMessage == eTouchEnd || aEvent->mMessage == ePointerUp ||
655 aEvent->mMessage == eDrop)) {
656 Document* doc = node->OwnerDoc();
657 while (doc) {
658 doc->SetUserHasInteracted();
659 doc = nsContentUtils::IsChildOfSameType(doc)
660 ? doc->GetInProcessParentDocument()
661 : nullptr;
666 WheelTransaction::OnEvent(aEvent);
668 // Focus events don't necessarily need a frame.
669 if (!mCurrentTarget && !aTargetContent) {
670 NS_ERROR("mCurrentTarget and aTargetContent are null");
671 return NS_ERROR_NULL_POINTER;
673 #ifdef DEBUG
674 if (aEvent->HasDragEventMessage() && PointerLockManager::IsLocked()) {
675 NS_ASSERTION(PointerLockManager::IsLocked(),
676 "Pointer is locked. Drag events should be suppressed when "
677 "the pointer is locked.");
679 #endif
680 // Store last known screenPoint and clientPoint so pointer lock
681 // can use these values as constants.
682 if (aEvent->IsTrusted() &&
683 ((mouseEvent && mouseEvent->IsReal()) ||
684 aEvent->mClass == eWheelEventClass) &&
685 !PointerLockManager::IsLocked()) {
686 // XXX Probably doesn't matter much, but storing these in CSS pixels instead
687 // of device pixels means behavior can be a bit odd if you zoom while
688 // pointer-locked.
689 sLastScreenPoint =
690 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
691 .extract();
692 sLastClientPoint = Event::GetClientCoords(
693 aPresContext, aEvent, aEvent->mRefPoint, CSSIntPoint(0, 0));
696 *aStatus = nsEventStatus_eIgnore;
698 if (aEvent->mClass == eQueryContentEventClass) {
699 HandleQueryContentEvent(aEvent->AsQueryContentEvent());
700 return NS_OK;
703 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
704 if (touchEvent && mInTouchDrag) {
705 if (touchEvent->mMessage == eTouchMove) {
706 GenerateDragGesture(aPresContext, touchEvent);
707 } else {
708 mInTouchDrag = false;
709 StopTrackingDragGesture(true);
713 switch (aEvent->mMessage) {
714 case eContextMenu:
715 if (PointerLockManager::IsLocked()) {
716 return NS_ERROR_DOM_INVALID_STATE_ERR;
718 break;
719 case eMouseTouchDrag:
720 mInTouchDrag = true;
721 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
722 break;
723 case eMouseDown: {
724 switch (mouseEvent->mButton) {
725 case MouseButton::ePrimary:
726 BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
727 mLastLeftMouseDownInfo.mClickCount = mouseEvent->mClickCount;
728 SetClickCount(mouseEvent, aStatus);
729 sNormalLMouseEventInProcess = true;
730 break;
731 case MouseButton::eMiddle:
732 mLastMiddleMouseDownInfo.mClickCount = mouseEvent->mClickCount;
733 SetClickCount(mouseEvent, aStatus);
734 break;
735 case MouseButton::eSecondary:
736 mLastRightMouseDownInfo.mClickCount = mouseEvent->mClickCount;
737 SetClickCount(mouseEvent, aStatus);
738 break;
740 NotifyTargetUserActivation(aEvent, aTargetContent);
741 break;
743 case eMouseUp: {
744 switch (mouseEvent->mButton) {
745 case MouseButton::ePrimary:
746 if (StaticPrefs::ui_click_hold_context_menus()) {
747 KillClickHoldTimer();
749 mInTouchDrag = false;
750 StopTrackingDragGesture(true);
751 sNormalLMouseEventInProcess = false;
752 // then fall through...
753 [[fallthrough]];
754 case MouseButton::eSecondary:
755 case MouseButton::eMiddle:
756 RefPtr<EventStateManager> esm =
757 ESMFromContentOrThis(aOverrideClickTarget);
758 esm->SetClickCount(mouseEvent, aStatus, aOverrideClickTarget);
759 break;
761 break;
763 case eMouseEnterIntoWidget:
764 PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
765 // In some cases on e10s eMouseEnterIntoWidget
766 // event was sent twice into child process of content.
767 // (From specific widget code (sending is not permanent) and
768 // from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
769 // IsCrossProcessForwardingStopped() helps to suppress sending accidental
770 // event from widget code.
771 aEvent->StopCrossProcessForwarding();
772 break;
773 case eMouseExitFromWidget:
774 // If this is a remote frame, we receive eMouseExitFromWidget from the
775 // parent the mouse exits our content. Since the parent may update the
776 // cursor while the mouse is outside our frame, and since PuppetWidget
777 // caches the current cursor internally, re-entering our content (say from
778 // over a window edge) wont update the cursor if the cached value and the
779 // current cursor match. So when the mouse exits a remote frame, clear the
780 // cached widget cursor so a proper update will occur when the mouse
781 // re-enters.
782 if (XRE_IsContentProcess()) {
783 ClearCachedWidgetCursor(mCurrentTarget);
786 // IsCrossProcessForwardingStopped() helps to suppress double event
787 // sending into process of content. For more information see comment
788 // above, at eMouseEnterIntoWidget case.
789 aEvent->StopCrossProcessForwarding();
791 // If the event is not a top-level window or puppet widget exit, then it's
792 // not really an exit --- we may have traversed widget boundaries but
793 // we're still in our toplevel window or puppet widget.
794 if (mouseEvent->mExitFrom.value() !=
795 WidgetMouseEvent::ePlatformTopLevel &&
796 mouseEvent->mExitFrom.value() != WidgetMouseEvent::ePuppet) {
797 // Treat it as a synthetic move so we don't generate spurious
798 // "exit" or "move" events. Any necessary "out" or "over" events
799 // will be generated by GenerateMouseEnterExit
800 mouseEvent->mMessage = eMouseMove;
801 mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
802 // then fall through...
803 } else {
804 MOZ_ASSERT_IF(XRE_IsParentProcess(),
805 mouseEvent->mExitFrom.value() ==
806 WidgetMouseEvent::ePlatformTopLevel);
807 MOZ_ASSERT_IF(XRE_IsContentProcess(), mouseEvent->mExitFrom.value() ==
808 WidgetMouseEvent::ePuppet);
809 // We should synthetize corresponding pointer events
810 GeneratePointerEnterExit(ePointerLeave, mouseEvent);
811 GenerateMouseEnterExit(mouseEvent);
812 // This is really an exit and should stop here
813 aEvent->mMessage = eVoidEvent;
814 break;
816 [[fallthrough]];
817 case eMouseMove:
818 case ePointerDown:
819 if (aEvent->mMessage == ePointerDown) {
820 PointerEventHandler::UpdateActivePointerState(mouseEvent,
821 aTargetContent);
822 PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
823 if (mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
824 NotifyTargetUserActivation(aEvent, aTargetContent);
827 LightDismissOpenPopovers(aEvent, aTargetContent);
829 [[fallthrough]];
830 case ePointerMove: {
831 if (!mInTouchDrag &&
832 PointerEventHandler::IsDragAndDropEnabled(*mouseEvent)) {
833 GenerateDragGesture(aPresContext, mouseEvent);
835 // on the Mac, GenerateDragGesture() may not return until the drag
836 // has completed and so |aTargetFrame| may have been deleted (moving
837 // a bookmark, for example). If this is the case, however, we know
838 // that ClearFrameRefs() has been called and it cleared out
839 // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
840 // into UpdateCursor().
841 UpdateCursor(aPresContext, mouseEvent, mCurrentTarget, aStatus);
843 UpdateLastRefPointOfMouseEvent(mouseEvent);
844 if (PointerLockManager::IsLocked()) {
845 ResetPointerToWindowCenterWhilePointerLocked(mouseEvent);
847 UpdateLastPointerPosition(mouseEvent);
849 GenerateMouseEnterExit(mouseEvent);
850 // Flush pending layout changes, so that later mouse move events
851 // will go to the right nodes.
852 FlushLayout(aPresContext);
853 break;
855 case ePointerUp:
856 LightDismissOpenPopovers(aEvent, aTargetContent);
857 break;
858 case ePointerGotCapture:
859 GenerateMouseEnterExit(mouseEvent);
860 break;
861 case eDragStart:
862 if (StaticPrefs::ui_click_hold_context_menus()) {
863 // an external drag gesture event came in, not generated internally
864 // by Gecko. Make sure we get rid of the click-hold timer.
865 KillClickHoldTimer();
867 break;
868 case eDragOver: {
869 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
870 MOZ_ASSERT(dragEvent);
871 if (dragEvent->mFlags.mIsSynthesizedForTests) {
872 dragEvent->InitDropEffectForTests();
874 // Send the enter/exit events before eDrop.
875 GenerateDragDropEnterExit(aPresContext, dragEvent);
876 break;
878 case eDrop:
879 if (aEvent->mFlags.mIsSynthesizedForTests) {
880 MOZ_ASSERT(aEvent->AsDragEvent());
881 aEvent->AsDragEvent()->InitDropEffectForTests();
883 break;
885 case eKeyPress: {
886 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
887 if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
888 keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
889 // If the eKeyPress event will be sent to a remote process, this
890 // process needs to wait reply from the remote process for checking if
891 // preceding eKeyDown event is consumed. If preceding eKeyDown event
892 // is consumed in the remote process, BrowserChild won't send the event
893 // back to this process. So, only when this process receives a reply
894 // eKeyPress event in BrowserParent, we should handle accesskey in this
895 // process.
896 if (IsTopLevelRemoteTarget(GetFocusedElement())) {
897 // However, if there is no accesskey target for the key combination,
898 // we don't need to wait reply from the remote process. Otherwise,
899 // Mark the event as waiting reply from remote process and stop
900 // propagation in this process.
901 if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
902 keyEvent->StopPropagation();
903 keyEvent->MarkAsWaitingReplyFromRemoteProcess();
906 // If the event target is in this process, we can handle accesskey now
907 // since if preceding eKeyDown event was consumed, eKeyPress event
908 // won't be dispatched by widget. So, coming eKeyPress event means
909 // that the preceding eKeyDown event wasn't consumed in this case.
910 else {
911 AutoTArray<uint32_t, 10> accessCharCodes;
912 keyEvent->GetAccessKeyCandidates(accessCharCodes);
914 if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
915 *aStatus = nsEventStatus_eConsumeNoDefault;
920 // then fall through...
921 [[fallthrough]];
922 case eKeyDown:
923 if (aEvent->mMessage == eKeyDown) {
924 NotifyTargetUserActivation(aEvent, aTargetContent);
926 [[fallthrough]];
927 case eKeyUp: {
928 Element* element = GetFocusedElement();
929 if (element) {
930 mCurrentTargetContent = element;
933 // NOTE: Don't refer TextComposition::IsComposing() since UI Events
934 // defines that KeyboardEvent.isComposing is true when it's
935 // dispatched after compositionstart and compositionend.
936 // TextComposition::IsComposing() is false even before
937 // compositionend if there is no composing string.
938 // And also don't expose other document's composition state.
939 // A native IME context is typically shared by multiple documents.
940 // So, don't use GetTextCompositionFor(nsIWidget*) here.
941 RefPtr<TextComposition> composition =
942 IMEStateManager::GetTextCompositionFor(aPresContext);
943 aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
945 // Widget may need to perform default action for specific keyboard
946 // event if it's not consumed. In this case, widget has already marked
947 // the event as "waiting reply from remote process". However, we need
948 // to reset it if the target (focused content) isn't in a remote process
949 // because PresShell needs to check if it's marked as so before
950 // dispatching events into the DOM tree.
951 if (aEvent->IsWaitingReplyFromRemoteProcess() &&
952 !aEvent->PropagationStopped() && !IsTopLevelRemoteTarget(element)) {
953 aEvent->ResetWaitingReplyFromRemoteProcessState();
955 } break;
956 case eWheel:
957 case eWheelOperationStart:
958 case eWheelOperationEnd: {
959 NS_ASSERTION(aEvent->IsTrusted(),
960 "Untrusted wheel event shouldn't be here");
961 using DeltaModeCheckingState = WidgetWheelEvent::DeltaModeCheckingState;
963 if (Element* element = GetFocusedElement()) {
964 mCurrentTargetContent = element;
967 if (aEvent->mMessage != eWheel) {
968 break;
971 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
972 WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);
974 // If we won't dispatch a DOM event for this event, nothing to do anymore.
975 if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
976 break;
979 if (StaticPrefs::dom_event_wheel_deltaMode_lines_always_disabled()) {
980 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unchecked;
981 } else if (ShouldAlwaysUseLineDeltas()) {
982 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Checked;
983 } else {
984 wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unknown;
987 // Init lineOrPageDelta values for line scroll events for some devices
988 // on some platforms which might dispatch wheel events which don't
989 // have lineOrPageDelta values. And also, if delta values are
990 // customized by prefs, this recomputes them.
991 DeltaAccumulator::GetInstance()->InitLineOrPageDelta(aTargetFrame, this,
992 wheelEvent);
993 } break;
994 case eSetSelection: {
995 RefPtr<Element> focuedElement = GetFocusedElement();
996 IMEStateManager::HandleSelectionEvent(aPresContext, focuedElement,
997 aEvent->AsSelectionEvent());
998 break;
1000 case eContentCommandCut:
1001 case eContentCommandCopy:
1002 case eContentCommandPaste:
1003 case eContentCommandDelete:
1004 case eContentCommandUndo:
1005 case eContentCommandRedo:
1006 case eContentCommandPasteTransferable:
1007 case eContentCommandLookUpDictionary:
1008 DoContentCommandEvent(aEvent->AsContentCommandEvent());
1009 break;
1010 case eContentCommandInsertText:
1011 DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
1012 break;
1013 case eContentCommandScroll:
1014 DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
1015 break;
1016 case eCompositionStart:
1017 if (aEvent->IsTrusted()) {
1018 // If the event is trusted event, set the selected text to data of
1019 // composition event.
1020 WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
1021 WidgetQueryContentEvent querySelectedTextEvent(
1022 true, eQuerySelectedText, compositionEvent->mWidget);
1023 HandleQueryContentEvent(&querySelectedTextEvent);
1024 if (querySelectedTextEvent.FoundSelection()) {
1025 compositionEvent->mData = querySelectedTextEvent.mReply->DataRef();
1027 NS_ASSERTION(querySelectedTextEvent.Succeeded(),
1028 "Failed to get selected text");
1030 break;
1031 case eTouchStart:
1032 SetGestureDownPoint(aEvent->AsTouchEvent());
1033 break;
1034 case eTouchEnd:
1035 NotifyTargetUserActivation(aEvent, aTargetContent);
1036 break;
1037 default:
1038 break;
1040 return NS_OK;
1043 void EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
1044 nsIContent* aTargetContent) {
1045 if (!aEvent->IsTrusted()) {
1046 return;
1049 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1050 if (mouseEvent && !mouseEvent->IsReal()) {
1051 return;
1054 nsCOMPtr<nsINode> node = aTargetContent;
1055 if (!node) {
1056 return;
1059 Document* doc = node->OwnerDoc();
1060 if (!doc) {
1061 return;
1064 // Don't gesture activate for key events for keys which are likely
1065 // to be interaction with the browser, OS.
1066 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
1067 if (keyEvent && !keyEvent->CanUserGestureActivateTarget()) {
1068 return;
1071 // Touch gestures that end outside the drag target were touches that turned
1072 // into scroll/pan/swipe actions. We don't want to gesture activate on such
1073 // actions, we want to only gesture activate on touches that are taps.
1074 // That is, touches that end in roughly the same place that they started.
1075 if (aEvent->mMessage == eTouchEnd && aEvent->AsTouchEvent() &&
1076 IsEventOutsideDragThreshold(aEvent->AsTouchEvent())) {
1077 return;
1080 // Do not treat the click on scrollbar as a user interaction with the web
1081 // content.
1082 if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
1083 (aEvent->mMessage == eMouseDown || aEvent->mMessage == ePointerDown) &&
1084 aTargetContent->IsInNativeAnonymousSubtree()) {
1085 nsIContent* current = aTargetContent;
1086 do {
1087 nsIContent* root = current->GetClosestNativeAnonymousSubtreeRoot();
1088 if (!root) {
1089 break;
1091 if (root->IsXULElement(nsGkAtoms::scrollbar)) {
1092 return;
1094 current = root->GetParent();
1095 } while (current);
1098 MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
1099 aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
1100 doc->NotifyUserGestureActivation();
1103 // https://html.spec.whatwg.org/multipage/popover.html#popover-light-dismiss
1104 void EventStateManager::LightDismissOpenPopovers(WidgetEvent* aEvent,
1105 nsIContent* aTargetContent) {
1106 MOZ_ASSERT(aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp,
1107 "Light dismiss must be called for pointer up/down only");
1109 if (!StaticPrefs::dom_element_popover_enabled() || !aEvent->IsTrusted() ||
1110 !aTargetContent) {
1111 return;
1114 Element* topmostPopover = aTargetContent->OwnerDoc()->GetTopmostAutoPopover();
1115 if (!topmostPopover) {
1116 return;
1119 // Pointerdown: set document's popover pointerdown target to the result of
1120 // running topmost clicked popover given target.
1121 if (aEvent->mMessage == ePointerDown) {
1122 mPopoverPointerDownTarget = aTargetContent->GetTopmostClickedPopover();
1123 return;
1126 // Pointerup: hide open popovers.
1127 RefPtr<nsINode> ancestor = aTargetContent->GetTopmostClickedPopover();
1128 bool sameTarget = mPopoverPointerDownTarget == ancestor;
1129 mPopoverPointerDownTarget = nullptr;
1130 if (!sameTarget) {
1131 return;
1134 if (!ancestor) {
1135 ancestor = aTargetContent->OwnerDoc();
1137 RefPtr<Document> doc(ancestor->OwnerDoc());
1138 doc->HideAllPopoversUntil(*ancestor, false, true);
1141 already_AddRefed<EventStateManager> EventStateManager::ESMFromContentOrThis(
1142 nsIContent* aContent) {
1143 if (aContent) {
1144 PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
1145 if (presShell) {
1146 nsPresContext* prescontext = presShell->GetPresContext();
1147 if (prescontext) {
1148 RefPtr<EventStateManager> esm = prescontext->EventStateManager();
1149 if (esm) {
1150 return esm.forget();
1156 RefPtr<EventStateManager> esm = this;
1157 return esm.forget();
1160 EventStateManager::LastMouseDownInfo& EventStateManager::GetLastMouseDownInfo(
1161 int16_t aButton) {
1162 switch (aButton) {
1163 case MouseButton::ePrimary:
1164 return mLastLeftMouseDownInfo;
1165 case MouseButton::eMiddle:
1166 return mLastMiddleMouseDownInfo;
1167 case MouseButton::eSecondary:
1168 return mLastRightMouseDownInfo;
1169 default:
1170 MOZ_ASSERT_UNREACHABLE("This button shouldn't use this method");
1171 return mLastLeftMouseDownInfo;
1175 void EventStateManager::HandleQueryContentEvent(
1176 WidgetQueryContentEvent* aEvent) {
1177 switch (aEvent->mMessage) {
1178 case eQuerySelectedText:
1179 case eQueryTextContent:
1180 case eQueryCaretRect:
1181 case eQueryTextRect:
1182 case eQueryEditorRect:
1183 if (!IsTargetCrossProcess(aEvent)) {
1184 break;
1186 // Will not be handled locally, remote the event
1187 GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
1188 return;
1189 // Following events have not been supported in e10s mode yet.
1190 case eQueryContentState:
1191 case eQuerySelectionAsTransferable:
1192 case eQueryCharacterAtPoint:
1193 case eQueryDOMWidgetHittest:
1194 case eQueryTextRectArray:
1195 break;
1196 default:
1197 return;
1200 // If there is an IMEContentObserver, we need to handle QueryContentEvent
1201 // with it.
1202 if (mIMEContentObserver) {
1203 RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
1204 contentObserver->HandleQueryContentEvent(aEvent);
1205 return;
1208 ContentEventHandler handler(mPresContext);
1209 handler.HandleQueryContentEvent(aEvent);
1212 static AccessKeyType GetAccessKeyTypeFor(nsISupports* aDocShell) {
1213 nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
1214 if (!treeItem) {
1215 return AccessKeyType::eNone;
1218 switch (treeItem->ItemType()) {
1219 case nsIDocShellTreeItem::typeChrome:
1220 return AccessKeyType::eChrome;
1221 case nsIDocShellTreeItem::typeContent:
1222 return AccessKeyType::eContent;
1223 default:
1224 return AccessKeyType::eNone;
1228 static bool IsAccessKeyTarget(Element* aElement, nsAString& aKey) {
1229 // Use GetAttr because we want Unicode case=insensitive matching
1230 // XXXbz shouldn't this be case-sensitive, per spec?
1231 nsString contentKey;
1232 if (!aElement || !aElement->GetAttr(nsGkAtoms::accesskey, contentKey) ||
1233 !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator)) {
1234 return false;
1237 if (!aElement->IsXULElement()) {
1238 return true;
1241 // For XUL we do visibility checks.
1242 nsIFrame* frame = aElement->GetPrimaryFrame();
1243 if (!frame) {
1244 return false;
1247 if (frame->IsFocusable()) {
1248 return true;
1251 if (!frame->IsVisibleConsideringAncestors()) {
1252 return false;
1255 // XUL controls can be activated.
1256 nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl();
1257 if (control) {
1258 return true;
1261 // XUL label elements are never focusable, so we need to check for them
1262 // explicitly before giving up.
1263 if (aElement->IsXULElement(nsGkAtoms::label)) {
1264 return true;
1267 return false;
1270 bool EventStateManager::CheckIfEventMatchesAccessKey(
1271 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext) {
1272 AutoTArray<uint32_t, 10> accessCharCodes;
1273 aEvent->GetAccessKeyCandidates(accessCharCodes);
1274 return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, accessCharCodes,
1275 nullptr, eAccessKeyProcessingNormal,
1276 false);
1279 bool EventStateManager::LookForAccessKeyAndExecute(
1280 nsTArray<uint32_t>& aAccessCharCodes, bool aIsTrustedEvent, bool aIsRepeat,
1281 bool aExecute) {
1282 int32_t count, start = -1;
1283 if (Element* focusedElement = GetFocusedElement()) {
1284 start = mAccessKeys.IndexOf(focusedElement);
1285 if (start == -1 && focusedElement->IsInNativeAnonymousSubtree()) {
1286 start = mAccessKeys.IndexOf(Element::FromNodeOrNull(
1287 focusedElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()));
1290 RefPtr<Element> element;
1291 int32_t length = mAccessKeys.Count();
1292 for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
1293 uint32_t ch = aAccessCharCodes[i];
1294 nsAutoString accessKey;
1295 AppendUCS4ToUTF16(ch, accessKey);
1296 for (count = 1; count <= length; ++count) {
1297 // mAccessKeys always stores Element instances.
1298 MOZ_DIAGNOSTIC_ASSERT(length == mAccessKeys.Count());
1299 element = mAccessKeys[(start + count) % length];
1300 if (IsAccessKeyTarget(element, accessKey)) {
1301 if (!aExecute) {
1302 return true;
1304 Document* doc = element->OwnerDoc();
1305 const bool shouldActivate = [&] {
1306 if (!StaticPrefs::accessibility_accesskeycausesactivation()) {
1307 return false;
1309 if (aIsRepeat && nsContentUtils::IsChromeDoc(doc)) {
1310 return false;
1313 // XXXedgar, Bug 1700646, maybe we could use other data structure to
1314 // make searching target with same accesskey easier, and current setup
1315 // could not ensure we cycle the target with tree order.
1316 int32_t j = 0;
1317 while (++j < length) {
1318 Element* el = mAccessKeys[(start + count + j) % length];
1319 if (IsAccessKeyTarget(el, accessKey)) {
1320 return false;
1323 return true;
1324 }();
1326 // TODO(bug 1641171): This shouldn't be needed if we considered the
1327 // accesskey combination properly.
1328 if (aIsTrustedEvent) {
1329 doc->NotifyUserGestureActivation();
1332 auto result =
1333 element->PerformAccesskey(shouldActivate, aIsTrustedEvent);
1334 if (result.isOk()) {
1335 if (result.unwrap() && aIsTrustedEvent) {
1336 // If this is a child process, inform the parent that we want the
1337 // focus, but pass false since we don't want to change the window
1338 // order.
1339 nsIDocShell* docShell = mPresContext->GetDocShell();
1340 nsCOMPtr<nsIBrowserChild> child =
1341 docShell ? docShell->GetBrowserChild() : nullptr;
1342 if (child) {
1343 child->SendRequestFocus(false, CallerType::System);
1346 return true;
1351 return false;
1354 // static
1355 void EventStateManager::GetAccessKeyLabelPrefix(Element* aElement,
1356 nsAString& aPrefix) {
1357 aPrefix.Truncate();
1358 nsAutoString separator, modifierText;
1359 nsContentUtils::GetModifierSeparatorText(separator);
1361 AccessKeyType accessKeyType =
1362 GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
1363 if (accessKeyType == AccessKeyType::eNone) {
1364 return;
1366 Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
1367 if (modifiers == MODIFIER_NONE) {
1368 return;
1371 if (modifiers & MODIFIER_CONTROL) {
1372 nsContentUtils::GetControlText(modifierText);
1373 aPrefix.Append(modifierText + separator);
1375 if (modifiers & MODIFIER_META) {
1376 nsContentUtils::GetCommandOrWinText(modifierText);
1377 aPrefix.Append(modifierText + separator);
1379 if (modifiers & MODIFIER_ALT) {
1380 nsContentUtils::GetAltText(modifierText);
1381 aPrefix.Append(modifierText + separator);
1383 if (modifiers & MODIFIER_SHIFT) {
1384 nsContentUtils::GetShiftText(modifierText);
1385 aPrefix.Append(modifierText + separator);
1389 struct MOZ_STACK_CLASS AccessKeyInfo {
1390 WidgetKeyboardEvent* event;
1391 nsTArray<uint32_t>& charCodes;
1393 AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes)
1394 : event(aEvent), charCodes(aCharCodes) {}
1397 bool EventStateManager::WalkESMTreeToHandleAccessKey(
1398 WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext,
1399 nsTArray<uint32_t>& aAccessCharCodes, nsIDocShellTreeItem* aBubbledFrom,
1400 ProcessingAccessKeyState aAccessKeyState, bool aExecute) {
1401 EnsureDocument(mPresContext);
1402 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
1403 if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
1404 return false;
1406 AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
1407 if (accessKeyType == AccessKeyType::eNone) {
1408 return false;
1410 // Alt or other accesskey modifier is down, we may need to do an accesskey.
1411 if (mAccessKeys.Count() > 0 &&
1412 aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
1413 // Someone registered an accesskey. Find and activate it.
1414 if (LookForAccessKeyAndExecute(aAccessCharCodes, aEvent->IsTrusted(),
1415 aEvent->mIsRepeat, aExecute)) {
1416 return true;
1420 int32_t childCount;
1421 docShell->GetInProcessChildCount(&childCount);
1422 for (int32_t counter = 0; counter < childCount; counter++) {
1423 // Not processing the child which bubbles up the handling
1424 nsCOMPtr<nsIDocShellTreeItem> subShellItem;
1425 docShell->GetInProcessChildAt(counter, getter_AddRefs(subShellItem));
1426 if (aAccessKeyState == eAccessKeyProcessingUp &&
1427 subShellItem == aBubbledFrom) {
1428 continue;
1431 nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
1432 if (subDS && IsShellVisible(subDS)) {
1433 // Guarantee subPresShell lifetime while we're handling access key
1434 // since somebody may assume that it won't be deleted before the
1435 // corresponding nsPresContext and EventStateManager.
1436 RefPtr<PresShell> subPresShell = subDS->GetPresShell();
1438 // Docshells need not have a presshell (eg. display:none
1439 // iframes, docshells in transition between documents, etc).
1440 if (!subPresShell) {
1441 // Oh, well. Just move on to the next child
1442 continue;
1445 RefPtr<nsPresContext> subPresContext = subPresShell->GetPresContext();
1447 RefPtr<EventStateManager> esm =
1448 static_cast<EventStateManager*>(subPresContext->EventStateManager());
1450 if (esm && esm->WalkESMTreeToHandleAccessKey(
1451 aEvent, subPresContext, aAccessCharCodes, nullptr,
1452 eAccessKeyProcessingDown, aExecute)) {
1453 return true;
1456 } // if end . checking all sub docshell ends here.
1458 // bubble up the process to the parent docshell if necessary
1459 if (eAccessKeyProcessingDown != aAccessKeyState) {
1460 nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
1461 docShell->GetInProcessParent(getter_AddRefs(parentShellItem));
1462 nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
1463 if (parentDS) {
1464 // Guarantee parentPresShell lifetime while we're handling access key
1465 // since somebody may assume that it won't be deleted before the
1466 // corresponding nsPresContext and EventStateManager.
1467 RefPtr<PresShell> parentPresShell = parentDS->GetPresShell();
1468 NS_ASSERTION(parentPresShell,
1469 "Our PresShell exists but the parent's does not?");
1471 RefPtr<nsPresContext> parentPresContext =
1472 parentPresShell->GetPresContext();
1473 NS_ASSERTION(parentPresContext, "PresShell without PresContext");
1475 RefPtr<EventStateManager> esm = static_cast<EventStateManager*>(
1476 parentPresContext->EventStateManager());
1477 if (esm && esm->WalkESMTreeToHandleAccessKey(
1478 aEvent, parentPresContext, aAccessCharCodes, docShell,
1479 eAccessKeyProcessingDown, aExecute)) {
1480 return true;
1483 } // if end. bubble up process
1485 // If the content access key modifier is pressed, try remote children
1486 if (aExecute &&
1487 aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
1488 mDocument && mDocument->GetWindow()) {
1489 // If the focus is currently on a node with a BrowserParent, the key event
1490 // should've gotten forwarded to the child process and HandleAccessKey
1491 // called from there.
1492 if (BrowserParent::GetFrom(GetFocusedElement())) {
1493 // If access key may be only in remote contents, this method won't handle
1494 // access key synchronously. In this case, only reply event should reach
1495 // here.
1496 MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
1497 !aEvent->IsWaitingReplyFromRemoteProcess());
1499 // If focus is somewhere else, then we need to check the remote children.
1500 // However, if the event has already been handled in a remote process,
1501 // then, focus is moved from the remote process after posting the event.
1502 // In such case, we shouldn't retry to handle access keys in remote
1503 // processes.
1504 else if (!aEvent->IsHandledInRemoteProcess()) {
1505 AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
1506 nsContentUtils::CallOnAllRemoteChildren(
1507 mDocument->GetWindow(),
1508 [&accessKeyInfo](BrowserParent* aBrowserParent) -> CallState {
1509 // Only forward accesskeys for the active tab.
1510 if (aBrowserParent->GetDocShellIsActive()) {
1511 // Even if there is no target for the accesskey in this process,
1512 // the event may match with a content accesskey. If so, the
1513 // keyboard event should be handled with reply event for
1514 // preventing double action. (e.g., Alt+Shift+F on Windows may
1515 // focus a content in remote and open "File" menu.)
1516 accessKeyInfo.event->StopPropagation();
1517 accessKeyInfo.event->MarkAsWaitingReplyFromRemoteProcess();
1518 aBrowserParent->HandleAccessKey(*accessKeyInfo.event,
1519 accessKeyInfo.charCodes);
1520 return CallState::Stop;
1523 return CallState::Continue;
1528 return false;
1529 } // end of HandleAccessKey
1531 static BrowserParent* GetBrowserParentAncestor(BrowserParent* aBrowserParent) {
1532 MOZ_ASSERT(aBrowserParent);
1534 BrowserBridgeParent* bbp = aBrowserParent->GetBrowserBridgeParent();
1535 if (!bbp) {
1536 return nullptr;
1539 return bbp->Manager();
1542 static void DispatchCrossProcessMouseExitEvents(WidgetMouseEvent* aMouseEvent,
1543 BrowserParent* aRemoteTarget,
1544 BrowserParent* aStopAncestor,
1545 bool aIsReallyExit) {
1546 MOZ_ASSERT(aMouseEvent);
1547 MOZ_ASSERT(aRemoteTarget);
1548 MOZ_ASSERT(aRemoteTarget != aStopAncestor);
1549 MOZ_ASSERT_IF(aStopAncestor, nsContentUtils::GetCommonBrowserParentAncestor(
1550 aRemoteTarget, aStopAncestor));
1552 while (aRemoteTarget != aStopAncestor) {
1553 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1554 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
1555 aMouseEvent->mRelatedTarget);
1556 mouseExitEvent->mExitFrom =
1557 Some(aIsReallyExit ? WidgetMouseEvent::ePuppet
1558 : WidgetMouseEvent::ePuppetParentToPuppetChild);
1559 aRemoteTarget->SendRealMouseEvent(*mouseExitEvent);
1561 aRemoteTarget = GetBrowserParentAncestor(aRemoteTarget);
1565 void EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
1566 BrowserParent* aRemoteTarget,
1567 nsEventStatus* aStatus) {
1568 MOZ_ASSERT(aEvent);
1569 MOZ_ASSERT(aRemoteTarget);
1570 MOZ_ASSERT(aStatus);
1572 BrowserParent* remote = aRemoteTarget;
1574 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1575 bool isContextMenuKey = mouseEvent && mouseEvent->IsContextMenuKeyEvent();
1576 if (aEvent->mClass == eKeyboardEventClass || isContextMenuKey) {
1577 // APZ attaches a LayersId to hit-testable events, for keyboard events,
1578 // we use focus.
1579 BrowserParent* preciseRemote = BrowserParent::GetFocused();
1580 if (preciseRemote) {
1581 remote = preciseRemote;
1583 // else there is a race between layout and focus tracking,
1584 // so fall back to delivering the event to the topmost child process.
1585 } else if (aEvent->mLayersId.IsValid()) {
1586 BrowserParent* preciseRemote =
1587 BrowserParent::GetBrowserParentFromLayersId(aEvent->mLayersId);
1588 if (preciseRemote) {
1589 remote = preciseRemote;
1591 // else there is a race between APZ and the LayersId to BrowserParent
1592 // mapping, so fall back to delivering the event to the topmost child
1593 // process.
1596 switch (aEvent->mClass) {
1597 case eMouseEventClass: {
1598 BrowserParent* oldRemote = BrowserParent::GetLastMouseRemoteTarget();
1600 // If this is a eMouseExitFromWidget event, need to redirect the event to
1601 // the last remote and and notify all its ancestors about the exit, if
1602 // any.
1603 if (mouseEvent->mMessage == eMouseExitFromWidget) {
1604 MOZ_ASSERT(mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet);
1605 MOZ_ASSERT(mouseEvent->mReason == WidgetMouseEvent::eReal);
1606 MOZ_ASSERT(!mouseEvent->mLayersId.IsValid());
1607 MOZ_ASSERT(remote->GetBrowserHost());
1609 if (oldRemote && oldRemote != remote) {
1610 Unused << NS_WARN_IF(nsContentUtils::GetCommonBrowserParentAncestor(
1611 remote, oldRemote) != remote);
1612 remote = oldRemote;
1615 DispatchCrossProcessMouseExitEvents(mouseEvent, remote, nullptr, true);
1616 return;
1619 if (BrowserParent* pointerLockedRemote =
1620 PointerLockManager::GetLockedRemoteTarget()) {
1621 remote = pointerLockedRemote;
1622 } else if (BrowserParent* pointerCapturedRemote =
1623 PointerEventHandler::GetPointerCapturingRemoteTarget(
1624 mouseEvent->pointerId)) {
1625 remote = pointerCapturedRemote;
1626 } else if (BrowserParent* capturingRemote =
1627 PresShell::GetCapturingRemoteTarget()) {
1628 remote = capturingRemote;
1631 // If a mouse is over a remote target A, and then moves to
1632 // remote target B, we'd deliver the event directly to remote target B
1633 // after the moving, A would never get notified that the mouse left.
1634 // So we generate a exit event to notify A after the move.
1635 // XXXedgar, if the synthesized mouse events could deliver to the correct
1636 // process directly (see
1637 // https://bugzilla.mozilla.org/show_bug.cgi?id=1549355), we probably
1638 // don't need to check mReason then.
1639 if (mouseEvent->mReason == WidgetMouseEvent::eReal &&
1640 remote != oldRemote) {
1641 MOZ_ASSERT(mouseEvent->mMessage != eMouseExitFromWidget);
1642 if (oldRemote) {
1643 BrowserParent* commonAncestor =
1644 nsContentUtils::GetCommonBrowserParentAncestor(remote, oldRemote);
1645 if (commonAncestor == oldRemote) {
1646 // Mouse moves to the inner OOP frame, it is not a really exit.
1647 DispatchCrossProcessMouseExitEvents(
1648 mouseEvent, GetBrowserParentAncestor(remote),
1649 GetBrowserParentAncestor(commonAncestor), false);
1650 } else if (commonAncestor == remote) {
1651 // Mouse moves to the outer OOP frame, it is a really exit.
1652 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1653 commonAncestor, true);
1654 } else {
1655 // Mouse moves to OOP frame in other subtree, it is a really exit,
1656 // need to notify all its ancestors before common ancestor about the
1657 // exit.
1658 DispatchCrossProcessMouseExitEvents(mouseEvent, oldRemote,
1659 commonAncestor, true);
1660 if (commonAncestor) {
1661 UniquePtr<WidgetMouseEvent> mouseExitEvent =
1662 CreateMouseOrPointerWidgetEvent(mouseEvent,
1663 eMouseExitFromWidget,
1664 mouseEvent->mRelatedTarget);
1665 mouseExitEvent->mExitFrom =
1666 Some(WidgetMouseEvent::ePuppetParentToPuppetChild);
1667 commonAncestor->SendRealMouseEvent(*mouseExitEvent);
1672 if (mouseEvent->mMessage != eMouseExitFromWidget &&
1673 mouseEvent->mMessage != eMouseEnterIntoWidget) {
1674 // This is to make cursor would be updated correctly.
1675 remote->MouseEnterIntoWidget();
1679 remote->SendRealMouseEvent(*mouseEvent);
1680 return;
1682 case eKeyboardEventClass: {
1683 auto* keyboardEvent = aEvent->AsKeyboardEvent();
1684 if (aEvent->mMessage == eKeyUp) {
1685 HandleKeyUpInteraction(keyboardEvent);
1687 remote->SendRealKeyEvent(*keyboardEvent);
1688 return;
1690 case eWheelEventClass: {
1691 if (BrowserParent* pointerLockedRemote =
1692 PointerLockManager::GetLockedRemoteTarget()) {
1693 remote = pointerLockedRemote;
1695 remote->SendMouseWheelEvent(*aEvent->AsWheelEvent());
1696 return;
1698 case eTouchEventClass: {
1699 // Let the child process synthesize a mouse event if needed, and
1700 // ensure we don't synthesize one in this process.
1701 *aStatus = nsEventStatus_eConsumeNoDefault;
1702 remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
1703 return;
1705 case eDragEventClass: {
1706 RefPtr<BrowserParent> browserParent = remote;
1707 browserParent->Manager()->MaybeInvokeDragSession(browserParent);
1709 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1710 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
1711 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
1712 nsCOMPtr<nsIPrincipal> principal;
1713 nsCOMPtr<nsIContentSecurityPolicy> csp;
1715 if (dragSession) {
1716 dragSession->DragEventDispatchedToChildProcess();
1717 dragSession->GetDragAction(&action);
1718 dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
1719 dragSession->GetCsp(getter_AddRefs(csp));
1720 RefPtr<DataTransfer> initialDataTransfer =
1721 dragSession->GetDataTransfer();
1722 if (initialDataTransfer) {
1723 dropEffect = initialDataTransfer->DropEffectInt();
1727 browserParent->SendRealDragEvent(*aEvent->AsDragEvent(), action,
1728 dropEffect, principal, csp);
1729 return;
1731 default: {
1732 MOZ_CRASH("Attempt to send non-whitelisted event?");
1737 bool EventStateManager::IsRemoteTarget(nsIContent* target) {
1738 return BrowserParent::GetFrom(target) || BrowserBridgeChild::GetFrom(target);
1741 bool EventStateManager::IsTopLevelRemoteTarget(nsIContent* target) {
1742 return !!BrowserParent::GetFrom(target);
1745 bool EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
1746 nsEventStatus* aStatus) {
1747 if (!aEvent->CanBeSentToRemoteProcess()) {
1748 return false;
1751 MOZ_ASSERT(!aEvent->HasBeenPostedToRemoteProcess(),
1752 "Why do we need to post same event to remote processes again?");
1754 // Collect the remote event targets we're going to forward this
1755 // event to.
1757 // NB: the elements of |remoteTargets| must be unique, for correctness.
1758 AutoTArray<RefPtr<BrowserParent>, 1> remoteTargets;
1759 if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) {
1760 // If this event only has one target, and it's remote, add it to
1761 // the array.
1762 nsIFrame* frame = aEvent->mMessage == eDragExit
1763 ? sLastDragOverFrame.GetFrame()
1764 : GetEventTarget();
1765 nsIContent* target = frame ? frame->GetContent() : nullptr;
1766 if (BrowserParent* remoteTarget = BrowserParent::GetFrom(target)) {
1767 remoteTargets.AppendElement(remoteTarget);
1769 } else {
1770 // This is a touch event with possibly multiple touch points.
1771 // Each touch point may have its own target. So iterate through
1772 // all of them and collect the unique set of targets for event
1773 // forwarding.
1775 // This loop is similar to the one used in
1776 // PresShell::DispatchTouchEvent().
1777 const WidgetTouchEvent::TouchArray& touches =
1778 aEvent->AsTouchEvent()->mTouches;
1779 for (uint32_t i = 0; i < touches.Length(); ++i) {
1780 Touch* touch = touches[i];
1781 // NB: the |mChanged| check is an optimization, subprocesses can
1782 // compute this for themselves. If the touch hasn't changed, we
1783 // may be able to avoid forwarding the event entirely (which is
1784 // not free).
1785 if (!touch || !touch->mChanged) {
1786 continue;
1788 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
1789 if (!targetPtr) {
1790 continue;
1792 nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr);
1793 BrowserParent* remoteTarget = BrowserParent::GetFrom(target);
1794 if (remoteTarget && !remoteTargets.Contains(remoteTarget)) {
1795 remoteTargets.AppendElement(remoteTarget);
1800 if (remoteTargets.Length() == 0) {
1801 return false;
1804 // Dispatch the event to the remote target.
1805 for (uint32_t i = 0; i < remoteTargets.Length(); ++i) {
1806 DispatchCrossProcessEvent(aEvent, remoteTargets[i], aStatus);
1808 return aEvent->HasBeenPostedToRemoteProcess();
1812 // CreateClickHoldTimer
1814 // Fire off a timer for determining if the user wants click-hold. This timer
1815 // is a one-shot that will be cancelled when the user moves enough to fire
1816 // a drag.
1818 void EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext,
1819 nsIFrame* inDownFrame,
1820 WidgetGUIEvent* inMouseDownEvent) {
1821 if (!inMouseDownEvent->IsTrusted() ||
1822 IsTopLevelRemoteTarget(mGestureDownContent) ||
1823 PointerLockManager::IsLocked()) {
1824 return;
1827 // just to be anal (er, safe)
1828 if (mClickHoldTimer) {
1829 mClickHoldTimer->Cancel();
1830 mClickHoldTimer = nullptr;
1833 // if content clicked on has a popup, don't even start the timer
1834 // since we'll end up conflicting and both will show.
1835 if (mGestureDownContent &&
1836 nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None,
1837 nsGkAtoms::popup)) {
1838 return;
1841 int32_t clickHoldDelay = StaticPrefs::ui_click_hold_context_menus_delay();
1842 NS_NewTimerWithFuncCallback(
1843 getter_AddRefs(mClickHoldTimer), sClickHoldCallback, this, clickHoldDelay,
1844 nsITimer::TYPE_ONE_SHOT, "EventStateManager::CreateClickHoldTimer");
1845 } // CreateClickHoldTimer
1848 // KillClickHoldTimer
1850 // Stop the timer that would show the context menu dead in its tracks
1852 void EventStateManager::KillClickHoldTimer() {
1853 if (mClickHoldTimer) {
1854 mClickHoldTimer->Cancel();
1855 mClickHoldTimer = nullptr;
1860 // sClickHoldCallback
1862 // This fires after the mouse has been down for a certain length of time.
1864 void EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM) {
1865 RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM);
1866 if (self) {
1867 self->FireContextClick();
1870 // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling
1871 // ClosePopup();
1873 } // sAutoHideCallback
1876 // FireContextClick
1878 // If we're this far, our timer has fired, which means the mouse has been down
1879 // for a certain period of time and has not moved enough to generate a
1880 // dragGesture. We can be certain the user wants a context-click at this stage,
1881 // so generate a dom event and fire it in.
1883 // After the event fires, check if PreventDefault() has been set on the event
1884 // which means that someone either ate the event or put up a context menu. This
1885 // is our cue to stop tracking the drag gesture. If we always did this,
1886 // draggable items w/out a context menu wouldn't be draggable after a certain
1887 // length of time, which is _not_ what we want.
1889 void EventStateManager::FireContextClick() {
1890 if (!mGestureDownContent || !mPresContext || PointerLockManager::IsLocked()) {
1891 return;
1894 #ifdef XP_MACOSX
1895 // Hack to ensure that we don't show a context menu when the user
1896 // let go of the mouse after a long cpu-hogging operation prevented
1897 // us from handling any OS events. See bug 117589.
1898 if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState,
1899 kCGMouseButtonLeft))
1900 return;
1901 #endif
1903 nsEventStatus status = nsEventStatus_eIgnore;
1905 // Dispatch to the DOM. We have to fake out the ESM and tell it that the
1906 // current target frame is actually where the mouseDown occurred, otherwise it
1907 // will use the frame the mouse is currently over which may or may not be
1908 // the same. (Note: saari and I have decided that we don't have to reset
1909 // |mCurrentTarget| when we're through because no one else is doing anything
1910 // more with this event and it will get reset on the very next event to the
1911 // correct frame).
1912 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent);
1913 // make sure the widget sticks around
1914 nsCOMPtr<nsIWidget> targetWidget;
1915 if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) {
1916 NS_ASSERTION(
1917 mPresContext == mCurrentTarget->PresContext(),
1918 "a prescontext returned a primary frame that didn't belong to it?");
1920 // before dispatching, check that we're not on something that
1921 // doesn't get a context menu
1922 bool allowedToDispatch = true;
1924 if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar,
1925 nsGkAtoms::scrollbarbutton,
1926 nsGkAtoms::button)) {
1927 allowedToDispatch = false;
1928 } else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) {
1929 // a <toolbarbutton> that has the container attribute set
1930 // will already have its own dropdown.
1931 if (nsContentUtils::HasNonEmptyAttr(
1932 mGestureDownContent, kNameSpaceID_None, nsGkAtoms::container)) {
1933 allowedToDispatch = false;
1934 } else {
1935 // If the toolbar button has an open menu, don't attempt to open
1936 // a second menu
1937 if (mGestureDownContent->IsElement() &&
1938 mGestureDownContent->AsElement()->AttrValueIs(
1939 kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true,
1940 eCaseMatters)) {
1941 allowedToDispatch = false;
1944 } else if (mGestureDownContent->IsHTMLElement()) {
1945 nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent));
1947 if (formCtrl) {
1948 allowedToDispatch =
1949 formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
1950 formCtrl->ControlType() == FormControlType::InputFile;
1951 } else if (mGestureDownContent->IsAnyOfHTMLElements(
1952 nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {
1953 allowedToDispatch = false;
1957 if (allowedToDispatch) {
1958 // init the event while mCurrentTarget is still good
1959 WidgetMouseEvent event(true, eContextMenu, targetWidget,
1960 WidgetMouseEvent::eReal);
1961 event.mClickCount = 1;
1962 FillInEventFromGestureDown(&event);
1964 // stop selection tracking, we're in control now
1965 if (mCurrentTarget) {
1966 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
1968 if (frameSel && frameSel->GetDragState()) {
1969 // note that this can cause selection changed events to fire if we're
1970 // in a text field, which will null out mCurrentTarget
1971 frameSel->SetDragState(false);
1975 AutoHandlingUserInputStatePusher userInpStatePusher(true, &event);
1977 // dispatch to DOM
1978 RefPtr<nsIContent> gestureDownContent = mGestureDownContent;
1979 RefPtr<nsPresContext> presContext = mPresContext;
1980 EventDispatcher::Dispatch(gestureDownContent, presContext, &event,
1981 nullptr, &status);
1983 // We don't need to dispatch to frame handling because no frames
1984 // watch eContextMenu except for nsMenuFrame and that's only for
1985 // dismissal. That's just as well since we don't really know
1986 // which frame to send it to.
1990 // now check if the event has been handled. If so, stop tracking a drag
1991 if (status == nsEventStatus_eConsumeNoDefault) {
1992 StopTrackingDragGesture(true);
1995 KillClickHoldTimer();
1997 } // FireContextClick
2000 // BeginTrackingDragGesture
2002 // Record that the mouse has gone down and that we should move to TRACKING state
2003 // of d&d gesture tracker.
2005 // We also use this to track click-hold context menus. When the mouse goes down,
2006 // fire off a short timer. If the timer goes off and we have yet to fire the
2007 // drag gesture (ie, the mouse hasn't moved a certain distance), then we can
2008 // assume the user wants a click-hold, so fire a context-click event. We only
2009 // want to cancel the drag gesture if the context-click event is handled.
2011 void EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext,
2012 WidgetMouseEvent* inDownEvent,
2013 nsIFrame* inDownFrame) {
2014 if (!inDownEvent->mWidget) {
2015 return;
2018 // Note that |inDownEvent| could be either a mouse down event or a
2019 // synthesized mouse move event.
2020 SetGestureDownPoint(inDownEvent);
2022 if (inDownFrame) {
2023 inDownFrame->GetContentForEvent(inDownEvent,
2024 getter_AddRefs(mGestureDownContent));
2026 mGestureDownFrameOwner = inDownFrame->GetContent();
2027 if (!mGestureDownFrameOwner) {
2028 mGestureDownFrameOwner = mGestureDownContent;
2031 mGestureModifiers = inDownEvent->mModifiers;
2032 mGestureDownButtons = inDownEvent->mButtons;
2034 if (inDownEvent->mMessage != eMouseTouchDrag &&
2035 StaticPrefs::ui_click_hold_context_menus()) {
2036 // fire off a timer to track click-hold
2037 CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
2041 void EventStateManager::SetGestureDownPoint(WidgetGUIEvent* aEvent) {
2042 mGestureDownPoint =
2043 GetEventRefPoint(aEvent) + aEvent->mWidget->WidgetToScreenOffset();
2046 LayoutDeviceIntPoint EventStateManager::GetEventRefPoint(
2047 WidgetEvent* aEvent) const {
2048 auto touchEvent = aEvent->AsTouchEvent();
2049 return (touchEvent && !touchEvent->mTouches.IsEmpty())
2050 ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
2051 : aEvent->mRefPoint;
2054 void EventStateManager::BeginTrackingRemoteDragGesture(
2055 nsIContent* aContent, RemoteDragStartData* aDragStartData) {
2056 mGestureDownContent = aContent;
2057 mGestureDownFrameOwner = aContent;
2058 mGestureDownInTextControl =
2059 aContent && aContent->IsInNativeAnonymousSubtree() &&
2060 TextControlElement::FromNodeOrNull(
2061 aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost());
2062 mGestureDownDragStartData = aDragStartData;
2066 // StopTrackingDragGesture
2068 // Record that the mouse has gone back up so that we should leave the TRACKING
2069 // state of d&d gesture tracker and return to the START state.
2071 void EventStateManager::StopTrackingDragGesture(bool aClearInChildProcesses) {
2072 mGestureDownContent = nullptr;
2073 mGestureDownFrameOwner = nullptr;
2074 mGestureDownInTextControl = false;
2075 mGestureDownDragStartData = nullptr;
2077 // If a content process starts a drag but the mouse is released before the
2078 // parent starts the actual drag, the content process will think a drag is
2079 // still happening. Inform any child processes with active drags that the drag
2080 // should be stopped.
2081 if (aClearInChildProcesses) {
2082 nsCOMPtr<nsIDragService> dragService =
2083 do_GetService("@mozilla.org/widget/dragservice;1");
2084 if (dragService) {
2085 nsCOMPtr<nsIDragSession> dragSession;
2086 dragService->GetCurrentSession(getter_AddRefs(dragSession));
2087 if (!dragSession) {
2088 // Only notify if there isn't a drag session active.
2089 dragService->RemoveAllChildProcesses();
2095 void EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent) {
2096 NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
2097 "Incorrect widget in event");
2099 // Set the coordinates in the new event to the coordinates of
2100 // the old event, adjusted for the fact that the widget might be
2101 // different
2102 aEvent->mRefPoint =
2103 mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
2104 aEvent->mModifiers = mGestureModifiers;
2105 aEvent->mButtons = mGestureDownButtons;
2108 void EventStateManager::MaybeFirePointerCancel(WidgetInputEvent* aEvent) {
2109 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
2110 AutoWeakFrame targetFrame = mCurrentTarget;
2112 if (!presShell || !targetFrame) {
2113 return;
2116 nsCOMPtr<nsIContent> content;
2117 targetFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
2118 if (!content) {
2119 return;
2122 nsEventStatus status = nsEventStatus_eIgnore;
2124 if (WidgetMouseEvent* aMouseEvent = aEvent->AsMouseEvent()) {
2125 WidgetPointerEvent event(*aMouseEvent);
2126 PointerEventHandler::InitPointerEventFromMouse(&event, aMouseEvent,
2127 ePointerCancel);
2129 event.convertToPointer = false;
2130 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2131 } else if (WidgetTouchEvent* aTouchEvent = aEvent->AsTouchEvent()) {
2132 WidgetPointerEvent event(aTouchEvent->IsTrusted(), ePointerCancel,
2133 aTouchEvent->mWidget);
2135 PointerEventHandler::InitPointerEventFromTouch(
2136 event, *aTouchEvent, *aTouchEvent->mTouches[0], true);
2138 event.convertToPointer = false;
2139 presShell->HandleEventWithTarget(&event, targetFrame, content, &status);
2140 } else {
2141 MOZ_ASSERT(false);
2144 // HandleEventWithTarget clears out mCurrentTarget, which may be used in the
2145 // caller GenerateDragGesture. We have to restore mCurrentTarget.
2146 mCurrentTarget = targetFrame;
2149 bool EventStateManager::IsEventOutsideDragThreshold(
2150 WidgetInputEvent* aEvent) const {
2151 static int32_t sPixelThresholdX = 0;
2152 static int32_t sPixelThresholdY = 0;
2154 if (!sPixelThresholdX) {
2155 sPixelThresholdX =
2156 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdX, 0);
2157 sPixelThresholdY =
2158 LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdY, 0);
2159 if (sPixelThresholdX <= 0) {
2160 sPixelThresholdX = 5;
2162 if (sPixelThresholdY <= 0) {
2163 sPixelThresholdY = 5;
2167 LayoutDeviceIntPoint pt =
2168 aEvent->mWidget->WidgetToScreenOffset() + GetEventRefPoint(aEvent);
2169 LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
2170 return Abs(distance.x) > sPixelThresholdX ||
2171 Abs(distance.y) > sPixelThresholdY;
2175 // GenerateDragGesture
2177 // If we're in the TRACKING state of the d&d gesture tracker, check the current
2178 // position of the mouse in relation to the old one. If we've moved a sufficient
2179 // amount from the mouse down, then fire off a drag gesture event.
2180 void EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
2181 WidgetInputEvent* aEvent) {
2182 NS_ASSERTION(aPresContext, "This shouldn't happen.");
2183 if (!IsTrackingDragGesture()) {
2184 return;
2187 AutoWeakFrame targetFrameBefore = mCurrentTarget;
2188 auto autoRestore = MakeScopeExit([&] { mCurrentTarget = targetFrameBefore; });
2189 mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
2191 if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
2192 StopTrackingDragGesture(true);
2193 return;
2196 // Check if selection is tracking drag gestures, if so
2197 // don't interfere!
2198 if (mCurrentTarget) {
2199 RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
2200 if (frameSel && frameSel->GetDragState()) {
2201 StopTrackingDragGesture(true);
2202 return;
2206 // If non-native code is capturing the mouse don't start a drag.
2207 if (PresShell::IsMouseCapturePreventingDrag()) {
2208 StopTrackingDragGesture(true);
2209 return;
2212 if (!IsEventOutsideDragThreshold(aEvent)) {
2213 // To keep the old behavior, flush layout even if we don't start dnd.
2214 FlushLayout(aPresContext);
2215 return;
2218 if (StaticPrefs::ui_click_hold_context_menus()) {
2219 // stop the click-hold before we fire off the drag gesture, in case
2220 // it takes a long time
2221 KillClickHoldTimer();
2224 nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell();
2225 if (!docshell) {
2226 return;
2229 nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow();
2230 if (!window) return;
2232 RefPtr<DataTransfer> dataTransfer =
2233 new DataTransfer(window, eDragStart, false, -1);
2234 auto protectDataTransfer = MakeScopeExit([&] {
2235 if (dataTransfer) {
2236 dataTransfer->Disconnect();
2240 RefPtr<Selection> selection;
2241 RefPtr<RemoteDragStartData> remoteDragStartData;
2242 nsCOMPtr<nsIContent> eventContent, targetContent;
2243 nsCOMPtr<nsIPrincipal> principal;
2244 nsCOMPtr<nsIContentSecurityPolicy> csp;
2245 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2246 bool allowEmptyDataTransfer = false;
2247 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
2248 if (eventContent) {
2249 // If the content is a text node in a password field, we shouldn't
2250 // allow to drag its raw text. Note that we've supported drag from
2251 // password fields but dragging data was masked text. So, it doesn't
2252 // make sense anyway.
2253 if (eventContent->IsText() && eventContent->HasFlag(NS_MAYBE_MASKED)) {
2254 // However, it makes sense to allow to drag selected password text
2255 // when copying selected password is allowed because users may want
2256 // to use drag and drop rather than copy and paste when web apps
2257 // request to input password twice for conforming new password but
2258 // they used password generator.
2259 TextEditor* textEditor =
2260 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
2261 eventContent);
2262 if (!textEditor || !textEditor->IsCopyToClipboardAllowed()) {
2263 StopTrackingDragGesture(true);
2264 return;
2267 DetermineDragTargetAndDefaultData(
2268 window, eventContent, dataTransfer, &allowEmptyDataTransfer,
2269 getter_AddRefs(selection), getter_AddRefs(remoteDragStartData),
2270 getter_AddRefs(targetContent), getter_AddRefs(principal),
2271 getter_AddRefs(csp), getter_AddRefs(cookieJarSettings));
2274 // Stop tracking the drag gesture now. This should stop us from
2275 // reentering GenerateDragGesture inside DOM event processing.
2276 // Pass false to avoid clearing the child process state since a real
2277 // drag should be starting.
2278 StopTrackingDragGesture(false);
2280 if (!targetContent) return;
2282 // Use our targetContent, now that we've determined it, as the
2283 // parent object of the DataTransfer.
2284 nsCOMPtr<nsIContent> parentContent =
2285 targetContent->FindFirstNonChromeOnlyAccessContent();
2286 dataTransfer->SetParentObject(parentContent);
2288 sLastDragOverFrame = nullptr;
2289 nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
2291 // get the widget from the target frame
2292 WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
2293 startEvent.mFlags.mIsSynthesizedForTests =
2294 aEvent->mFlags.mIsSynthesizedForTests;
2295 FillInEventFromGestureDown(&startEvent);
2297 startEvent.mDataTransfer = dataTransfer;
2298 if (aEvent->AsMouseEvent()) {
2299 startEvent.mInputSource = aEvent->AsMouseEvent()->mInputSource;
2300 } else if (aEvent->AsTouchEvent()) {
2301 startEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
2302 } else {
2303 MOZ_ASSERT(false);
2306 // Dispatch to the DOM. By setting mCurrentTarget we are faking
2307 // out the ESM and telling it that the current target frame is
2308 // actually where the mouseDown occurred, otherwise it will use
2309 // the frame the mouse is currently over which may or may not be
2310 // the same.
2312 // Hold onto old target content through the event and reset after.
2313 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
2315 // Set the current target to the content for the mouse down
2316 mCurrentTargetContent = targetContent;
2318 // Dispatch the dragstart event to the DOM.
2319 nsEventStatus status = nsEventStatus_eIgnore;
2320 EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent, nullptr,
2321 &status);
2323 WidgetDragEvent* event = &startEvent;
2325 nsCOMPtr<nsIObserverService> observerService =
2326 mozilla::services::GetObserverService();
2327 // Emit observer event to allow addons to modify the DataTransfer
2328 // object.
2329 if (observerService) {
2330 observerService->NotifyObservers(dataTransfer, "on-datatransfer-available",
2331 nullptr);
2334 if (status != nsEventStatus_eConsumeNoDefault) {
2335 bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
2336 allowEmptyDataTransfer, targetContent,
2337 selection, remoteDragStartData,
2338 principal, csp, cookieJarSettings);
2339 if (dragStarted) {
2340 sActiveESM = nullptr;
2341 MaybeFirePointerCancel(aEvent);
2342 aEvent->StopPropagation();
2346 // Reset mCurretTargetContent to what it was
2347 mCurrentTargetContent = targetBeforeEvent;
2349 // Now flush all pending notifications, for better responsiveness
2350 // while dragging.
2351 FlushLayout(aPresContext);
2352 } // GenerateDragGesture
2354 void EventStateManager::DetermineDragTargetAndDefaultData(
2355 nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget,
2356 DataTransfer* aDataTransfer, bool* aAllowEmptyDataTransfer,
2357 Selection** aSelection, RemoteDragStartData** aRemoteDragStartData,
2358 nsIContent** aTargetNode, nsIPrincipal** aPrincipal,
2359 nsIContentSecurityPolicy** aCsp,
2360 nsICookieJarSettings** aCookieJarSettings) {
2361 *aTargetNode = nullptr;
2362 *aAllowEmptyDataTransfer = false;
2363 nsCOMPtr<nsIContent> dragDataNode;
2365 nsIContent* editingElement = aSelectionTarget->IsEditable()
2366 ? aSelectionTarget->GetEditingHost()
2367 : nullptr;
2369 // In chrome, only allow dragging inside editable areas.
2370 bool isChromeContext = !aWindow->GetBrowsingContext()->IsContent();
2371 if (isChromeContext && !editingElement) {
2372 if (mGestureDownDragStartData) {
2373 // A child process started a drag so use any data it assigned for the dnd
2374 // session.
2375 mGestureDownDragStartData->AddInitialDnDDataTo(aDataTransfer, aPrincipal,
2376 aCsp, aCookieJarSettings);
2377 mGestureDownDragStartData.forget(aRemoteDragStartData);
2378 *aAllowEmptyDataTransfer = true;
2380 } else {
2381 mGestureDownDragStartData = nullptr;
2383 // GetDragData determines if a selection, link or image in the content
2384 // should be dragged, and places the data associated with the drag in the
2385 // data transfer.
2386 // mGestureDownContent is the node where the mousedown event for the drag
2387 // occurred, and aSelectionTarget is the node to use when a selection is
2388 // used
2389 bool canDrag;
2390 bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
2391 nsresult rv = nsContentAreaDragDrop::GetDragData(
2392 aWindow, mGestureDownContent, aSelectionTarget, wasAlt, aDataTransfer,
2393 &canDrag, aSelection, getter_AddRefs(dragDataNode), aPrincipal, aCsp,
2394 aCookieJarSettings);
2395 if (NS_FAILED(rv) || !canDrag) {
2396 return;
2400 // if GetDragData returned a node, use that as the node being dragged.
2401 // Otherwise, if a selection is being dragged, use the node within the
2402 // selection that was dragged. Otherwise, just use the mousedown target.
2403 nsIContent* dragContent = mGestureDownContent;
2404 if (dragDataNode)
2405 dragContent = dragDataNode;
2406 else if (*aSelection)
2407 dragContent = aSelectionTarget;
2409 nsIContent* originalDragContent = dragContent;
2411 // If a selection isn't being dragged, look for an ancestor with the
2412 // draggable property set. If one is found, use that as the target of the
2413 // drag instead of the node that was clicked on. If a draggable node wasn't
2414 // found, just use the clicked node.
2415 if (!*aSelection) {
2416 while (dragContent) {
2417 if (auto htmlElement = nsGenericHTMLElement::FromNode(dragContent)) {
2418 if (htmlElement->Draggable()) {
2419 // We let draggable elements to trigger dnd even if there is no data
2420 // in the DataTransfer.
2421 *aAllowEmptyDataTransfer = true;
2422 break;
2424 } else {
2425 if (dragContent->IsXULElement()) {
2426 // All XUL elements are draggable, so if a XUL element is
2427 // encountered, stop looking for draggable nodes and just use the
2428 // original clicked node instead.
2429 // XXXndeakin
2430 // In the future, we will want to improve this so that XUL has a
2431 // better way to specify whether something is draggable than just
2432 // on/off.
2433 dragContent = mGestureDownContent;
2434 break;
2436 // otherwise, it's not an HTML or XUL element, so just keep looking
2438 dragContent = dragContent->GetFlattenedTreeParent();
2442 // if no node in the hierarchy was found to drag, but the GetDragData method
2443 // returned a node, use that returned node. Otherwise, nothing is draggable.
2444 if (!dragContent && dragDataNode) dragContent = dragDataNode;
2446 if (dragContent) {
2447 // if an ancestor node was used instead, clear the drag data
2448 // XXXndeakin rework this a bit. Find a way to just not call GetDragData if
2449 // we don't need to.
2450 if (dragContent != originalDragContent) aDataTransfer->ClearAll();
2451 *aTargetNode = dragContent;
2452 NS_ADDREF(*aTargetNode);
2456 bool EventStateManager::DoDefaultDragStart(
2457 nsPresContext* aPresContext, WidgetDragEvent* aDragEvent,
2458 DataTransfer* aDataTransfer, bool aAllowEmptyDataTransfer,
2459 nsIContent* aDragTarget, Selection* aSelection,
2460 RemoteDragStartData* aDragStartData, nsIPrincipal* aPrincipal,
2461 nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings) {
2462 nsCOMPtr<nsIDragService> dragService =
2463 do_GetService("@mozilla.org/widget/dragservice;1");
2464 if (!dragService) return false;
2466 // Default handling for the dragstart event.
2468 // First, check if a drag session already exists. This means that the drag
2469 // service was called directly within a draggesture handler. In this case,
2470 // don't do anything more, as it is assumed that the handler is managing
2471 // drag and drop manually. Make sure to return true to indicate that a drag
2472 // began. However, if we're handling drag session for synthesized events,
2473 // we need to initialize some information of the session. Therefore, we
2474 // need to keep going for synthesized case.
2475 nsCOMPtr<nsIDragSession> dragSession;
2476 dragService->GetCurrentSession(getter_AddRefs(dragSession));
2477 if (dragSession && !dragSession->IsSynthesizedForTests()) {
2478 return true;
2481 // No drag session is currently active, so check if a handler added
2482 // any items to be dragged. If not, there isn't anything to drag.
2483 uint32_t count = 0;
2484 if (aDataTransfer) {
2485 count = aDataTransfer->MozItemCount();
2487 if (!aAllowEmptyDataTransfer && !count) {
2488 return false;
2491 // Get the target being dragged, which may not be the same as the
2492 // target of the mouse event. If one wasn't set in the
2493 // aDataTransfer during the event handler, just use the original
2494 // target instead.
2495 nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget();
2496 if (!dragTarget) {
2497 dragTarget = aDragTarget;
2498 if (!dragTarget) {
2499 return false;
2503 // check which drag effect should initially be used. If the effect was not
2504 // set, just use all actions, otherwise Windows won't allow a drop.
2505 uint32_t action = aDataTransfer->EffectAllowedInt();
2506 if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
2507 action = nsIDragService::DRAGDROP_ACTION_COPY |
2508 nsIDragService::DRAGDROP_ACTION_MOVE |
2509 nsIDragService::DRAGDROP_ACTION_LINK;
2512 // get any custom drag image that was set
2513 int32_t imageX, imageY;
2514 RefPtr<Element> dragImage = aDataTransfer->GetDragImage(&imageX, &imageY);
2516 nsCOMPtr<nsIArray> transArray = aDataTransfer->GetTransferables(dragTarget);
2517 if (!transArray) {
2518 return false;
2521 RefPtr<DataTransfer> dataTransfer;
2522 if (!dragSession) {
2523 // After this function returns, the DataTransfer will be cleared so it
2524 // appears empty to content. We need to pass a DataTransfer into the Drag
2525 // Session, so we need to make a copy.
2526 aDataTransfer->Clone(aDragTarget, eDrop, aDataTransfer->MozUserCancelled(),
2527 false, getter_AddRefs(dataTransfer));
2529 // Copy over the drop effect, as Clone doesn't copy it for us.
2530 dataTransfer->SetDropEffectInt(aDataTransfer->DropEffectInt());
2531 } else {
2532 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
2533 MOZ_ASSERT(aDragEvent->mFlags.mIsSynthesizedForTests);
2534 // If we're initializing synthesized drag session, we should use given
2535 // DataTransfer as is because it'll be used with following drag events
2536 // in any tests, therefore it should be set to nsIDragSession.dataTransfer
2537 // because it and DragEvent.dataTransfer should be same instance.
2538 dataTransfer = aDataTransfer;
2541 // XXXndeakin don't really want to create a new drag DOM event
2542 // here, but we need something to pass to the InvokeDragSession
2543 // methods.
2544 RefPtr<DragEvent> event =
2545 NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
2547 // Use InvokeDragSessionWithSelection if a selection is being dragged,
2548 // such that the image can be generated from the selected text. However,
2549 // use InvokeDragSessionWithImage if a custom image was set or something
2550 // other than a selection is being dragged.
2551 if (!dragImage && aSelection) {
2552 dragService->InvokeDragSessionWithSelection(aSelection, aPrincipal, aCsp,
2553 aCookieJarSettings, transArray,
2554 action, event, dataTransfer);
2555 } else if (aDragStartData) {
2556 MOZ_ASSERT(XRE_IsParentProcess());
2557 dragService->InvokeDragSessionWithRemoteImage(
2558 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2559 aDragStartData, event, dataTransfer);
2560 } else {
2561 dragService->InvokeDragSessionWithImage(
2562 dragTarget, aPrincipal, aCsp, aCookieJarSettings, transArray, action,
2563 dragImage, imageX, imageY, event, dataTransfer);
2566 return true;
2569 void EventStateManager::ChangeZoom(bool aIncrease) {
2570 // Send the zoom change to the top level browser so it will be handled by the
2571 // front end in the same way as other zoom actions.
2572 nsIDocShell* docShell = mDocument->GetDocShell();
2573 if (!docShell) {
2574 return;
2577 BrowsingContext* bc = docShell->GetBrowsingContext();
2578 if (!bc) {
2579 return;
2582 if (XRE_IsParentProcess()) {
2583 bc->Canonical()->DispatchWheelZoomChange(aIncrease);
2584 } else if (BrowserChild* child = BrowserChild::GetFrom(docShell)) {
2585 child->SendWheelZoomChange(aIncrease);
2589 void EventStateManager::DoScrollHistory(int32_t direction) {
2590 nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
2591 if (pcContainer) {
2592 nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer));
2593 if (webNav) {
2594 // positive direction to go back one step, nonpositive to go forward
2595 // This is doing user-initiated history traversal, hence we want
2596 // to require that history entries we navigate to have user interaction.
2597 if (direction > 0)
2598 webNav->GoBack(StaticPrefs::browser_navigation_requireUserInteraction(),
2599 true);
2600 else
2601 webNav->GoForward(
2602 StaticPrefs::browser_navigation_requireUserInteraction(), true);
2607 void EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
2608 int32_t adjustment) {
2609 // Exclude content in chrome docshells.
2610 nsIContent* content = aTargetFrame->GetContent();
2611 if (content && !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) {
2612 // Positive adjustment to decrease zoom, negative to increase
2613 const bool increase = adjustment <= 0;
2614 EnsureDocument(mPresContext);
2615 ChangeZoom(increase);
2619 static nsIFrame* GetParentFrameToScroll(nsIFrame* aFrame) {
2620 if (!aFrame) return nullptr;
2622 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2623 nsLayoutUtils::IsReallyFixedPos(aFrame))
2624 return aFrame->PresContext()->GetPresShell()->GetRootScrollFrame();
2626 return aFrame->GetParent();
2629 void EventStateManager::DispatchLegacyMouseScrollEvents(
2630 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent, nsEventStatus* aStatus) {
2631 MOZ_ASSERT(aEvent);
2632 MOZ_ASSERT(aStatus);
2634 if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) {
2635 return;
2638 // Ignore mouse wheel transaction for computing legacy mouse wheel
2639 // events' delta value.
2640 // DOM event's delta vales are computed from CSS pixels.
2641 auto scrollAmountInCSSPixels =
2642 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
2644 // XXX We don't deal with fractional amount in legacy event, though the
2645 // default action handler (DoScrollText()) deals with it.
2646 // If we implemented such strict computation, we would need additional
2647 // accumulated delta values. It would made the code more complicated.
2648 // And also it would computes different delta values from older version.
2649 // It doesn't make sense to implement such code for legacy events and
2650 // rare cases.
2651 int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
2652 switch (aEvent->mDeltaMode) {
2653 case WheelEvent_Binding::DOM_DELTA_PAGE:
2654 scrollDeltaX = !aEvent->mLineOrPageDeltaX
2656 : (aEvent->mLineOrPageDeltaX > 0
2657 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2658 : UIEvent_Binding::SCROLL_PAGE_UP);
2659 scrollDeltaY = !aEvent->mLineOrPageDeltaY
2661 : (aEvent->mLineOrPageDeltaY > 0
2662 ? UIEvent_Binding::SCROLL_PAGE_DOWN
2663 : UIEvent_Binding::SCROLL_PAGE_UP);
2664 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2665 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2666 break;
2668 case WheelEvent_Binding::DOM_DELTA_LINE:
2669 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2670 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2671 pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
2672 pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
2673 break;
2675 case WheelEvent_Binding::DOM_DELTA_PIXEL:
2676 scrollDeltaX = aEvent->mLineOrPageDeltaX;
2677 scrollDeltaY = aEvent->mLineOrPageDeltaY;
2678 pixelDeltaX = RoundDown(aEvent->mDeltaX);
2679 pixelDeltaY = RoundDown(aEvent->mDeltaY);
2680 break;
2682 default:
2683 MOZ_CRASH("Invalid deltaMode value comes");
2686 // Send the legacy events in following order:
2687 // 1. Vertical scroll
2688 // 2. Vertical pixel scroll (even if #1 isn't consumed)
2689 // 3. Horizontal scroll (even if #1 and/or #2 are consumed)
2690 // 4. Horizontal pixel scroll (even if #3 isn't consumed)
2692 AutoWeakFrame targetFrame(aTargetFrame);
2694 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
2695 !aEvent->DefaultPrevented(),
2696 "If you make legacy events dispatched for default prevented wheel "
2697 "event, you need to initialize stateX and stateY");
2698 EventState stateX, stateY;
2699 if (scrollDeltaY) {
2700 SendLineScrollEvent(aTargetFrame, aEvent, stateY, scrollDeltaY,
2701 DELTA_DIRECTION_Y);
2702 if (!targetFrame.IsAlive()) {
2703 *aStatus = nsEventStatus_eConsumeNoDefault;
2704 return;
2708 if (pixelDeltaY) {
2709 SendPixelScrollEvent(aTargetFrame, aEvent, stateY, pixelDeltaY,
2710 DELTA_DIRECTION_Y);
2711 if (!targetFrame.IsAlive()) {
2712 *aStatus = nsEventStatus_eConsumeNoDefault;
2713 return;
2717 if (scrollDeltaX) {
2718 SendLineScrollEvent(aTargetFrame, aEvent, stateX, scrollDeltaX,
2719 DELTA_DIRECTION_X);
2720 if (!targetFrame.IsAlive()) {
2721 *aStatus = nsEventStatus_eConsumeNoDefault;
2722 return;
2726 if (pixelDeltaX) {
2727 SendPixelScrollEvent(aTargetFrame, aEvent, stateX, pixelDeltaX,
2728 DELTA_DIRECTION_X);
2729 if (!targetFrame.IsAlive()) {
2730 *aStatus = nsEventStatus_eConsumeNoDefault;
2731 return;
2735 if (stateY.mDefaultPrevented) {
2736 *aStatus = nsEventStatus_eConsumeNoDefault;
2737 aEvent->PreventDefault(!stateY.mDefaultPreventedByContent);
2740 if (stateX.mDefaultPrevented) {
2741 *aStatus = nsEventStatus_eConsumeNoDefault;
2742 aEvent->PreventDefault(!stateX.mDefaultPreventedByContent);
2746 void EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
2747 WidgetWheelEvent* aEvent,
2748 EventState& aState, int32_t aDelta,
2749 DeltaDirection aDeltaDirection) {
2750 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2751 if (!targetContent) {
2752 targetContent = GetFocusedElement();
2753 if (!targetContent) {
2754 return;
2758 while (targetContent->IsText()) {
2759 targetContent = targetContent->GetFlattenedTreeParent();
2762 WidgetMouseScrollEvent event(aEvent->IsTrusted(),
2763 eLegacyMouseLineOrPageScroll, aEvent->mWidget);
2764 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2765 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2766 event.mRefPoint = aEvent->mRefPoint;
2767 event.mTimeStamp = aEvent->mTimeStamp;
2768 event.mModifiers = aEvent->mModifiers;
2769 event.mButtons = aEvent->mButtons;
2770 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2771 event.mDelta = aDelta;
2772 event.mInputSource = aEvent->mInputSource;
2774 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2775 nsEventStatus status = nsEventStatus_eIgnore;
2776 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2777 &status);
2778 aState.mDefaultPrevented =
2779 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2780 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2783 void EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
2784 WidgetWheelEvent* aEvent,
2785 EventState& aState,
2786 int32_t aPixelDelta,
2787 DeltaDirection aDeltaDirection) {
2788 nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
2789 if (!targetContent) {
2790 targetContent = GetFocusedElement();
2791 if (!targetContent) {
2792 return;
2796 while (targetContent->IsText()) {
2797 targetContent = targetContent->GetFlattenedTreeParent();
2800 WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMousePixelScroll,
2801 aEvent->mWidget);
2802 event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
2803 event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
2804 event.mRefPoint = aEvent->mRefPoint;
2805 event.mTimeStamp = aEvent->mTimeStamp;
2806 event.mModifiers = aEvent->mModifiers;
2807 event.mButtons = aEvent->mButtons;
2808 event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
2809 event.mDelta = aPixelDelta;
2810 event.mInputSource = aEvent->mInputSource;
2812 RefPtr<nsPresContext> presContext = aTargetFrame->PresContext();
2813 nsEventStatus status = nsEventStatus_eIgnore;
2814 EventDispatcher::Dispatch(targetContent, presContext, &event, nullptr,
2815 &status);
2816 aState.mDefaultPrevented =
2817 event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
2818 aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
2821 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2822 nsIFrame* aTargetFrame, WidgetWheelEvent* aEvent,
2823 ComputeScrollTargetOptions aOptions) {
2824 return ComputeScrollTargetAndMayAdjustWheelEvent(
2825 aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY, aEvent, aOptions);
2828 // Overload ComputeScrollTargetAndMayAdjustWheelEvent method to allow passing
2829 // "test" dx and dy when looking for which scrollbarmediators to activate when
2830 // two finger down on trackpad and before any actual motion
2831 nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
2832 nsIFrame* aTargetFrame, double aDirectionX, double aDirectionY,
2833 WidgetWheelEvent* aEvent, ComputeScrollTargetOptions aOptions) {
2834 bool isAutoDir = false;
2835 bool honoursRoot = false;
2836 if (MAY_BE_ADJUSTED_BY_AUTO_DIR & aOptions) {
2837 // If the scroll is respected as auto-dir, aDirection* should always be
2838 // equivalent to the event's delta vlaues(Currently, there are only one case
2839 // where aDirection*s have different values from the widget wheel event's
2840 // original delta values and the only case isn't auto-dir, see
2841 // ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets).
2842 MOZ_ASSERT(aDirectionX == aEvent->mDeltaX &&
2843 aDirectionY == aEvent->mDeltaY);
2845 WheelDeltaAdjustmentStrategy strategy =
2846 GetWheelDeltaAdjustmentStrategy(*aEvent);
2847 switch (strategy) {
2848 case WheelDeltaAdjustmentStrategy::eAutoDir:
2849 isAutoDir = true;
2850 honoursRoot = false;
2851 break;
2852 case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
2853 isAutoDir = true;
2854 honoursRoot = true;
2855 break;
2856 default:
2857 break;
2861 if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
2862 // If the user recently scrolled with the mousewheel, then they probably
2863 // want to scroll the same view as before instead of the view under the
2864 // cursor. WheelTransaction tracks the frame currently being
2865 // scrolled with the mousewheel. We consider the transaction ended when the
2866 // mouse moves more than "mousewheel.transaction.ignoremovedelay"
2867 // milliseconds after the last scroll operation, or any time the mouse moves
2868 // out of the frame, or when more than "mousewheel.transaction.timeout"
2869 // milliseconds have passed after the last operation, even if the mouse
2870 // hasn't moved.
2871 nsIFrame* lastScrollFrame = WheelTransaction::GetScrollTargetFrame();
2872 if (lastScrollFrame) {
2873 nsIScrollableFrame* scrollableFrame =
2874 lastScrollFrame->GetScrollTargetFrame();
2875 if (scrollableFrame) {
2876 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
2877 MOZ_ASSERT(frameToScroll);
2878 if (isAutoDir) {
2879 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *lastScrollFrame,
2880 honoursRoot);
2881 // Note that calling this function will not always cause the delta to
2882 // be adjusted, it only adjusts the delta when it should, because
2883 // Adjust() internally calls ShouldBeAdjusted() before making
2884 // adjustment.
2885 adjuster.Adjust();
2887 return frameToScroll;
2892 // If the event doesn't cause scroll actually, we cannot find scroll target
2893 // because we check if the event can cause scroll actually on each found
2894 // scrollable frame.
2895 if (!aDirectionX && !aDirectionY) {
2896 return nullptr;
2899 bool checkIfScrollableX;
2900 bool checkIfScrollableY;
2901 if (isAutoDir) {
2902 // Always check the frame's scrollability in both the two directions for an
2903 // auto-dir scroll. That is, for an auto-dir scroll,
2904 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS and
2905 // PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS should be ignored.
2906 checkIfScrollableX = true;
2907 checkIfScrollableY = true;
2908 } else {
2909 checkIfScrollableX =
2910 aDirectionX &&
2911 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
2912 checkIfScrollableY =
2913 aDirectionY &&
2914 (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
2917 nsIFrame* scrollFrame = !(aOptions & START_FROM_PARENT)
2918 ? aTargetFrame
2919 : GetParentFrameToScroll(aTargetFrame);
2920 for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
2921 // Check whether the frame wants to provide us with a scrollable view.
2922 nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
2923 if (!scrollableFrame) {
2924 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
2925 if (menuPopupFrame) {
2926 return nullptr;
2928 continue;
2931 nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
2932 MOZ_ASSERT(frameToScroll);
2934 if (!checkIfScrollableX && !checkIfScrollableY) {
2935 return frameToScroll;
2938 // If the frame disregards the direction the user is trying to scroll, then
2939 // it should just bubbles the scroll event up to its parental scroll frame
2941 Maybe<layers::ScrollDirection> disregardedDirection =
2942 WheelHandlingUtils::GetDisregardedWheelScrollDirection(scrollFrame);
2943 if (disregardedDirection) {
2944 switch (disregardedDirection.ref()) {
2945 case layers::ScrollDirection::eHorizontal:
2946 if (checkIfScrollableX) {
2947 continue;
2949 break;
2950 case layers::ScrollDirection::eVertical:
2951 if (checkIfScrollableY) {
2952 continue;
2954 break;
2958 layers::ScrollDirections directions =
2959 scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
2960 if ((!(directions.contains(layers::ScrollDirection::eVertical)) &&
2961 !(directions.contains(layers::ScrollDirection::eHorizontal))) ||
2962 (checkIfScrollableY && !checkIfScrollableX &&
2963 !(directions.contains(layers::ScrollDirection::eVertical))) ||
2964 (checkIfScrollableX && !checkIfScrollableY &&
2965 !(directions.contains(layers::ScrollDirection::eHorizontal)))) {
2966 continue;
2969 // Computes whether the currently checked frame is scrollable by this wheel
2970 // event.
2971 bool canScroll = false;
2972 if (isAutoDir) {
2973 ESMAutoDirWheelDeltaAdjuster adjuster(*aEvent, *scrollFrame, honoursRoot);
2974 if (adjuster.ShouldBeAdjusted()) {
2975 adjuster.Adjust();
2976 canScroll = true;
2977 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
2978 aDirectionY)) {
2979 canScroll = true;
2981 } else if (WheelHandlingUtils::CanScrollOn(scrollableFrame, aDirectionX,
2982 aDirectionY)) {
2983 canScroll = true;
2986 if (canScroll) {
2987 return frameToScroll;
2990 // Where we are at is the block ending in a for loop.
2991 // The current frame has been checked to be unscrollable by this wheel
2992 // event, continue the loop to check its parent, if any.
2995 nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(
2996 aTargetFrame->PresShell()->GetRootFrame());
2997 aOptions =
2998 static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
2999 if (!newFrame) {
3000 return nullptr;
3002 return ComputeScrollTargetAndMayAdjustWheelEvent(newFrame, aEvent, aOptions);
3005 nsSize EventStateManager::GetScrollAmount(
3006 nsPresContext* aPresContext, WidgetWheelEvent* aEvent,
3007 nsIScrollableFrame* aScrollableFrame) {
3008 MOZ_ASSERT(aPresContext);
3009 MOZ_ASSERT(aEvent);
3011 const bool isPage = aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PAGE;
3012 if (!aScrollableFrame) {
3013 // If there is no scrollable frame, we should use root, see below.
3014 aScrollableFrame =
3015 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
3018 if (aScrollableFrame) {
3019 return isPage ? aScrollableFrame->GetPageScrollAmount()
3020 : aScrollableFrame->GetLineScrollAmount();
3023 // If there is no scrollable frame and page scrolling, use viewport size.
3024 if (isPage) {
3025 return aPresContext->GetVisibleArea().Size();
3028 // Otherwise use root frame's font metrics.
3030 // FIXME(emilio): Should this use the root element's style frame? The root
3031 // frame will always have the initial font. Then again it should never matter
3032 // for content, we should always have a root scrollable frame in html
3033 // documents.
3034 nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
3035 if (!rootFrame) {
3036 return nsSize(0, 0);
3038 RefPtr<nsFontMetrics> fm =
3039 nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame);
3040 NS_ENSURE_TRUE(fm, nsSize(0, 0));
3041 return nsSize(fm->AveCharWidth(), fm->MaxHeight());
3044 void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
3045 WidgetWheelEvent* aEvent) {
3046 MOZ_ASSERT(aScrollableFrame);
3047 MOZ_ASSERT(aEvent);
3049 nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
3050 MOZ_ASSERT(scrollFrame);
3052 AutoWeakFrame scrollFrameWeak(scrollFrame);
3053 AutoWeakFrame eventFrameWeak(mCurrentTarget);
3054 if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak,
3055 eventFrameWeak)) {
3056 return;
3059 // Default action's actual scroll amount should be computed from device
3060 // pixels.
3061 nsPresContext* pc = scrollFrame->PresContext();
3062 nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame);
3063 nsIntSize scrollAmountInDevPixels(
3064 pc->AppUnitsToDevPixels(scrollAmount.width),
3065 pc->AppUnitsToDevPixels(scrollAmount.height));
3066 nsIntPoint actualDevPixelScrollAmount =
3067 DeltaAccumulator::GetInstance()->ComputeScrollAmountForDefaultAction(
3068 aEvent, scrollAmountInDevPixels);
3070 // Don't scroll around the axis whose overflow style is hidden.
3071 ScrollStyles overflowStyle = aScrollableFrame->GetScrollStyles();
3072 if (overflowStyle.mHorizontal == StyleOverflow::Hidden) {
3073 actualDevPixelScrollAmount.x = 0;
3075 if (overflowStyle.mVertical == StyleOverflow::Hidden) {
3076 actualDevPixelScrollAmount.y = 0;
3079 ScrollSnapFlags snapFlags = ScrollSnapFlags::Disabled;
3080 mozilla::ScrollOrigin origin = mozilla::ScrollOrigin::NotSpecified;
3081 switch (aEvent->mDeltaMode) {
3082 case WheelEvent_Binding::DOM_DELTA_LINE:
3083 origin = mozilla::ScrollOrigin::MouseWheel;
3084 snapFlags = ScrollSnapFlags::IntendedDirection;
3085 break;
3086 case WheelEvent_Binding::DOM_DELTA_PAGE:
3087 origin = mozilla::ScrollOrigin::Pages;
3088 snapFlags = ScrollSnapFlags::IntendedDirection |
3089 ScrollSnapFlags::IntendedEndPosition;
3090 break;
3091 case WheelEvent_Binding::DOM_DELTA_PIXEL:
3092 origin = mozilla::ScrollOrigin::Pixels;
3093 break;
3094 default:
3095 MOZ_CRASH("Invalid deltaMode value comes");
3098 // We shouldn't scroll more one page at once except when over one page scroll
3099 // is allowed for the event.
3100 nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
3101 nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
3102 pc->AppUnitsToDevPixels(pageSize.height));
3103 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
3104 DeprecatedAbs(actualDevPixelScrollAmount.x.value) >
3105 devPixelPageSize.width) {
3106 actualDevPixelScrollAmount.x = (actualDevPixelScrollAmount.x >= 0)
3107 ? devPixelPageSize.width
3108 : -devPixelPageSize.width;
3111 if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
3112 DeprecatedAbs(actualDevPixelScrollAmount.y.value) >
3113 devPixelPageSize.height) {
3114 actualDevPixelScrollAmount.y = (actualDevPixelScrollAmount.y >= 0)
3115 ? devPixelPageSize.height
3116 : -devPixelPageSize.height;
3119 bool isDeltaModePixel =
3120 (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL);
3122 ScrollMode mode;
3123 switch (aEvent->mScrollType) {
3124 case WidgetWheelEvent::SCROLL_DEFAULT:
3125 if (isDeltaModePixel) {
3126 mode = ScrollMode::Normal;
3127 } else if (aEvent->mFlags.mHandledByAPZ) {
3128 mode = ScrollMode::SmoothMsd;
3129 } else {
3130 mode = ScrollMode::Smooth;
3132 break;
3133 case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
3134 mode = ScrollMode::Instant;
3135 break;
3136 case WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY:
3137 mode = ScrollMode::Normal;
3138 break;
3139 case WidgetWheelEvent::SCROLL_SMOOTHLY:
3140 mode = ScrollMode::Smooth;
3141 break;
3142 default:
3143 MOZ_CRASH("Invalid mScrollType value comes");
3146 nsIScrollableFrame::ScrollMomentum momentum =
3147 aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
3148 : nsIScrollableFrame::NOT_MOMENTUM;
3150 nsIntPoint overflow;
3151 aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
3152 ScrollUnit::DEVICE_PIXELS, mode, &overflow, origin,
3153 momentum, snapFlags);
3155 if (!scrollFrameWeak.IsAlive()) {
3156 // If the scroll causes changing the layout, we can think that the event
3157 // has been completely consumed by the content. Then, users probably don't
3158 // want additional action.
3159 aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0;
3160 } else if (isDeltaModePixel) {
3161 aEvent->mOverflowDeltaX = overflow.x;
3162 aEvent->mOverflowDeltaY = overflow.y;
3163 } else {
3164 aEvent->mOverflowDeltaX =
3165 static_cast<double>(overflow.x) / scrollAmountInDevPixels.width;
3166 aEvent->mOverflowDeltaY =
3167 static_cast<double>(overflow.y) / scrollAmountInDevPixels.height;
3170 // If CSS overflow properties caused not to scroll, the overflowDelta* values
3171 // should be same as delta* values since they may be used as gesture event by
3172 // widget. However, if there is another scrollable element in the ancestor
3173 // along the axis, probably users don't want the operation to cause
3174 // additional action such as moving history. In such case, overflowDelta
3175 // values should stay zero.
3176 if (scrollFrameWeak.IsAlive()) {
3177 if (aEvent->mDeltaX && overflowStyle.mHorizontal == StyleOverflow::Hidden &&
3178 !ComputeScrollTargetAndMayAdjustWheelEvent(
3179 scrollFrame, aEvent,
3180 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS_WITH_AUTO_DIR)) {
3181 aEvent->mOverflowDeltaX = aEvent->mDeltaX;
3183 if (aEvent->mDeltaY && overflowStyle.mVertical == StyleOverflow::Hidden &&
3184 !ComputeScrollTargetAndMayAdjustWheelEvent(
3185 scrollFrame, aEvent,
3186 COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS_WITH_AUTO_DIR)) {
3187 aEvent->mOverflowDeltaY = aEvent->mDeltaY;
3191 NS_ASSERTION(
3192 aEvent->mOverflowDeltaX == 0 ||
3193 (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
3194 "The sign of mOverflowDeltaX is different from the scroll direction");
3195 NS_ASSERTION(
3196 aEvent->mOverflowDeltaY == 0 ||
3197 (aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0),
3198 "The sign of mOverflowDeltaY is different from the scroll direction");
3200 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent);
3203 void EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
3204 nsIFrame* targetFrame) {
3205 NS_ASSERTION(aEvent->mMessage == eGestureNotify,
3206 "DecideGestureEvent called with a non-gesture event");
3208 /* Check the ancestor tree to decide if any frame is willing* to receive
3209 * a MozPixelScroll event. If that's the case, the current touch gesture
3210 * will be used as a pan gesture; otherwise it will be a regular
3211 * mousedown/mousemove/click event.
3213 * *willing: determine if it makes sense to pan the element using scroll
3214 * events:
3215 * - For web content: if there are any visible scrollbars on the touch point
3216 * - For XUL: if it's an scrollable element that can currently scroll in some
3217 * direction.
3219 * Note: we'll have to one-off various cases to ensure a good usable behavior
3221 WidgetGestureNotifyEvent::PanDirection panDirection =
3222 WidgetGestureNotifyEvent::ePanNone;
3223 bool displayPanFeedback = false;
3224 for (nsIFrame* current = targetFrame; current;
3225 current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
3226 // e10s - mark remote content as pannable. This is a work around since
3227 // we don't have access to remote frame scroll info here. Apz data may
3228 // assist is solving this.
3229 if (current && IsTopLevelRemoteTarget(current->GetContent())) {
3230 panDirection = WidgetGestureNotifyEvent::ePanBoth;
3231 // We don't know when we reach bounds, so just disable feedback for now.
3232 displayPanFeedback = false;
3233 break;
3236 LayoutFrameType currentFrameType = current->Type();
3238 // Scrollbars should always be draggable
3239 if (currentFrameType == LayoutFrameType::Scrollbar) {
3240 panDirection = WidgetGestureNotifyEvent::ePanNone;
3241 break;
3244 // Special check for trees
3245 if (nsTreeBodyFrame* treeFrame = do_QueryFrame(current)) {
3246 if (treeFrame->GetHorizontalOverflow()) {
3247 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3249 if (treeFrame->GetVerticalOverflow()) {
3250 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3252 break;
3255 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(current)) {
3256 layers::ScrollDirections scrollbarVisibility =
3257 scrollableFrame->GetScrollbarVisibility();
3259 // Check if we have visible scrollbars
3260 if (scrollbarVisibility.contains(layers::ScrollDirection::eVertical)) {
3261 panDirection = WidgetGestureNotifyEvent::ePanVertical;
3262 displayPanFeedback = true;
3263 break;
3266 if (scrollbarVisibility.contains(layers::ScrollDirection::eHorizontal)) {
3267 panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
3268 displayPanFeedback = true;
3270 } // scrollableFrame
3271 } // ancestor chain
3272 aEvent->mDisplayPanFeedback = displayPanFeedback;
3273 aEvent->mPanDirection = panDirection;
3276 #ifdef XP_MACOSX
3277 static nsINode* GetCrossDocParentNode(nsINode* aChild) {
3278 MOZ_ASSERT(aChild, "The child is null!");
3279 MOZ_ASSERT(XRE_IsParentProcess());
3281 nsINode* parent = aChild->GetParentNode();
3282 if (parent && parent->IsContent() && aChild->IsContent()) {
3283 parent = aChild->AsContent()->GetFlattenedTreeParent();
3286 if (parent || !aChild->IsDocument()) {
3287 return parent;
3290 return aChild->AsDocument()->GetEmbedderElement();
3293 static bool NodeAllowsClickThrough(nsINode* aNode) {
3294 while (aNode) {
3295 if (aNode->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::tree)) {
3296 return false;
3298 if (aNode->IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::resizer)) {
3299 return true;
3301 aNode = GetCrossDocParentNode(aNode);
3303 return true;
3305 #endif
3307 void EventStateManager::PostHandleKeyboardEvent(
3308 WidgetKeyboardEvent* aKeyboardEvent, nsIFrame* aTargetFrame,
3309 nsEventStatus& aStatus) {
3310 if (aStatus == nsEventStatus_eConsumeNoDefault) {
3311 return;
3314 RefPtr<nsPresContext> presContext = mPresContext;
3316 if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3317 if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
3318 RefPtr<BrowserParent> remote =
3319 aTargetFrame ? BrowserParent::GetFrom(aTargetFrame->GetContent())
3320 : nullptr;
3321 if (remote) {
3322 // remote is null-checked above in order to let pre-existing event
3323 // targeting code's chrome vs. content decision override in case of
3324 // disagreement in order not to disrupt non-Fission e10s mode in case
3325 // there are still bugs in the Fission-mode code. That is, if remote
3326 // is nullptr, the pre-existing event targeting code has deemed this
3327 // event to belong to chrome rather than content.
3328 BrowserParent* preciseRemote = BrowserParent::GetFocused();
3329 if (preciseRemote) {
3330 remote = preciseRemote;
3332 // else there was a race between layout and focus tracking
3334 if (remote && !remote->IsReadyToHandleInputEvents()) {
3335 // We need to dispatch the event to the browser element again if we were
3336 // waiting for the key reply but the event wasn't sent to the content
3337 // process due to the remote browser wasn't ready.
3338 WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
3339 aKeyboardEvent->MarkAsHandledInRemoteProcess();
3340 RefPtr<Element> ownerElement = remote->GetOwnerElement();
3341 EventDispatcher::Dispatch(ownerElement, presContext, &keyEvent);
3342 if (keyEvent.DefaultPrevented()) {
3343 aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
3344 aStatus = nsEventStatus_eConsumeNoDefault;
3345 return;
3349 // The widget expects a reply for every keyboard event. If the event wasn't
3350 // dispatched to a content process (non-e10s or no content process
3351 // running), we need to short-circuit here. Otherwise, we need to wait for
3352 // the content process to handle the event.
3353 if (aKeyboardEvent->mWidget) {
3354 aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
3356 if (aKeyboardEvent->DefaultPrevented()) {
3357 aStatus = nsEventStatus_eConsumeNoDefault;
3358 return;
3362 // XXX Currently, our automated tests don't support mKeyNameIndex.
3363 // Therefore, we still need to handle this with keyCode.
3364 switch (aKeyboardEvent->mKeyCode) {
3365 case NS_VK_TAB:
3366 case NS_VK_F6:
3367 // This is to prevent keyboard scrolling while alt modifier in use.
3368 if (!aKeyboardEvent->IsAlt()) {
3369 aStatus = nsEventStatus_eConsumeNoDefault;
3371 // Handling the tab event after it was sent to content is bad,
3372 // because to the FocusManager the remote-browser looks like one
3373 // element, so we would just move the focus to the next element
3374 // in chrome, instead of handling it in content.
3375 if (aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
3376 break;
3379 EnsureDocument(presContext);
3380 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3381 if (fm && mDocument) {
3382 // Shift focus forward or back depending on shift key
3383 bool isDocMove = aKeyboardEvent->IsControl() ||
3384 aKeyboardEvent->mKeyCode == NS_VK_F6;
3385 uint32_t dir =
3386 aKeyboardEvent->IsShift()
3387 ? (isDocMove ? static_cast<uint32_t>(
3388 nsIFocusManager::MOVEFOCUS_BACKWARDDOC)
3389 : static_cast<uint32_t>(
3390 nsIFocusManager::MOVEFOCUS_BACKWARD))
3391 : (isDocMove ? static_cast<uint32_t>(
3392 nsIFocusManager::MOVEFOCUS_FORWARDDOC)
3393 : static_cast<uint32_t>(
3394 nsIFocusManager::MOVEFOCUS_FORWARD));
3395 RefPtr<Element> result;
3396 fm->MoveFocus(mDocument->GetWindow(), nullptr, dir,
3397 nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
3400 return;
3401 case 0:
3402 // We handle keys with no specific keycode value below.
3403 break;
3404 default:
3405 return;
3408 switch (aKeyboardEvent->mKeyNameIndex) {
3409 case KEY_NAME_INDEX_ZoomIn:
3410 case KEY_NAME_INDEX_ZoomOut:
3411 ChangeZoom(aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn);
3412 aStatus = nsEventStatus_eConsumeNoDefault;
3413 break;
3414 default:
3415 break;
3419 nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
3420 WidgetEvent* aEvent,
3421 nsIFrame* aTargetFrame,
3422 nsEventStatus* aStatus,
3423 nsIContent* aOverrideClickTarget) {
3424 NS_ENSURE_ARG(aPresContext);
3425 NS_ENSURE_ARG_POINTER(aStatus);
3427 mCurrentTarget = aTargetFrame;
3428 mCurrentTargetContent = nullptr;
3430 HandleCrossProcessEvent(aEvent, aStatus);
3431 // NOTE: the above call may have destroyed aTargetFrame, please use
3432 // mCurrentTarget henceforth. This is to avoid using it accidentally:
3433 aTargetFrame = nullptr;
3435 // Most of the events we handle below require a frame.
3436 // Add special cases here.
3437 if (!mCurrentTarget && aEvent->mMessage != eMouseUp &&
3438 aEvent->mMessage != eMouseDown && aEvent->mMessage != eDragEnter &&
3439 aEvent->mMessage != eDragOver && aEvent->mMessage != ePointerUp &&
3440 aEvent->mMessage != ePointerCancel) {
3441 return NS_OK;
3444 // Keep the prescontext alive, we might need it after event dispatch
3445 RefPtr<nsPresContext> presContext = aPresContext;
3446 nsresult ret = NS_OK;
3448 switch (aEvent->mMessage) {
3449 case eMouseDown: {
3450 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
3451 if (mouseEvent->mButton == MouseButton::ePrimary &&
3452 !sNormalLMouseEventInProcess) {
3453 // We got a mouseup event while a mousedown event was being processed.
3454 // Make sure that the capturing content is cleared.
3455 PresShell::ReleaseCapturingContent();
3456 break;
3459 // For remote content, capture the event in the parent process at the
3460 // <xul:browser remote> element. This will ensure that subsequent
3461 // mousemove/mouseup events will continue to be dispatched to this element
3462 // and therefore forwarded to the child.
3463 if (aEvent->HasBeenPostedToRemoteProcess() &&
3464 !PresShell::GetCapturingContent()) {
3465 if (nsIContent* content =
3466 mCurrentTarget ? mCurrentTarget->GetContent() : nullptr) {
3467 PresShell::SetCapturingContent(content, CaptureFlags::None, aEvent);
3468 } else {
3469 PresShell::ReleaseCapturingContent();
3473 // If MouseEvent::PreventClickEvent() was called by chrome script,
3474 // we need to forget the clicking content and click count for the
3475 // following eMouseUp event.
3476 if (mouseEvent->mClickEventPrevented) {
3477 RefPtr<EventStateManager> esm =
3478 ESMFromContentOrThis(aOverrideClickTarget);
3479 switch (mouseEvent->mButton) {
3480 case MouseButton::ePrimary:
3481 case MouseButton::eSecondary:
3482 case MouseButton::eMiddle: {
3483 LastMouseDownInfo& mouseDownInfo =
3484 GetLastMouseDownInfo(mouseEvent->mButton);
3485 mouseDownInfo.mLastMouseDownContent = nullptr;
3486 mouseDownInfo.mClickCount = 0;
3487 mouseDownInfo.mLastMouseDownInputControlType = Nothing();
3488 break;
3491 default:
3492 break;
3496 nsCOMPtr<nsIContent> activeContent;
3497 // When content calls PreventDefault on pointerdown, we also call
3498 // PreventDefault on the subsequent mouse events to suppress default
3499 // behaviors. Normally, aStatus should be nsEventStatus_eConsumeNoDefault
3500 // when the event is DefaultPrevented but it's reset to
3501 // nsEventStatus_eIgnore in EventStateManager::PreHandleEvent. So we also
3502 // check if the event is DefaultPrevented.
3503 if (nsEventStatus_eConsumeNoDefault != *aStatus &&
3504 !aEvent->DefaultPrevented()) {
3505 nsCOMPtr<nsIContent> newFocus;
3506 bool suppressBlur = false;
3507 if (mCurrentTarget) {
3508 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
3509 activeContent = mCurrentTarget->GetContent();
3511 // In some cases, we do not want to even blur the current focused
3512 // element. Those cases are:
3513 // 1. -moz-user-focus CSS property is set to 'ignore';
3514 // 2. XUL control element has the disabled property set to 'true'.
3516 // We can't use nsIFrame::IsFocusable() because we want to blur when
3517 // we click on a visibility: none element.
3518 // We can't use nsIContent::IsFocusable() because we want to blur when
3519 // we click on a non-focusable element like a <div>.
3520 // We have to use |aEvent->mTarget| to not make sure we do not check
3521 // an anonymous node of the targeted element.
3522 suppressBlur =
3523 mCurrentTarget->StyleUI()->UserFocus() == StyleUserFocus::Ignore;
3525 if (!suppressBlur) {
3526 if (Element* element =
3527 Element::FromEventTargetOrNull(aEvent->mTarget)) {
3528 if (nsCOMPtr<nsIDOMXULControlElement> xulControl =
3529 element->AsXULControl()) {
3530 bool disabled = false;
3531 xulControl->GetDisabled(&disabled);
3532 suppressBlur = disabled;
3538 // When a root content which isn't editable but has an editable HTML
3539 // <body> element is clicked, we should redirect the focus to the
3540 // the <body> element. E.g., when an user click bottom of the editor
3541 // where is outside of the <body> element, the <body> should be focused
3542 // and the user can edit immediately after that.
3544 // NOTE: The newFocus isn't editable that also means it's not in
3545 // designMode. In designMode, all contents are not focusable.
3546 if (newFocus && !newFocus->IsEditable()) {
3547 Document* doc = newFocus->GetComposedDoc();
3548 if (doc && newFocus == doc->GetRootElement()) {
3549 nsIContent* bodyContent =
3550 nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
3551 if (bodyContent && bodyContent->GetPrimaryFrame()) {
3552 newFocus = bodyContent;
3557 // When the mouse is pressed, the default action is to focus the
3558 // target. Look for the nearest enclosing focusable frame.
3560 // TODO: Probably this should be moved to Element::PostHandleEvent.
3561 for (; newFocus; newFocus = newFocus->GetFlattenedTreeParent()) {
3562 if (!newFocus->IsElement()) {
3563 continue;
3566 nsIFrame* frame = newFocus->GetPrimaryFrame();
3567 if (!frame) {
3568 continue;
3571 // If the mousedown happened inside a popup, don't try to set focus on
3572 // one of its containing elements
3573 if (frame->IsMenuPopupFrame()) {
3574 newFocus = nullptr;
3575 break;
3578 if (frame->IsFocusable(/* aWithMouse = */ true)) {
3579 break;
3582 if (ShadowRoot* root = newFocus->GetShadowRoot()) {
3583 if (root->DelegatesFocus()) {
3584 if (Element* firstFocusable =
3585 root->GetFocusDelegate(/* aWithMouse */ true)) {
3586 newFocus = firstFocusable;
3587 break;
3593 MOZ_ASSERT_IF(newFocus, newFocus->IsElement());
3595 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3596 // if something was found to focus, focus it. Otherwise, if the
3597 // element that was clicked doesn't have -moz-user-focus: ignore,
3598 // clear the existing focus. For -moz-user-focus: ignore, the focus
3599 // is just left as is.
3600 // Another effect of mouse clicking, handled in Selection, is that
3601 // it should update the caret position to where the mouse was
3602 // clicked. Because the focus is cleared when clicking on a
3603 // non-focusable node, the next press of the tab key will cause
3604 // focus to be shifted from the caret position instead of the root.
3605 if (newFocus) {
3606 // use the mouse flag and the noscroll flag so that the content
3607 // doesn't unexpectedly scroll when clicking an element that is
3608 // only half visible
3609 uint32_t flags =
3610 nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
3611 // If this was a touch-generated event, pass that information:
3612 if (mouseEvent->mInputSource ==
3613 MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3614 flags |= nsIFocusManager::FLAG_BYTOUCH;
3616 fm->SetFocus(MOZ_KnownLive(newFocus->AsElement()), flags);
3617 } else if (!suppressBlur) {
3618 // clear the focus within the frame and then set it as the
3619 // focused frame
3620 EnsureDocument(mPresContext);
3621 if (mDocument) {
3622 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mDocument->GetWindow();
3623 #ifdef XP_MACOSX
3624 if (!activeContent || !activeContent->IsXULElement())
3625 #endif
3626 fm->ClearFocus(outerWindow);
3627 // Prevent switch frame if we're already not in the foreground tab
3628 // and we're in a content process.
3629 // TODO: If we were inactive frame in this tab, and now in
3630 // background tab, we shouldn't make the tab foreground, but
3631 // we should set focus to clicked document in the background
3632 // tab. However, nsFocusManager does not have proper method
3633 // for doing this. Therefore, we should skip setting focus
3634 // to clicked document for now.
3635 if (XRE_IsParentProcess() || IsInActiveTab(mDocument)) {
3636 fm->SetFocusedWindow(outerWindow);
3642 // The rest is left button-specific.
3643 if (mouseEvent->mButton != MouseButton::ePrimary) {
3644 break;
3647 // The nearest enclosing element goes into the :active state. If we're
3648 // not an element (so we're text or something) we need to obtain
3649 // our parent element and put it into :active instead.
3650 if (activeContent && !activeContent->IsElement()) {
3651 if (nsIContent* par = activeContent->GetFlattenedTreeParent()) {
3652 activeContent = par;
3655 } else {
3656 // if we're here, the event handler returned false, so stop
3657 // any of our own processing of a drag. Workaround for bug 43258.
3658 StopTrackingDragGesture(true);
3660 // XXX Why do we always set this is active? Active window may be changed
3661 // by a mousedown event listener.
3662 SetActiveManager(this, activeContent);
3663 } break;
3664 case ePointerCancel:
3665 case ePointerUp: {
3666 WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
3667 MOZ_ASSERT(pointerEvent);
3668 // Implicitly releasing capture for given pointer. ePointerLostCapture
3669 // should be send after ePointerUp or ePointerCancel.
3670 PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
3671 PointerEventHandler::UpdateActivePointerState(pointerEvent);
3673 if (pointerEvent->mMessage == ePointerCancel ||
3674 pointerEvent->mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3675 // After pointercancel, pointer becomes invalid so we can remove
3676 // relevant helper from table. Regarding pointerup with non-hoverable
3677 // device, the pointer also becomes invalid. Hoverable (mouse/pen)
3678 // pointers are valid all the time (not only between down/up).
3679 GenerateMouseEnterExit(pointerEvent);
3680 mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
3682 break;
3684 case eMouseUp: {
3685 // We can unconditionally stop capturing because
3686 // we should never be capturing when the mouse button is up
3687 PresShell::ReleaseCapturingContent();
3689 WidgetMouseEvent* mouseUpEvent = aEvent->AsMouseEvent();
3690 // If the mouseup event is a synthesized mouse event due to a touch, do
3691 // not clear the activation state. Element activation is handled by APZ.
3692 if (!mouseUpEvent || mouseUpEvent->mInputSource !=
3693 dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
3694 ClearGlobalActiveContent(this);
3696 if (mouseUpEvent && EventCausesClickEvents(*mouseUpEvent)) {
3697 // Make sure to dispatch the click even if there is no frame for
3698 // the current target element. This is required for Web compatibility.
3699 RefPtr<EventStateManager> esm =
3700 ESMFromContentOrThis(aOverrideClickTarget);
3701 ret =
3702 esm->PostHandleMouseUp(mouseUpEvent, aStatus, aOverrideClickTarget);
3705 if (PresShell* presShell = presContext->GetPresShell()) {
3706 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
3707 frameSelection->SetDragState(false);
3709 } break;
3710 case eWheelOperationEnd: {
3711 MOZ_ASSERT(aEvent->IsTrusted());
3712 ScrollbarsForWheel::MayInactivate();
3713 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3714 nsIScrollableFrame* scrollTarget =
3715 do_QueryFrame(ComputeScrollTargetAndMayAdjustWheelEvent(
3716 mCurrentTarget, wheelEvent,
3717 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR));
3718 // If the wheel event was handled by APZ, APZ will perform the scroll
3719 // snap.
3720 if (scrollTarget && !WheelTransaction::HandledByApz()) {
3721 scrollTarget->ScrollSnap();
3723 } break;
3724 case eWheel:
3725 case eWheelOperationStart: {
3726 MOZ_ASSERT(aEvent->IsTrusted());
3728 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
3729 ScrollbarsForWheel::Inactivate();
3730 break;
3733 WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
3734 MOZ_ASSERT(wheelEvent);
3736 // When APZ is enabled, the actual scroll animation might be handled by
3737 // the compositor.
3738 WheelPrefs::Action action =
3739 wheelEvent->mFlags.mHandledByAPZ
3740 ? WheelPrefs::ACTION_NONE
3741 : WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
3743 WheelDeltaAdjustmentStrategy strategy =
3744 GetWheelDeltaAdjustmentStrategy(*wheelEvent);
3745 // Adjust the delta values of the wheel event if the current default
3746 // action is to horizontalize scrolling. I.e., deltaY values are set to
3747 // deltaX and deltaY and deltaZ values are set to 0.
3748 // If horizontalized, the delta values will be restored and its overflow
3749 // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
3750 // being destroyed.
3751 WheelDeltaHorizontalizer horizontalizer(*wheelEvent);
3752 if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
3753 horizontalizer.Horizontalize();
3756 // Since ComputeScrollTargetAndMayAdjustWheelEvent() may adjust the delta
3757 // if the event is auto-dir. So we use |ESMAutoDirWheelDeltaRestorer|
3758 // here.
3759 // An instance of |ESMAutoDirWheelDeltaRestorer| is used to monitor
3760 // auto-dir adjustment which may happen during its lifetime. If the delta
3761 // values is adjusted during its lifetime, the instance will restore the
3762 // adjusted delta when it's being destrcuted.
3763 ESMAutoDirWheelDeltaRestorer restorer(*wheelEvent);
3764 nsIFrame* frameToScroll = ComputeScrollTargetAndMayAdjustWheelEvent(
3765 mCurrentTarget, wheelEvent,
3766 COMPUTE_DEFAULT_ACTION_TARGET_WITH_AUTO_DIR);
3768 switch (action) {
3769 case WheelPrefs::ACTION_SCROLL:
3770 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL: {
3771 // For scrolling of default action, we should honor the mouse wheel
3772 // transaction.
3774 ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget,
3775 wheelEvent);
3777 if (aEvent->mMessage != eWheel ||
3778 (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
3779 break;
3782 nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll);
3783 ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget);
3785 nsIFrame* rootScrollFrame =
3786 !mCurrentTarget
3787 ? nullptr
3788 : mCurrentTarget->PresShell()->GetRootScrollFrame();
3789 nsIScrollableFrame* rootScrollableFrame = nullptr;
3790 if (rootScrollFrame) {
3791 rootScrollableFrame = do_QueryFrame(rootScrollFrame);
3793 if (!scrollTarget || scrollTarget == rootScrollableFrame) {
3794 wheelEvent->mViewPortIsOverscrolled = true;
3796 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
3797 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
3798 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
3799 wheelEvent);
3800 if (scrollTarget) {
3801 DoScrollText(scrollTarget, wheelEvent);
3802 } else {
3803 WheelTransaction::EndTransaction();
3804 ScrollbarsForWheel::Inactivate();
3806 break;
3808 case WheelPrefs::ACTION_HISTORY: {
3809 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3810 // the direction is oblique, don't perform history back/forward.
3811 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3812 if (!intDelta) {
3813 break;
3815 DoScrollHistory(intDelta);
3816 break;
3818 case WheelPrefs::ACTION_ZOOM: {
3819 // If this event doesn't cause eLegacyMouseLineOrPageScroll event or
3820 // the direction is oblique, don't perform zoom in/out.
3821 int32_t intDelta = wheelEvent->GetPreferredIntDelta();
3822 if (!intDelta) {
3823 break;
3825 DoScrollZoom(mCurrentTarget, intDelta);
3826 break;
3828 case WheelPrefs::ACTION_NONE:
3829 default:
3830 bool allDeltaOverflown = false;
3831 if (StaticPrefs::dom_event_wheel_event_groups_enabled() &&
3832 (wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0)) {
3833 if (frameToScroll) {
3834 WheelTransaction::WillHandleDefaultAction(
3835 wheelEvent, frameToScroll, mCurrentTarget);
3836 } else {
3837 WheelTransaction::EndTransaction();
3840 if (wheelEvent->mFlags.mHandledByAPZ) {
3841 if (wheelEvent->mCanTriggerSwipe) {
3842 // For events that can trigger swipes, APZ needs to know whether
3843 // scrolling is possible in the requested direction. It does this
3844 // by looking at the scroll overflow values on mCanTriggerSwipe
3845 // events after they have been processed.
3846 allDeltaOverflown = !ComputeScrollTarget(
3847 mCurrentTarget, wheelEvent, COMPUTE_DEFAULT_ACTION_TARGET);
3849 } else {
3850 // The event was processed neither by APZ nor by us, so all of the
3851 // delta values must be overflown delta values.
3852 allDeltaOverflown = true;
3855 if (!allDeltaOverflown) {
3856 break;
3858 wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
3859 wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
3860 WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(
3861 wheelEvent);
3862 wheelEvent->mViewPortIsOverscrolled = true;
3863 break;
3865 *aStatus = nsEventStatus_eConsumeNoDefault;
3866 } break;
3868 case eGestureNotify: {
3869 if (nsEventStatus_eConsumeNoDefault != *aStatus) {
3870 DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
3872 } break;
3874 case eDragEnter:
3875 case eDragOver: {
3876 NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event");
3878 // Check if the drag is occurring inside a scrollable area. If so, scroll
3879 // the area when the mouse is near the edges.
3880 if (mCurrentTarget && aEvent->mMessage == eDragOver) {
3881 nsIFrame* checkFrame = mCurrentTarget;
3882 while (checkFrame) {
3883 nsIScrollableFrame* scrollFrame = do_QueryFrame(checkFrame);
3884 // Break out so only the innermost scrollframe is scrolled.
3885 if (scrollFrame && scrollFrame->DragScroll(aEvent)) {
3886 break;
3888 checkFrame = checkFrame->GetParent();
3892 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
3893 if (!dragSession) break;
3895 // Reset the flag.
3896 dragSession->SetOnlyChromeDrop(false);
3897 if (mPresContext) {
3898 EnsureDocument(mPresContext);
3900 bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
3902 // the initial dataTransfer is the one from the dragstart event that
3903 // was set on the dragSession when the drag began.
3904 RefPtr<DataTransfer> dataTransfer;
3905 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
3907 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
3909 // collect any changes to moz cursor settings stored in the event's
3910 // data transfer.
3911 UpdateDragDataTransfer(dragEvent);
3913 // cancelling a dragenter or dragover event means that a drop should be
3914 // allowed, so update the dropEffect and the canDrop state to indicate
3915 // that a drag is allowed. If the event isn't cancelled, a drop won't be
3916 // allowed. Essentially, to allow a drop somewhere, specify the effects
3917 // using the effectAllowed and dropEffect properties in a dragenter or
3918 // dragover event and cancel the event. To not allow a drop somewhere,
3919 // don't cancel the event or set the effectAllowed or dropEffect to
3920 // "none". This way, if the event is just ignored, no drop will be
3921 // allowed.
3922 uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
3923 uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
3924 if (nsEventStatus_eConsumeNoDefault == *aStatus) {
3925 // If the event has initialized its mDataTransfer, use it.
3926 // Or the event has not been initialized its mDataTransfer, but
3927 // it's set before dispatch because of synthesized, but without
3928 // testing session (e.g., emulating drag from another app), use it
3929 // coming from outside.
3930 // XXX Perhaps, for the latter case, we need new API because we don't
3931 // have a chance to initialize allowed effects of the session.
3932 if (dragEvent->mDataTransfer) {
3933 // get the dataTransfer and the dropEffect that was set on it
3934 dataTransfer = dragEvent->mDataTransfer;
3935 dropEffect = dataTransfer->DropEffectInt();
3936 } else {
3937 // if dragEvent->mDataTransfer is null, it means that no attempt was
3938 // made to access the dataTransfer during the event, yet the event
3939 // was cancelled. Instead, use the initial data transfer available
3940 // from the drag session. The drop effect would not have been
3941 // initialized (which is done in DragEvent::GetDataTransfer),
3942 // so set it from the drag action. We'll still want to filter it
3943 // based on the effectAllowed below.
3944 dataTransfer = initialDataTransfer;
3946 dragSession->GetDragAction(&action);
3948 // filter the drop effect based on the action. Use UNINITIALIZED as
3949 // any effect is allowed.
3950 dropEffect = nsContentUtils::FilterDropEffect(
3951 action, nsIDragService::DRAGDROP_ACTION_UNINITIALIZED);
3954 // At this point, if the dataTransfer is null, it means that the
3955 // drag was originally started by directly calling the drag service.
3956 // Just assume that all effects are allowed.
3957 uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
3958 if (dataTransfer) {
3959 effectAllowed = dataTransfer->EffectAllowedInt();
3962 // set the drag action based on the drop effect and effect allowed.
3963 // The drop effect field on the drag transfer object specifies the
3964 // desired current drop effect. However, it cannot be used if the
3965 // effectAllowed state doesn't include that type of action. If the
3966 // dropEffect is "none", then the action will be 'none' so a drop will
3967 // not be allowed.
3968 if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED ||
3969 dropEffect & effectAllowed)
3970 action = dropEffect;
3972 if (action == nsIDragService::DRAGDROP_ACTION_NONE)
3973 dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
3975 // inform the drag session that a drop is allowed on this node.
3976 dragSession->SetDragAction(action);
3977 dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE);
3979 // For now, do this only for dragover.
3980 // XXXsmaug dragenter needs some more work.
3981 if (aEvent->mMessage == eDragOver && !isChromeDoc) {
3982 // Someone has called preventDefault(), check whether is was on
3983 // content or chrome.
3984 dragSession->SetOnlyChromeDrop(
3985 !dragEvent->mDefaultPreventedOnContent);
3987 } else if (aEvent->mMessage == eDragOver && !isChromeDoc) {
3988 // No one called preventDefault(), so handle drop only in chrome.
3989 dragSession->SetOnlyChromeDrop(true);
3991 if (ContentChild* child = ContentChild::GetSingleton()) {
3992 child->SendUpdateDropEffect(action, dropEffect);
3994 if (aEvent->HasBeenPostedToRemoteProcess()) {
3995 dragSession->SetCanDrop(true);
3996 } else if (initialDataTransfer) {
3997 // Now set the drop effect in the initial dataTransfer. This ensures
3998 // that we can get the desired drop effect in the drop event. For events
3999 // dispatched to content, the content process will take care of setting
4000 // this.
4001 initialDataTransfer->SetDropEffectInt(dropEffect);
4003 } break;
4005 case eDrop: {
4006 if (aEvent->mFlags.mIsSynthesizedForTests) {
4007 if (nsCOMPtr<nsIDragSession> dragSession =
4008 nsContentUtils::GetDragSession()) {
4009 MOZ_ASSERT(dragSession->IsSynthesizedForTests());
4010 RefPtr<WindowContext> sourceWC;
4011 DebugOnly<nsresult> rvIgnored =
4012 dragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
4013 NS_WARNING_ASSERTION(
4014 NS_SUCCEEDED(rvIgnored),
4015 "nsIDragSession::GetSourceDocument() failed, but ignored");
4016 // If the drag source hasn't been initialized, i.e., dragstart was
4017 // consumed by the test, the test needs to dispatch "dragend" event
4018 // instead of the drag session. Therefore, it does not make sense
4019 // to set drag end point in such case (you hit assersion if you do
4020 // it).
4021 if (sourceWC) {
4022 CSSIntPoint dropPointInScreen =
4023 Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
4024 .extract();
4025 dragSession->SetDragEndPointForTests(dropPointInScreen.x,
4026 dropPointInScreen.y);
4030 sLastDragOverFrame = nullptr;
4031 ClearGlobalActiveContent(this);
4032 break;
4034 case eDragExit:
4035 // make sure to fire the enter and exit_synth events after the
4036 // eDragExit event, otherwise we'll clean up too early
4037 GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
4038 if (ContentChild* child = ContentChild::GetSingleton()) {
4039 // SendUpdateDropEffect to prevent nsIDragService from waiting for
4040 // response of forwarded dragexit event.
4041 child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
4042 nsIDragService::DRAGDROP_ACTION_NONE);
4044 break;
4046 case eKeyUp:
4047 // If space key is released, we need to inactivate the element which was
4048 // activated by preceding space key down.
4049 // XXX Currently, we don't store the reason of activation. Therefore,
4050 // this may cancel what is activated by a mousedown, but it must not
4051 // cause actual problem in web apps in the wild since it must be
4052 // rare case that users release space key during a mouse click/drag.
4053 if (aEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
4054 ClearGlobalActiveContent(this);
4056 break;
4058 case eKeyPress: {
4059 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
4060 PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
4061 } break;
4063 case eMouseEnterIntoWidget:
4064 if (mCurrentTarget) {
4065 nsCOMPtr<nsIContent> targetContent;
4066 mCurrentTarget->GetContentForEvent(aEvent,
4067 getter_AddRefs(targetContent));
4068 SetContentState(targetContent, ElementState::HOVER);
4070 break;
4072 case eMouseExitFromWidget:
4073 PointerEventHandler::UpdateActivePointerState(aEvent->AsMouseEvent());
4074 break;
4076 #ifdef XP_MACOSX
4077 case eMouseActivate:
4078 if (mCurrentTarget) {
4079 nsCOMPtr<nsIContent> targetContent;
4080 mCurrentTarget->GetContentForEvent(aEvent,
4081 getter_AddRefs(targetContent));
4082 if (!NodeAllowsClickThrough(targetContent)) {
4083 *aStatus = nsEventStatus_eConsumeNoDefault;
4086 break;
4087 #endif
4089 default:
4090 break;
4093 // Reset target frame to null to avoid mistargeting after reentrant event
4094 mCurrentTarget = nullptr;
4095 mCurrentTargetContent = nullptr;
4097 return ret;
4100 BrowserParent* EventStateManager::GetCrossProcessTarget() {
4101 return IMEStateManager::GetActiveBrowserParent();
4104 bool EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent) {
4105 // Check to see if there is a focused, editable content in chrome,
4106 // in that case, do not forward IME events to content
4107 Element* focusedElement = GetFocusedElement();
4108 if (focusedElement && focusedElement->IsEditable()) {
4109 return false;
4111 return IMEStateManager::GetActiveBrowserParent() != nullptr;
4114 void EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) {
4115 RefPtr<nsPresContext> presContext = aPresContext;
4116 if (presContext) {
4117 IMEStateManager::OnDestroyPresContext(*presContext);
4120 // Bug 70855: Presentation is going away, possibly for a reframe.
4121 // Reset the hover state so that if we're recreating the presentation,
4122 // we won't have the old hover state still set in the new presentation,
4123 // as if the new presentation is resized, a new element may be hovered.
4124 ResetHoverState();
4126 mPointersEnterLeaveHelper.Clear();
4127 PointerEventHandler::NotifyDestroyPresContext(presContext);
4130 void EventStateManager::ResetHoverState() {
4131 if (mHoverContent) {
4132 SetContentState(nullptr, ElementState::HOVER);
4136 void EventStateManager::SetPresContext(nsPresContext* aPresContext) {
4137 mPresContext = aPresContext;
4140 void EventStateManager::ClearFrameRefs(nsIFrame* aFrame) {
4141 if (aFrame && aFrame == mCurrentTarget) {
4142 mCurrentTargetContent = aFrame->GetContent();
4146 struct CursorImage {
4147 gfx::IntPoint mHotspot;
4148 nsCOMPtr<imgIContainer> mContainer;
4149 ImageResolution mResolution;
4150 bool mEarlierCursorLoading = false;
4153 // Given the event that we're processing, and the computed cursor and hotspot,
4154 // determine whether the custom CSS cursor should be blocked (that is, not
4155 // honored).
4157 // We will not honor it all of the following are true:
4159 // * layout.cursor.block.enabled is true.
4160 // * the size of the custom cursor is bigger than layout.cursor.block.max-size.
4161 // * the bounds of the cursor would end up outside of the viewport of the
4162 // top-level content document.
4164 // This is done in order to prevent hijacking the cursor, see bug 1445844 and
4165 // co.
4166 static bool ShouldBlockCustomCursor(nsPresContext* aPresContext,
4167 WidgetEvent* aEvent,
4168 const CursorImage& aCursor) {
4169 if (!StaticPrefs::layout_cursor_block_enabled()) {
4170 return false;
4173 int32_t width = 0;
4174 int32_t height = 0;
4175 aCursor.mContainer->GetWidth(&width);
4176 aCursor.mContainer->GetHeight(&height);
4177 aCursor.mResolution.ApplyTo(width, height);
4179 int32_t maxSize = StaticPrefs::layout_cursor_block_max_size();
4181 if (width <= maxSize && height <= maxSize) {
4182 return false;
4185 auto input = DOMIntersectionObserver::ComputeInput(*aPresContext->Document(),
4186 nullptr, nullptr);
4188 if (!input.mRootFrame) {
4189 return false;
4192 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4193 aEvent, RelativeTo{input.mRootFrame});
4195 // The cursor size won't be affected by our full zoom in the parent process,
4196 // so undo that before checking the rect.
4197 float zoom = aPresContext->GetFullZoom();
4199 // Also adjust for accessibility cursor scaling factor.
4200 zoom /= LookAndFeel::GetFloat(LookAndFeel::FloatID::CursorScale, 1.0f);
4202 nsSize size(CSSPixel::ToAppUnits(width / zoom),
4203 CSSPixel::ToAppUnits(height / zoom));
4204 nsPoint hotspot(
4205 CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.x / zoom)),
4206 CSSPixel::ToAppUnits(ViewAs<CSSPixel>(aCursor.mHotspot.y / zoom)));
4208 const nsRect cursorRect(point - hotspot, size);
4209 auto output = DOMIntersectionObserver::Intersect(input, cursorRect);
4210 return !output.mIntersectionRect ||
4211 !(*output.mIntersectionRect == cursorRect);
4214 static gfx::IntPoint ComputeHotspot(imgIContainer* aContainer,
4215 const Maybe<gfx::Point>& aHotspot) {
4216 MOZ_ASSERT(aContainer);
4218 // css3-ui says to use the CSS-specified hotspot if present,
4219 // otherwise use the intrinsic hotspot, otherwise use the top left
4220 // corner.
4221 if (aHotspot) {
4222 int32_t imgWidth, imgHeight;
4223 aContainer->GetWidth(&imgWidth);
4224 aContainer->GetHeight(&imgHeight);
4225 auto hotspot = gfx::IntPoint::Round(*aHotspot);
4226 return {std::max(std::min(hotspot.x.value, imgWidth - 1), 0),
4227 std::max(std::min(hotspot.y.value, imgHeight - 1), 0)};
4230 gfx::IntPoint hotspot;
4231 aContainer->GetHotspotX(&hotspot.x.value);
4232 aContainer->GetHotspotY(&hotspot.y.value);
4233 return hotspot;
4236 static CursorImage ComputeCustomCursor(nsPresContext* aPresContext,
4237 WidgetEvent* aEvent,
4238 const nsIFrame& aFrame,
4239 const nsIFrame::Cursor& aCursor) {
4240 if (aCursor.mAllowCustomCursor == nsIFrame::AllowCustomCursorImage::No) {
4241 return {};
4243 const ComputedStyle& style =
4244 aCursor.mStyle ? *aCursor.mStyle : *aFrame.Style();
4246 // If we are falling back because any cursor before us is loading, let the
4247 // consumer know.
4248 bool loading = false;
4249 for (const auto& image : style.StyleUI()->Cursor().images.AsSpan()) {
4250 MOZ_ASSERT(image.image.IsImageRequestType(),
4251 "Cursor image should only parse url() types");
4252 uint32_t status;
4253 imgRequestProxy* req = image.image.GetImageRequest();
4254 if (!req || NS_FAILED(req->GetImageStatus(&status))) {
4255 continue;
4257 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4258 loading = true;
4259 continue;
4261 if (status & imgIRequest::STATUS_ERROR) {
4262 continue;
4264 nsCOMPtr<imgIContainer> container;
4265 req->GetImage(getter_AddRefs(container));
4266 if (!container) {
4267 continue;
4269 StyleImageOrientation orientation =
4270 aFrame.StyleVisibility()->UsedImageOrientation(req);
4271 container = nsLayoutUtils::OrientImage(container, orientation);
4272 Maybe<gfx::Point> specifiedHotspot =
4273 image.has_hotspot ? Some(gfx::Point{image.hotspot_x, image.hotspot_y})
4274 : Nothing();
4275 gfx::IntPoint hotspot = ComputeHotspot(container, specifiedHotspot);
4276 CursorImage result{hotspot, std::move(container),
4277 image.image.GetResolution(style), loading};
4278 if (ShouldBlockCustomCursor(aPresContext, aEvent, result)) {
4279 continue;
4281 // This is the one we want!
4282 return result;
4284 return {{}, nullptr, {}, loading};
4287 void EventStateManager::UpdateCursor(nsPresContext* aPresContext,
4288 WidgetMouseEvent* aEvent,
4289 nsIFrame* aTargetFrame,
4290 nsEventStatus* aStatus) {
4291 if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
4292 return;
4295 auto cursor = StyleCursorKind::Default;
4296 nsCOMPtr<imgIContainer> container;
4297 ImageResolution resolution;
4298 Maybe<gfx::IntPoint> hotspot;
4300 if (mHidingCursorWhileTyping && aEvent->IsReal()) {
4301 // Any non-synthetic mouse event makes us show the cursor again.
4302 mHidingCursorWhileTyping = false;
4305 if (mHidingCursorWhileTyping) {
4306 cursor = StyleCursorKind::None;
4307 } else if (mLockCursor != kInvalidCursorKind) {
4308 // If cursor is locked just use the locked one
4309 cursor = mLockCursor;
4310 } else if (aTargetFrame) {
4311 // If not locked, look for correct cursor
4312 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4313 aEvent, RelativeTo{aTargetFrame});
4314 Maybe<nsIFrame::Cursor> framecursor = aTargetFrame->GetCursor(pt);
4315 // Avoid setting cursor when the mouse is over a windowless plugin.
4316 if (!framecursor) {
4317 if (XRE_IsContentProcess()) {
4318 mLastFrameConsumedSetCursor = true;
4320 return;
4322 // Make sure cursors get reset after the mouse leaves a
4323 // windowless plugin frame.
4324 if (mLastFrameConsumedSetCursor) {
4325 ClearCachedWidgetCursor(aTargetFrame);
4326 mLastFrameConsumedSetCursor = false;
4329 const CursorImage customCursor =
4330 ComputeCustomCursor(aPresContext, aEvent, *aTargetFrame, *framecursor);
4332 // If the current cursor is from the same frame, and it is now
4333 // loading some new image for the cursor, we should wait for a
4334 // while rather than taking its fallback cursor directly.
4335 if (customCursor.mEarlierCursorLoading &&
4336 gLastCursorSourceFrame == aTargetFrame &&
4337 TimeStamp::NowLoRes() - gLastCursorUpdateTime <
4338 TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
4339 return;
4341 cursor = framecursor->mCursor;
4342 container = std::move(customCursor.mContainer);
4343 resolution = customCursor.mResolution;
4344 hotspot = Some(customCursor.mHotspot);
4347 if (aTargetFrame) {
4348 if (cursor == StyleCursorKind::Pointer && IsSelectingLink(aTargetFrame)) {
4349 cursor = aTargetFrame->GetWritingMode().IsVertical()
4350 ? StyleCursorKind::VerticalText
4351 : StyleCursorKind::Text;
4353 SetCursor(cursor, container, resolution, hotspot,
4354 aTargetFrame->GetNearestWidget(), false);
4355 gLastCursorSourceFrame = aTargetFrame;
4356 gLastCursorUpdateTime = TimeStamp::NowLoRes();
4359 if (mLockCursor != kInvalidCursorKind || StyleCursorKind::Auto != cursor) {
4360 *aStatus = nsEventStatus_eConsumeDoDefault;
4364 void EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame) {
4365 if (!aTargetFrame) {
4366 return;
4368 nsIWidget* aWidget = aTargetFrame->GetNearestWidget();
4369 if (!aWidget) {
4370 return;
4372 aWidget->ClearCachedCursor();
4375 void EventStateManager::StartHidingCursorWhileTyping(nsIWidget* aWidget) {
4376 if (mHidingCursorWhileTyping || sCursorSettingManager != this) {
4377 return;
4379 mHidingCursorWhileTyping = true;
4380 SetCursor(StyleCursorKind::None, nullptr, {}, {}, aWidget, false);
4383 nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
4384 imgIContainer* aContainer,
4385 const ImageResolution& aResolution,
4386 const Maybe<gfx::IntPoint>& aHotspot,
4387 nsIWidget* aWidget, bool aLockCursor) {
4388 EnsureDocument(mPresContext);
4389 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
4390 sCursorSettingManager = this;
4392 NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
4393 if (aLockCursor) {
4394 if (StyleCursorKind::Auto != aCursor) {
4395 mLockCursor = aCursor;
4396 } else {
4397 // If cursor style is set to auto we unlock the cursor again.
4398 mLockCursor = kInvalidCursorKind;
4401 nsCursor c;
4402 switch (aCursor) {
4403 case StyleCursorKind::Auto:
4404 case StyleCursorKind::Default:
4405 c = eCursor_standard;
4406 break;
4407 case StyleCursorKind::Pointer:
4408 c = eCursor_hyperlink;
4409 break;
4410 case StyleCursorKind::Crosshair:
4411 c = eCursor_crosshair;
4412 break;
4413 case StyleCursorKind::Move:
4414 c = eCursor_move;
4415 break;
4416 case StyleCursorKind::Text:
4417 c = eCursor_select;
4418 break;
4419 case StyleCursorKind::Wait:
4420 c = eCursor_wait;
4421 break;
4422 case StyleCursorKind::Help:
4423 c = eCursor_help;
4424 break;
4425 case StyleCursorKind::NResize:
4426 c = eCursor_n_resize;
4427 break;
4428 case StyleCursorKind::SResize:
4429 c = eCursor_s_resize;
4430 break;
4431 case StyleCursorKind::WResize:
4432 c = eCursor_w_resize;
4433 break;
4434 case StyleCursorKind::EResize:
4435 c = eCursor_e_resize;
4436 break;
4437 case StyleCursorKind::NwResize:
4438 c = eCursor_nw_resize;
4439 break;
4440 case StyleCursorKind::SeResize:
4441 c = eCursor_se_resize;
4442 break;
4443 case StyleCursorKind::NeResize:
4444 c = eCursor_ne_resize;
4445 break;
4446 case StyleCursorKind::SwResize:
4447 c = eCursor_sw_resize;
4448 break;
4449 case StyleCursorKind::Copy: // CSS3
4450 c = eCursor_copy;
4451 break;
4452 case StyleCursorKind::Alias:
4453 c = eCursor_alias;
4454 break;
4455 case StyleCursorKind::ContextMenu:
4456 c = eCursor_context_menu;
4457 break;
4458 case StyleCursorKind::Cell:
4459 c = eCursor_cell;
4460 break;
4461 case StyleCursorKind::Grab:
4462 c = eCursor_grab;
4463 break;
4464 case StyleCursorKind::Grabbing:
4465 c = eCursor_grabbing;
4466 break;
4467 case StyleCursorKind::Progress:
4468 c = eCursor_spinning;
4469 break;
4470 case StyleCursorKind::ZoomIn:
4471 c = eCursor_zoom_in;
4472 break;
4473 case StyleCursorKind::ZoomOut:
4474 c = eCursor_zoom_out;
4475 break;
4476 case StyleCursorKind::NotAllowed:
4477 c = eCursor_not_allowed;
4478 break;
4479 case StyleCursorKind::ColResize:
4480 c = eCursor_col_resize;
4481 break;
4482 case StyleCursorKind::RowResize:
4483 c = eCursor_row_resize;
4484 break;
4485 case StyleCursorKind::NoDrop:
4486 c = eCursor_no_drop;
4487 break;
4488 case StyleCursorKind::VerticalText:
4489 c = eCursor_vertical_text;
4490 break;
4491 case StyleCursorKind::AllScroll:
4492 c = eCursor_all_scroll;
4493 break;
4494 case StyleCursorKind::NeswResize:
4495 c = eCursor_nesw_resize;
4496 break;
4497 case StyleCursorKind::NwseResize:
4498 c = eCursor_nwse_resize;
4499 break;
4500 case StyleCursorKind::NsResize:
4501 c = eCursor_ns_resize;
4502 break;
4503 case StyleCursorKind::EwResize:
4504 c = eCursor_ew_resize;
4505 break;
4506 case StyleCursorKind::None:
4507 c = eCursor_none;
4508 break;
4509 default:
4510 MOZ_ASSERT_UNREACHABLE("Unknown cursor kind");
4511 c = eCursor_standard;
4512 break;
4515 uint32_t x = aHotspot ? aHotspot->x.value : 0;
4516 uint32_t y = aHotspot ? aHotspot->y.value : 0;
4517 aWidget->SetCursor(nsIWidget::Cursor{c, aContainer, x, y, aResolution});
4518 return NS_OK;
4521 class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback {
4522 public:
4523 explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
4525 MOZ_CAN_RUN_SCRIPT
4526 void HandleEvent(EventChainPostVisitor& aVisitor) override {
4527 if (aVisitor.mPresContext) {
4528 nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget);
4529 if (frame) {
4530 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
4531 &aVisitor.mEventStatus);
4536 nsCOMPtr<nsIContent> mTarget;
4539 static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
4540 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4541 EventTarget* aRelatedTarget) {
4542 // This method does not support creating a mouse/pointer button change event
4543 // because of no data about the changing state.
4544 MOZ_ASSERT(aMessage != eMouseDown);
4545 MOZ_ASSERT(aMessage != eMouseUp);
4546 MOZ_ASSERT(aMessage != ePointerDown);
4547 MOZ_ASSERT(aMessage != ePointerUp);
4548 // This method is currently designed to create the following events.
4549 MOZ_ASSERT(aMessage == eMouseOver || aMessage == eMouseEnter ||
4550 aMessage == eMouseOut || aMessage == eMouseLeave ||
4551 aMessage == ePointerOver || aMessage == ePointerEnter ||
4552 aMessage == ePointerOut || aMessage == ePointerLeave ||
4553 aMessage == eMouseEnterIntoWidget ||
4554 aMessage == eMouseExitFromWidget);
4556 WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
4557 UniquePtr<WidgetMouseEvent> newEvent;
4558 if (sourcePointer) {
4559 AUTO_PROFILER_LABEL("CreateMouseOrPointerWidgetEvent", OTHER);
4561 WidgetPointerEvent* newPointerEvent = new WidgetPointerEvent(
4562 aMouseEvent->IsTrusted(), aMessage, aMouseEvent->mWidget);
4563 newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary;
4564 newPointerEvent->mWidth = sourcePointer->mWidth;
4565 newPointerEvent->mHeight = sourcePointer->mHeight;
4566 newPointerEvent->mInputSource = sourcePointer->mInputSource;
4568 newEvent = WrapUnique(newPointerEvent);
4569 } else {
4570 newEvent = MakeUnique<WidgetMouseEvent>(aMouseEvent->IsTrusted(), aMessage,
4571 aMouseEvent->mWidget,
4572 WidgetMouseEvent::eReal);
4574 newEvent->mRelatedTarget = aRelatedTarget;
4575 newEvent->mRefPoint = aMouseEvent->mRefPoint;
4576 newEvent->mModifiers = aMouseEvent->mModifiers;
4577 if (!aMouseEvent->mFlags.mDispatchedAtLeastOnce &&
4578 aMouseEvent->InputSourceSupportsHover()) {
4579 // If we synthesize a pointer event or a mouse event from another event
4580 // which changes a button state whose input soucre supports hover state and
4581 // the source event has not been dispatched yet, we should set to the button
4582 // state of the synthesizing event to previous one.
4583 // Note that we don't need to do this if the input source does not support
4584 // hover state because a WPT check the behavior (see below) and the other
4585 // browsers pass the test even though this is inconsistent behavior.
4586 newEvent->mButton =
4587 sourcePointer ? MouseButton::eNotPressed : MouseButton::ePrimary;
4588 if (aMouseEvent->IsPressingButton()) {
4589 // If the source event has not been dispatched into the DOM yet, we
4590 // need to remove the flag which is being pressed.
4591 newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
4592 aMouseEvent->mButtons &
4593 ~MouseButtonsFlagToChange(
4594 static_cast<MouseButton>(aMouseEvent->mButton)));
4595 } else if (aMouseEvent->IsReleasingButton()) {
4596 // If the source event has not been dispatched into the DOM yet, we
4597 // need to add the flag which is being released.
4598 newEvent->mButtons = static_cast<decltype(WidgetMouseEvent::mButtons)>(
4599 aMouseEvent->mButtons |
4600 MouseButtonsFlagToChange(
4601 static_cast<MouseButton>(aMouseEvent->mButton)));
4602 } else {
4603 // The source event does not change the buttons state so that we can
4604 // set mButtons value as-is.
4605 newEvent->mButtons = aMouseEvent->mButtons;
4607 // Adjust pressure if it does not matches with mButtons.
4608 // FIXME: We may use wrong pressure value if the source event has not been
4609 // dispatched into the DOM yet. However, fixing this requires to store the
4610 // last pressure value somewhere.
4611 if (newEvent->mButtons && aMouseEvent->mPressure == 0) {
4612 newEvent->mPressure = 0.5f;
4613 } else if (!newEvent->mButtons && aMouseEvent->mPressure != 0) {
4614 newEvent->mPressure = 0;
4615 } else {
4616 newEvent->mPressure = aMouseEvent->mPressure;
4618 } else {
4619 // If the event has already been dispatched into the tree, web apps has
4620 // already handled the button state change, so the button state of the
4621 // source event has already synced.
4622 // If the input source does not have hover state, we don't need to modify
4623 // the state because the other browsers behave so and tested by
4624 // pointerevent_attributes_nohover_pointers.html even though this is
4625 // different expectation from
4626 // pointerevent_attributes_hoverable_pointers.html, but the other browsers
4627 // pass both of them.
4628 newEvent->mButton = aMouseEvent->mButton;
4629 newEvent->mButtons = aMouseEvent->mButtons;
4630 newEvent->mPressure = aMouseEvent->mPressure;
4633 newEvent->mInputSource = aMouseEvent->mInputSource;
4634 newEvent->pointerId = aMouseEvent->pointerId;
4636 return newEvent;
4639 nsIFrame* EventStateManager::DispatchMouseOrPointerEvent(
4640 WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
4641 nsIContent* aTargetContent, nsIContent* aRelatedContent) {
4642 // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
4643 // "[When the mouse is locked on an element...e]vents that require the concept
4644 // of a mouse cursor must not be dispatched (for example: mouseover,
4645 // mouseout).
4646 if (PointerLockManager::IsLocked() &&
4647 (aMessage == eMouseLeave || aMessage == eMouseEnter ||
4648 aMessage == eMouseOver || aMessage == eMouseOut)) {
4649 mCurrentTargetContent = nullptr;
4650 nsCOMPtr<Element> pointerLockedElement =
4651 PointerLockManager::GetLockedElement();
4652 if (!pointerLockedElement) {
4653 NS_WARNING("Should have pointer locked element, but didn't.");
4654 return nullptr;
4656 return mPresContext->GetPrimaryFrameFor(pointerLockedElement);
4659 mCurrentTargetContent = nullptr;
4661 if (!aTargetContent) {
4662 return nullptr;
4665 nsCOMPtr<nsIContent> targetContent = aTargetContent;
4666 nsCOMPtr<nsIContent> relatedContent = aRelatedContent;
4668 UniquePtr<WidgetMouseEvent> dispatchEvent =
4669 CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, relatedContent);
4671 AutoWeakFrame previousTarget = mCurrentTarget;
4672 mCurrentTargetContent = targetContent;
4674 nsIFrame* targetFrame = nullptr;
4676 nsEventStatus status = nsEventStatus_eIgnore;
4677 ESMEventCB callback(targetContent);
4678 RefPtr<nsPresContext> presContext = mPresContext;
4679 EventDispatcher::Dispatch(targetContent, presContext, dispatchEvent.get(),
4680 nullptr, &status, &callback);
4682 if (mPresContext) {
4683 // Although the primary frame was checked in event callback, it may not be
4684 // the same object after event dispatch and handling, so refetch it.
4685 targetFrame = mPresContext->GetPrimaryFrameFor(targetContent);
4687 // If we are entering/leaving remote content, dispatch a mouse enter/exit
4688 // event to the remote frame.
4689 if (IsTopLevelRemoteTarget(targetContent)) {
4690 if (aMessage == eMouseOut) {
4691 // For remote content, send a puppet widget mouse exit event.
4692 UniquePtr<WidgetMouseEvent> remoteEvent =
4693 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
4694 relatedContent);
4695 remoteEvent->mExitFrom = Some(WidgetMouseEvent::ePuppet);
4697 // mCurrentTarget is set to the new target, so we must reset it to the
4698 // old target and then dispatch a cross-process event. (mCurrentTarget
4699 // will be set back below.) HandleCrossProcessEvent will query for the
4700 // proper target via GetEventTarget which will return mCurrentTarget.
4701 mCurrentTarget = targetFrame;
4702 HandleCrossProcessEvent(remoteEvent.get(), &status);
4703 } else if (aMessage == eMouseOver) {
4704 UniquePtr<WidgetMouseEvent> remoteEvent =
4705 CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget,
4706 relatedContent);
4707 HandleCrossProcessEvent(remoteEvent.get(), &status);
4712 mCurrentTargetContent = nullptr;
4713 mCurrentTarget = previousTarget;
4715 return targetFrame;
4718 static nsIContent* FindCommonAncestor(nsIContent* aNode1, nsIContent* aNode2) {
4719 if (!aNode1 || !aNode2) {
4720 return nullptr;
4722 return nsContentUtils::GetCommonFlattenedTreeAncestor(aNode1, aNode2);
4725 class EnterLeaveDispatcher {
4726 public:
4727 EnterLeaveDispatcher(EventStateManager* aESM, nsIContent* aTarget,
4728 nsIContent* aRelatedTarget,
4729 WidgetMouseEvent* aMouseEvent,
4730 EventMessage aEventMessage)
4731 : mESM(aESM), mMouseEvent(aMouseEvent), mEventMessage(aEventMessage) {
4732 nsPIDOMWindowInner* win =
4733 aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr;
4734 if (aMouseEvent->AsPointerEvent()
4735 ? win && win->HasPointerEnterLeaveEventListeners()
4736 : win && win->HasMouseEnterLeaveEventListeners()) {
4737 mRelatedTarget =
4738 aRelatedTarget ? aRelatedTarget->FindFirstNonChromeOnlyAccessContent()
4739 : nullptr;
4740 nsINode* commonParent = FindCommonAncestor(aTarget, aRelatedTarget);
4741 nsIContent* current = aTarget;
4742 // Note, it is ok if commonParent is null!
4743 while (current && current != commonParent) {
4744 if (!current->ChromeOnlyAccess()) {
4745 mTargets.AppendObject(current);
4747 // mouseenter/leave is fired only on elements.
4748 current = current->GetFlattenedTreeParent();
4753 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
4754 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch() {
4755 if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
4756 for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
4757 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4758 MOZ_KnownLive(mTargets[i]),
4759 mRelatedTarget);
4761 } else {
4762 for (int32_t i = 0; i < mTargets.Count(); ++i) {
4763 mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
4764 MOZ_KnownLive(mTargets[i]),
4765 mRelatedTarget);
4770 // Nothing overwrites anything after constructor. Please remove MOZ_KnownLive
4771 // and MOZ_KNOWN_LIVE if anything marked as such becomes mutable.
4772 const RefPtr<EventStateManager> mESM;
4773 nsCOMArray<nsIContent> mTargets;
4774 MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRelatedTarget;
4775 WidgetMouseEvent* mMouseEvent;
4776 EventMessage mEventMessage;
4779 void EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
4780 nsIContent* aMovingInto) {
4781 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
4783 if (!wrapper || !wrapper->mLastOverElement) {
4784 return;
4786 // Before firing mouseout, check for recursion
4787 if (wrapper->mLastOverElement == wrapper->mFirstOutEventElement) {
4788 return;
4791 if (RefPtr<nsFrameLoaderOwner> flo =
4792 do_QueryObject(wrapper->mLastOverElement)) {
4793 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
4794 if (nsIDocShell* docshell = bc->GetDocShell()) {
4795 if (RefPtr<nsPresContext> presContext = docshell->GetPresContext()) {
4796 EventStateManager* kidESM = presContext->EventStateManager();
4797 // Not moving into any element in this subdocument
4798 kidESM->NotifyMouseOut(aMouseEvent, nullptr);
4803 // That could have caused DOM events which could wreak havoc. Reverify
4804 // things and be careful.
4805 if (!wrapper->mLastOverElement) {
4806 return;
4809 // Store the first mouseOut event we fire and don't refire mouseOut
4810 // to that element while the first mouseOut is still ongoing.
4811 wrapper->mFirstOutEventElement = wrapper->mLastOverElement;
4813 // Don't touch hover state if aMovingInto is non-null. Caller will update
4814 // hover state itself, and we have optimizations for hover switching between
4815 // two nearby elements both deep in the DOM tree that would be defeated by
4816 // switching the hover state to null here.
4817 bool isPointer = aMouseEvent->mClass == ePointerEventClass;
4818 if (!aMovingInto && !isPointer) {
4819 // Unset :hover
4820 SetContentState(nullptr, ElementState::HOVER);
4823 EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement,
4824 aMovingInto, aMouseEvent,
4825 isPointer ? ePointerLeave : eMouseLeave);
4827 // Fire mouseout
4828 nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
4829 DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut,
4830 lastOverElement, aMovingInto);
4831 leaveDispatcher.Dispatch();
4833 wrapper->mLastOverFrame = nullptr;
4834 wrapper->mLastOverElement = nullptr;
4836 // Turn recursion protection back off
4837 wrapper->mFirstOutEventElement = nullptr;
4840 void EventStateManager::RecomputeMouseEnterStateForRemoteFrame(
4841 Element& aElement) {
4842 if (!mMouseEnterLeaveHelper ||
4843 mMouseEnterLeaveHelper->mLastOverElement != &aElement) {
4844 return;
4847 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
4848 remote->MouseEnterIntoWidget();
4852 void EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
4853 nsIContent* aContent) {
4854 NS_ASSERTION(aContent, "Mouse must be over something");
4856 RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
4858 if (!wrapper || wrapper->mLastOverElement == aContent) return;
4860 // Before firing mouseover, check for recursion
4861 if (aContent == wrapper->mFirstOverEventElement) return;
4863 // Check to see if we're a subdocument and if so update the parent
4864 // document's ESM state to indicate that the mouse is over the
4865 // content associated with our subdocument.
4866 EnsureDocument(mPresContext);
4867 if (Document* parentDoc = mDocument->GetInProcessParentDocument()) {
4868 if (nsCOMPtr<nsIContent> docContent = mDocument->GetEmbedderElement()) {
4869 if (PresShell* parentPresShell = parentDoc->GetPresShell()) {
4870 RefPtr<EventStateManager> parentESM =
4871 parentPresShell->GetPresContext()->EventStateManager();
4872 parentESM->NotifyMouseOver(aMouseEvent, docContent);
4876 // Firing the DOM event in the parent document could cause all kinds
4877 // of havoc. Reverify and take care.
4878 if (wrapper->mLastOverElement == aContent) return;
4880 // Remember mLastOverElement as the related content for the
4881 // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it,
4882 // bug 298477.
4883 nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
4885 bool isPointer = aMouseEvent->mClass == ePointerEventClass;
4887 EnterLeaveDispatcher enterDispatcher(this, aContent, lastOverElement,
4888 aMouseEvent,
4889 isPointer ? ePointerEnter : eMouseEnter);
4891 if (!isPointer) {
4892 SetContentState(aContent, ElementState::HOVER);
4895 NotifyMouseOut(aMouseEvent, aContent);
4897 // Store the first mouseOver event we fire and don't refire mouseOver
4898 // to that element while the first mouseOver is still ongoing.
4899 wrapper->mFirstOverEventElement = aContent;
4901 // Fire mouseover
4902 wrapper->mLastOverFrame = DispatchMouseOrPointerEvent(
4903 aMouseEvent, isPointer ? ePointerOver : eMouseOver, aContent,
4904 lastOverElement);
4905 enterDispatcher.Dispatch();
4906 wrapper->mLastOverElement = aContent;
4908 // Turn recursion protection back off
4909 wrapper->mFirstOverEventElement = nullptr;
4912 // Returns the center point of the window's client area. This is
4913 // in widget coordinates, i.e. relative to the widget's top-left
4914 // corner, not in screen coordinates, the same units that UIEvent::
4915 // refpoint is in. It may not be the exact center of the window if
4916 // the platform requires rounding the coordinate.
4917 static LayoutDeviceIntPoint GetWindowClientRectCenter(nsIWidget* aWidget) {
4918 NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0));
4920 LayoutDeviceIntRect rect = aWidget->GetClientBounds();
4921 LayoutDeviceIntPoint point(rect.x + rect.width / 2, rect.y + rect.height / 2);
4922 int32_t round = aWidget->RoundsWidgetCoordinatesTo();
4923 point.x = point.x / round * round;
4924 point.y = point.y / round * round;
4925 return point - aWidget->WidgetToScreenOffset();
4928 void EventStateManager::GeneratePointerEnterExit(EventMessage aMessage,
4929 WidgetMouseEvent* aEvent) {
4930 WidgetPointerEvent pointerEvent(*aEvent);
4931 pointerEvent.mMessage = aMessage;
4932 GenerateMouseEnterExit(&pointerEvent);
4935 /* static */
4936 void EventStateManager::UpdateLastRefPointOfMouseEvent(
4937 WidgetMouseEvent* aMouseEvent) {
4938 if (aMouseEvent->mMessage != eMouseMove &&
4939 aMouseEvent->mMessage != ePointerMove) {
4940 return;
4943 // Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
4944 // Movement is calculated in UIEvent::GetMovementPoint() as:
4945 // previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
4946 if (PointerLockManager::IsLocked() && aMouseEvent->mWidget) {
4947 // The pointer is locked. If the pointer is not located at the center of
4948 // the window, dispatch a synthetic mousemove to return the pointer there.
4949 // Doing this between "real" pointer moves gives the impression that the
4950 // (locked) pointer can continue moving and won't stop at the screen
4951 // boundary. We cancel the synthetic event so that we don't end up
4952 // dispatching the centering move event to content.
4953 aMouseEvent->mLastRefPoint =
4954 GetWindowClientRectCenter(aMouseEvent->mWidget);
4956 } else if (sLastRefPoint == kInvalidRefPoint) {
4957 // We don't have a valid previous mousemove mRefPoint. This is either
4958 // the first move we've encountered, or the mouse has just re-entered
4959 // the application window. We should report (0,0) movement for this
4960 // case, so make the current and previous mRefPoints the same.
4961 aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
4962 } else {
4963 aMouseEvent->mLastRefPoint = sLastRefPoint;
4967 /* static */
4968 void EventStateManager::ResetPointerToWindowCenterWhilePointerLocked(
4969 WidgetMouseEvent* aMouseEvent) {
4970 MOZ_ASSERT(PointerLockManager::IsLocked());
4971 if ((aMouseEvent->mMessage != eMouseMove &&
4972 aMouseEvent->mMessage != ePointerMove) ||
4973 !aMouseEvent->mWidget) {
4974 return;
4977 // We generate pointermove from mousemove event, so only synthesize native
4978 // mouse move and update sSynthCenteringPoint by mousemove event.
4979 bool updateSynthCenteringPoint = aMouseEvent->mMessage == eMouseMove;
4981 // The pointer is locked. If the pointer is not located at the center of
4982 // the window, dispatch a synthetic mousemove to return the pointer there.
4983 // Doing this between "real" pointer moves gives the impression that the
4984 // (locked) pointer can continue moving and won't stop at the screen
4985 // boundary. We cancel the synthetic event so that we don't end up
4986 // dispatching the centering move event to content.
4987 LayoutDeviceIntPoint center = GetWindowClientRectCenter(aMouseEvent->mWidget);
4989 if (aMouseEvent->mRefPoint != center && updateSynthCenteringPoint) {
4990 // Mouse move doesn't finish at the center of the window. Dispatch a
4991 // synthetic native mouse event to move the pointer back to the center
4992 // of the window, to faciliate more movement. But first, record that
4993 // we've dispatched a synthetic mouse movement, so we can cancel it
4994 // in the other branch here.
4995 sSynthCenteringPoint = center;
4996 // XXX Once we fix XXX comments in SetPointerLock about this API, we could
4997 // restrict that this API works only in the automation mode or in the
4998 // pointer locked situation.
4999 aMouseEvent->mWidget->SynthesizeNativeMouseMove(
5000 center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
5001 } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
5002 // This is the "synthetic native" event we dispatched to re-center the
5003 // pointer. Cancel it so we don't expose the centering move to content.
5004 aMouseEvent->StopPropagation();
5005 // Clear sSynthCenteringPoint so we don't cancel other events
5006 // targeted at the center.
5007 if (updateSynthCenteringPoint) {
5008 sSynthCenteringPoint = kInvalidRefPoint;
5013 /* static */
5014 void EventStateManager::UpdateLastPointerPosition(
5015 WidgetMouseEvent* aMouseEvent) {
5016 if (aMouseEvent->mMessage != eMouseMove) {
5017 return;
5019 sLastRefPoint = aMouseEvent->mRefPoint;
5022 void EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) {
5023 EnsureDocument(mPresContext);
5024 if (!mDocument) return;
5026 // Hold onto old target content through the event and reset after.
5027 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
5029 switch (aMouseEvent->mMessage) {
5030 case eMouseMove:
5031 case ePointerMove:
5032 case ePointerDown:
5033 case ePointerGotCapture: {
5034 // Get the target content target (mousemove target == mouseover target)
5035 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
5036 if (!targetElement) {
5037 // We're always over the document root, even if we're only
5038 // over dead space in a page (whose frame is not associated with
5039 // any content) or in print preview dead space
5040 targetElement = mDocument->GetRootElement();
5042 if (targetElement) {
5043 NotifyMouseOver(aMouseEvent, targetElement);
5045 } break;
5046 case ePointerUp: {
5047 // Get the target content target (mousemove target == mouseover target)
5048 nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
5049 if (!targetElement) {
5050 // We're always over the document root, even if we're only
5051 // over dead space in a page (whose frame is not associated with
5052 // any content) or in print preview dead space
5053 targetElement = mDocument->GetRootElement();
5055 if (targetElement) {
5056 RefPtr<OverOutElementsWrapper> helper =
5057 GetWrapperByEventID(aMouseEvent);
5058 if (helper) {
5059 helper->mLastOverElement = targetElement;
5061 NotifyMouseOut(aMouseEvent, nullptr);
5063 } break;
5064 case ePointerLeave:
5065 case ePointerCancel:
5066 case eMouseExitFromWidget: {
5067 // This is actually the window mouse exit or pointer leave event. We're
5068 // not moving into any new element.
5070 RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
5071 if (helper && helper->mLastOverFrame &&
5072 nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) !=
5073 nsContentUtils::GetTopLevelWidget(
5074 helper->mLastOverFrame->GetNearestWidget())) {
5075 // the Mouse/PointerOut event widget doesn't have same top widget with
5076 // mLastOverFrame, it's a spurious event for mLastOverFrame
5077 break;
5080 // Reset sLastRefPoint, so that we'll know not to report any
5081 // movement the next time we re-enter the window.
5082 sLastRefPoint = kInvalidRefPoint;
5084 NotifyMouseOut(aMouseEvent, nullptr);
5085 } break;
5086 default:
5087 break;
5090 // reset mCurretTargetContent to what it was
5091 mCurrentTargetContent = targetBeforeEvent;
5094 OverOutElementsWrapper* EventStateManager::GetWrapperByEventID(
5095 WidgetMouseEvent* aEvent) {
5096 WidgetPointerEvent* pointer = aEvent->AsPointerEvent();
5097 if (!pointer) {
5098 MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr);
5099 if (!mMouseEnterLeaveHelper) {
5100 mMouseEnterLeaveHelper = new OverOutElementsWrapper();
5102 return mMouseEnterLeaveHelper;
5104 return mPointersEnterLeaveHelper.GetOrInsertNew(pointer->pointerId);
5107 /* static */
5108 void EventStateManager::SetPointerLock(nsIWidget* aWidget,
5109 nsIContent* aElement) {
5110 // Reset mouse wheel transaction
5111 WheelTransaction::EndTransaction();
5113 // Deal with DnD events
5114 nsCOMPtr<nsIDragService> dragService =
5115 do_GetService("@mozilla.org/widget/dragservice;1");
5117 if (PointerLockManager::IsLocked()) {
5118 MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
5120 // Release all pointer capture when a pointer lock is successfully applied
5121 // on an element.
5122 PointerEventHandler::ReleaseAllPointerCapture();
5124 // Store the last known ref point so we can reposition the pointer after
5125 // unlock.
5126 sPreLockPoint = sLastRefPoint;
5128 // Fire a synthetic mouse move to ensure event state is updated. We first
5129 // set the mouse to the center of the window, so that the mouse event
5130 // doesn't report any movement.
5131 // XXX Cannot we do synthesize the native mousemove in the parent process
5132 // with calling LockNativePointer below? Then, we could make this API
5133 // work only in the automation mode.
5134 sLastRefPoint = GetWindowClientRectCenter(aWidget);
5135 aWidget->SynthesizeNativeMouseMove(
5136 sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
5138 // Suppress DnD
5139 if (dragService) {
5140 dragService->Suppress();
5143 // Activate native pointer lock on platforms where it is required (Wayland)
5144 aWidget->LockNativePointer();
5145 } else {
5146 if (aWidget) {
5147 // Deactivate native pointer lock on platforms where it is required
5148 aWidget->UnlockNativePointer();
5151 // Unlocking, so return pointer to the original position by firing a
5152 // synthetic mouse event. We first reset sLastRefPoint to its
5153 // pre-pointerlock position, so that the synthetic mouse event reports
5154 // no movement.
5155 sLastRefPoint = sPreLockPoint;
5156 // Reset SynthCenteringPoint to invalid so that next time we start
5157 // locking pointer, it has its initial value.
5158 sSynthCenteringPoint = kInvalidRefPoint;
5159 if (aWidget) {
5160 // XXX Cannot we do synthesize the native mousemove in the parent process
5161 // with calling `UnlockNativePointer` above? Then, we could make this
5162 // API work only in the automation mode.
5163 aWidget->SynthesizeNativeMouseMove(
5164 sPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr);
5167 // Unsuppress DnD
5168 if (dragService) {
5169 dragService->Unsuppress();
5174 void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
5175 WidgetDragEvent* aDragEvent) {
5176 // Hold onto old target content through the event and reset after.
5177 nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
5179 switch (aDragEvent->mMessage) {
5180 case eDragOver: {
5181 // when dragging from one frame to another, events are fired in the
5182 // order: dragexit, dragenter, dragleave
5183 if (sLastDragOverFrame != mCurrentTarget) {
5184 // We'll need the content, too, to check if it changed separately from
5185 // the frames.
5186 nsCOMPtr<nsIContent> lastContent;
5187 nsCOMPtr<nsIContent> targetContent;
5188 mCurrentTarget->GetContentForEvent(aDragEvent,
5189 getter_AddRefs(targetContent));
5190 if (targetContent && targetContent->IsText()) {
5191 targetContent = targetContent->GetFlattenedTreeParent();
5194 if (sLastDragOverFrame) {
5195 // The frame has changed but the content may not have. Check before
5196 // dispatching to content
5197 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5198 getter_AddRefs(lastContent));
5199 if (lastContent && lastContent->IsText()) {
5200 lastContent = lastContent->GetFlattenedTreeParent();
5203 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5204 FireDragEnterOrExit(presContext, aDragEvent, eDragExit, targetContent,
5205 lastContent, sLastDragOverFrame);
5206 nsIContent* target = sLastDragOverFrame
5207 ? sLastDragOverFrame.GetFrame()->GetContent()
5208 : nullptr;
5209 // XXXedgar, look like we need to consider fission OOP iframe, too.
5210 if (IsTopLevelRemoteTarget(target)) {
5211 // Dragging something and moving from web content to chrome only
5212 // fires dragexit and dragleave to xul:browser. We have to forward
5213 // dragexit to sLastDragOverFrame when its content is a remote
5214 // target. We don't forward dragleave since it's generated from
5215 // dragexit.
5216 WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
5217 aDragEvent->mWidget);
5218 remoteEvent.AssignDragEventData(*aDragEvent, true);
5219 remoteEvent.mFlags.mIsSynthesizedForTests =
5220 aDragEvent->mFlags.mIsSynthesizedForTests;
5221 nsEventStatus remoteStatus = nsEventStatus_eIgnore;
5222 HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
5226 AutoWeakFrame currentTraget = mCurrentTarget;
5227 FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, lastContent,
5228 targetContent, currentTraget);
5230 if (sLastDragOverFrame) {
5231 RefPtr<nsPresContext> presContext = sLastDragOverFrame->PresContext();
5232 FireDragEnterOrExit(presContext, aDragEvent, eDragLeave,
5233 targetContent, lastContent, sLastDragOverFrame);
5236 sLastDragOverFrame = mCurrentTarget;
5238 } break;
5240 case eDragExit: {
5241 // This is actually the window mouse exit event.
5242 if (sLastDragOverFrame) {
5243 nsCOMPtr<nsIContent> lastContent;
5244 sLastDragOverFrame->GetContentForEvent(aDragEvent,
5245 getter_AddRefs(lastContent));
5247 RefPtr<nsPresContext> lastDragOverFramePresContext =
5248 sLastDragOverFrame->PresContext();
5249 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent, eDragExit,
5250 nullptr, lastContent, sLastDragOverFrame);
5251 FireDragEnterOrExit(lastDragOverFramePresContext, aDragEvent,
5252 eDragLeave, nullptr, lastContent,
5253 sLastDragOverFrame);
5255 sLastDragOverFrame = nullptr;
5257 } break;
5259 default:
5260 break;
5263 // reset mCurretTargetContent to what it was
5264 mCurrentTargetContent = targetBeforeEvent;
5266 // Now flush all pending notifications, for better responsiveness.
5267 FlushLayout(aPresContext);
5270 void EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
5271 WidgetDragEvent* aDragEvent,
5272 EventMessage aMessage,
5273 nsIContent* aRelatedTarget,
5274 nsIContent* aTargetContent,
5275 AutoWeakFrame& aTargetFrame) {
5276 MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
5277 aMessage == eDragEnter);
5278 nsEventStatus status = nsEventStatus_eIgnore;
5279 WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
5280 event.AssignDragEventData(*aDragEvent, false);
5281 event.mFlags.mIsSynthesizedForTests =
5282 aDragEvent->mFlags.mIsSynthesizedForTests;
5283 event.mRelatedTarget = aRelatedTarget;
5284 if (aMessage == eDragExit && !StaticPrefs::dom_event_dragexit_enabled()) {
5285 event.mFlags.mOnlyChromeDispatch = true;
5288 mCurrentTargetContent = aTargetContent;
5290 if (aTargetContent != aRelatedTarget) {
5291 // XXX This event should still go somewhere!!
5292 if (aTargetContent) {
5293 EventDispatcher::Dispatch(aTargetContent, aPresContext, &event, nullptr,
5294 &status);
5297 // adjust the drag hover if the dragenter event was cancelled or this is a
5298 // drag exit
5299 if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) {
5300 SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr,
5301 ElementState::DRAGOVER);
5304 // collect any changes to moz cursor settings stored in the event's
5305 // data transfer.
5306 UpdateDragDataTransfer(&event);
5309 // Finally dispatch the event to the frame
5310 if (aTargetFrame) {
5311 aTargetFrame->HandleEvent(aPresContext, &event, &status);
5315 void EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent) {
5316 NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!");
5317 if (!dragEvent->mDataTransfer) {
5318 return;
5321 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5323 if (dragSession) {
5324 // the initial dataTransfer is the one from the dragstart event that
5325 // was set on the dragSession when the drag began.
5326 RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
5327 if (initialDataTransfer) {
5328 // retrieve the current moz cursor setting and save it.
5329 nsAutoString mozCursor;
5330 dragEvent->mDataTransfer->GetMozCursor(mozCursor);
5331 initialDataTransfer->SetMozCursor(mozCursor);
5336 nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
5337 nsEventStatus* aStatus,
5338 nsIContent* aOverrideClickTarget) {
5339 nsCOMPtr<nsIContent> mouseContent = aOverrideClickTarget;
5340 if (!mouseContent && mCurrentTarget) {
5341 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
5343 if (mouseContent && mouseContent->IsText()) {
5344 nsINode* parent = mouseContent->GetFlattenedTreeParentNode();
5345 if (parent && parent->IsContent()) {
5346 mouseContent = parent->AsContent();
5350 LastMouseDownInfo& mouseDownInfo = GetLastMouseDownInfo(aEvent->mButton);
5351 if (aEvent->mMessage == eMouseDown) {
5352 mouseDownInfo.mLastMouseDownContent =
5353 !aEvent->mClickEventPrevented ? mouseContent : nullptr;
5355 if (mouseDownInfo.mLastMouseDownContent) {
5356 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
5357 mouseDownInfo.mLastMouseDownContent)) {
5358 mouseDownInfo.mLastMouseDownInputControlType =
5359 Some(input->ControlType());
5360 } else if (mouseDownInfo.mLastMouseDownContent
5361 ->IsInNativeAnonymousSubtree()) {
5362 if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(
5363 mouseDownInfo.mLastMouseDownContent
5364 ->GetFlattenedTreeParent())) {
5365 mouseDownInfo.mLastMouseDownInputControlType =
5366 Some(input->ControlType());
5370 } else {
5371 aEvent->mClickTarget =
5372 !aEvent->mClickEventPrevented
5373 ? GetCommonAncestorForMouseUp(
5374 mouseContent, mouseDownInfo.mLastMouseDownContent,
5375 mouseDownInfo.mLastMouseDownInputControlType)
5376 : nullptr;
5377 if (aEvent->mClickTarget) {
5378 aEvent->mClickCount = mouseDownInfo.mClickCount;
5379 mouseDownInfo.mClickCount = 0;
5380 } else {
5381 aEvent->mClickCount = 0;
5383 mouseDownInfo.mLastMouseDownContent = nullptr;
5384 mouseDownInfo.mLastMouseDownInputControlType = Nothing();
5387 return NS_OK;
5390 // static
5391 bool EventStateManager::EventCausesClickEvents(
5392 const WidgetMouseEvent& aMouseEvent) {
5393 if (NS_WARN_IF(aMouseEvent.mMessage != eMouseUp)) {
5394 return false;
5396 // If the mouseup event is synthesized event, we don't need to dispatch
5397 // click events.
5398 if (!aMouseEvent.IsReal()) {
5399 return false;
5401 // If mouse is still over same element, clickcount will be > 1.
5402 // If it has moved it will be zero, so no click.
5403 if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
5404 return false;
5406 // If click event was explicitly prevented, we shouldn't dispatch it.
5407 if (aMouseEvent.mClickEventPrevented) {
5408 return false;
5410 // Check that the window isn't disabled before firing a click
5411 // (see bug 366544).
5412 return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
5415 nsresult EventStateManager::InitAndDispatchClickEvent(
5416 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5417 EventMessage aMessage, PresShell* aPresShell, nsIContent* aMouseUpContent,
5418 AutoWeakFrame aCurrentTarget, bool aNoContentDispatch,
5419 nsIContent* aOverrideClickTarget) {
5420 MOZ_ASSERT(aMouseUpEvent);
5421 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5422 MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget);
5424 WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage,
5425 aMouseUpEvent->mWidget, WidgetMouseEvent::eReal);
5427 event.mRefPoint = aMouseUpEvent->mRefPoint;
5428 event.mClickCount = aMouseUpEvent->mClickCount;
5429 event.mModifiers = aMouseUpEvent->mModifiers;
5430 event.mButtons = aMouseUpEvent->mButtons;
5431 event.mTimeStamp = aMouseUpEvent->mTimeStamp;
5432 event.mFlags.mOnlyChromeDispatch = aNoContentDispatch;
5433 event.mFlags.mNoContentDispatch = aNoContentDispatch;
5434 event.mButton = aMouseUpEvent->mButton;
5435 event.pointerId = aMouseUpEvent->pointerId;
5436 event.mInputSource = aMouseUpEvent->mInputSource;
5437 nsIContent* target = aMouseUpContent;
5438 nsIFrame* targetFrame = aCurrentTarget;
5439 if (aOverrideClickTarget) {
5440 target = aOverrideClickTarget;
5441 targetFrame = aOverrideClickTarget->GetPrimaryFrame();
5444 if (!target->IsInComposedDoc()) {
5445 return NS_OK;
5448 // Use local event status for each click event dispatching since it'll be
5449 // cleared by EventStateManager::PreHandleEvent(). Therefore, dispatching
5450 // an event means that previous event status will be ignored.
5451 nsEventStatus status = nsEventStatus_eIgnore;
5452 nsresult rv = aPresShell->HandleEventWithTarget(
5453 &event, targetFrame, MOZ_KnownLive(target), &status);
5455 // Copy mMultipleActionsPrevented flag from a click event to the mouseup
5456 // event only when it's set to true. It may be set to true if an editor has
5457 // already handled it. This is important to avoid two or more default
5458 // actions handled here.
5459 aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
5460 event.mFlags.mMultipleActionsPrevented;
5461 // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
5462 // overwrite it.
5463 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
5464 return rv;
5466 // If new status is nsEventStatus_eConsumeNoDefault or
5467 // nsEventStatus_eConsumeDoDefault, use it.
5468 if (status == nsEventStatus_eConsumeNoDefault ||
5469 status == nsEventStatus_eConsumeDoDefault) {
5470 *aStatus = status;
5471 return rv;
5473 // Otherwise, keep the original status.
5474 return rv;
5477 nsresult EventStateManager::PostHandleMouseUp(
5478 WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus,
5479 nsIContent* aOverrideClickTarget) {
5480 MOZ_ASSERT(aMouseUpEvent);
5481 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5482 MOZ_ASSERT(aStatus);
5484 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
5485 if (!presShell) {
5486 return NS_OK;
5489 nsCOMPtr<nsIContent> clickTarget =
5490 nsIContent::FromEventTargetOrNull(aMouseUpEvent->mClickTarget);
5491 NS_ENSURE_STATE(clickTarget);
5493 // Fire click events if the event target is still available.
5494 // Note that do not include the eMouseUp event's status since we ignore it
5495 // for compatibility with the other browsers.
5496 nsEventStatus status = nsEventStatus_eIgnore;
5497 nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, &status,
5498 clickTarget, aOverrideClickTarget);
5499 if (NS_WARN_IF(NS_FAILED(rv))) {
5500 return rv;
5503 // Do not do anything if preceding click events are consumed.
5504 // Note that Chromium dispatches "paste" event and actually pates clipboard
5505 // text into focused editor even if the preceding click events are consumed.
5506 // However, this is different from our traditional behavior and does not
5507 // conform to DOM events. If we need to keep compatibility with Chromium,
5508 // we should change it later.
5509 if (status == nsEventStatus_eConsumeNoDefault) {
5510 *aStatus = nsEventStatus_eConsumeNoDefault;
5511 return NS_OK;
5514 // Handle middle click paste if it's enabled and the mouse button is middle.
5515 if (aMouseUpEvent->mButton != MouseButton::eMiddle ||
5516 !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
5517 return NS_OK;
5519 DebugOnly<nsresult> rvIgnored =
5520 HandleMiddleClickPaste(presShell, aMouseUpEvent, &status, nullptr);
5521 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5522 "Failed to paste for a middle click");
5524 // If new status is nsEventStatus_eConsumeNoDefault or
5525 // nsEventStatus_eConsumeDoDefault, use it.
5526 if (*aStatus != nsEventStatus_eConsumeNoDefault &&
5527 (status == nsEventStatus_eConsumeNoDefault ||
5528 status == nsEventStatus_eConsumeDoDefault)) {
5529 *aStatus = status;
5532 // Don't return error even if middle mouse paste fails since we haven't
5533 // handled it here.
5534 return NS_OK;
5537 nsresult EventStateManager::DispatchClickEvents(
5538 PresShell* aPresShell, WidgetMouseEvent* aMouseUpEvent,
5539 nsEventStatus* aStatus, nsIContent* aClickTarget,
5540 nsIContent* aOverrideClickTarget) {
5541 MOZ_ASSERT(aPresShell);
5542 MOZ_ASSERT(aMouseUpEvent);
5543 MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
5544 MOZ_ASSERT(aStatus);
5545 MOZ_ASSERT(aClickTarget || aOverrideClickTarget);
5547 bool notDispatchToContents =
5548 (aMouseUpEvent->mButton == MouseButton::eMiddle ||
5549 aMouseUpEvent->mButton == MouseButton::eSecondary);
5551 bool fireAuxClick = notDispatchToContents;
5553 AutoWeakFrame currentTarget = aClickTarget->GetPrimaryFrame();
5554 nsresult rv = InitAndDispatchClickEvent(
5555 aMouseUpEvent, aStatus, eMouseClick, aPresShell, aClickTarget,
5556 currentTarget, notDispatchToContents, aOverrideClickTarget);
5557 if (NS_WARN_IF(NS_FAILED(rv))) {
5558 return rv;
5561 // Fire auxclick event if necessary.
5562 if (fireAuxClick && *aStatus != nsEventStatus_eConsumeNoDefault &&
5563 aClickTarget && aClickTarget->IsInComposedDoc()) {
5564 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick,
5565 aPresShell, aClickTarget, currentTarget,
5566 false, aOverrideClickTarget);
5567 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
5570 // Fire double click event if click count is 2.
5571 if (aMouseUpEvent->mClickCount == 2 && !fireAuxClick && aClickTarget &&
5572 aClickTarget->IsInComposedDoc()) {
5573 rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
5574 aPresShell, aClickTarget, currentTarget,
5575 notDispatchToContents, aOverrideClickTarget);
5576 if (NS_WARN_IF(NS_FAILED(rv))) {
5577 return rv;
5581 return rv;
5584 nsresult EventStateManager::HandleMiddleClickPaste(
5585 PresShell* aPresShell, WidgetMouseEvent* aMouseEvent,
5586 nsEventStatus* aStatus, EditorBase* aEditorBase) {
5587 MOZ_ASSERT(aPresShell);
5588 MOZ_ASSERT(aMouseEvent);
5589 MOZ_ASSERT((aMouseEvent->mMessage == eMouseAuxClick &&
5590 aMouseEvent->mButton == MouseButton::eMiddle) ||
5591 EventCausesClickEvents(*aMouseEvent));
5592 MOZ_ASSERT(aStatus);
5593 MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
5595 // Even if we're called twice or more for a mouse operation, we should
5596 // handle only once. Although mMultipleActionsPrevented may be set to
5597 // true by different event handler in the future, we can use it for now.
5598 if (aMouseEvent->mFlags.mMultipleActionsPrevented) {
5599 return NS_OK;
5601 aMouseEvent->mFlags.mMultipleActionsPrevented = true;
5603 RefPtr<Selection> selection;
5604 if (aEditorBase) {
5605 selection = aEditorBase->GetSelection();
5606 if (NS_WARN_IF(!selection)) {
5607 return NS_ERROR_FAILURE;
5609 } else {
5610 Document* document = aPresShell->GetDocument();
5611 if (NS_WARN_IF(!document)) {
5612 return NS_ERROR_FAILURE;
5614 selection = nsCopySupport::GetSelectionForCopy(document);
5615 if (NS_WARN_IF(!selection)) {
5616 return NS_ERROR_FAILURE;
5619 const nsRange* range = selection->GetRangeAt(0);
5620 if (range) {
5621 nsINode* target = range->GetStartContainer();
5622 if (target && target->OwnerDoc()->IsInChromeDocShell()) {
5623 // In Chrome document, limit middle-click pasting to only the editor
5624 // because it looks odd if pasting works in the focused editor when you
5625 // middle-click toolbar or something which are far from the editor.
5626 // However, as DevTools especially Web Console module assumes that paste
5627 // event will be fired when middle-click even on not editor, don't limit
5628 // it.
5629 return NS_OK;
5634 // Don't modify selection here because we've already set caret to the point
5635 // at "mousedown" event.
5637 int32_t clipboardType = nsIClipboard::kGlobalClipboard;
5638 nsCOMPtr<nsIClipboard> clipboardService =
5639 do_GetService("@mozilla.org/widget/clipboard;1");
5640 if (clipboardService && clipboardService->IsClipboardTypeSupported(
5641 nsIClipboard::kSelectionClipboard)) {
5642 clipboardType = nsIClipboard::kSelectionClipboard;
5645 // Fire ePaste event by ourselves since we need to dispatch "paste" event
5646 // even if the middle click event was consumed for compatibility with
5647 // Chromium.
5648 if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType, aPresShell,
5649 selection)) {
5650 *aStatus = nsEventStatus_eConsumeNoDefault;
5651 return NS_OK;
5654 // Although we've fired "paste" event, there is no editor to accept the
5655 // clipboard content.
5656 if (!aEditorBase) {
5657 return NS_OK;
5660 // Check if the editor is still the good target to paste.
5661 if (aEditorBase->Destroyed() || aEditorBase->IsReadonly()) {
5662 // XXX Should we consume the event when the editor is readonly and/or
5663 // disabled?
5664 return NS_OK;
5667 // The selection may have been modified during reflow. Therefore, we
5668 // should adjust event target to pass IsAcceptableInputEvent().
5669 const nsRange* range = selection->GetRangeAt(0);
5670 if (!range) {
5671 return NS_OK;
5673 WidgetMouseEvent mouseEvent(*aMouseEvent);
5674 mouseEvent.mOriginalTarget = range->GetStartContainer();
5675 if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
5676 !aEditorBase->IsAcceptableInputEvent(&mouseEvent)) {
5677 return NS_OK;
5680 // If Control key is pressed, we should paste clipboard content as
5681 // quotation. Otherwise, paste it as is.
5682 if (aMouseEvent->IsControl()) {
5683 DebugOnly<nsresult> rv = aEditorBase->PasteAsQuotationAsAction(
5684 clipboardType, EditorBase::DispatchPasteEvent::No);
5685 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
5686 } else {
5687 DebugOnly<nsresult> rv = aEditorBase->PasteAsAction(
5688 clipboardType, EditorBase::DispatchPasteEvent::No);
5689 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
5691 *aStatus = nsEventStatus_eConsumeNoDefault;
5693 return NS_OK;
5696 void EventStateManager::ConsumeInteractionData(
5697 Record<nsString, dom::InteractionData>& aInteractions) {
5698 OnTypingInteractionEnded();
5700 aInteractions.Entries().Clear();
5701 auto newEntry = aInteractions.Entries().AppendElement();
5702 newEntry->mKey = u"Typing"_ns;
5703 newEntry->mValue = gTypingInteraction;
5704 gTypingInteraction = {};
5707 nsIFrame* EventStateManager::GetEventTarget() {
5708 PresShell* presShell;
5709 if (mCurrentTarget || !mPresContext ||
5710 !(presShell = mPresContext->GetPresShell())) {
5711 return mCurrentTarget;
5714 if (mCurrentTargetContent) {
5715 mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent);
5716 if (mCurrentTarget) {
5717 return mCurrentTarget;
5721 nsIFrame* frame = presShell->GetCurrentEventFrame();
5722 return (mCurrentTarget = frame);
5725 already_AddRefed<nsIContent> EventStateManager::GetEventTargetContent(
5726 WidgetEvent* aEvent) {
5727 if (aEvent && (aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) {
5728 nsCOMPtr<nsIContent> content = GetFocusedElement();
5729 return content.forget();
5732 if (mCurrentTargetContent) {
5733 nsCOMPtr<nsIContent> content = mCurrentTargetContent;
5734 return content.forget();
5737 nsCOMPtr<nsIContent> content;
5738 if (PresShell* presShell = mPresContext->GetPresShell()) {
5739 content = presShell->GetEventTargetContent(aEvent);
5742 // Some events here may set mCurrentTarget but not set the corresponding
5743 // event target in the PresShell.
5744 if (!content && mCurrentTarget) {
5745 mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content));
5748 return content.forget();
5751 static Element* GetLabelTarget(nsIContent* aPossibleLabel) {
5752 mozilla::dom::HTMLLabelElement* label =
5753 mozilla::dom::HTMLLabelElement::FromNode(aPossibleLabel);
5754 if (!label) return nullptr;
5756 return label->GetLabeledElement();
5759 /* static */
5760 inline void EventStateManager::DoStateChange(Element* aElement,
5761 ElementState aState,
5762 bool aAddState) {
5763 if (aAddState) {
5764 aElement->AddStates(aState);
5765 } else {
5766 aElement->RemoveStates(aState);
5770 /* static */
5771 inline void EventStateManager::DoStateChange(nsIContent* aContent,
5772 ElementState aState,
5773 bool aStateAdded) {
5774 if (aContent->IsElement()) {
5775 DoStateChange(aContent->AsElement(), aState, aStateAdded);
5779 /* static */
5780 void EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
5781 nsIContent* aStopBefore,
5782 ElementState aState,
5783 bool aAddState) {
5784 for (; aStartNode && aStartNode != aStopBefore;
5785 aStartNode = aStartNode->GetFlattenedTreeParent()) {
5786 // We might be starting with a non-element (e.g. a text node) and
5787 // if someone is doing something weird might be ending with a
5788 // non-element too (e.g. a document fragment)
5789 if (!aStartNode->IsElement()) {
5790 continue;
5792 Element* element = aStartNode->AsElement();
5793 DoStateChange(element, aState, aAddState);
5794 Element* labelTarget = GetLabelTarget(element);
5795 if (labelTarget) {
5796 DoStateChange(labelTarget, aState, aAddState);
5800 if (aAddState) {
5801 // We might be in a situation where a node was in hover both
5802 // because it was hovered and because the label for it was
5803 // hovered, and while we stopped hovering the node the label is
5804 // still hovered. Or we might have had two nested labels for the
5805 // same node, and while one is no longer hovered the other still
5806 // is. In that situation, the label that's still hovered will be
5807 // aStopBefore or some ancestor of it, and the call we just made
5808 // to UpdateAncestorState with aAddState = false would have
5809 // removed the hover state from the node. But the node should
5810 // still be in hover state. To handle this situation we need to
5811 // keep walking up the tree and any time we find a label mark its
5812 // corresponding node as still in our state.
5813 for (; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) {
5814 if (!aStartNode->IsElement()) {
5815 continue;
5818 Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
5819 if (labelTarget && !labelTarget->State().HasState(aState)) {
5820 DoStateChange(labelTarget, aState, true);
5826 // static
5827 bool CanContentHaveActiveState(nsIContent& aContent) {
5828 // Editable content can never become active since their default actions
5829 // are disabled. Watch out for editable content in native anonymous
5830 // subtrees though, as they belong to text controls.
5831 return !aContent.IsEditable() || aContent.IsInNativeAnonymousSubtree();
5834 bool EventStateManager::SetContentState(nsIContent* aContent,
5835 ElementState aState) {
5836 MOZ_ASSERT(ManagesState(aState), "Unexpected state");
5838 nsCOMPtr<nsIContent> notifyContent1;
5839 nsCOMPtr<nsIContent> notifyContent2;
5840 bool updateAncestors;
5842 if (aState == ElementState::HOVER || aState == ElementState::ACTIVE) {
5843 // Hover and active are hierarchical
5844 updateAncestors = true;
5846 // check to see that this state is allowed by style. Check dragover too?
5847 // XXX Is this even what we want?
5848 if (mCurrentTarget &&
5849 mCurrentTarget->StyleUI()->UserInput() == StyleUserInput::None) {
5850 return false;
5853 if (aState == ElementState::ACTIVE) {
5854 if (aContent && !CanContentHaveActiveState(*aContent)) {
5855 aContent = nullptr;
5857 if (aContent != mActiveContent) {
5858 notifyContent1 = aContent;
5859 notifyContent2 = mActiveContent;
5860 mActiveContent = aContent;
5862 } else {
5863 NS_ASSERTION(aState == ElementState::HOVER, "How did that happen?");
5864 nsIContent* newHover;
5866 if (mPresContext->IsDynamic()) {
5867 newHover = aContent;
5868 } else {
5869 NS_ASSERTION(!aContent || aContent->GetComposedDoc() ==
5870 mPresContext->PresShell()->GetDocument(),
5871 "Unexpected document");
5872 nsIFrame* frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
5873 if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
5874 // The scrollbars of viewport should not ignore the hover state.
5875 // Because they are *not* the content of the web page.
5876 newHover = aContent;
5877 } else {
5878 // All contents of the web page should ignore the hover state.
5879 newHover = nullptr;
5883 if (newHover != mHoverContent) {
5884 notifyContent1 = newHover;
5885 notifyContent2 = mHoverContent;
5886 mHoverContent = newHover;
5889 } else {
5890 updateAncestors = false;
5891 if (aState == ElementState::DRAGOVER) {
5892 if (aContent != sDragOverContent) {
5893 notifyContent1 = aContent;
5894 notifyContent2 = sDragOverContent;
5895 sDragOverContent = aContent;
5897 } else if (aState == ElementState::URLTARGET) {
5898 if (aContent != mURLTargetContent) {
5899 notifyContent1 = aContent;
5900 notifyContent2 = mURLTargetContent;
5901 mURLTargetContent = aContent;
5906 // We need to keep track of which of notifyContent1 and notifyContent2 is
5907 // getting the state set and which is getting it unset. If both are
5908 // non-null, then notifyContent1 is having the state set and notifyContent2
5909 // is having it unset. But if one of them is null, we need to keep track of
5910 // the right thing for notifyContent1 explicitly.
5911 bool content1StateSet = true;
5912 if (!notifyContent1) {
5913 // This is ok because FindCommonAncestor wouldn't find anything
5914 // anyway if notifyContent1 is null.
5915 notifyContent1 = notifyContent2;
5916 notifyContent2 = nullptr;
5917 content1StateSet = false;
5920 if (notifyContent1 && mPresContext) {
5921 EnsureDocument(mPresContext);
5922 if (mDocument) {
5923 nsAutoScriptBlocker scriptBlocker;
5925 if (updateAncestors) {
5926 nsCOMPtr<nsIContent> commonAncestor =
5927 FindCommonAncestor(notifyContent1, notifyContent2);
5928 if (notifyContent2) {
5929 // It's very important to first notify the state removal and
5930 // then the state addition, because due to labels it's
5931 // possible that we're removing state from some element but
5932 // then adding it again (say because mHoverContent changed
5933 // from a control to its label).
5934 UpdateAncestorState(notifyContent2, commonAncestor, aState, false);
5936 UpdateAncestorState(notifyContent1, commonAncestor, aState,
5937 content1StateSet);
5938 } else {
5939 if (notifyContent2) {
5940 DoStateChange(notifyContent2, aState, false);
5942 DoStateChange(notifyContent1, aState, content1StateSet);
5947 return true;
5950 void EventStateManager::ResetLastOverForContent(
5951 const uint32_t& aIdx, const RefPtr<OverOutElementsWrapper>& aElemWrapper,
5952 nsIContent* aContent) {
5953 if (aElemWrapper && aElemWrapper->mLastOverElement &&
5954 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
5955 aElemWrapper->mLastOverElement, aContent)) {
5956 aElemWrapper->mLastOverElement = nullptr;
5960 void EventStateManager::RemoveNodeFromChainIfNeeded(ElementState aState,
5961 nsIContent* aContentRemoved,
5962 bool aNotify) {
5963 MOZ_ASSERT(aState == ElementState::HOVER || aState == ElementState::ACTIVE);
5964 if (!aContentRemoved->IsElement() ||
5965 !aContentRemoved->AsElement()->State().HasState(aState)) {
5966 return;
5969 nsCOMPtr<nsIContent>& leaf =
5970 aState == ElementState::HOVER ? mHoverContent : mActiveContent;
5972 MOZ_ASSERT(leaf);
5973 // These two NS_ASSERTIONS below can fail for Shadow DOM sometimes, and it's
5974 // not clear how to best handle it, see
5975 // https://github.com/whatwg/html/issues/4795 and bug 1551621.
5976 NS_ASSERTION(
5977 nsContentUtils::ContentIsFlattenedTreeDescendantOf(leaf, aContentRemoved),
5978 "Flat tree and active / hover chain got out of sync");
5980 nsIContent* newLeaf = aContentRemoved->GetFlattenedTreeParent();
5981 MOZ_ASSERT(!newLeaf || newLeaf->IsElement());
5982 NS_ASSERTION(!newLeaf || newLeaf->AsElement()->State().HasState(aState),
5983 "State got out of sync because of shadow DOM");
5984 if (aNotify) {
5985 SetContentState(newLeaf, aState);
5986 } else {
5987 // We don't update the removed content's state here, since removing NAC
5988 // happens from layout and we don't really want to notify at that point or
5989 // what not.
5991 // Also, NAC is not observable and NAC being removed will go away soon.
5992 leaf = newLeaf;
5994 MOZ_ASSERT(leaf == newLeaf || (aState == ElementState::ACTIVE && !leaf &&
5995 !CanContentHaveActiveState(*newLeaf)));
5998 void EventStateManager::NativeAnonymousContentRemoved(nsIContent* aContent) {
5999 MOZ_ASSERT(aContent->IsRootOfNativeAnonymousSubtree());
6000 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, false);
6001 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, false);
6003 nsCOMPtr<nsIContent>& lastLeftMouseDownContent =
6004 mLastLeftMouseDownInfo.mLastMouseDownContent;
6005 if (lastLeftMouseDownContent &&
6006 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6007 lastLeftMouseDownContent, aContent)) {
6008 lastLeftMouseDownContent = aContent->GetFlattenedTreeParent();
6011 nsCOMPtr<nsIContent>& lastMiddleMouseDownContent =
6012 mLastMiddleMouseDownInfo.mLastMouseDownContent;
6013 if (lastMiddleMouseDownContent &&
6014 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6015 lastMiddleMouseDownContent, aContent)) {
6016 lastMiddleMouseDownContent = aContent->GetFlattenedTreeParent();
6019 nsCOMPtr<nsIContent>& lastRightMouseDownContent =
6020 mLastRightMouseDownInfo.mLastMouseDownContent;
6021 if (lastRightMouseDownContent &&
6022 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6023 lastRightMouseDownContent, aContent)) {
6024 lastRightMouseDownContent = aContent->GetFlattenedTreeParent();
6028 void EventStateManager::ContentRemoved(Document* aDocument,
6029 nsIContent* aContent) {
6031 * Anchor and area elements when focused or hovered might make the UI to show
6032 * the current link. We want to make sure that the UI gets informed when they
6033 * are actually removed from the DOM.
6035 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
6036 (aContent->AsElement()->State().HasAtLeastOneOfStates(
6037 ElementState::FOCUS | ElementState::HOVER))) {
6038 Element* element = aContent->AsElement();
6039 element->LeaveLink(element->GetPresContext(Element::eForComposedDoc));
6042 if (aContent->IsElement()) {
6043 if (RefPtr<nsPresContext> presContext = mPresContext) {
6044 IMEStateManager::OnRemoveContent(*presContext,
6045 MOZ_KnownLive(*aContent->AsElement()));
6047 WheelTransaction::OnRemoveElement(aContent);
6050 // inform the focus manager that the content is being removed. If this
6051 // content is focused, the focus will be removed without firing events.
6052 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6053 fm->ContentRemoved(aDocument, aContent);
6056 RemoveNodeFromChainIfNeeded(ElementState::HOVER, aContent, true);
6057 RemoveNodeFromChainIfNeeded(ElementState::ACTIVE, aContent, true);
6059 if (sDragOverContent &&
6060 sDragOverContent->OwnerDoc() == aContent->OwnerDoc() &&
6061 nsContentUtils::ContentIsFlattenedTreeDescendantOf(sDragOverContent,
6062 aContent)) {
6063 sDragOverContent = nullptr;
6066 PointerEventHandler::ReleaseIfCaptureByDescendant(aContent);
6068 // See bug 292146 for why we want to null this out
6069 ResetLastOverForContent(0, mMouseEnterLeaveHelper, aContent);
6070 for (const auto& entry : mPointersEnterLeaveHelper) {
6071 ResetLastOverForContent(entry.GetKey(), entry.GetData(), aContent);
6075 void EventStateManager::TextControlRootWillBeRemoved(
6076 TextControlElement& aTextControlElement) {
6077 if (!mGestureDownInTextControl || !mGestureDownFrameOwner ||
6078 !mGestureDownFrameOwner->IsInNativeAnonymousSubtree()) {
6079 return;
6081 // If we track gesture to start drag in aTextControlElement, we should keep
6082 // tracking it with aTextContrlElement itself for now because this may be
6083 // caused by reframing aTextControlElement which may not be intended by the
6084 // user.
6085 if (&aTextControlElement ==
6086 mGestureDownFrameOwner
6087 ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
6088 mGestureDownFrameOwner = &aTextControlElement;
6092 void EventStateManager::TextControlRootAdded(
6093 Element& aAnonymousDivElement, TextControlElement& aTextControlElement) {
6094 if (!mGestureDownInTextControl ||
6095 mGestureDownFrameOwner != &aTextControlElement) {
6096 return;
6098 // If we track gesture to start drag in aTextControlElement, but the frame
6099 // owner is the text control element itself, the anonymous nodes in it are
6100 // recreated by a reframe. If so, we should keep tracking it with the
6101 // recreated native anonymous node.
6102 mGestureDownFrameOwner =
6103 aAnonymousDivElement.GetFirstChild()
6104 ? aAnonymousDivElement.GetFirstChild()
6105 : static_cast<nsIContent*>(&aAnonymousDivElement);
6108 bool EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) {
6109 return !(aEvent->mMessage == eMouseDown &&
6110 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
6111 !sNormalLMouseEventInProcess);
6114 //-------------------------------------------
6115 // Access Key Registration
6116 //-------------------------------------------
6117 void EventStateManager::RegisterAccessKey(Element* aElement, uint32_t aKey) {
6118 if (aElement && !mAccessKeys.Contains(aElement)) {
6119 mAccessKeys.AppendObject(aElement);
6123 void EventStateManager::UnregisterAccessKey(Element* aElement, uint32_t aKey) {
6124 if (aElement) {
6125 mAccessKeys.RemoveObject(aElement);
6129 uint32_t EventStateManager::GetRegisteredAccessKey(Element* aElement) {
6130 MOZ_ASSERT(aElement);
6132 if (!mAccessKeys.Contains(aElement)) {
6133 return 0;
6136 nsAutoString accessKey;
6137 aElement->GetAttr(nsGkAtoms::accesskey, accessKey);
6138 return accessKey.First();
6141 void EventStateManager::EnsureDocument(nsPresContext* aPresContext) {
6142 if (!mDocument) mDocument = aPresContext->Document();
6145 void EventStateManager::FlushLayout(nsPresContext* aPresContext) {
6146 MOZ_ASSERT(aPresContext, "nullptr ptr");
6147 if (RefPtr<PresShell> presShell = aPresContext->GetPresShell()) {
6148 presShell->FlushPendingNotifications(FlushType::InterruptibleLayout);
6152 Element* EventStateManager::GetFocusedElement() {
6153 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6154 EnsureDocument(mPresContext);
6155 if (!fm || !mDocument) {
6156 return nullptr;
6159 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6160 return nsFocusManager::GetFocusedDescendant(
6161 mDocument->GetWindow(), nsFocusManager::eOnlyCurrentWindow,
6162 getter_AddRefs(focusedWindow));
6165 //-------------------------------------------------------
6166 // Return true if the docshell is visible
6168 bool EventStateManager::IsShellVisible(nsIDocShell* aShell) {
6169 NS_ASSERTION(aShell, "docshell is null");
6171 nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell);
6172 if (!basewin) return true;
6174 bool isVisible = true;
6175 basewin->GetVisibility(&isVisible);
6177 // We should be doing some additional checks here so that
6178 // we don't tab into hidden tabs of tabbrowser. -bryner
6180 return isVisible;
6183 nsresult EventStateManager::DoContentCommandEvent(
6184 WidgetContentCommandEvent* aEvent) {
6185 EnsureDocument(mPresContext);
6186 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
6187 nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow());
6188 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
6190 nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
6191 NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
6192 const char* cmd;
6193 switch (aEvent->mMessage) {
6194 case eContentCommandCut:
6195 cmd = "cmd_cut";
6196 break;
6197 case eContentCommandCopy:
6198 cmd = "cmd_copy";
6199 break;
6200 case eContentCommandPaste:
6201 cmd = "cmd_paste";
6202 break;
6203 case eContentCommandDelete:
6204 cmd = "cmd_delete";
6205 break;
6206 case eContentCommandUndo:
6207 cmd = "cmd_undo";
6208 break;
6209 case eContentCommandRedo:
6210 cmd = "cmd_redo";
6211 break;
6212 case eContentCommandPasteTransferable:
6213 cmd = "cmd_pasteTransferable";
6214 break;
6215 case eContentCommandLookUpDictionary:
6216 cmd = "cmd_lookUpDictionary";
6217 break;
6218 default:
6219 return NS_ERROR_NOT_IMPLEMENTED;
6221 // If user tries to do something, user must try to do it in visible window.
6222 // So, let's retrieve controller of visible window.
6223 nsCOMPtr<nsIController> controller;
6224 nsresult rv =
6225 root->GetControllerForCommand(cmd, true, getter_AddRefs(controller));
6226 NS_ENSURE_SUCCESS(rv, rv);
6227 if (!controller) {
6228 // When GetControllerForCommand succeeded but there is no controller, the
6229 // command isn't supported.
6230 aEvent->mIsEnabled = false;
6231 } else {
6232 bool canDoIt;
6233 rv = controller->IsCommandEnabled(cmd, &canDoIt);
6234 NS_ENSURE_SUCCESS(rv, rv);
6235 aEvent->mIsEnabled = canDoIt;
6236 if (canDoIt && !aEvent->mOnlyEnabledCheck) {
6237 switch (aEvent->mMessage) {
6238 case eContentCommandPasteTransferable: {
6239 BrowserParent* remote = BrowserParent::GetFocused();
6240 if (remote) {
6241 nsCOMPtr<nsITransferable> transferable = aEvent->mTransferable;
6242 IPCTransferableData ipcTransferableData;
6243 nsContentUtils::TransferableToIPCTransferableData(
6244 transferable, &ipcTransferableData, false, remote->Manager());
6245 bool isPrivateData = transferable->GetIsPrivateData();
6246 nsCOMPtr<nsIPrincipal> requestingPrincipal =
6247 transferable->GetRequestingPrincipal();
6248 nsContentPolicyType contentPolicyType =
6249 transferable->GetContentPolicyType();
6250 remote->SendPasteTransferable(std::move(ipcTransferableData),
6251 isPrivateData, requestingPrincipal,
6252 contentPolicyType);
6253 rv = NS_OK;
6254 } else {
6255 nsCOMPtr<nsICommandController> commandController =
6256 do_QueryInterface(controller);
6257 NS_ENSURE_STATE(commandController);
6259 RefPtr<nsCommandParams> params = new nsCommandParams();
6260 rv = params->SetISupports("transferable", aEvent->mTransferable);
6261 if (NS_WARN_IF(NS_FAILED(rv))) {
6262 return rv;
6264 rv = commandController->DoCommandWithParams(cmd, params);
6266 break;
6269 case eContentCommandLookUpDictionary: {
6270 nsCOMPtr<nsICommandController> commandController =
6271 do_QueryInterface(controller);
6272 if (NS_WARN_IF(!commandController)) {
6273 return NS_ERROR_FAILURE;
6276 RefPtr<nsCommandParams> params = new nsCommandParams();
6277 rv = params->SetInt("x", aEvent->mRefPoint.x);
6278 if (NS_WARN_IF(NS_FAILED(rv))) {
6279 return rv;
6282 rv = params->SetInt("y", aEvent->mRefPoint.y);
6283 if (NS_WARN_IF(NS_FAILED(rv))) {
6284 return rv;
6287 rv = commandController->DoCommandWithParams(cmd, params);
6288 break;
6291 default:
6292 rv = controller->DoCommand(cmd);
6293 break;
6295 NS_ENSURE_SUCCESS(rv, rv);
6298 aEvent->mSucceeded = true;
6299 return NS_OK;
6302 nsresult EventStateManager::DoContentCommandInsertTextEvent(
6303 WidgetContentCommandEvent* aEvent) {
6304 MOZ_ASSERT(aEvent);
6305 MOZ_ASSERT(aEvent->mMessage == eContentCommandInsertText);
6306 MOZ_DIAGNOSTIC_ASSERT(aEvent->mString.isSome());
6307 MOZ_DIAGNOSTIC_ASSERT(!aEvent->mString.ref().IsEmpty());
6309 aEvent->mIsEnabled = false;
6310 aEvent->mSucceeded = false;
6312 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6314 if (XRE_IsParentProcess()) {
6315 // Handle it in focused content process if there is.
6316 if (BrowserParent* remote = BrowserParent::GetFocused()) {
6317 remote->SendInsertText(aEvent->mString.ref());
6318 aEvent->mIsEnabled = true; // XXX it can be a lie...
6319 aEvent->mSucceeded = true;
6320 return NS_OK;
6324 // If there is no active editor in this process, we should treat the command
6325 // is disabled.
6326 RefPtr<EditorBase> activeEditor =
6327 nsContentUtils::GetActiveEditor(mPresContext);
6328 if (!activeEditor) {
6329 aEvent->mSucceeded = true;
6330 return NS_OK;
6333 nsresult rv = activeEditor->InsertTextAsAction(aEvent->mString.ref());
6334 aEvent->mIsEnabled = rv != NS_SUCCESS_DOM_NO_OPERATION;
6335 aEvent->mSucceeded = NS_SUCCEEDED(rv);
6336 return NS_OK;
6339 nsresult EventStateManager::DoContentCommandScrollEvent(
6340 WidgetContentCommandEvent* aEvent) {
6341 NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
6342 PresShell* presShell = mPresContext->GetPresShell();
6343 NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
6344 NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
6346 ScrollUnit scrollUnit;
6347 switch (aEvent->mScroll.mUnit) {
6348 case WidgetContentCommandEvent::eCmdScrollUnit_Line:
6349 scrollUnit = ScrollUnit::LINES;
6350 break;
6351 case WidgetContentCommandEvent::eCmdScrollUnit_Page:
6352 scrollUnit = ScrollUnit::PAGES;
6353 break;
6354 case WidgetContentCommandEvent::eCmdScrollUnit_Whole:
6355 scrollUnit = ScrollUnit::WHOLE;
6356 break;
6357 default:
6358 return NS_ERROR_INVALID_ARG;
6361 aEvent->mSucceeded = true;
6363 nsIScrollableFrame* sf =
6364 presShell->GetScrollableFrameToScroll(layers::EitherScrollDirection);
6365 aEvent->mIsEnabled =
6366 sf ? (aEvent->mScroll.mIsHorizontal ? WheelHandlingUtils::CanScrollOn(
6367 sf, aEvent->mScroll.mAmount, 0)
6368 : WheelHandlingUtils::CanScrollOn(
6369 sf, 0, aEvent->mScroll.mAmount))
6370 : false;
6372 if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
6373 return NS_OK;
6376 nsIntPoint pt(0, 0);
6377 if (aEvent->mScroll.mIsHorizontal) {
6378 pt.x = aEvent->mScroll.mAmount;
6379 } else {
6380 pt.y = aEvent->mScroll.mAmount;
6383 // The caller may want synchronous scrolling.
6384 sf->ScrollBy(pt, scrollUnit, ScrollMode::Instant);
6385 return NS_OK;
6388 void EventStateManager::SetActiveManager(EventStateManager* aNewESM,
6389 nsIContent* aContent) {
6390 if (sActiveESM && aNewESM != sActiveESM) {
6391 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6393 sActiveESM = aNewESM;
6394 if (sActiveESM && aContent) {
6395 sActiveESM->SetContentState(aContent, ElementState::ACTIVE);
6399 void EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer) {
6400 if (aClearer) {
6401 aClearer->SetContentState(nullptr, ElementState::ACTIVE);
6402 if (sDragOverContent) {
6403 aClearer->SetContentState(nullptr, ElementState::DRAGOVER);
6406 if (sActiveESM && aClearer != sActiveESM) {
6407 sActiveESM->SetContentState(nullptr, ElementState::ACTIVE);
6409 sActiveESM = nullptr;
6412 /******************************************************************/
6413 /* mozilla::EventStateManager::DeltaAccumulator */
6414 /******************************************************************/
6416 void EventStateManager::DeltaAccumulator::InitLineOrPageDelta(
6417 nsIFrame* aTargetFrame, EventStateManager* aESM, WidgetWheelEvent* aEvent) {
6418 MOZ_ASSERT(aESM);
6419 MOZ_ASSERT(aEvent);
6421 // Reset if the previous wheel event is too old.
6422 if (!mLastTime.IsNull()) {
6423 TimeDuration duration = TimeStamp::Now() - mLastTime;
6424 if (duration.ToMilliseconds() >
6425 StaticPrefs::mousewheel_transaction_timeout()) {
6426 Reset();
6429 // If we have accumulated delta, we may need to reset it.
6430 if (IsInTransaction()) {
6431 // If wheel event type is changed, reset the values.
6432 if (mHandlingDeltaMode != aEvent->mDeltaMode ||
6433 mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) {
6434 Reset();
6435 } else {
6436 // If the delta direction is changed, we should reset only the
6437 // accumulated values.
6438 if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) {
6439 mX = mPendingScrollAmountX = 0.0;
6441 if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) {
6442 mY = mPendingScrollAmountY = 0.0;
6447 mHandlingDeltaMode = aEvent->mDeltaMode;
6448 mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta;
6451 nsIFrame* frame = aESM->ComputeScrollTarget(aTargetFrame, aEvent,
6452 COMPUTE_DEFAULT_ACTION_TARGET);
6453 nsPresContext* pc =
6454 frame ? frame->PresContext() : aTargetFrame->PresContext();
6455 nsIScrollableFrame* scrollTarget = do_QueryFrame(frame);
6456 aEvent->mScrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget);
6459 // If it's handling neither a device that does not provide line or page deltas
6460 // nor delta values multiplied by prefs, we must not modify lineOrPageDelta
6461 // values.
6462 // TODO(emilio): Does this care about overridden scroll speed?
6463 if (!mIsNoLineOrPageDeltaDevice &&
6464 !EventStateManager::WheelPrefs::GetInstance()
6465 ->NeedToComputeLineOrPageDelta(aEvent)) {
6466 // Set the delta values to mX and mY. They would be used when above block
6467 // resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction
6468 // is changed.
6469 // NOTE: We shouldn't accumulate the delta values, it might could cause
6470 // overflow even though it's not a realistic situation.
6471 if (aEvent->mDeltaX) {
6472 mX = aEvent->mDeltaX;
6474 if (aEvent->mDeltaY) {
6475 mY = aEvent->mDeltaY;
6477 mLastTime = TimeStamp::Now();
6478 return;
6481 mX += aEvent->mDeltaX;
6482 mY += aEvent->mDeltaY;
6484 if (mHandlingDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6485 // Records pixel delta values and init mLineOrPageDeltaX and
6486 // mLineOrPageDeltaY for wheel events which are caused by pixel only
6487 // devices. Ignore mouse wheel transaction for computing this. The
6488 // lineOrPageDelta values will be used by dispatching legacy
6489 // eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling
6490 // of default action. The transaction should be used only for the default
6491 // action.
6492 auto scrollAmountInCSSPixels =
6493 CSSIntSize::FromAppUnitsRounded(aEvent->mScrollAmount);
6495 aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width;
6496 aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height;
6498 mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width;
6499 mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height;
6500 } else {
6501 aEvent->mLineOrPageDeltaX = RoundDown(mX);
6502 aEvent->mLineOrPageDeltaY = RoundDown(mY);
6503 mX -= aEvent->mLineOrPageDeltaX;
6504 mY -= aEvent->mLineOrPageDeltaY;
6507 mLastTime = TimeStamp::Now();
6510 void EventStateManager::DeltaAccumulator::Reset() {
6511 mX = mY = 0.0;
6512 mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
6513 mHandlingDeltaMode = UINT32_MAX;
6514 mIsNoLineOrPageDeltaDevice = false;
6517 nsIntPoint
6518 EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
6519 WidgetWheelEvent* aEvent, const nsIntSize& aScrollAmountInDevPixels) {
6520 MOZ_ASSERT(aEvent);
6522 DeltaValues acceleratedDelta = WheelTransaction::AccelerateWheelDelta(aEvent);
6524 nsIntPoint result(0, 0);
6525 if (aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_PIXEL) {
6526 mPendingScrollAmountX += acceleratedDelta.deltaX;
6527 mPendingScrollAmountY += acceleratedDelta.deltaY;
6528 } else {
6529 mPendingScrollAmountX +=
6530 aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
6531 mPendingScrollAmountY +=
6532 aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
6534 result.x = RoundDown(mPendingScrollAmountX);
6535 result.y = RoundDown(mPendingScrollAmountY);
6536 mPendingScrollAmountX -= result.x;
6537 mPendingScrollAmountY -= result.y;
6539 return result;
6542 /******************************************************************/
6543 /* mozilla::EventStateManager::WheelPrefs */
6544 /******************************************************************/
6546 // static
6547 EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::GetInstance() {
6548 if (!sInstance) {
6549 sInstance = new WheelPrefs();
6551 return sInstance;
6554 // static
6555 void EventStateManager::WheelPrefs::Shutdown() {
6556 delete sInstance;
6557 sInstance = nullptr;
6560 // static
6561 void EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName,
6562 void* aClosure) {
6563 // forget all prefs, it's not problem for performance.
6564 sInstance->Reset();
6565 DeltaAccumulator::GetInstance()->Reset();
6568 EventStateManager::WheelPrefs::WheelPrefs() {
6569 Reset();
6570 Preferences::RegisterPrefixCallback(OnPrefChanged, "mousewheel.");
6573 EventStateManager::WheelPrefs::~WheelPrefs() {
6574 Preferences::UnregisterPrefixCallback(OnPrefChanged, "mousewheel.");
6577 void EventStateManager::WheelPrefs::Reset() { memset(mInit, 0, sizeof(mInit)); }
6579 EventStateManager::WheelPrefs::Index EventStateManager::WheelPrefs::GetIndexFor(
6580 const WidgetWheelEvent* aEvent) {
6581 if (!aEvent) {
6582 return INDEX_DEFAULT;
6585 Modifiers modifiers = (aEvent->mModifiers & (MODIFIER_ALT | MODIFIER_CONTROL |
6586 MODIFIER_META | MODIFIER_SHIFT));
6588 switch (modifiers) {
6589 case MODIFIER_ALT:
6590 return INDEX_ALT;
6591 case MODIFIER_CONTROL:
6592 return INDEX_CONTROL;
6593 case MODIFIER_META:
6594 return INDEX_META;
6595 case MODIFIER_SHIFT:
6596 return INDEX_SHIFT;
6597 default:
6598 // If two or more modifier keys are pressed, we should use default
6599 // settings.
6600 return INDEX_DEFAULT;
6604 void EventStateManager::WheelPrefs::GetBasePrefName(
6605 EventStateManager::WheelPrefs::Index aIndex, nsACString& aBasePrefName) {
6606 aBasePrefName.AssignLiteral("mousewheel.");
6607 switch (aIndex) {
6608 case INDEX_ALT:
6609 aBasePrefName.AppendLiteral("with_alt.");
6610 break;
6611 case INDEX_CONTROL:
6612 aBasePrefName.AppendLiteral("with_control.");
6613 break;
6614 case INDEX_META:
6615 aBasePrefName.AppendLiteral("with_meta.");
6616 break;
6617 case INDEX_SHIFT:
6618 aBasePrefName.AppendLiteral("with_shift.");
6619 break;
6620 case INDEX_DEFAULT:
6621 default:
6622 aBasePrefName.AppendLiteral("default.");
6623 break;
6627 void EventStateManager::WheelPrefs::Init(
6628 EventStateManager::WheelPrefs::Index aIndex) {
6629 if (mInit[aIndex]) {
6630 return;
6632 mInit[aIndex] = true;
6634 nsAutoCString basePrefName;
6635 GetBasePrefName(aIndex, basePrefName);
6637 nsAutoCString prefNameX(basePrefName);
6638 prefNameX.AppendLiteral("delta_multiplier_x");
6639 mMultiplierX[aIndex] =
6640 static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100;
6642 nsAutoCString prefNameY(basePrefName);
6643 prefNameY.AppendLiteral("delta_multiplier_y");
6644 mMultiplierY[aIndex] =
6645 static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100;
6647 nsAutoCString prefNameZ(basePrefName);
6648 prefNameZ.AppendLiteral("delta_multiplier_z");
6649 mMultiplierZ[aIndex] =
6650 static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
6652 nsAutoCString prefNameAction(basePrefName);
6653 prefNameAction.AppendLiteral("action");
6654 int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
6655 if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
6656 NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
6657 action = ACTION_SCROLL;
6659 mActions[aIndex] = static_cast<Action>(action);
6661 // Compute action values overridden by .override_x pref.
6662 // At present, override is possible only for the x-direction
6663 // because this pref is introduced mainly for tilt wheels.
6664 // Note that ACTION_HORIZONTALIZED_SCROLL isn't a valid value for this pref
6665 // because it affects only to deltaY.
6666 prefNameAction.AppendLiteral(".override_x");
6667 int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1);
6668 if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST) ||
6669 actionOverrideX == ACTION_HORIZONTALIZED_SCROLL) {
6670 NS_WARNING("Unsupported action override pref value, didn't override.");
6671 actionOverrideX = -1;
6673 mOverriddenActionsX[aIndex] = (actionOverrideX == -1)
6674 ? static_cast<Action>(action)
6675 : static_cast<Action>(actionOverrideX);
6678 void EventStateManager::WheelPrefs::GetMultiplierForDeltaXAndY(
6679 const WidgetWheelEvent* aEvent, Index aIndex, double* aMultiplierForDeltaX,
6680 double* aMultiplierForDeltaY) {
6681 *aMultiplierForDeltaX = mMultiplierX[aIndex];
6682 *aMultiplierForDeltaY = mMultiplierY[aIndex];
6683 // If the event has been horizontalized(I.e. treated as a horizontal wheel
6684 // scroll for a vertical wheel scroll), then we should swap mMultiplierX and
6685 // mMultiplierY. By doing this, multipliers will still apply to the delta
6686 // values they origianlly corresponded to.
6687 if (aEvent->mDeltaValuesHorizontalizedForDefaultHandler &&
6688 ComputeActionFor(aEvent) == ACTION_HORIZONTALIZED_SCROLL) {
6689 std::swap(*aMultiplierForDeltaX, *aMultiplierForDeltaY);
6693 void EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(
6694 WidgetWheelEvent* aEvent) {
6695 if (aEvent->mCustomizedByUserPrefs) {
6696 return;
6699 Index index = GetIndexFor(aEvent);
6700 Init(index);
6702 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6703 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6704 &multiplierForDeltaY);
6705 aEvent->mDeltaX *= multiplierForDeltaX;
6706 aEvent->mDeltaY *= multiplierForDeltaY;
6707 aEvent->mDeltaZ *= mMultiplierZ[index];
6709 // If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute
6710 // value, we should use lineOrPageDelta values which were set by widget.
6711 // Otherwise, we need to compute them from accumulated delta values.
6712 if (!NeedToComputeLineOrPageDelta(aEvent)) {
6713 aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(multiplierForDeltaX);
6714 aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(multiplierForDeltaY);
6715 } else {
6716 aEvent->mLineOrPageDeltaX = 0;
6717 aEvent->mLineOrPageDeltaY = 0;
6720 aEvent->mCustomizedByUserPrefs =
6721 ((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) ||
6722 (mMultiplierZ[index] != 1.0));
6725 void EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta(
6726 WidgetWheelEvent* aEvent) {
6727 Index index = GetIndexFor(aEvent);
6728 Init(index);
6730 // XXX If the multiplier pref value is negative, the scroll direction was
6731 // changed and caused to scroll different direction. In such case,
6732 // this method reverts the sign of overflowDelta. Does it make widget
6733 // happy? Although, widget can know the pref applied delta values by
6734 // referrencing the deltaX and deltaY of the event.
6736 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6737 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6738 &multiplierForDeltaY);
6739 if (multiplierForDeltaX) {
6740 aEvent->mOverflowDeltaX /= multiplierForDeltaX;
6742 if (multiplierForDeltaY) {
6743 aEvent->mOverflowDeltaY /= multiplierForDeltaY;
6747 EventStateManager::WheelPrefs::Action
6748 EventStateManager::WheelPrefs::ComputeActionFor(
6749 const WidgetWheelEvent* aEvent) {
6750 Index index = GetIndexFor(aEvent);
6751 Init(index);
6753 bool deltaXPreferred = (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
6754 Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
6755 Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
6756 if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL ||
6757 actions[index] == ACTION_HORIZONTALIZED_SCROLL) {
6758 return actions[index];
6761 // Momentum events shouldn't run special actions.
6762 if (aEvent->mIsMomentum) {
6763 // Use the default action. Note that user might kill the wheel scrolling.
6764 Init(INDEX_DEFAULT);
6765 if (actions[INDEX_DEFAULT] == ACTION_SCROLL ||
6766 actions[INDEX_DEFAULT] == ACTION_HORIZONTALIZED_SCROLL) {
6767 return actions[INDEX_DEFAULT];
6769 return ACTION_NONE;
6772 return actions[index];
6775 bool EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta(
6776 const WidgetWheelEvent* aEvent) {
6777 Index index = GetIndexFor(aEvent);
6778 Init(index);
6780 return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
6781 (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
6784 void EventStateManager::WheelPrefs::GetUserPrefsForEvent(
6785 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
6786 double* aOutMultiplierY) {
6787 Index index = GetIndexFor(aEvent);
6788 Init(index);
6790 double multiplierForDeltaX = 1.0, multiplierForDeltaY = 1.0;
6791 GetMultiplierForDeltaXAndY(aEvent, index, &multiplierForDeltaX,
6792 &multiplierForDeltaY);
6793 *aOutMultiplierX = multiplierForDeltaX;
6794 *aOutMultiplierY = multiplierForDeltaY;
6797 // static
6798 Maybe<layers::APZWheelAction> EventStateManager::APZWheelActionFor(
6799 const WidgetWheelEvent* aEvent) {
6800 if (aEvent->mMessage != eWheel) {
6801 return Nothing();
6803 WheelPrefs::Action action =
6804 WheelPrefs::GetInstance()->ComputeActionFor(aEvent);
6805 switch (action) {
6806 case WheelPrefs::ACTION_SCROLL:
6807 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
6808 return Some(layers::APZWheelAction::Scroll);
6809 case WheelPrefs::ACTION_PINCH_ZOOM:
6810 return Some(layers::APZWheelAction::PinchZoom);
6811 default:
6812 return Nothing();
6816 // static
6817 WheelDeltaAdjustmentStrategy EventStateManager::GetWheelDeltaAdjustmentStrategy(
6818 const WidgetWheelEvent& aEvent) {
6819 if (aEvent.mMessage != eWheel) {
6820 return WheelDeltaAdjustmentStrategy::eNone;
6822 switch (WheelPrefs::GetInstance()->ComputeActionFor(&aEvent)) {
6823 case WheelPrefs::ACTION_SCROLL:
6824 if (StaticPrefs::mousewheel_autodir_enabled() && 0 == aEvent.mDeltaZ) {
6825 if (StaticPrefs::mousewheel_autodir_honourroot()) {
6826 return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour;
6828 return WheelDeltaAdjustmentStrategy::eAutoDir;
6830 return WheelDeltaAdjustmentStrategy::eNone;
6831 case WheelPrefs::ACTION_HORIZONTALIZED_SCROLL:
6832 return WheelDeltaAdjustmentStrategy::eHorizontalize;
6833 default:
6834 break;
6836 return WheelDeltaAdjustmentStrategy::eNone;
6839 void EventStateManager::GetUserPrefsForWheelEvent(
6840 const WidgetWheelEvent* aEvent, double* aOutMultiplierX,
6841 double* aOutMultiplierY) {
6842 WheelPrefs::GetInstance()->GetUserPrefsForEvent(aEvent, aOutMultiplierX,
6843 aOutMultiplierY);
6846 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
6847 const WidgetWheelEvent* aEvent) {
6848 Index index = GetIndexFor(aEvent);
6849 Init(index);
6850 return Abs(mMultiplierX[index]) >=
6851 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
6854 bool EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
6855 const WidgetWheelEvent* aEvent) {
6856 Index index = GetIndexFor(aEvent);
6857 Init(index);
6858 return Abs(mMultiplierY[index]) >=
6859 MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
6862 } // namespace mozilla