LCOV - code coverage report
Current view: top level - layout/base - PositionedEventTargeting.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 34 260 13.1 %
Date: 2017-07-14 16:53:18 Functions: 2 16 12.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13