Coverage Report

Created: 2018-09-25 14:53

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