summaryrefslogtreecommitdiffstats
path: root/layout/base/PositionedEventTargeting.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/base/PositionedEventTargeting.cpp614
1 files changed, 614 insertions, 0 deletions
diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp
new file mode 100644
index 0000000000..533f3e4932
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PositionedEventTargeting.h"
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/ToString.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "nsContainerFrame.h"
+#include "nsFrameList.h" // for DEBUG_FRAME_DUMP
+#include "nsHTMLParts.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsFontMetrics.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/Element.h"
+#include "nsRegion.h"
+#include "nsDeviceContext.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// If debugging this code you may wish to enable this logging, via
+// the env var MOZ_LOG="event.retarget:4". For extra logging (getting
+// frame dumps, use MOZ_LOG="event.retarget:5".
+static mozilla::LazyLogModule sEvtTgtLog("event.retarget");
+#define PET_LOG(...) MOZ_LOG(sEvtTgtLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+/*
+ * The basic goal of FindFrameTargetedByInputEvent() is to find a good
+ * target element that can respond to mouse events. Both mouse events and touch
+ * events are targeted at this element. Note that even for touch events, we
+ * check responsiveness to mouse events. We assume Web authors
+ * designing for touch events will take their own steps to account for
+ * inaccurate touch events.
+ *
+ * GetClickableAncestor() encapsulates the heuristic that determines whether an
+ * element is expected to respond to mouse events. An element is deemed
+ * "clickable" if it has registered listeners for "click", "mousedown" or
+ * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
+ * <select>, <textarea>, <label>), or has role="button", or is a link, or
+ * is a suitable XUL element.
+ * Any descendant (in the same document) of a clickable element is also
+ * deemed clickable since events will propagate to the clickable element from
+ * its descendant.
+ *
+ * If the element directly under the event position is clickable (or
+ * event radii are disabled), we always use that element. Otherwise we collect
+ * all frames intersecting a rectangle around the event position (taking CSS
+ * transforms into account) and choose the best candidate in GetClosest().
+ * Only GetClickableAncestor() candidates are considered; if none are found,
+ * then we revert to targeting the element under the event position.
+ * We ignore candidates outside the document subtree rooted by the
+ * document of the element directly under the event position. This ensures that
+ * event listeners in ancestor documents don't make it completely impossible
+ * to target a non-clickable element in a child document.
+ *
+ * When both a frame and its ancestor are in the candidate list, we ignore
+ * the ancestor. Otherwise a large ancestor element with a mouse event listener
+ * and some descendant elements that need to be individually targetable would
+ * disable intelligent targeting of those descendants within its bounds.
+ *
+ * GetClosest() computes the transformed axis-aligned bounds of each
+ * candidate frame, then computes the Manhattan distance from the event point
+ * to the bounds rect (which can be zero). The frame with the
+ * shortest distance is chosen. For visited links we multiply the distance
+ * by a specified constant weight; this can be used to make visited links
+ * more or less likely to be targeted than non-visited links.
+ */
+
+// Enum that determines which type of elements to count as targets in the
+// search. Clickable elements are generally ones that respond to click events,
+// like form inputs and links and things with click event listeners.
+// Touchable elements are a much narrower set of elements; ones with touchstart
+// and touchend listeners.
+enum class SearchType {
+ None,
+ Clickable,
+ Touchable,
+};
+
+struct EventRadiusPrefs {
+ bool mEnabled; // other fields are valid iff this field is true
+ uint32_t mVisitedWeight; // in percent, i.e. default is 100
+ uint32_t mRadiusTopmm;
+ uint32_t mRadiusRightmm;
+ uint32_t mRadiusBottommm;
+ uint32_t mRadiusLeftmm;
+ bool mTouchOnly;
+ bool mReposition;
+ SearchType mSearchType;
+
+ explicit EventRadiusPrefs(EventClassID aEventClassID) {
+ if (aEventClassID == eTouchEventClass) {
+ mEnabled = StaticPrefs::ui_touch_radius_enabled();
+ mVisitedWeight = StaticPrefs::ui_touch_radius_visitedWeight();
+ mRadiusTopmm = StaticPrefs::ui_touch_radius_topmm();
+ mRadiusRightmm = StaticPrefs::ui_touch_radius_rightmm();
+ mRadiusBottommm = StaticPrefs::ui_touch_radius_bottommm();
+ mRadiusLeftmm = StaticPrefs::ui_touch_radius_leftmm();
+ mTouchOnly = false; // Always false, unlike mouse events.
+ mReposition = false; // Always false, unlike mouse events.
+ mSearchType = SearchType::Touchable;
+
+ } else if (aEventClassID == eMouseEventClass) {
+ mEnabled = StaticPrefs::ui_mouse_radius_enabled();
+ mVisitedWeight = StaticPrefs::ui_mouse_radius_visitedWeight();
+ mRadiusTopmm = StaticPrefs::ui_mouse_radius_topmm();
+ mRadiusRightmm = StaticPrefs::ui_mouse_radius_rightmm();
+ mRadiusBottommm = StaticPrefs::ui_mouse_radius_bottommm();
+ mRadiusLeftmm = StaticPrefs::ui_mouse_radius_leftmm();
+ mTouchOnly = StaticPrefs::ui_mouse_radius_inputSource_touchOnly();
+ mReposition = StaticPrefs::ui_mouse_radius_reposition();
+ mSearchType = SearchType::Clickable;
+
+ } else {
+ mEnabled = false;
+ mVisitedWeight = 0;
+ mRadiusTopmm = 0;
+ mRadiusRightmm = 0;
+ mRadiusBottommm = 0;
+ mRadiusLeftmm = 0;
+ mTouchOnly = false;
+ mReposition = false;
+ mSearchType = SearchType::None;
+ }
+ }
+};
+
+static bool HasMouseListener(nsIContent* aContent) {
+ if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
+ return elm->HasListenersFor(nsGkAtoms::onclick) ||
+ elm->HasListenersFor(nsGkAtoms::onmousedown) ||
+ elm->HasListenersFor(nsGkAtoms::onmouseup);
+ }
+
+ return false;
+}
+
+static bool HasTouchListener(nsIContent* aContent) {
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ // FIXME: Should this really use the pref rather than TouchEvent::PrefEnabled
+ // or such?
+ if (!StaticPrefs::dom_w3c_touch_events_enabled()) {
+ return false;
+ }
+
+ return elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchstart) ||
+ elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchend);
+}
+
+static bool HasPointerListener(nsIContent* aContent) {
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ return elm->HasListenersFor(nsGkAtoms::onpointerdown) ||
+ elm->HasListenersFor(nsGkAtoms::onpointerup);
+}
+
+static bool IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor,
+ nsAutoString* aLabelTargetId) {
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
+ content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
+ *aLabelTargetId);
+ }
+ if (content == aAncestor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static nsIContent* GetTouchableAncestor(nsIFrame* aFrame,
+ nsAtom* aStopAt = nullptr) {
+ // Input events propagate up the content tree so we'll follow the content
+ // ancestors to look for elements accepting the touch event.
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aStopAt && content->IsHTMLElement(aStopAt)) {
+ break;
+ }
+ if (HasTouchListener(content)) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nsIContent* GetClickableAncestor(
+ nsIFrame* aFrame, nsAtom* aStopAt = nullptr,
+ nsAutoString* aLabelTargetId = nullptr) {
+ // If the frame is `cursor:pointer` or inherits `cursor:pointer` from an
+ // ancestor, treat it as clickable. This is a heuristic to deal with pages
+ // where the click event listener is on the <body> or <html> element but it
+ // triggers an action on some specific element. We want the specific element
+ // to be considered clickable, and at least some pages that do this indicate
+ // the clickability by setting `cursor:pointer`, so we use that here.
+ // Note that descendants of `cursor:pointer` elements that override the
+ // inherited `pointer` to `auto` or any other value are NOT treated as
+ // clickable, because it seems like the content author is trying to express
+ // non-clickability on that sub-element.
+ // In the future depending on real-world cases it might make sense to expand
+ // this check to any non-auto cursor. Such a change would also pick up things
+ // like contenteditable or input fields, which can then be removed from the
+ // loop below, and would have better performance.
+ if (aFrame->StyleUI()->Cursor().keyword == StyleCursorKind::Pointer) {
+ return aFrame->GetContent();
+ }
+
+ // Input events propagate up the content tree so we'll follow the content
+ // ancestors to look for elements accepting the click.
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aStopAt && content->IsHTMLElement(aStopAt)) {
+ break;
+ }
+ if (HasTouchListener(content) || HasMouseListener(content) ||
+ HasPointerListener(content)) {
+ return content;
+ }
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input,
+ nsGkAtoms::select, nsGkAtoms::textarea)) {
+ return content;
+ }
+ if (content->IsHTMLElement(nsGkAtoms::label)) {
+ if (aLabelTargetId) {
+ content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
+ *aLabelTargetId);
+ }
+ return content;
+ }
+
+ // Bug 921928: we don't have access to the content of remote iframe.
+ // So fluffing won't go there. We do an optimistic assumption here:
+ // that the content of the remote iframe needs to be a target.
+ if (content->IsHTMLElement(nsGkAtoms::iframe) &&
+ content->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::mozbrowser,
+ nsGkAtoms::_true, eIgnoreCase) &&
+ content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ return content;
+ }
+
+ // See nsCSSFrameConstructor::FindXULTagData. This code is not
+ // really intended to be used with XUL, though.
+ if (content->IsAnyOfXULElements(
+ nsGkAtoms::button, nsGkAtoms::checkbox, nsGkAtoms::radio,
+ nsGkAtoms::menu, nsGkAtoms::menuitem, nsGkAtoms::menulist,
+ nsGkAtoms::scrollbarbutton, nsGkAtoms::resizer)) {
+ return content;
+ }
+
+ static Element::AttrValuesArray clickableRoles[] = {
+ nsGkAtoms::button, nsGkAtoms::key, nullptr};
+ if (auto* element = Element::FromNode(*content)) {
+ if (element->IsLink()) {
+ return content;
+ }
+ if (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
+ clickableRoles, eIgnoreCase) >= 0) {
+ return content;
+ }
+ }
+ if (content->IsEditable()) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nscoord AppUnitsFromMM(RelativeTo aFrame, uint32_t aMM) {
+ nsPresContext* pc = aFrame.mFrame->PresContext();
+ float result = float(aMM) * (pc->DeviceContext()->AppUnitsPerPhysicalInch() /
+ MM_PER_INCH_FLOAT);
+ if (aFrame.mViewportType == ViewportType::Layout) {
+ PresShell* presShell = pc->PresShell();
+ result = result / presShell->GetResolution();
+ }
+ return NSToCoordRound(result);
+}
+
+/**
+ * Clip aRect with the bounds of aFrame in the coordinate system of
+ * aRootFrame. aRootFrame is an ancestor of aFrame.
+ */
+static nsRect ClipToFrame(RelativeTo aRootFrame, const nsIFrame* aFrame,
+ nsRect& aRect) {
+ nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
+ nsRect result = bound.Intersect(aRect);
+ return result;
+}
+
+static nsRect GetTargetRect(RelativeTo aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame,
+ const nsIFrame* aRestrictToDescendants,
+ const EventRadiusPrefs& aPrefs, uint32_t aFlags) {
+ nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs.mRadiusTopmm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusRightmm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusBottommm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusLeftmm));
+ nsRect r(aPointRelativeToRootFrame, nsSize(0, 0));
+ r.Inflate(m);
+ if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
+ // Don't clip this rect to the root scroll frame if the flag to ignore the
+ // root scroll frame is set. Note that the GetClosest code will still
+ // enforce that the target found is a descendant of aRestrictToDescendants.
+ r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
+ }
+ return r;
+}
+
+static float ComputeDistanceFromRect(const nsPoint& aPoint,
+ const nsRect& aRect) {
+ nscoord dx =
+ std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
+ nscoord dy =
+ std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
+ return float(NS_hypot(dx, dy));
+}
+
+static float ComputeDistanceFromRegion(const nsPoint& aPoint,
+ const nsRegion& aRegion) {
+ MOZ_ASSERT(!aRegion.IsEmpty(),
+ "can't compute distance between point and empty region");
+ float minDist = -1;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ float dist = ComputeDistanceFromRect(aPoint, iter.Get());
+ if (dist < minDist || minDist < 0) {
+ minDist = dist;
+ }
+ }
+ return minDist;
+}
+
+// Subtract aRegion from aExposedRegion as long as that doesn't make the
+// exposed region get too complex or removes a big chunk of the exposed region.
+static void SubtractFromExposedRegion(nsRegion* aExposedRegion,
+ const nsRegion& aRegion) {
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+
+ nsRegion tmp;
+ tmp.Sub(*aExposedRegion, aRegion);
+ // Don't let *aExposedRegion get too complex, but don't let it fluff out to
+ // its bounds either. Do let aExposedRegion get more complex if by doing so
+ // we reduce its area by at least half.
+ if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area() / 2) {
+ *aExposedRegion = tmp;
+ }
+}
+
+static nsIFrame* GetClosest(RelativeTo aRoot,
+ const nsPoint& aPointRelativeToRootFrame,
+ const nsRect& aTargetRect,
+ const EventRadiusPrefs& aPrefs,
+ const nsIFrame* aRestrictToDescendants,
+ nsIContent* aClickableAncestor,
+ nsTArray<nsIFrame*>& aCandidates) {
+ nsIFrame* bestTarget = nullptr;
+ // Lower is better; distance is in appunits
+ float bestDistance = 1e6f;
+ nsRegion exposedRegion(aTargetRect);
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ nsIFrame* f = aCandidates[i];
+
+ bool preservesAxisAlignedRectangles = false;
+ nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
+ f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot,
+ &preservesAxisAlignedRectangles);
+ PET_LOG("Checking candidate %p with border box %s\n", f,
+ ToString(borderBox).c_str());
+ nsRegion region;
+ region.And(exposedRegion, borderBox);
+ if (region.IsEmpty()) {
+ PET_LOG(" candidate %p had empty hit region\n", f);
+ continue;
+ }
+
+ if (preservesAxisAlignedRectangles) {
+ // Subtract from the exposed region if we have a transform that won't make
+ // the bounds include a bunch of area that we don't actually cover.
+ SubtractFromExposedRegion(&exposedRegion, region);
+ }
+
+ nsAutoString labelTargetId;
+ if (aClickableAncestor &&
+ !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
+ PET_LOG(" candidate %p is not a descendant of required ancestor\n", f);
+ continue;
+ }
+
+ if (aPrefs.mSearchType == SearchType::Clickable) {
+ nsIContent* clickableContent =
+ GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
+ if (!aClickableAncestor && !clickableContent) {
+ PET_LOG(" candidate %p was not clickable\n", f);
+ continue;
+ }
+ } else if (aPrefs.mSearchType == SearchType::Touchable) {
+ nsIContent* touchableContent = GetTouchableAncestor(f, nsGkAtoms::body);
+ if (!touchableContent) {
+ PET_LOG(" candidate %p was not touchable\n", f);
+ continue;
+ }
+ }
+
+ // If our current closest frame is a descendant of 'f', skip 'f' (prefer
+ // the nested frame).
+ if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(
+ f, bestTarget, aRoot.mFrame)) {
+ PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
+ continue;
+ }
+ if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(
+ aRestrictToDescendants, f, aRoot.mFrame)) {
+ PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f,
+ aRestrictToDescendants);
+ continue;
+ }
+
+ // distance is in appunits
+ float distance =
+ ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
+ nsIContent* content = f->GetContent();
+ if (content && content->IsElement() &&
+ content->AsElement()->State().HasState(
+ ElementState(ElementState::VISITED))) {
+ distance *= aPrefs.mVisitedWeight / 100.0f;
+ }
+ if (distance < bestDistance) {
+ PET_LOG(" candidate %p is the new best\n", f);
+ bestDistance = distance;
+ bestTarget = f;
+ }
+ }
+ return bestTarget;
+}
+
+// Walk from aTarget up to aRoot, and return the first frame found with an
+// explicit z-index set on it. If no such frame is found, aRoot is returned.
+static const nsIFrame* FindZIndexAncestor(const nsIFrame* aTarget,
+ const nsIFrame* aRoot) {
+ const nsIFrame* candidate = aTarget;
+ while (candidate && candidate != aRoot) {
+ if (candidate->ZIndex().valueOr(0) > 0) {
+ PET_LOG("Restricting search to z-index root %p\n", candidate);
+ return candidate;
+ }
+ candidate = candidate->GetParent();
+ }
+ return aRoot;
+}
+
+nsIFrame* FindFrameTargetedByInputEvent(
+ WidgetGUIEvent* aEvent, RelativeTo aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) {
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+ EnumSet<FrameForPointOption> options;
+ if (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) {
+ options += FrameForPointOption::IgnoreRootScrollFrame;
+ }
+ nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
+ aRootFrame, aPointRelativeToRootFrame, options);
+ PET_LOG(
+ "Found initial target %p for event class %s message %s point %s "
+ "relative to root frame %s\n",
+ target, ToChar(aEvent->mClass), ToChar(aEvent->mMessage),
+ ToString(aPointRelativeToRootFrame).c_str(),
+ ToString(aRootFrame).c_str());
+
+ EventRadiusPrefs prefs(aEvent->mClass);
+ if (!prefs.mEnabled || EventRetargetSuppression::IsActive()) {
+ PET_LOG("Retargeting disabled\n");
+ return target;
+ }
+
+ // Do not modify targeting for actual mouse hardware; only for mouse
+ // events generated by touch-screen hardware.
+ if (aEvent->mClass == eMouseEventClass && prefs.mTouchOnly &&
+ aEvent->AsMouseEvent()->mInputSource !=
+ MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ PET_LOG("Mouse input event is not from a touch source\n");
+ return target;
+ }
+
+ // If the exact target is non-null, only consider candidate targets in the
+ // same document as the exact target. Otherwise, if an ancestor document has
+ // a mouse event handler for example, targets that are !GetClickableAncestor
+ // can never be targeted --- something nsSubDocumentFrame in an ancestor
+ // document would be targeted instead.
+ const nsIFrame* restrictToDescendants = [&]() -> const nsIFrame* {
+ if (target && target->PresContext() != aRootFrame.mFrame->PresContext()) {
+ return target->PresShell()->GetRootFrame();
+ }
+ return aRootFrame.mFrame;
+ }();
+
+ // If the target element inside an element with a z-index, restrict the
+ // search to other elements inside that z-index. This is a heuristic
+ // intended to help with a class of scenarios involving web modals or
+ // web popup type things. In particular it helps alleviate bug 1666792.
+ restrictToDescendants = FindZIndexAncestor(target, restrictToDescendants);
+
+ nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
+ restrictToDescendants, prefs, aFlags);
+ PET_LOG("Expanded point to target rect %s\n", ToString(targetRect).c_str());
+ AutoTArray<nsIFrame*, 8> candidates;
+ nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect,
+ candidates, options);
+ if (NS_FAILED(rv)) {
+ return target;
+ }
+
+ nsIContent* clickableAncestor = nullptr;
+ if (target) {
+ clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
+ if (clickableAncestor) {
+ PET_LOG("Target %p is clickable\n", target);
+ // If the target that was directly hit has a clickable ancestor, that
+ // means it too is clickable. And since it is the same as or a
+ // descendant of clickableAncestor, it should become the root for the
+ // GetClosest search.
+ clickableAncestor = target->GetContent();
+ }
+ }
+
+ nsIFrame* closest =
+ GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
+ restrictToDescendants, clickableAncestor, candidates);
+ if (closest) {
+ target = closest;
+ }
+
+ PET_LOG("Final target is %p\n", target);
+
+#ifdef DEBUG_FRAME_DUMP
+ // At verbose logging level, dump the frame tree to help with debugging.
+ // Note that dumping the frame tree at the top of the function may flood
+ // logcat on Android devices and cause the PET_LOGs to get dropped.
+ if (MOZ_LOG_TEST(sEvtTgtLog, LogLevel::Verbose)) {
+ if (target) {
+ target->DumpFrameTree();
+ } else {
+ aRootFrame.mFrame->DumpFrameTree();
+ }
+ }
+#endif
+
+ if (!target || !prefs.mReposition) {
+ // No repositioning required for this event
+ return target;
+ }
+
+ // Take the point relative to the root frame, make it relative to the target,
+ // clamp it to the bounds, and then make it relative to the root frame again.
+ nsPoint point = aPointRelativeToRootFrame;
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
+ nsLayoutUtils::TransformPoint(aRootFrame, RelativeTo{target}, point)) {
+ return target;
+ }
+ point = target->GetRectRelativeToSelf().ClampPoint(point);
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
+ nsLayoutUtils::TransformPoint(RelativeTo{target}, aRootFrame, point)) {
+ return target;
+ }
+ // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
+ // get back the (now-clamped) coordinates in the event's widget's space.
+ nsView* view = aRootFrame.mFrame->GetView();
+ if (!view) {
+ return target;
+ }
+ LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
+ aRootFrame.mFrame->PresContext(), view, point, aRootFrame.mViewportType,
+ aEvent->mWidget);
+ if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
+ // If that succeeded, we update the point in the event
+ aEvent->mRefPoint = widgetPoint;
+ }
+ return target;
+}
+
+uint32_t EventRetargetSuppression::sSuppressionCount = 0;
+
+EventRetargetSuppression::EventRetargetSuppression() { sSuppressionCount++; }
+
+EventRetargetSuppression::~EventRetargetSuppression() { sSuppressionCount--; }
+
+bool EventRetargetSuppression::IsActive() { return sSuppressionCount > 0; }
+
+} // namespace mozilla