Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / layout / base / PositionedEventTargeting.cpp
blobe159239f16ee12cc8b9ef0c9d85da36b104b3733
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PositionedEventTargeting.h"
9 #include "mozilla/EventListenerManager.h"
10 #include "mozilla/MouseEvents.h"
11 #include "mozilla/Preferences.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/StaticPrefs_dom.h"
14 #include "mozilla/StaticPrefs_ui.h"
15 #include "mozilla/ToString.h"
16 #include "mozilla/dom/MouseEventBinding.h"
17 #include "nsContainerFrame.h"
18 #include "nsFrameList.h" // for DEBUG_FRAME_DUMP
19 #include "nsHTMLParts.h"
20 #include "nsLayoutUtils.h"
21 #include "nsGkAtoms.h"
22 #include "nsFontMetrics.h"
23 #include "nsPrintfCString.h"
24 #include "mozilla/dom/Element.h"
25 #include "nsRegion.h"
26 #include "nsDeviceContext.h"
27 #include "nsIContentInlines.h"
28 #include "nsIFrame.h"
29 #include <algorithm>
31 using namespace mozilla;
32 using namespace mozilla::dom;
34 // If debugging this code you may wish to enable this logging, via
35 // the env var MOZ_LOG="event.retarget:4". For extra logging (getting
36 // frame dumps, use MOZ_LOG="event.retarget:5".
37 static mozilla::LazyLogModule sEvtTgtLog("event.retarget");
38 #define PET_LOG(...) MOZ_LOG(sEvtTgtLog, LogLevel::Debug, (__VA_ARGS__))
40 namespace mozilla {
43 * The basic goal of FindFrameTargetedByInputEvent() is to find a good
44 * target element that can respond to mouse events. Both mouse events and touch
45 * events are targeted at this element. Note that even for touch events, we
46 * check responsiveness to mouse events. We assume Web authors
47 * designing for touch events will take their own steps to account for
48 * inaccurate touch events.
50 * GetClickableAncestor() encapsulates the heuristic that determines whether an
51 * element is expected to respond to mouse events. An element is deemed
52 * "clickable" if it has registered listeners for "click", "mousedown" or
53 * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
54 * <select>, <textarea>, <label>), or has role="button", or is a link, or
55 * is a suitable XUL element.
56 * Any descendant (in the same document) of a clickable element is also
57 * deemed clickable since events will propagate to the clickable element from
58 * its descendant.
60 * If the element directly under the event position is clickable (or
61 * event radii are disabled), we always use that element. Otherwise we collect
62 * all frames intersecting a rectangle around the event position (taking CSS
63 * transforms into account) and choose the best candidate in GetClosest().
64 * Only GetClickableAncestor() candidates are considered; if none are found,
65 * then we revert to targeting the element under the event position.
66 * We ignore candidates outside the document subtree rooted by the
67 * document of the element directly under the event position. This ensures that
68 * event listeners in ancestor documents don't make it completely impossible
69 * to target a non-clickable element in a child document.
71 * When both a frame and its ancestor are in the candidate list, we ignore
72 * the ancestor. Otherwise a large ancestor element with a mouse event listener
73 * and some descendant elements that need to be individually targetable would
74 * disable intelligent targeting of those descendants within its bounds.
76 * GetClosest() computes the transformed axis-aligned bounds of each
77 * candidate frame, then computes the Manhattan distance from the event point
78 * to the bounds rect (which can be zero). The frame with the
79 * shortest distance is chosen. For visited links we multiply the distance
80 * by a specified constant weight; this can be used to make visited links
81 * more or less likely to be targeted than non-visited links.
84 // Enum that determines which type of elements to count as targets in the
85 // search. Clickable elements are generally ones that respond to click events,
86 // like form inputs and links and things with click event listeners.
87 // Touchable elements are a much narrower set of elements; ones with touchstart
88 // and touchend listeners.
89 enum class SearchType {
90 None,
91 Clickable,
92 Touchable,
95 struct EventRadiusPrefs {
96 bool mEnabled; // other fields are valid iff this field is true
97 uint32_t mVisitedWeight; // in percent, i.e. default is 100
98 uint32_t mRadiusTopmm;
99 uint32_t mRadiusRightmm;
100 uint32_t mRadiusBottommm;
101 uint32_t mRadiusLeftmm;
102 bool mTouchOnly;
103 bool mReposition;
104 SearchType mSearchType;
106 explicit EventRadiusPrefs(EventClassID aEventClassID) {
107 if (aEventClassID == eTouchEventClass) {
108 mEnabled = StaticPrefs::ui_touch_radius_enabled();
109 mVisitedWeight = StaticPrefs::ui_touch_radius_visitedWeight();
110 mRadiusTopmm = StaticPrefs::ui_touch_radius_topmm();
111 mRadiusRightmm = StaticPrefs::ui_touch_radius_rightmm();
112 mRadiusBottommm = StaticPrefs::ui_touch_radius_bottommm();
113 mRadiusLeftmm = StaticPrefs::ui_touch_radius_leftmm();
114 mTouchOnly = false; // Always false, unlike mouse events.
115 mReposition = false; // Always false, unlike mouse events.
116 mSearchType = SearchType::Touchable;
118 } else if (aEventClassID == eMouseEventClass) {
119 mEnabled = StaticPrefs::ui_mouse_radius_enabled();
120 mVisitedWeight = StaticPrefs::ui_mouse_radius_visitedWeight();
121 mRadiusTopmm = StaticPrefs::ui_mouse_radius_topmm();
122 mRadiusRightmm = StaticPrefs::ui_mouse_radius_rightmm();
123 mRadiusBottommm = StaticPrefs::ui_mouse_radius_bottommm();
124 mRadiusLeftmm = StaticPrefs::ui_mouse_radius_leftmm();
125 mTouchOnly = StaticPrefs::ui_mouse_radius_inputSource_touchOnly();
126 mReposition = StaticPrefs::ui_mouse_radius_reposition();
127 mSearchType = SearchType::Clickable;
129 } else {
130 mEnabled = false;
131 mVisitedWeight = 0;
132 mRadiusTopmm = 0;
133 mRadiusRightmm = 0;
134 mRadiusBottommm = 0;
135 mRadiusLeftmm = 0;
136 mTouchOnly = false;
137 mReposition = false;
138 mSearchType = SearchType::None;
143 static bool HasMouseListener(nsIContent* aContent) {
144 if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
145 return elm->HasListenersFor(nsGkAtoms::onclick) ||
146 elm->HasListenersFor(nsGkAtoms::onmousedown) ||
147 elm->HasListenersFor(nsGkAtoms::onmouseup);
150 return false;
153 static bool HasTouchListener(nsIContent* aContent) {
154 EventListenerManager* elm = aContent->GetExistingListenerManager();
155 if (!elm) {
156 return false;
159 // FIXME: Should this really use the pref rather than TouchEvent::PrefEnabled
160 // or such?
161 if (!StaticPrefs::dom_w3c_touch_events_enabled()) {
162 return false;
165 return elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchstart) ||
166 elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchend);
169 static bool HasPointerListener(nsIContent* aContent) {
170 EventListenerManager* elm = aContent->GetExistingListenerManager();
171 if (!elm) {
172 return false;
175 return elm->HasListenersFor(nsGkAtoms::onpointerdown) ||
176 elm->HasListenersFor(nsGkAtoms::onpointerup);
179 static bool IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor,
180 nsAutoString* aLabelTargetId) {
181 for (nsIContent* content = aFrame->GetContent(); content;
182 content = content->GetFlattenedTreeParent()) {
183 if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
184 content->AsElement()->GetAttr(nsGkAtoms::_for, *aLabelTargetId);
186 if (content == aAncestor) {
187 return true;
190 return false;
193 static nsIContent* GetTouchableAncestor(nsIFrame* aFrame,
194 nsAtom* aStopAt = nullptr) {
195 // Input events propagate up the content tree so we'll follow the content
196 // ancestors to look for elements accepting the touch event.
197 for (nsIContent* content = aFrame->GetContent(); content;
198 content = content->GetFlattenedTreeParent()) {
199 if (aStopAt && content->IsHTMLElement(aStopAt)) {
200 break;
202 if (HasTouchListener(content)) {
203 return content;
206 return nullptr;
209 static nsIContent* GetClickableAncestor(
210 nsIFrame* aFrame, nsAtom* aStopAt = nullptr,
211 nsAutoString* aLabelTargetId = nullptr) {
212 // If the frame is `cursor:pointer` or inherits `cursor:pointer` from an
213 // ancestor, treat it as clickable. This is a heuristic to deal with pages
214 // where the click event listener is on the <body> or <html> element but it
215 // triggers an action on some specific element. We want the specific element
216 // to be considered clickable, and at least some pages that do this indicate
217 // the clickability by setting `cursor:pointer`, so we use that here.
218 // Note that descendants of `cursor:pointer` elements that override the
219 // inherited `pointer` to `auto` or any other value are NOT treated as
220 // clickable, because it seems like the content author is trying to express
221 // non-clickability on that sub-element.
222 // In the future depending on real-world cases it might make sense to expand
223 // this check to any non-auto cursor. Such a change would also pick up things
224 // like contenteditable or input fields, which can then be removed from the
225 // loop below, and would have better performance.
226 if (aFrame->StyleUI()->Cursor().keyword == StyleCursorKind::Pointer) {
227 return aFrame->GetContent();
230 // Input events propagate up the content tree so we'll follow the content
231 // ancestors to look for elements accepting the click.
232 for (nsIContent* content = aFrame->GetContent(); content;
233 content = content->GetFlattenedTreeParent()) {
234 if (aStopAt && content->IsHTMLElement(aStopAt)) {
235 break;
237 if (HasTouchListener(content) || HasMouseListener(content) ||
238 HasPointerListener(content)) {
239 return content;
241 if (content->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input,
242 nsGkAtoms::select, nsGkAtoms::textarea)) {
243 return content;
245 if (content->IsHTMLElement(nsGkAtoms::label)) {
246 if (aLabelTargetId) {
247 content->AsElement()->GetAttr(nsGkAtoms::_for, *aLabelTargetId);
249 return content;
252 // Bug 921928: we don't have access to the content of remote iframe.
253 // So fluffing won't go there. We do an optimistic assumption here:
254 // that the content of the remote iframe needs to be a target.
255 if (content->IsHTMLElement(nsGkAtoms::iframe) &&
256 content->AsElement()->AttrValueIs(kNameSpaceID_None,
257 nsGkAtoms::mozbrowser,
258 nsGkAtoms::_true, eIgnoreCase) &&
259 content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
260 nsGkAtoms::_true, eIgnoreCase)) {
261 return content;
264 // See nsCSSFrameConstructor::FindXULTagData. This code is not
265 // really intended to be used with XUL, though.
266 if (content->IsAnyOfXULElements(
267 nsGkAtoms::button, nsGkAtoms::checkbox, nsGkAtoms::radio,
268 nsGkAtoms::menu, nsGkAtoms::menuitem, nsGkAtoms::menulist,
269 nsGkAtoms::scrollbarbutton, nsGkAtoms::resizer)) {
270 return content;
273 static Element::AttrValuesArray clickableRoles[] = {
274 nsGkAtoms::button, nsGkAtoms::key, nullptr};
275 if (auto* element = Element::FromNode(*content)) {
276 if (element->IsLink()) {
277 return content;
279 if (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
280 clickableRoles, eIgnoreCase) >= 0) {
281 return content;
284 if (content->IsEditable()) {
285 return content;
288 return nullptr;
291 static nscoord AppUnitsFromMM(RelativeTo aFrame, uint32_t aMM) {
292 nsPresContext* pc = aFrame.mFrame->PresContext();
293 float result = float(aMM) * (pc->DeviceContext()->AppUnitsPerPhysicalInch() /
294 MM_PER_INCH_FLOAT);
295 if (aFrame.mViewportType == ViewportType::Layout) {
296 PresShell* presShell = pc->PresShell();
297 result = result / presShell->GetResolution();
299 return NSToCoordRound(result);
303 * Clip aRect with the bounds of aFrame in the coordinate system of
304 * aRootFrame. aRootFrame is an ancestor of aFrame.
306 static nsRect ClipToFrame(RelativeTo aRootFrame, const nsIFrame* aFrame,
307 nsRect& aRect) {
308 nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
309 aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
310 nsRect result = bound.Intersect(aRect);
311 return result;
314 static nsRect GetTargetRect(RelativeTo aRootFrame,
315 const nsPoint& aPointRelativeToRootFrame,
316 const nsIFrame* aRestrictToDescendants,
317 const EventRadiusPrefs& aPrefs, uint32_t aFlags) {
318 nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs.mRadiusTopmm),
319 AppUnitsFromMM(aRootFrame, aPrefs.mRadiusRightmm),
320 AppUnitsFromMM(aRootFrame, aPrefs.mRadiusBottommm),
321 AppUnitsFromMM(aRootFrame, aPrefs.mRadiusLeftmm));
322 nsRect r(aPointRelativeToRootFrame, nsSize(0, 0));
323 r.Inflate(m);
324 if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
325 // Don't clip this rect to the root scroll frame if the flag to ignore the
326 // root scroll frame is set. Note that the GetClosest code will still
327 // enforce that the target found is a descendant of aRestrictToDescendants.
328 r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
330 return r;
333 static float ComputeDistanceFromRect(const nsPoint& aPoint,
334 const nsRect& aRect) {
335 nscoord dx =
336 std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
337 nscoord dy =
338 std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
339 return float(NS_hypot(dx, dy));
342 static float ComputeDistanceFromRegion(const nsPoint& aPoint,
343 const nsRegion& aRegion) {
344 MOZ_ASSERT(!aRegion.IsEmpty(),
345 "can't compute distance between point and empty region");
346 float minDist = -1;
347 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
348 float dist = ComputeDistanceFromRect(aPoint, iter.Get());
349 if (dist < minDist || minDist < 0) {
350 minDist = dist;
353 return minDist;
356 // Subtract aRegion from aExposedRegion as long as that doesn't make the
357 // exposed region get too complex or removes a big chunk of the exposed region.
358 static void SubtractFromExposedRegion(nsRegion* aExposedRegion,
359 const nsRegion& aRegion) {
360 if (aRegion.IsEmpty()) {
361 return;
364 nsRegion tmp;
365 tmp.Sub(*aExposedRegion, aRegion);
366 // Don't let *aExposedRegion get too complex, but don't let it fluff out to
367 // its bounds either. Do let aExposedRegion get more complex if by doing so
368 // we reduce its area by at least half.
369 if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area() / 2) {
370 *aExposedRegion = tmp;
374 static nsIFrame* GetClosest(RelativeTo aRoot,
375 const nsPoint& aPointRelativeToRootFrame,
376 const nsRect& aTargetRect,
377 const EventRadiusPrefs& aPrefs,
378 const nsIFrame* aRestrictToDescendants,
379 nsIContent* aClickableAncestor,
380 nsTArray<nsIFrame*>& aCandidates) {
381 nsIFrame* bestTarget = nullptr;
382 // Lower is better; distance is in appunits
383 float bestDistance = 1e6f;
384 nsRegion exposedRegion(aTargetRect);
385 for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
386 nsIFrame* f = aCandidates[i];
388 bool preservesAxisAlignedRectangles = false;
389 nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
390 f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot,
391 &preservesAxisAlignedRectangles);
392 PET_LOG("Checking candidate %p with border box %s\n", f,
393 ToString(borderBox).c_str());
394 nsRegion region;
395 region.And(exposedRegion, borderBox);
396 if (region.IsEmpty()) {
397 PET_LOG(" candidate %p had empty hit region\n", f);
398 continue;
401 if (preservesAxisAlignedRectangles) {
402 // Subtract from the exposed region if we have a transform that won't make
403 // the bounds include a bunch of area that we don't actually cover.
404 SubtractFromExposedRegion(&exposedRegion, region);
407 nsAutoString labelTargetId;
408 if (aClickableAncestor &&
409 !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
410 PET_LOG(" candidate %p is not a descendant of required ancestor\n", f);
411 continue;
414 if (aPrefs.mSearchType == SearchType::Clickable) {
415 nsIContent* clickableContent =
416 GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
417 if (!aClickableAncestor && !clickableContent) {
418 PET_LOG(" candidate %p was not clickable\n", f);
419 continue;
421 } else if (aPrefs.mSearchType == SearchType::Touchable) {
422 nsIContent* touchableContent = GetTouchableAncestor(f, nsGkAtoms::body);
423 if (!touchableContent) {
424 PET_LOG(" candidate %p was not touchable\n", f);
425 continue;
429 // If our current closest frame is a descendant of 'f', skip 'f' (prefer
430 // the nested frame).
431 if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(
432 f, bestTarget, aRoot.mFrame)) {
433 PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
434 continue;
436 if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(
437 aRestrictToDescendants, f, aRoot.mFrame)) {
438 PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f,
439 aRestrictToDescendants);
440 continue;
443 // distance is in appunits
444 float distance =
445 ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
446 nsIContent* content = f->GetContent();
447 if (content && content->IsElement() &&
448 content->AsElement()->State().HasState(
449 ElementState(ElementState::VISITED))) {
450 distance *= aPrefs.mVisitedWeight / 100.0f;
452 if (distance < bestDistance) {
453 PET_LOG(" candidate %p is the new best\n", f);
454 bestDistance = distance;
455 bestTarget = f;
458 return bestTarget;
461 // Walk from aTarget up to aRoot, and return the first frame found with an
462 // explicit z-index set on it. If no such frame is found, aRoot is returned.
463 static const nsIFrame* FindZIndexAncestor(const nsIFrame* aTarget,
464 const nsIFrame* aRoot) {
465 const nsIFrame* candidate = aTarget;
466 while (candidate && candidate != aRoot) {
467 if (candidate->ZIndex().valueOr(0) > 0) {
468 PET_LOG("Restricting search to z-index root %p\n", candidate);
469 return candidate;
471 candidate = candidate->GetParent();
473 return aRoot;
476 nsIFrame* FindFrameTargetedByInputEvent(
477 WidgetGUIEvent* aEvent, RelativeTo aRootFrame,
478 const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) {
479 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
480 EnumSet<FrameForPointOption> options;
481 if (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) {
482 options += FrameForPointOption::IgnoreRootScrollFrame;
484 nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
485 aRootFrame, aPointRelativeToRootFrame, options);
486 PET_LOG(
487 "Found initial target %p for event class %s message %s point %s "
488 "relative to root frame %s\n",
489 target, ToChar(aEvent->mClass), ToChar(aEvent->mMessage),
490 ToString(aPointRelativeToRootFrame).c_str(),
491 ToString(aRootFrame).c_str());
493 EventRadiusPrefs prefs(aEvent->mClass);
494 if (!prefs.mEnabled || EventRetargetSuppression::IsActive()) {
495 PET_LOG("Retargeting disabled\n");
496 return target;
499 // Do not modify targeting for actual mouse hardware; only for mouse
500 // events generated by touch-screen hardware.
501 if (aEvent->mClass == eMouseEventClass && prefs.mTouchOnly &&
502 aEvent->AsMouseEvent()->mInputSource !=
503 MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
504 PET_LOG("Mouse input event is not from a touch source\n");
505 return target;
508 // If the exact target is non-null, only consider candidate targets in the
509 // same document as the exact target. Otherwise, if an ancestor document has
510 // a mouse event handler for example, targets that are !GetClickableAncestor
511 // can never be targeted --- something nsSubDocumentFrame in an ancestor
512 // document would be targeted instead.
513 const nsIFrame* restrictToDescendants = [&]() -> const nsIFrame* {
514 if (target && target->PresContext() != aRootFrame.mFrame->PresContext()) {
515 return target->PresShell()->GetRootFrame();
517 return aRootFrame.mFrame;
518 }();
520 // Ignore retarget if target is editable.
521 nsIContent* targetContent = target ? target->GetContent() : nullptr;
522 if (targetContent && targetContent->IsEditable()) {
523 PET_LOG("Target %p is editable\n", target);
524 return target;
527 // If the target element inside an element with a z-index, restrict the
528 // search to other elements inside that z-index. This is a heuristic
529 // intended to help with a class of scenarios involving web modals or
530 // web popup type things. In particular it helps alleviate bug 1666792.
531 restrictToDescendants = FindZIndexAncestor(target, restrictToDescendants);
533 nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
534 restrictToDescendants, prefs, aFlags);
535 PET_LOG("Expanded point to target rect %s\n", ToString(targetRect).c_str());
536 AutoTArray<nsIFrame*, 8> candidates;
537 nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect,
538 candidates, options);
539 if (NS_FAILED(rv)) {
540 return target;
543 nsIContent* clickableAncestor = nullptr;
544 if (target) {
545 clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
546 if (clickableAncestor) {
547 PET_LOG("Target %p is clickable\n", target);
548 // If the target that was directly hit has a clickable ancestor, that
549 // means it too is clickable. And since it is the same as or a
550 // descendant of clickableAncestor, it should become the root for the
551 // GetClosest search.
552 clickableAncestor = target->GetContent();
556 nsIFrame* closest =
557 GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
558 restrictToDescendants, clickableAncestor, candidates);
559 if (closest) {
560 target = closest;
563 PET_LOG("Final target is %p\n", target);
565 #ifdef DEBUG_FRAME_DUMP
566 // At verbose logging level, dump the frame tree to help with debugging.
567 // Note that dumping the frame tree at the top of the function may flood
568 // logcat on Android devices and cause the PET_LOGs to get dropped.
569 if (MOZ_LOG_TEST(sEvtTgtLog, LogLevel::Verbose)) {
570 if (target) {
571 target->DumpFrameTree();
572 } else {
573 aRootFrame.mFrame->DumpFrameTree();
576 #endif
578 if (!target || !prefs.mReposition) {
579 // No repositioning required for this event
580 return target;
583 // Take the point relative to the root frame, make it relative to the target,
584 // clamp it to the bounds, and then make it relative to the root frame again.
585 nsPoint point = aPointRelativeToRootFrame;
586 if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
587 nsLayoutUtils::TransformPoint(aRootFrame, RelativeTo{target}, point)) {
588 return target;
590 point = target->GetRectRelativeToSelf().ClampPoint(point);
591 if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
592 nsLayoutUtils::TransformPoint(RelativeTo{target}, aRootFrame, point)) {
593 return target;
595 // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
596 // get back the (now-clamped) coordinates in the event's widget's space.
597 nsView* view = aRootFrame.mFrame->GetView();
598 if (!view) {
599 return target;
601 LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
602 aRootFrame.mFrame->PresContext(), view, point, aRootFrame.mViewportType,
603 aEvent->mWidget);
604 if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
605 // If that succeeded, we update the point in the event
606 aEvent->mRefPoint = widgetPoint;
608 return target;
611 uint32_t EventRetargetSuppression::sSuppressionCount = 0;
613 EventRetargetSuppression::EventRetargetSuppression() { sSuppressionCount++; }
615 EventRetargetSuppression::~EventRetargetSuppression() { sSuppressionCount--; }
617 bool EventRetargetSuppression::IsActive() { return sSuppressionCount > 0; }
619 } // namespace mozilla