MouseEvent.cpp (19625B)
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 "MouseEvent.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/EventForwards.h" 11 #include "mozilla/MouseEvents.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/StaticPrefs_dom.h" 14 #include "mozilla/ViewportUtils.h" 15 #include "nsContentUtils.h" 16 #include "nsIFrame.h" 17 #include "nsIScreenManager.h" 18 #include "nsLayoutUtils.h" 19 20 namespace mozilla::dom { 21 22 static nsIntPoint DevPixelsToCSSPixels(const LayoutDeviceIntPoint& aPoint, 23 nsPresContext* aContext) { 24 return nsIntPoint(aContext->DevPixelsToIntCSSPixels(aPoint.x), 25 aContext->DevPixelsToIntCSSPixels(aPoint.y)); 26 } 27 28 MouseEvent::MouseEvent(EventTarget* aOwner, nsPresContext* aPresContext, 29 WidgetMouseEventBase* aEvent) 30 : UIEvent(aOwner, aPresContext, 31 aEvent ? aEvent 32 : new WidgetMouseEvent(false, eVoidEvent, nullptr, 33 WidgetMouseEvent::eReal)) { 34 // There's no way to make this class' ctor allocate an WidgetMouseScrollEvent. 35 // It's not that important, though, since a scroll event is not a real 36 // DOM event. 37 38 WidgetMouseEventBase* const mouseEventBase = mEvent->AsMouseEventBase(); 39 MOZ_ASSERT(mouseEventBase); 40 if (aEvent) { 41 mEventIsInternal = false; 42 } else { 43 mEventIsInternal = true; 44 mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); 45 mouseEventBase->mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; 46 } 47 48 mUseFractionalCoords = mouseEventBase->DOMEventShouldUseFractionalCoords(); 49 mWidgetOrScreenRelativePoint = mEvent->mRefPoint; 50 51 if (const WidgetMouseEvent* mouseEvent = mouseEventBase->AsMouseEvent()) { 52 MOZ_ASSERT(mouseEvent->mReason != WidgetMouseEvent::eSynthesized, 53 "Don't dispatch DOM events from synthesized mouse events"); 54 mDetail = static_cast<int32_t>(mouseEvent->mClickCount); 55 } 56 } 57 58 void MouseEvent::InitMouseEventInternal( 59 const nsAString& aType, bool aCanBubble, bool aCancelable, 60 nsGlobalWindowInner* aView, int32_t aDetail, double aScreenX, 61 double aScreenY, double aClientX, double aClientY, bool aCtrlKey, 62 bool aAltKey, bool aShiftKey, bool aMetaKey, uint16_t aButton, 63 EventTarget* aRelatedTarget) { 64 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 65 66 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); 67 68 switch (mEvent->mClass) { 69 case eMouseEventClass: 70 case eMouseScrollEventClass: 71 case eWheelEventClass: 72 case eDragEventClass: 73 case ePointerEventClass: 74 case eSimpleGestureEventClass: { 75 WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); 76 mouseEventBase->mRelatedTarget = aRelatedTarget; 77 mouseEventBase->mButton = aButton; 78 mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, 79 aMetaKey); 80 mDefaultClientPoint = CSSDoublePoint(aClientX, aClientY); 81 mWidgetOrScreenRelativePoint = 82 LayoutDeviceDoublePoint(aScreenX, aScreenY); 83 mouseEventBase->mRefPoint = 84 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint); 85 86 WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); 87 if (mouseEvent) { 88 mouseEvent->mClickCount = aDetail; 89 } 90 91 mUseFractionalCoords = 92 mouseEventBase->DOMEventShouldUseFractionalCoords(); 93 if (!mUseFractionalCoords) { 94 // If we should not use fractional coordinates for this event, we need 95 // to drop the fractional part as defined for the backward compatibility 96 // when we treated the input values are integer coordinates. These 97 // values will be exposed as screenX, screenY, clientX and clientY as-is 98 // too. That matches with the Pointer Events spec definitions too. 99 // https://w3c.github.io/pointerevents/#event-coordinates 100 mDefaultClientPoint = CSSIntPoint::Floor(mDefaultClientPoint); 101 mWidgetOrScreenRelativePoint = 102 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint); 103 } 104 break; 105 } 106 default: 107 break; 108 } 109 } 110 111 void MouseEvent::InitMouseEventInternal( 112 const nsAString& aType, bool aCanBubble, bool aCancelable, 113 nsGlobalWindowInner* aView, int32_t aDetail, double aScreenX, 114 double aScreenY, double aClientX, double aClientY, int16_t aButton, 115 EventTarget* aRelatedTarget, const nsAString& aModifiersList) { 116 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 117 118 Modifiers modifiers = ComputeModifierState(aModifiersList); 119 120 InitMouseEventInternal( 121 aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, 122 aClientX, aClientY, (modifiers & MODIFIER_CONTROL) != 0, 123 (modifiers & MODIFIER_ALT) != 0, (modifiers & MODIFIER_SHIFT) != 0, 124 (modifiers & MODIFIER_META) != 0, aButton, aRelatedTarget); 125 126 switch (mEvent->mClass) { 127 case eMouseEventClass: 128 case eMouseScrollEventClass: 129 case eWheelEventClass: 130 case eDragEventClass: 131 case ePointerEventClass: 132 case eSimpleGestureEventClass: 133 mEvent->AsInputEvent()->mModifiers = modifiers; 134 return; 135 default: 136 MOZ_CRASH("There is no space to store the modifiers"); 137 } 138 } 139 140 void MouseEvent::InitializeExtraMouseEventDictionaryMembers( 141 const MouseEventInit& aParam) { 142 InitModifiers(aParam); 143 mEvent->AsMouseEventBase()->mButtons = aParam.mButtons; 144 mMovementPoint.x = aParam.mMovementX; 145 mMovementPoint.y = aParam.mMovementY; 146 } 147 148 already_AddRefed<MouseEvent> MouseEvent::Constructor( 149 const GlobalObject& aGlobal, const nsAString& aType, 150 const MouseEventInit& aParam) { 151 nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); 152 RefPtr<MouseEvent> e = new MouseEvent(t, nullptr, nullptr); 153 bool trusted = e->Init(t); 154 e->InitMouseEventInternal( 155 aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, aParam.mDetail, 156 aParam.mScreenX, aParam.mScreenY, aParam.mClientX, aParam.mClientY, 157 aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey, aParam.mMetaKey, 158 aParam.mButton, aParam.mRelatedTarget); 159 e->InitializeExtraMouseEventDictionaryMembers(aParam); 160 e->SetTrusted(trusted); 161 e->SetComposed(aParam.mComposed); 162 MOZ_ASSERT(!trusted || !IsPointerEventMessage(e->mEvent->mMessage), 163 "Please use PointerEvent constructor!"); 164 return e.forget(); 165 } 166 167 void MouseEvent::InitNSMouseEvent(const nsAString& aType, bool aCanBubble, 168 bool aCancelable, nsGlobalWindowInner* aView, 169 int32_t aDetail, int32_t aScreenX, 170 int32_t aScreenY, int32_t aClientX, 171 int32_t aClientY, bool aCtrlKey, bool aAltKey, 172 bool aShiftKey, bool aMetaKey, 173 uint16_t aButton, EventTarget* aRelatedTarget, 174 float aPressure, uint16_t aInputSource) { 175 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 176 177 InitMouseEventInternal(aType, aCanBubble, aCancelable, aView, aDetail, 178 aScreenX, aScreenY, aClientX, aClientY, aCtrlKey, 179 aAltKey, aShiftKey, aMetaKey, aButton, aRelatedTarget); 180 181 WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); 182 mouseEventBase->mPressure = aPressure; 183 mouseEventBase->mInputSource = aInputSource; 184 } 185 186 void MouseEvent::DuplicatePrivateData() { 187 // If this is a event not created from WidgetMouseEventBase or its subclasses 188 // (i.e., created by JS), mDefaultClientPoint and mMovementPoint are 189 // initialized as expected values. Therefore, we don't need to recompute it. 190 if (!mEventIsInternal) { 191 mDefaultClientPoint = ClientPoint(); 192 mMovementPoint = GetMovementPoint(); 193 } 194 // However, mPagePoint needs to include the scroll position. Therefore, we 195 // need to compute here. 196 mPagePoint = PagePoint(); 197 198 // mEvent->mRefPoint is computed by UIEvent::DuplicatePrivateData() with 199 // the device pixel scale, but if we need to store fractional values to 200 // mWidgetRelativePoint, we need to do same thing by ourselves. 201 Maybe<const CSSDoublePoint> maybeScreenPoint; 202 if (mUseFractionalCoords) { 203 maybeScreenPoint.emplace(ScreenPoint(CallerType::System)); 204 } 205 { 206 // mPresContext will be cleared by Event::DuplicatePrivateData(), but we 207 // need it after a call of it. So, we need to grab it. 208 RefPtr<nsPresContext> presContext = mPresContext.get(); 209 UIEvent::DuplicatePrivateData(); 210 mPresContext = presContext.get(); 211 } 212 // Starting from here, mWidgetOrScreenRelativePoint (and 213 // WidgetGUIEvent::mWidget) stores a screen point because we're now don't 214 // store widget in mEvent. Therefore, we cannot compute a screen point from 215 // widget relative point without the widget. 216 if (maybeScreenPoint.isSome()) { 217 // ScreenPoint() has already computed it with the scale of mPresContext. 218 // Therefore, we don't need to take care of it again. 219 MOZ_ASSERT(!mEvent || !mEvent->AsGUIEvent()->mWidget); 220 mWidgetOrScreenRelativePoint = 221 maybeScreenPoint.ref() * CSSToLayoutDeviceScale(1); 222 } else { 223 // As mentioned above, mEvent->mRefPoint is already computed by UIEvent, so, 224 // do not need to compute the scale. 225 mWidgetOrScreenRelativePoint = mEvent->mRefPoint; 226 } 227 } 228 229 void MouseEvent::PreventClickEvent() { 230 if (WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent()) { 231 mouseEvent->mClickEventPrevented = true; 232 } 233 } 234 235 bool MouseEvent::ClickEventPrevented() { 236 if (WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent()) { 237 return mouseEvent->mClickEventPrevented; 238 } 239 return false; 240 } 241 242 already_AddRefed<Event> MouseEvent::GetTriggerEvent() const { 243 if (WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent()) { 244 NS_WARNING_ASSERTION( 245 mouseEvent->mMessage == eXULPopupShowing, 246 "triggerEvent is supported for popupshowing event only"); 247 RefPtr<Event> e = mouseEvent->mTriggerEvent; 248 return e.forget(); 249 } 250 return nullptr; 251 } 252 253 int16_t MouseEvent::Button() { 254 switch (mEvent->mClass) { 255 case eMouseEventClass: 256 case eMouseScrollEventClass: 257 case eWheelEventClass: 258 case eDragEventClass: 259 case ePointerEventClass: 260 case eSimpleGestureEventClass: 261 return mEvent->AsMouseEventBase()->mButton; 262 default: 263 NS_WARNING("Tried to get mouse mButton for non-mouse event!"); 264 return MouseButton::ePrimary; 265 } 266 } 267 268 uint16_t MouseEvent::Buttons() const { 269 switch (mEvent->mClass) { 270 case eMouseEventClass: 271 case eMouseScrollEventClass: 272 case eWheelEventClass: 273 case eDragEventClass: 274 case ePointerEventClass: 275 case eSimpleGestureEventClass: 276 return mEvent->AsMouseEventBase()->mButtons; 277 default: 278 MOZ_CRASH("Tried to get mouse buttons for non-mouse event!"); 279 } 280 } 281 282 already_AddRefed<EventTarget> MouseEvent::GetRelatedTarget() { 283 nsCOMPtr<EventTarget> relatedTarget; 284 switch (mEvent->mClass) { 285 case eMouseEventClass: 286 case eMouseScrollEventClass: 287 case eWheelEventClass: 288 case eDragEventClass: 289 case ePointerEventClass: 290 case eSimpleGestureEventClass: 291 relatedTarget = mEvent->AsMouseEventBase()->mRelatedTarget; 292 break; 293 default: 294 break; 295 } 296 297 return EnsureWebAccessibleRelatedTarget(relatedTarget); 298 } 299 300 CSSDoublePoint MouseEvent::ScreenPoint(CallerType aCallerType) const { 301 if (mEvent->mFlags.mIsPositionless) { 302 return {}; 303 } 304 305 // If this is a trusted event, mWidgetRelativeOffset is a copy of 306 // mEvent->mRefPoint, so, the values are integer. 307 // If this is an untrusted event, mWidgetRelativeOffset should be floored when 308 // it's initialized. 309 MOZ_ASSERT_IF(!mUseFractionalCoords, 310 mWidgetOrScreenRelativePoint == 311 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint)); 312 if (nsContentUtils::ShouldResistFingerprinting( 313 aCallerType, GetParentObject(), RFPTarget::MouseEventScreenPoint)) { 314 // Sanitize to something sort of like client coords, but not quite 315 // (defaulting to (0,0) instead of our pre-specified client coords). 316 const CSSDoublePoint clientPoint = Event::GetClientCoords( 317 mPresContext, mEvent, mWidgetOrScreenRelativePoint, 318 CSSDoublePoint{0, 0}); 319 return mUseFractionalCoords ? clientPoint : RoundedToInt(clientPoint); 320 } 321 322 const CSSDoublePoint screenPoint = 323 Event::GetScreenCoords(mPresContext, mEvent, mWidgetOrScreenRelativePoint) 324 .extract(); 325 return mUseFractionalCoords ? screenPoint : RoundedToInt(screenPoint); 326 } 327 328 LayoutDeviceIntPoint MouseEvent::ScreenPointLayoutDevicePix() const { 329 const CSSDoublePoint point = ScreenPoint(CallerType::System); 330 auto scale = mPresContext ? mPresContext->CSSToDevPixelScale() 331 : CSSToLayoutDeviceScale(); 332 return LayoutDeviceIntPoint::Round(point * scale); 333 } 334 335 DesktopIntPoint MouseEvent::ScreenPointDesktopPix() const { 336 const CSSDoublePoint point = ScreenPoint(CallerType::System); 337 auto scale = 338 mPresContext 339 ? mPresContext->CSSToDevPixelScale() / 340 mPresContext->DeviceContext()->GetDesktopToDeviceScale() 341 : CSSToDesktopScale(); 342 return DesktopIntPoint::Round(point * scale); 343 } 344 345 already_AddRefed<nsIScreen> MouseEvent::GetScreen() { 346 nsCOMPtr<nsIScreenManager> screenMgr = 347 do_GetService("@mozilla.org/gfx/screenmanager;1"); 348 if (!screenMgr) { 349 return nullptr; 350 } 351 return screenMgr->ScreenForRect( 352 DesktopIntRect(ScreenPointDesktopPix(), DesktopIntSize(1, 1))); 353 } 354 355 CSSDoublePoint MouseEvent::PagePoint() const { 356 if (mEvent->mFlags.mIsPositionless) { 357 return {}; 358 } 359 360 if (mPrivateDataDuplicated) { 361 // mPagePoint should be floored when it started to cache the values after 362 // the propagation. 363 MOZ_ASSERT_IF(!mUseFractionalCoords, 364 mPagePoint == CSSIntPoint::Floor(mPagePoint)); 365 return mPagePoint; 366 } 367 368 // If this is a trusted event, mWidgetRelativeOffset is a copy of 369 // mEvent->mRefPoint, so, the values are integer. 370 // If this is an untrusted event, mWidgetRelativeOffset should be floored when 371 // it's initialized. 372 MOZ_ASSERT_IF(!mUseFractionalCoords, 373 mWidgetOrScreenRelativePoint == 374 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint)); 375 // If this is a trusted event, mDefaultClientPoint should be floored when 376 // it started to cache the values after the propagation. 377 // If this is an untrusted event, mDefaultClientPoint should be floored when 378 // it's initialized. 379 MOZ_ASSERT_IF(!mUseFractionalCoords, 380 mDefaultClientPoint == CSSIntPoint::Floor(mDefaultClientPoint)); 381 const CSSDoublePoint pagePoint = Event::GetPageCoords( 382 mPresContext, mEvent, mWidgetOrScreenRelativePoint, mDefaultClientPoint); 383 return mUseFractionalCoords ? pagePoint : RoundedToInt(pagePoint); 384 } 385 386 CSSDoublePoint MouseEvent::ClientPoint() const { 387 if (mEvent->mFlags.mIsPositionless) { 388 return {}; 389 } 390 391 // If this is a trusted event, mWidgetRelativeOffset is a copy of 392 // mEvent->mRefPoint, so, the values are integer. 393 // If this is an untrusted event, mWidgetRelativeOffset should be floored when 394 // it's initialized. 395 MOZ_ASSERT_IF(!mUseFractionalCoords, 396 mWidgetOrScreenRelativePoint == 397 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint)); 398 // If this is a trusted event, mDefaultClientPoint should be floored when 399 // it started to cache the values after the propagation. 400 // If this is an untrusted event, mDefaultClientPoint should be floored when 401 // it's initialized. 402 MOZ_ASSERT_IF(!mUseFractionalCoords, 403 mDefaultClientPoint == CSSIntPoint::Floor(mDefaultClientPoint)); 404 const CSSDoublePoint clientPoint = Event::GetClientCoords( 405 mPresContext, mEvent, mWidgetOrScreenRelativePoint, mDefaultClientPoint); 406 return mUseFractionalCoords ? clientPoint : RoundedToInt(clientPoint); 407 } 408 409 CSSDoublePoint MouseEvent::OffsetPoint() const { 410 if (mEvent->mFlags.mIsPositionless) { 411 return {}; 412 } 413 414 // If this is a trusted event, mWidgetRelativeOffset is a copy of 415 // mEvent->mRefPoint, so, the values are integer. 416 // If this is an untrusted event, mWidgetRelativeOffset should be floored when 417 // it's initialized. 418 MOZ_ASSERT_IF(!mUseFractionalCoords, 419 mWidgetOrScreenRelativePoint == 420 LayoutDeviceIntPoint::Floor(mWidgetOrScreenRelativePoint)); 421 // If this is a trusted event, mDefaultClientPoint should be floored when 422 // it started to cache the values after the propagation. 423 // If this is an untrusted event, mDefaultClientPoint should be floored when 424 // it's initialized. 425 MOZ_ASSERT_IF(!mUseFractionalCoords, 426 mDefaultClientPoint == CSSIntPoint::Floor(mDefaultClientPoint)); 427 RefPtr<nsPresContext> presContext(mPresContext); 428 const CSSDoublePoint offsetPoint = Event::GetOffsetCoords( 429 presContext, mEvent, mWidgetOrScreenRelativePoint, mDefaultClientPoint); 430 return mUseFractionalCoords ? offsetPoint : RoundedToInt(offsetPoint); 431 } 432 433 nsIntPoint MouseEvent::GetMovementPoint() const { 434 if (mEvent->mFlags.mIsPositionless) { 435 return nsIntPoint(0, 0); 436 } 437 438 if (mPrivateDataDuplicated || mEventIsInternal) { 439 return mMovementPoint; 440 } 441 442 if (!mEvent || !mEvent->AsGUIEvent()->mWidget || 443 (mEvent->mMessage != eMouseMove && mEvent->mMessage != ePointerMove && 444 !(StaticPrefs::dom_event_pointer_rawupdate_movement_enabled() && 445 mEvent->mMessage == ePointerRawUpdate))) { 446 // Pointer Lock spec defines that movementX/Y must be zero for all mouse 447 // events except mousemove. 448 return nsIntPoint(0, 0); 449 } 450 451 // Calculate the delta between the last screen point and the current one. 452 nsIntPoint current = DevPixelsToCSSPixels(mEvent->mRefPoint, mPresContext); 453 nsIntPoint last = DevPixelsToCSSPixels(mEvent->mLastRefPoint, mPresContext); 454 return current - last; 455 } 456 457 bool MouseEvent::AltKey() { return mEvent->AsInputEvent()->IsAlt(); } 458 459 bool MouseEvent::CtrlKey() { return mEvent->AsInputEvent()->IsControl(); } 460 461 bool MouseEvent::ShiftKey() { return mEvent->AsInputEvent()->IsShift(); } 462 463 bool MouseEvent::MetaKey() { return mEvent->AsInputEvent()->IsMeta(); } 464 465 float MouseEvent::MozPressure(CallerType aCallerType) const { 466 if (nsContentUtils::ShouldResistFingerprinting(aCallerType, GetParentObject(), 467 RFPTarget::PointerEvents)) { 468 // Use the spoofed value from PointerEvent::Pressure 469 return Buttons() == 0 ? 0.0f : 0.5f; 470 } 471 472 return mEvent->AsMouseEventBase()->mPressure; 473 } 474 475 uint16_t MouseEvent::InputSource(CallerType aCallerType) const { 476 if (nsContentUtils::ShouldResistFingerprinting(aCallerType, GetParentObject(), 477 RFPTarget::PointerEvents)) { 478 return MouseEvent_Binding::MOZ_SOURCE_MOUSE; 479 } 480 481 return mEvent->AsMouseEventBase()->mInputSource; 482 } 483 484 } // namespace mozilla::dom 485 486 using namespace mozilla; 487 using namespace mozilla::dom; 488 489 already_AddRefed<MouseEvent> NS_NewDOMMouseEvent(EventTarget* aOwner, 490 nsPresContext* aPresContext, 491 WidgetMouseEvent* aEvent) { 492 RefPtr<MouseEvent> it = new MouseEvent(aOwner, aPresContext, aEvent); 493 return it.forget(); 494 }