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