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
|