summaryrefslogtreecommitdiffstats
path: root/layout/base/PositionedEventTargeting.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/PositionedEventTargeting.cpp')
-rw-r--r--layout/base/PositionedEventTargeting.cpp656
1 files changed, 656 insertions, 0 deletions
diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp
new file mode 100644
index 000000000..8374ab9d2
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -0,0 +1,656 @@
+/* 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/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.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 "nsIFrame.h"
+#include <algorithm>
+#include "LayersLogging.h"
+
+// If debugging this code you may wish to enable this logging, and also
+// uncomment the DumpFrameTree call near the bottom of the file.
+#define PET_LOG(...)
+// #define PET_LOG(...) printf_stderr("PET: " __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.
+ */
+
+struct EventRadiusPrefs
+{
+ uint32_t mVisitedWeight; // in percent, i.e. default is 100
+ uint32_t mSideRadii[4]; // TRBL order, in millimetres
+ bool mEnabled;
+ bool mRegistered;
+ bool mTouchOnly;
+ bool mRepositionEventCoords;
+ bool mTouchClusterDetectionEnabled;
+ bool mSimplifiedClusterDetection;
+ uint32_t mLimitReadableSize;
+ uint32_t mKeepLimitSizeForCluster;
+};
+
+static EventRadiusPrefs sMouseEventRadiusPrefs;
+static EventRadiusPrefs sTouchEventRadiusPrefs;
+
+static const EventRadiusPrefs*
+GetPrefsFor(EventClassID aEventClassID)
+{
+ EventRadiusPrefs* prefs = nullptr;
+ const char* prefBranch = nullptr;
+ if (aEventClassID == eTouchEventClass) {
+ prefBranch = "touch";
+ prefs = &sTouchEventRadiusPrefs;
+ } else if (aEventClassID == eMouseEventClass) {
+ // Mostly for testing purposes
+ prefBranch = "mouse";
+ prefs = &sMouseEventRadiusPrefs;
+ } else {
+ return nullptr;
+ }
+
+ if (!prefs->mRegistered) {
+ prefs->mRegistered = true;
+
+ nsPrintfCString enabledPref("ui.%s.radius.enabled", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mEnabled, enabledPref.get(), false);
+
+ nsPrintfCString visitedWeightPref("ui.%s.radius.visitedWeight", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mVisitedWeight, visitedWeightPref.get(), 100);
+
+ static const char prefNames[4][9] =
+ { "topmm", "rightmm", "bottommm", "leftmm" };
+ for (int32_t i = 0; i < 4; ++i) {
+ nsPrintfCString radiusPref("ui.%s.radius.%s", prefBranch, prefNames[i]);
+ Preferences::AddUintVarCache(&prefs->mSideRadii[i], radiusPref.get(), 0);
+ }
+
+ if (aEventClassID == eMouseEventClass) {
+ Preferences::AddBoolVarCache(&prefs->mTouchOnly,
+ "ui.mouse.radius.inputSource.touchOnly", true);
+ } else {
+ prefs->mTouchOnly = false;
+ }
+
+ nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
+
+ nsPrintfCString touchClusterPref("ui.zoomedview.enabled", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionEnabled, touchClusterPref.get(), false);
+
+ nsPrintfCString simplifiedClusterDetectionPref("ui.zoomedview.simplified", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mSimplifiedClusterDetection, simplifiedClusterDetectionPref.get(), false);
+
+ nsPrintfCString limitReadableSizePref("ui.zoomedview.limitReadableSize", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mLimitReadableSize, limitReadableSizePref.get(), 8);
+
+ nsPrintfCString keepLimitSize("ui.zoomedview.keepLimitSize", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mKeepLimitSizeForCluster, keepLimitSize.get(), 16);
+ }
+
+ return prefs;
+}
+
+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 gTouchEventsRegistered = false;
+static int32_t gTouchEventsEnabled = 0;
+
+static bool
+HasTouchListener(nsIContent* aContent)
+{
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ if (!gTouchEventsRegistered) {
+ Preferences::AddIntVarCache(&gTouchEventsEnabled,
+ "dom.w3c_touch_events.enabled", gTouchEventsEnabled);
+ gTouchEventsRegistered = true;
+ }
+
+ if (!gTouchEventsEnabled) {
+ return false;
+ }
+
+ return elm->HasListenersFor(nsGkAtoms::ontouchstart) ||
+ elm->HasListenersFor(nsGkAtoms::ontouchend);
+}
+
+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->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
+ }
+ if (content == aAncestor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static nsIContent*
+GetClickableAncestor(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
+{
+ // 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 (stopAt && content->IsHTMLElement(stopAt)) {
+ break;
+ }
+ if (HasTouchListener(content) || HasMouseListener(content)) {
+ return content;
+ }
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::button,
+ nsGkAtoms::input,
+ nsGkAtoms::select,
+ nsGkAtoms::textarea)) {
+ return content;
+ }
+ if (content->IsHTMLElement(nsGkAtoms::label)) {
+ if (aLabelTargetId) {
+ content->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->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser,
+ nsGkAtoms::_true, eIgnoreCase) &&
+ content->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::autorepeatbutton,
+ nsGkAtoms::menu,
+ nsGkAtoms::menubutton,
+ nsGkAtoms::menuitem,
+ nsGkAtoms::menulist,
+ nsGkAtoms::scrollbarbutton,
+ nsGkAtoms::resizer)) {
+ return content;
+ }
+
+ static nsIContent::AttrValuesArray clickableRoles[] =
+ { &nsGkAtoms::button, &nsGkAtoms::key, nullptr };
+ if (content->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
+ clickableRoles, eIgnoreCase) >= 0) {
+ return content;
+ }
+ if (content->IsEditable()) {
+ return content;
+ }
+ nsCOMPtr<nsIURI> linkURI;
+ if (content->IsLink(getter_AddRefs(linkURI))) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nscoord
+AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM)
+{
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float result = float(aMM) *
+ (pc->DeviceContext()->AppUnitsPerPhysicalInch() / MM_PER_INCH_FLOAT);
+ if (presShell->ScaleToResolution()) {
+ 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(nsIFrame* aRootFrame, 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(nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame,
+ nsIFrame* aRestrictToDescendants, const EventRadiusPrefs* aPrefs,
+ uint32_t aFlags)
+{
+ nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[0]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[1]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[2]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[3]));
+ 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;
+ }
+}
+
+// Search in the list of frames aCandidates if the element with the id "aLabelTargetId"
+// is present.
+static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates, const nsAutoString& aLabelTargetId)
+{
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ nsIFrame* f = aCandidates[i];
+ nsIContent* aContent = f->GetContent();
+ if (aContent && aContent->IsElement()) {
+ if (aContent->GetID() && aLabelTargetId == nsAtomString(aContent->GetID())) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool
+IsLargeElement(nsIFrame* aFrame, const EventRadiusPrefs* aPrefs)
+{
+ uint32_t keepLimitSizeForCluster = aPrefs->mKeepLimitSizeForCluster;
+ nsSize frameSize = aFrame->GetSize();
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float cumulativeResolution = presShell->GetCumulativeResolution();
+ if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) > keepLimitSizeForCluster &&
+ (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) > keepLimitSizeForCluster) {
+ return true;
+ }
+ return false;
+}
+
+static nsIFrame*
+GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
+ const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
+ nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
+ nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster)
+{
+ std::vector<nsIContent*> mContentsInCluster; // List of content elements in the cluster without duplicate
+ 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];
+ PET_LOG("Checking candidate %p\n", f);
+
+ bool preservesAxisAlignedRectangles = false;
+ nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f,
+ nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles);
+ 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;
+ }
+
+ nsIContent* clickableContent = GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
+ if (!aClickableAncestor && !clickableContent) {
+ PET_LOG(" candidate %p was not clickable\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)) {
+ PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
+ continue;
+ }
+ if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) {
+ PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f, aRestrictToDescendants);
+ continue;
+ }
+
+ // If the first clickable ancestor of f is a label element
+ // and "for" attribute is present in label element, search the frame list for the "for" element
+ // If this element is present in the current list, do not count the frame in
+ // the cluster elements counter
+ if ((labelTargetId.IsEmpty() || !IsElementPresent(aCandidates, labelTargetId)) &&
+ !IsLargeElement(f, aPrefs)) {
+ if (std::find(mContentsInCluster.begin(), mContentsInCluster.end(), clickableContent) == mContentsInCluster.end()) {
+ mContentsInCluster.push_back(clickableContent);
+ }
+ }
+
+ // distance is in appunits
+ float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
+ nsIContent* content = f->GetContent();
+ if (content && content->IsElement() &&
+ content->AsElement()->State().HasState(
+ EventStates(NS_EVENT_STATE_VISITED))) {
+ distance *= aPrefs->mVisitedWeight / 100.0f;
+ }
+ if (distance < bestDistance) {
+ PET_LOG(" candidate %p is the new best\n", f);
+ bestDistance = distance;
+ bestTarget = f;
+ }
+ }
+ *aElementsInCluster = mContentsInCluster.size();
+ return bestTarget;
+}
+
+/*
+ * Return always true when touch cluster detection is OFF.
+ * When cluster detection is ON, return true:
+ * if the text inside the frame is readable (by human eyes)
+ * or
+ * if the structure is too complex to determine the size.
+ * In both cases, the frame is considered as clickable.
+ *
+ * Frames with a too small size will return false.
+ * In this case, the frame is considered not clickable.
+ */
+static bool
+IsElementClickableAndReadable(nsIFrame* aFrame, WidgetGUIEvent* aEvent, const EventRadiusPrefs* aPrefs)
+{
+ if (!aPrefs->mTouchClusterDetectionEnabled) {
+ return true;
+ }
+
+ if (aPrefs->mSimplifiedClusterDetection) {
+ return true;
+ }
+
+ if (aEvent->mClass != eMouseEventClass) {
+ return true;
+ }
+
+ uint32_t limitReadableSize = aPrefs->mLimitReadableSize;
+ nsSize frameSize = aFrame->GetSize();
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float cumulativeResolution = presShell->GetCumulativeResolution();
+ if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) < limitReadableSize ||
+ (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) < limitReadableSize) {
+ return false;
+ }
+ // We want to detect small clickable text elements using the font size.
+ // Two common cases are supported for now:
+ // 1. text node
+ // 2. any element with only one child of type text node
+ // All the other cases are currently ignored.
+ nsIContent *content = aFrame->GetContent();
+ bool testFontSize = false;
+ if (content) {
+ nsINodeList* childNodes = content->ChildNodes();
+ uint32_t childNodeCount = childNodes->Length();
+ if ((content->IsNodeOfType(nsINode::eTEXT)) ||
+ // click occurs on the text inside <a></a> or other clickable tags with text inside
+
+ (childNodeCount == 1 && childNodes->Item(0) &&
+ childNodes->Item(0)->IsNodeOfType(nsINode::eTEXT))) {
+ // The click occurs on an element with only one text node child. In this case, the font size
+ // can be tested.
+ // The number of child nodes is tested to avoid the following cases (See bug 1172488):
+ // Some jscript libraries transform text elements into Canvas elements but keep the text nodes
+ // with a very small size (1px) to handle the selection of text.
+ // With such libraries, the font size of the text elements is not relevant to detect small elements.
+
+ testFontSize = true;
+ }
+ }
+
+ if (testFontSize) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ if (fm && fm->EmHeight() > 0 && // See bug 1171731
+ (pc->AppUnitsToGfxUnits(fm->EmHeight()) * cumulativeResolution) < limitReadableSize) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsIFrame*
+FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
+ nsIFrame* aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame,
+ uint32_t aFlags)
+{
+ uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ?
+ nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0;
+ nsIFrame* target =
+ nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags);
+ PET_LOG("Found initial target %p for event class %s point %s relative to root frame %p\n",
+ target, (aEvent->mClass == eMouseEventClass ? "mouse" :
+ (aEvent->mClass == eTouchEventClass ? "touch" : "other")),
+ mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(), aRootFrame);
+
+ const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass);
+ if (!prefs || !prefs->mEnabled) {
+ PET_LOG("Retargeting disabled\n");
+ return target;
+ }
+ nsIContent* clickableAncestor = nullptr;
+ if (target) {
+ clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
+ if (clickableAncestor) {
+ if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
+ aEvent->AsMouseEventBase()->hitCluster = true;
+ }
+ 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();
+ }
+ }
+
+ // 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()->inputSource !=
+ nsIDOMMouseEvent::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.
+ nsIFrame* restrictToDescendants = target ?
+ target->PresContext()->PresShell()->GetRootFrame() : aRootFrame;
+
+ nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
+ restrictToDescendants, prefs, aFlags);
+ PET_LOG("Expanded point to target rect %s\n",
+ mozilla::layers::Stringify(targetRect).c_str());
+ AutoTArray<nsIFrame*,8> candidates;
+ nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags);
+ if (NS_FAILED(rv)) {
+ return target;
+ }
+
+ int32_t elementsInCluster = 0;
+
+ nsIFrame* closestClickable =
+ GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
+ restrictToDescendants, clickableAncestor, candidates,
+ &elementsInCluster);
+ if (closestClickable) {
+ if ((prefs->mTouchClusterDetectionEnabled && elementsInCluster > 1) ||
+ (!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {
+ if (aEvent->mClass == eMouseEventClass) {
+ WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();
+ mouseEventBase->hitCluster = true;
+ }
+ }
+ target = closestClickable;
+ }
+ PET_LOG("Final target is %p\n", target);
+
+ // Uncomment this to 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.
+ // aRootFrame->DumpFrameTree();
+
+ if (!target || !prefs->mRepositionEventCoords) {
+ // 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, target, point)) {
+ return target;
+ }
+ point = target->GetRectRelativeToSelf().ClampPoint(point);
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(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->GetView();
+ if (!view) {
+ return target;
+ }
+ LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
+ aRootFrame->PresContext(), view, point, aEvent->mWidget);
+ if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
+ // If that succeeded, we update the point in the event
+ aEvent->mRefPoint = widgetPoint;
+ }
+ return target;
+}
+
+} // namespace mozilla