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