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