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 "PositionedEventTargeting.h"
8 
9 #include "mozilla/EventListenerManager.h"
10 #include "mozilla/EventStates.h"
11 #include "mozilla/MouseEvents.h"
12 #include "mozilla/Preferences.h"
13 #include "nsLayoutUtils.h"
14 #include "nsGkAtoms.h"
15 #include "nsFontMetrics.h"
16 #include "nsPrintfCString.h"
17 #include "mozilla/dom/Element.h"
18 #include "nsRegion.h"
19 #include "nsDeviceContext.h"
20 #include "nsIContentInlines.h"
21 #include "nsIFrame.h"
22 #include <algorithm>
23 #include "LayersLogging.h"
24 
25 // If debugging this code you may wish to enable this logging, and also
26 // uncomment the DumpFrameTree call near the bottom of the file.
27 #define PET_LOG(...)
28 // #define PET_LOG(...) printf_stderr("PET: " __VA_ARGS__);
29 
30 namespace mozilla {
31 
32 /*
33  * The basic goal of FindFrameTargetedByInputEvent() is to find a good
34  * target element that can respond to mouse events. Both mouse events and touch
35  * events are targeted at this element. Note that even for touch events, we
36  * check responsiveness to mouse events. We assume Web authors
37  * designing for touch events will take their own steps to account for
38  * inaccurate touch events.
39  *
40  * GetClickableAncestor() encapsulates the heuristic that determines whether an
41  * element is expected to respond to mouse events. An element is deemed
42  * "clickable" if it has registered listeners for "click", "mousedown" or
43  * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
44  * <select>, <textarea>, <label>), or has role="button", or is a link, or
45  * is a suitable XUL element.
46  * Any descendant (in the same document) of a clickable element is also
47  * deemed clickable since events will propagate to the clickable element from
48  * its descendant.
49  *
50  * If the element directly under the event position is clickable (or
51  * event radii are disabled), we always use that element. Otherwise we collect
52  * all frames intersecting a rectangle around the event position (taking CSS
53  * transforms into account) and choose the best candidate in GetClosest().
54  * Only GetClickableAncestor() candidates are considered; if none are found,
55  * then we revert to targeting the element under the event position.
56  * We ignore candidates outside the document subtree rooted by the
57  * document of the element directly under the event position. This ensures that
58  * event listeners in ancestor documents don't make it completely impossible
59  * to target a non-clickable element in a child document.
60  *
61  * When both a frame and its ancestor are in the candidate list, we ignore
62  * the ancestor. Otherwise a large ancestor element with a mouse event listener
63  * and some descendant elements that need to be individually targetable would
64  * disable intelligent targeting of those descendants within its bounds.
65  *
66  * GetClosest() computes the transformed axis-aligned bounds of each
67  * candidate frame, then computes the Manhattan distance from the event point
68  * to the bounds rect (which can be zero). The frame with the
69  * shortest distance is chosen. For visited links we multiply the distance
70  * by a specified constant weight; this can be used to make visited links
71  * more or less likely to be targeted than non-visited links.
72  */
73 
74 struct EventRadiusPrefs {
75   uint32_t mVisitedWeight;  // in percent, i.e. default is 100
76   uint32_t mSideRadii[4];   // TRBL order, in millimetres
77   bool mEnabled;
78   bool mRegistered;
79   bool mTouchOnly;
80   bool mRepositionEventCoords;
81   bool mTouchClusterDetectionEnabled;
82   bool mSimplifiedClusterDetection;
83   uint32_t mLimitReadableSize;
84   uint32_t mKeepLimitSizeForCluster;
85 };
86 
87 static EventRadiusPrefs sMouseEventRadiusPrefs;
88 static EventRadiusPrefs sTouchEventRadiusPrefs;
89 
GetPrefsFor(EventClassID aEventClassID)90 static const EventRadiusPrefs* GetPrefsFor(EventClassID aEventClassID) {
91   EventRadiusPrefs* prefs = nullptr;
92   const char* prefBranch = nullptr;
93   if (aEventClassID == eTouchEventClass) {
94     prefBranch = "touch";
95     prefs = &sTouchEventRadiusPrefs;
96   } else if (aEventClassID == eMouseEventClass) {
97     // Mostly for testing purposes
98     prefBranch = "mouse";
99     prefs = &sMouseEventRadiusPrefs;
100   } else {
101     return nullptr;
102   }
103 
104   if (!prefs->mRegistered) {
105     prefs->mRegistered = true;
106 
107     nsPrintfCString enabledPref("ui.%s.radius.enabled", prefBranch);
108     Preferences::AddBoolVarCache(&prefs->mEnabled, enabledPref.get(), false);
109 
110     nsPrintfCString visitedWeightPref("ui.%s.radius.visitedWeight", prefBranch);
111     Preferences::AddUintVarCache(&prefs->mVisitedWeight,
112                                  visitedWeightPref.get(), 100);
113 
114     static const char prefNames[4][9] = {"topmm", "rightmm", "bottommm",
115                                          "leftmm"};
116     for (int32_t i = 0; i < 4; ++i) {
117       nsPrintfCString radiusPref("ui.%s.radius.%s", prefBranch, prefNames[i]);
118       Preferences::AddUintVarCache(&prefs->mSideRadii[i], radiusPref.get(), 0);
119     }
120 
121     if (aEventClassID == eMouseEventClass) {
122       Preferences::AddBoolVarCache(
123           &prefs->mTouchOnly, "ui.mouse.radius.inputSource.touchOnly", true);
124     } else {
125       prefs->mTouchOnly = false;
126     }
127 
128     nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
129     Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords,
130                                  repositionPref.get(), false);
131 
132     // These values were formerly set by ui.zoomedview preferences.
133     prefs->mTouchClusterDetectionEnabled = false;
134     prefs->mSimplifiedClusterDetection = false;
135     prefs->mLimitReadableSize = 8;
136     prefs->mKeepLimitSizeForCluster = 16;
137   }
138 
139   return prefs;
140 }
141 
HasMouseListener(nsIContent * aContent)142 static bool HasMouseListener(nsIContent* aContent) {
143   if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
144     return elm->HasListenersFor(nsGkAtoms::onclick) ||
145            elm->HasListenersFor(nsGkAtoms::onmousedown) ||
146            elm->HasListenersFor(nsGkAtoms::onmouseup);
147   }
148 
149   return false;
150 }
151 
152 static bool gTouchEventsRegistered = false;
153 static int32_t gTouchEventsEnabled = 0;
154 
HasTouchListener(nsIContent * aContent)155 static bool HasTouchListener(nsIContent* aContent) {
156   EventListenerManager* elm = aContent->GetExistingListenerManager();
157   if (!elm) {
158     return false;
159   }
160 
161   if (!gTouchEventsRegistered) {
162     Preferences::AddIntVarCache(&gTouchEventsEnabled,
163                                 "dom.w3c_touch_events.enabled",
164                                 gTouchEventsEnabled);
165     gTouchEventsRegistered = true;
166   }
167 
168   if (!gTouchEventsEnabled) {
169     return false;
170   }
171 
172   return elm->HasListenersFor(nsGkAtoms::ontouchstart) ||
173          elm->HasListenersFor(nsGkAtoms::ontouchend);
174 }
175 
IsDescendant(nsIFrame * aFrame,nsIContent * aAncestor,nsAutoString * aLabelTargetId)176 static bool IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor,
177                          nsAutoString* aLabelTargetId) {
178   for (nsIContent* content = aFrame->GetContent(); content;
179        content = content->GetFlattenedTreeParent()) {
180     if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
181       content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
182                                     *aLabelTargetId);
183     }
184     if (content == aAncestor) {
185       return true;
186     }
187   }
188   return false;
189 }
190 
GetClickableAncestor(nsIFrame * aFrame,nsAtom * stopAt=nullptr,nsAutoString * aLabelTargetId=nullptr)191 static nsIContent* GetClickableAncestor(
192     nsIFrame* aFrame, nsAtom* stopAt = nullptr,
193     nsAutoString* aLabelTargetId = nullptr) {
194   // Input events propagate up the content tree so we'll follow the content
195   // ancestors to look for elements accepting the click.
196   for (nsIContent* content = aFrame->GetContent(); content;
197        content = content->GetFlattenedTreeParent()) {
198     if (stopAt && content->IsHTMLElement(stopAt)) {
199       break;
200     }
201     if (HasTouchListener(content) || HasMouseListener(content)) {
202       return content;
203     }
204     if (content->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input,
205                                      nsGkAtoms::select, nsGkAtoms::textarea)) {
206       return content;
207     }
208     if (content->IsHTMLElement(nsGkAtoms::label)) {
209       if (aLabelTargetId) {
210         content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
211                                       *aLabelTargetId);
212       }
213       return content;
214     }
215 
216     // Bug 921928: we don't have access to the content of remote iframe.
217     // So fluffing won't go there. We do an optimistic assumption here:
218     // that the content of the remote iframe needs to be a target.
219     if (content->IsHTMLElement(nsGkAtoms::iframe) &&
220         content->AsElement()->AttrValueIs(kNameSpaceID_None,
221                                           nsGkAtoms::mozbrowser,
222                                           nsGkAtoms::_true, eIgnoreCase) &&
223         content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
224                                           nsGkAtoms::_true, eIgnoreCase)) {
225       return content;
226     }
227 
228     // See nsCSSFrameConstructor::FindXULTagData. This code is not
229     // really intended to be used with XUL, though.
230     if (content->IsAnyOfXULElements(
231             nsGkAtoms::button, nsGkAtoms::checkbox, nsGkAtoms::radio,
232             nsGkAtoms::autorepeatbutton, nsGkAtoms::menu, nsGkAtoms::menubutton,
233             nsGkAtoms::menuitem, nsGkAtoms::menulist,
234             nsGkAtoms::scrollbarbutton, nsGkAtoms::resizer)) {
235       return content;
236     }
237 
238     static Element::AttrValuesArray clickableRoles[] = {
239         &nsGkAtoms::button, &nsGkAtoms::key, nullptr};
240     if (content->IsElement() && content->AsElement()->FindAttrValueIn(
241                                     kNameSpaceID_None, nsGkAtoms::role,
242                                     clickableRoles, eIgnoreCase) >= 0) {
243       return content;
244     }
245     if (content->IsEditable()) {
246       return content;
247     }
248     nsCOMPtr<nsIURI> linkURI;
249     if (content->IsLink(getter_AddRefs(linkURI))) {
250       return content;
251     }
252   }
253   return nullptr;
254 }
255 
AppUnitsFromMM(nsIFrame * aFrame,uint32_t aMM)256 static nscoord AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM) {
257   nsPresContext* pc = aFrame->PresContext();
258   nsIPresShell* presShell = pc->PresShell();
259   float result = float(aMM) * (pc->DeviceContext()->AppUnitsPerPhysicalInch() /
260                                MM_PER_INCH_FLOAT);
261   if (presShell->ScaleToResolution()) {
262     result = result / presShell->GetResolution();
263   }
264   return NSToCoordRound(result);
265 }
266 
267 /**
268  * Clip aRect with the bounds of aFrame in the coordinate system of
269  * aRootFrame. aRootFrame is an ancestor of aFrame.
270  */
ClipToFrame(nsIFrame * aRootFrame,nsIFrame * aFrame,nsRect & aRect)271 static nsRect ClipToFrame(nsIFrame* aRootFrame, nsIFrame* aFrame,
272                           nsRect& aRect) {
273   nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
274       aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
275   nsRect result = bound.Intersect(aRect);
276   return result;
277 }
278 
GetTargetRect(nsIFrame * aRootFrame,const nsPoint & aPointRelativeToRootFrame,nsIFrame * aRestrictToDescendants,const EventRadiusPrefs * aPrefs,uint32_t aFlags)279 static nsRect GetTargetRect(nsIFrame* aRootFrame,
280                             const nsPoint& aPointRelativeToRootFrame,
281                             nsIFrame* aRestrictToDescendants,
282                             const EventRadiusPrefs* aPrefs, uint32_t aFlags) {
283   nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[0]),
284              AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[1]),
285              AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[2]),
286              AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[3]));
287   nsRect r(aPointRelativeToRootFrame, nsSize(0, 0));
288   r.Inflate(m);
289   if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
290     // Don't clip this rect to the root scroll frame if the flag to ignore the
291     // root scroll frame is set. Note that the GetClosest code will still
292     // enforce that the target found is a descendant of aRestrictToDescendants.
293     r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
294   }
295   return r;
296 }
297 
ComputeDistanceFromRect(const nsPoint & aPoint,const nsRect & aRect)298 static float ComputeDistanceFromRect(const nsPoint& aPoint,
299                                      const nsRect& aRect) {
300   nscoord dx =
301       std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
302   nscoord dy =
303       std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
304   return float(NS_hypot(dx, dy));
305 }
306 
ComputeDistanceFromRegion(const nsPoint & aPoint,const nsRegion & aRegion)307 static float ComputeDistanceFromRegion(const nsPoint& aPoint,
308                                        const nsRegion& aRegion) {
309   MOZ_ASSERT(!aRegion.IsEmpty(),
310              "can't compute distance between point and empty region");
311   float minDist = -1;
312   for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
313     float dist = ComputeDistanceFromRect(aPoint, iter.Get());
314     if (dist < minDist || minDist < 0) {
315       minDist = dist;
316     }
317   }
318   return minDist;
319 }
320 
321 // Subtract aRegion from aExposedRegion as long as that doesn't make the
322 // exposed region get too complex or removes a big chunk of the exposed region.
SubtractFromExposedRegion(nsRegion * aExposedRegion,const nsRegion & aRegion)323 static void SubtractFromExposedRegion(nsRegion* aExposedRegion,
324                                       const nsRegion& aRegion) {
325   if (aRegion.IsEmpty()) return;
326 
327   nsRegion tmp;
328   tmp.Sub(*aExposedRegion, aRegion);
329   // Don't let *aExposedRegion get too complex, but don't let it fluff out to
330   // its bounds either. Do let aExposedRegion get more complex if by doing so
331   // we reduce its area by at least half.
332   if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area() / 2) {
333     *aExposedRegion = tmp;
334   }
335 }
336 
337 // Search in the list of frames aCandidates if the element with the id
338 // "aLabelTargetId" is present.
IsElementPresent(nsTArray<nsIFrame * > & aCandidates,const nsAutoString & aLabelTargetId)339 static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates,
340                              const nsAutoString& aLabelTargetId) {
341   for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
342     nsIFrame* f = aCandidates[i];
343     nsIContent* aContent = f->GetContent();
344     if (aContent && aContent->IsElement()) {
345       if (aContent->GetID() &&
346           aLabelTargetId == nsAtomString(aContent->GetID())) {
347         return true;
348       }
349     }
350   }
351   return false;
352 }
353 
IsLargeElement(nsIFrame * aFrame,const EventRadiusPrefs * aPrefs)354 static bool IsLargeElement(nsIFrame* aFrame, const EventRadiusPrefs* aPrefs) {
355   uint32_t keepLimitSizeForCluster = aPrefs->mKeepLimitSizeForCluster;
356   nsSize frameSize = aFrame->GetSize();
357   nsPresContext* pc = aFrame->PresContext();
358   nsIPresShell* presShell = pc->PresShell();
359   float cumulativeResolution = presShell->GetCumulativeResolution();
360   if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) >
361           keepLimitSizeForCluster &&
362       (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) >
363           keepLimitSizeForCluster) {
364     return true;
365   }
366   return false;
367 }
368 
GetClosest(nsIFrame * aRoot,const nsPoint & aPointRelativeToRootFrame,const nsRect & aTargetRect,const EventRadiusPrefs * aPrefs,nsIFrame * aRestrictToDescendants,nsIContent * aClickableAncestor,nsTArray<nsIFrame * > & aCandidates,int32_t * aElementsInCluster)369 static nsIFrame* GetClosest(
370     nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
371     const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
372     nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
373     nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster) {
374   std::vector<nsIContent*> mContentsInCluster;  // List of content elements in
375                                                 // the cluster without duplicate
376   nsIFrame* bestTarget = nullptr;
377   // Lower is better; distance is in appunits
378   float bestDistance = 1e6f;
379   nsRegion exposedRegion(aTargetRect);
380   for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
381     nsIFrame* f = aCandidates[i];
382     PET_LOG("Checking candidate %p\n", f);
383 
384     bool preservesAxisAlignedRectangles = false;
385     nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
386         f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot,
387         &preservesAxisAlignedRectangles);
388     nsRegion region;
389     region.And(exposedRegion, borderBox);
390     if (region.IsEmpty()) {
391       PET_LOG("  candidate %p had empty hit region\n", f);
392       continue;
393     }
394 
395     if (preservesAxisAlignedRectangles) {
396       // Subtract from the exposed region if we have a transform that won't make
397       // the bounds include a bunch of area that we don't actually cover.
398       SubtractFromExposedRegion(&exposedRegion, region);
399     }
400 
401     nsAutoString labelTargetId;
402     if (aClickableAncestor &&
403         !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
404       PET_LOG("  candidate %p is not a descendant of required ancestor\n", f);
405       continue;
406     }
407 
408     nsIContent* clickableContent =
409         GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
410     if (!aClickableAncestor && !clickableContent) {
411       PET_LOG("  candidate %p was not clickable\n", f);
412       continue;
413     }
414     // If our current closest frame is a descendant of 'f', skip 'f' (prefer
415     // the nested frame).
416     if (bestTarget &&
417         nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) {
418       PET_LOG("  candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
419       continue;
420     }
421     if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(
422                                    aRestrictToDescendants, f, aRoot)) {
423       PET_LOG("  candidate %p was not descendant of restrictroot %p\n", f,
424               aRestrictToDescendants);
425       continue;
426     }
427 
428     // If the first clickable ancestor of f is a label element
429     // and "for" attribute is present in label element, search the frame list
430     // for the "for" element If this element is present in the current list, do
431     // not count the frame in the cluster elements counter
432     if ((labelTargetId.IsEmpty() ||
433          !IsElementPresent(aCandidates, labelTargetId)) &&
434         !IsLargeElement(f, aPrefs)) {
435       if (std::find(mContentsInCluster.begin(), mContentsInCluster.end(),
436                     clickableContent) == mContentsInCluster.end()) {
437         mContentsInCluster.push_back(clickableContent);
438       }
439     }
440 
441     // distance is in appunits
442     float distance =
443         ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
444     nsIContent* content = f->GetContent();
445     if (content && content->IsElement() &&
446         content->AsElement()->State().HasState(
447             EventStates(NS_EVENT_STATE_VISITED))) {
448       distance *= aPrefs->mVisitedWeight / 100.0f;
449     }
450     if (distance < bestDistance) {
451       PET_LOG("  candidate %p is the new best\n", f);
452       bestDistance = distance;
453       bestTarget = f;
454     }
455   }
456   *aElementsInCluster = mContentsInCluster.size();
457   return bestTarget;
458 }
459 
460 /*
461  * Return always true when touch cluster detection is OFF.
462  * When cluster detection is ON, return true:
463  *   if the text inside the frame is readable (by human eyes)
464  *   or
465  *   if the structure is too complex to determine the size.
466  * In both cases, the frame is considered as clickable.
467  *
468  * Frames with a too small size will return false.
469  * In this case, the frame is considered not clickable.
470  */
IsElementClickableAndReadable(nsIFrame * aFrame,WidgetGUIEvent * aEvent,const EventRadiusPrefs * aPrefs)471 static bool IsElementClickableAndReadable(nsIFrame* aFrame,
472                                           WidgetGUIEvent* aEvent,
473                                           const EventRadiusPrefs* aPrefs) {
474   if (!aPrefs->mTouchClusterDetectionEnabled) {
475     return true;
476   }
477 
478   if (aPrefs->mSimplifiedClusterDetection) {
479     return true;
480   }
481 
482   if (aEvent->mClass != eMouseEventClass) {
483     return true;
484   }
485 
486   uint32_t limitReadableSize = aPrefs->mLimitReadableSize;
487   nsSize frameSize = aFrame->GetSize();
488   nsPresContext* pc = aFrame->PresContext();
489   nsIPresShell* presShell = pc->PresShell();
490   float cumulativeResolution = presShell->GetCumulativeResolution();
491   if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) <
492           limitReadableSize ||
493       (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) <
494           limitReadableSize) {
495     return false;
496   }
497   // We want to detect small clickable text elements using the font size.
498   // Two common cases are supported for now:
499   //    1. text node
500   //    2. any element with only one child of type text node
501   // All the other cases are currently ignored.
502   nsIContent* content = aFrame->GetContent();
503   bool testFontSize = false;
504   if (content) {
505     nsINodeList* childNodes = content->ChildNodes();
506     uint32_t childNodeCount = childNodes->Length();
507     if ((content->IsNodeOfType(nsINode::eTEXT)) ||
508         // click occurs on the text inside <a></a> or other clickable tags with
509         // text inside
510 
511         (childNodeCount == 1 && childNodes->Item(0) &&
512          childNodes->Item(0)->IsNodeOfType(nsINode::eTEXT))) {
513       // The click occurs on an element with only one text node child. In this
514       // case, the font size can be tested. The number of child nodes is tested
515       // to avoid the following cases (See bug 1172488):
516       //   Some jscript libraries transform text elements into Canvas elements
517       //   but keep the text nodes with a very small size (1px) to handle the
518       //   selection of text. With such libraries, the font size of the text
519       //   elements is not relevant to detect small elements.
520 
521       testFontSize = true;
522     }
523   }
524 
525   if (testFontSize) {
526     RefPtr<nsFontMetrics> fm =
527         nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
528     if (fm && fm->EmHeight() > 0 &&  // See bug 1171731
529         (pc->AppUnitsToGfxUnits(fm->EmHeight()) * cumulativeResolution) <
530             limitReadableSize) {
531       return false;
532     }
533   }
534 
535   return true;
536 }
537 
FindFrameTargetedByInputEvent(WidgetGUIEvent * aEvent,nsIFrame * aRootFrame,const nsPoint & aPointRelativeToRootFrame,uint32_t aFlags)538 nsIFrame* FindFrameTargetedByInputEvent(
539     WidgetGUIEvent* aEvent, nsIFrame* aRootFrame,
540     const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) {
541   uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)
542                        ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME
543                        : 0;
544   nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
545       aRootFrame, aPointRelativeToRootFrame, flags);
546   PET_LOG(
547       "Found initial target %p for event class %s point %s relative to root "
548       "frame %p\n",
549       target,
550       (aEvent->mClass == eMouseEventClass
551            ? "mouse"
552            : (aEvent->mClass == eTouchEventClass ? "touch" : "other")),
553       mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(),
554       aRootFrame);
555 
556   const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass);
557   if (!prefs || !prefs->mEnabled) {
558     PET_LOG("Retargeting disabled\n");
559     return target;
560   }
561   nsIContent* clickableAncestor = nullptr;
562   if (target) {
563     clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
564     if (clickableAncestor) {
565       if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
566         aEvent->AsMouseEventBase()->hitCluster = true;
567       }
568       PET_LOG("Target %p is clickable\n", target);
569       // If the target that was directly hit has a clickable ancestor, that
570       // means it too is clickable. And since it is the same as or a descendant
571       // of clickableAncestor, it should become the root for the GetClosest
572       // search.
573       clickableAncestor = target->GetContent();
574     }
575   }
576 
577   // Do not modify targeting for actual mouse hardware; only for mouse
578   // events generated by touch-screen hardware.
579   if (aEvent->mClass == eMouseEventClass && prefs->mTouchOnly &&
580       aEvent->AsMouseEvent()->inputSource !=
581           nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
582     PET_LOG("Mouse input event is not from a touch source\n");
583     return target;
584   }
585 
586   // If the exact target is non-null, only consider candidate targets in the
587   // same document as the exact target. Otherwise, if an ancestor document has
588   // a mouse event handler for example, targets that are !GetClickableAncestor
589   // can never be targeted --- something nsSubDocumentFrame in an ancestor
590   // document would be targeted instead.
591   nsIFrame* restrictToDescendants =
592       target ? target->PresShell()->GetRootFrame() : aRootFrame;
593 
594   nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
595                                     restrictToDescendants, prefs, aFlags);
596   PET_LOG("Expanded point to target rect %s\n",
597           mozilla::layers::Stringify(targetRect).c_str());
598   AutoTArray<nsIFrame*, 8> candidates;
599   nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect,
600                                                 candidates, flags);
601   if (NS_FAILED(rv)) {
602     return target;
603   }
604 
605   int32_t elementsInCluster = 0;
606 
607   nsIFrame* closestClickable = GetClosest(
608       aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
609       restrictToDescendants, clickableAncestor, candidates, &elementsInCluster);
610   if (closestClickable) {
611     if ((prefs->mTouchClusterDetectionEnabled && elementsInCluster > 1) ||
612         (!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {
613       if (aEvent->mClass == eMouseEventClass) {
614         WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();
615         mouseEventBase->hitCluster = true;
616       }
617     }
618     target = closestClickable;
619   }
620   PET_LOG("Final target is %p\n", target);
621 
622   // Uncomment this to dump the frame tree to help with debugging.
623   // Note that dumping the frame tree at the top of the function may flood
624   // logcat on Android devices and cause the PET_LOGs to get dropped.
625   // aRootFrame->DumpFrameTree();
626 
627   if (!target || !prefs->mRepositionEventCoords) {
628     // No repositioning required for this event
629     return target;
630   }
631 
632   // Take the point relative to the root frame, make it relative to the target,
633   // clamp it to the bounds, and then make it relative to the root frame again.
634   nsPoint point = aPointRelativeToRootFrame;
635   if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
636       nsLayoutUtils::TransformPoint(aRootFrame, target, point)) {
637     return target;
638   }
639   point = target->GetRectRelativeToSelf().ClampPoint(point);
640   if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
641       nsLayoutUtils::TransformPoint(target, aRootFrame, point)) {
642     return target;
643   }
644   // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
645   // get back the (now-clamped) coordinates in the event's widget's space.
646   nsView* view = aRootFrame->GetView();
647   if (!view) {
648     return target;
649   }
650   LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
651       aRootFrame->PresContext(), view, point, aEvent->mWidget);
652   if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
653     // If that succeeded, we update the point in the event
654     aEvent->mRefPoint = widgetPoint;
655   }
656   return target;
657 }
658 
659 }  // namespace mozilla
660