Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include "PositionedEventTargeting.h" |
michael@0 | 6 | |
michael@0 | 7 | #include "mozilla/EventListenerManager.h" |
michael@0 | 8 | #include "mozilla/EventStates.h" |
michael@0 | 9 | #include "mozilla/MouseEvents.h" |
michael@0 | 10 | #include "mozilla/Preferences.h" |
michael@0 | 11 | #include "nsLayoutUtils.h" |
michael@0 | 12 | #include "nsGkAtoms.h" |
michael@0 | 13 | #include "nsPrintfCString.h" |
michael@0 | 14 | #include "mozilla/dom/Element.h" |
michael@0 | 15 | #include "nsRegion.h" |
michael@0 | 16 | #include "nsDeviceContext.h" |
michael@0 | 17 | #include "nsIFrame.h" |
michael@0 | 18 | #include <algorithm> |
michael@0 | 19 | |
michael@0 | 20 | namespace mozilla { |
michael@0 | 21 | |
michael@0 | 22 | /* |
michael@0 | 23 | * The basic goal of FindFrameTargetedByInputEvent() is to find a good |
michael@0 | 24 | * target element that can respond to mouse events. Both mouse events and touch |
michael@0 | 25 | * events are targeted at this element. Note that even for touch events, we |
michael@0 | 26 | * check responsiveness to mouse events. We assume Web authors |
michael@0 | 27 | * designing for touch events will take their own steps to account for |
michael@0 | 28 | * inaccurate touch events. |
michael@0 | 29 | * |
michael@0 | 30 | * IsElementClickable() encapsulates the heuristic that determines whether an |
michael@0 | 31 | * element is expected to respond to mouse events. An element is deemed |
michael@0 | 32 | * "clickable" if it has registered listeners for "click", "mousedown" or |
michael@0 | 33 | * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>, |
michael@0 | 34 | * <select>, <textarea>, <label>), or has role="button", or is a link, or |
michael@0 | 35 | * is a suitable XUL element. |
michael@0 | 36 | * Any descendant (in the same document) of a clickable element is also |
michael@0 | 37 | * deemed clickable since events will propagate to the clickable element from its |
michael@0 | 38 | * descendant. |
michael@0 | 39 | * |
michael@0 | 40 | * If the element directly under the event position is clickable (or |
michael@0 | 41 | * event radii are disabled), we always use that element. Otherwise we collect |
michael@0 | 42 | * all frames intersecting a rectangle around the event position (taking CSS |
michael@0 | 43 | * transforms into account) and choose the best candidate in GetClosest(). |
michael@0 | 44 | * Only IsElementClickable() candidates are considered; if none are found, |
michael@0 | 45 | * then we revert to targeting the element under the event position. |
michael@0 | 46 | * We ignore candidates outside the document subtree rooted by the |
michael@0 | 47 | * document of the element directly under the event position. This ensures that |
michael@0 | 48 | * event listeners in ancestor documents don't make it completely impossible |
michael@0 | 49 | * to target a non-clickable element in a child document. |
michael@0 | 50 | * |
michael@0 | 51 | * When both a frame and its ancestor are in the candidate list, we ignore |
michael@0 | 52 | * the ancestor. Otherwise a large ancestor element with a mouse event listener |
michael@0 | 53 | * and some descendant elements that need to be individually targetable would |
michael@0 | 54 | * disable intelligent targeting of those descendants within its bounds. |
michael@0 | 55 | * |
michael@0 | 56 | * GetClosest() computes the transformed axis-aligned bounds of each |
michael@0 | 57 | * candidate frame, then computes the Manhattan distance from the event point |
michael@0 | 58 | * to the bounds rect (which can be zero). The frame with the |
michael@0 | 59 | * shortest distance is chosen. For visited links we multiply the distance |
michael@0 | 60 | * by a specified constant weight; this can be used to make visited links |
michael@0 | 61 | * more or less likely to be targeted than non-visited links. |
michael@0 | 62 | */ |
michael@0 | 63 | |
michael@0 | 64 | struct EventRadiusPrefs |
michael@0 | 65 | { |
michael@0 | 66 | uint32_t mVisitedWeight; // in percent, i.e. default is 100 |
michael@0 | 67 | uint32_t mSideRadii[4]; // TRBL order, in millimetres |
michael@0 | 68 | bool mEnabled; |
michael@0 | 69 | bool mRegistered; |
michael@0 | 70 | bool mTouchOnly; |
michael@0 | 71 | }; |
michael@0 | 72 | |
michael@0 | 73 | static EventRadiusPrefs sMouseEventRadiusPrefs; |
michael@0 | 74 | static EventRadiusPrefs sTouchEventRadiusPrefs; |
michael@0 | 75 | |
michael@0 | 76 | static const EventRadiusPrefs* |
michael@0 | 77 | GetPrefsFor(nsEventStructType aEventStructType) |
michael@0 | 78 | { |
michael@0 | 79 | EventRadiusPrefs* prefs = nullptr; |
michael@0 | 80 | const char* prefBranch = nullptr; |
michael@0 | 81 | if (aEventStructType == NS_TOUCH_EVENT) { |
michael@0 | 82 | prefBranch = "touch"; |
michael@0 | 83 | prefs = &sTouchEventRadiusPrefs; |
michael@0 | 84 | } else if (aEventStructType == NS_MOUSE_EVENT) { |
michael@0 | 85 | // Mostly for testing purposes |
michael@0 | 86 | prefBranch = "mouse"; |
michael@0 | 87 | prefs = &sMouseEventRadiusPrefs; |
michael@0 | 88 | } else { |
michael@0 | 89 | return nullptr; |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | if (!prefs->mRegistered) { |
michael@0 | 93 | prefs->mRegistered = true; |
michael@0 | 94 | |
michael@0 | 95 | nsPrintfCString enabledPref("ui.%s.radius.enabled", prefBranch); |
michael@0 | 96 | Preferences::AddBoolVarCache(&prefs->mEnabled, enabledPref.get(), false); |
michael@0 | 97 | |
michael@0 | 98 | nsPrintfCString visitedWeightPref("ui.%s.radius.visitedWeight", prefBranch); |
michael@0 | 99 | Preferences::AddUintVarCache(&prefs->mVisitedWeight, visitedWeightPref.get(), 100); |
michael@0 | 100 | |
michael@0 | 101 | static const char prefNames[4][9] = |
michael@0 | 102 | { "topmm", "rightmm", "bottommm", "leftmm" }; |
michael@0 | 103 | for (int32_t i = 0; i < 4; ++i) { |
michael@0 | 104 | nsPrintfCString radiusPref("ui.%s.radius.%s", prefBranch, prefNames[i]); |
michael@0 | 105 | Preferences::AddUintVarCache(&prefs->mSideRadii[i], radiusPref.get(), 0); |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | if (aEventStructType == NS_MOUSE_EVENT) { |
michael@0 | 109 | Preferences::AddBoolVarCache(&prefs->mTouchOnly, |
michael@0 | 110 | "ui.mouse.radius.inputSource.touchOnly", true); |
michael@0 | 111 | } else { |
michael@0 | 112 | prefs->mTouchOnly = false; |
michael@0 | 113 | } |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | return prefs; |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | static bool |
michael@0 | 120 | HasMouseListener(nsIContent* aContent) |
michael@0 | 121 | { |
michael@0 | 122 | if (EventListenerManager* elm = aContent->GetExistingListenerManager()) { |
michael@0 | 123 | return elm->HasListenersFor(nsGkAtoms::onclick) || |
michael@0 | 124 | elm->HasListenersFor(nsGkAtoms::onmousedown) || |
michael@0 | 125 | elm->HasListenersFor(nsGkAtoms::onmouseup); |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | return false; |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | static bool gTouchEventsRegistered = false; |
michael@0 | 132 | static int32_t gTouchEventsEnabled = 0; |
michael@0 | 133 | |
michael@0 | 134 | static bool |
michael@0 | 135 | HasTouchListener(nsIContent* aContent) |
michael@0 | 136 | { |
michael@0 | 137 | EventListenerManager* elm = aContent->GetExistingListenerManager(); |
michael@0 | 138 | if (!elm) { |
michael@0 | 139 | return false; |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | if (!gTouchEventsRegistered) { |
michael@0 | 143 | Preferences::AddIntVarCache(&gTouchEventsEnabled, |
michael@0 | 144 | "dom.w3c_touch_events.enabled", gTouchEventsEnabled); |
michael@0 | 145 | gTouchEventsRegistered = true; |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | if (!gTouchEventsEnabled) { |
michael@0 | 149 | return false; |
michael@0 | 150 | } |
michael@0 | 151 | |
michael@0 | 152 | return elm->HasListenersFor(nsGkAtoms::ontouchstart) || |
michael@0 | 153 | elm->HasListenersFor(nsGkAtoms::ontouchend); |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | static bool |
michael@0 | 157 | IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr) |
michael@0 | 158 | { |
michael@0 | 159 | // Input events propagate up the content tree so we'll follow the content |
michael@0 | 160 | // ancestors to look for elements accepting the click. |
michael@0 | 161 | for (nsIContent* content = aFrame->GetContent(); content; |
michael@0 | 162 | content = content->GetFlattenedTreeParent()) { |
michael@0 | 163 | nsIAtom* tag = content->Tag(); |
michael@0 | 164 | if (content->IsHTML() && stopAt && tag == stopAt) { |
michael@0 | 165 | break; |
michael@0 | 166 | } |
michael@0 | 167 | if (HasTouchListener(content) || HasMouseListener(content)) { |
michael@0 | 168 | return true; |
michael@0 | 169 | } |
michael@0 | 170 | if (content->IsHTML()) { |
michael@0 | 171 | if (tag == nsGkAtoms::button || |
michael@0 | 172 | tag == nsGkAtoms::input || |
michael@0 | 173 | tag == nsGkAtoms::select || |
michael@0 | 174 | tag == nsGkAtoms::textarea || |
michael@0 | 175 | tag == nsGkAtoms::label) { |
michael@0 | 176 | return true; |
michael@0 | 177 | } |
michael@0 | 178 | // Bug 921928: we don't have access to the content of remote iframe. |
michael@0 | 179 | // So fluffing won't go there. We do an optimistic assumption here: |
michael@0 | 180 | // that the content of the remote iframe needs to be a target. |
michael@0 | 181 | if (tag == nsGkAtoms::iframe && |
michael@0 | 182 | content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser, |
michael@0 | 183 | nsGkAtoms::_true, eIgnoreCase) && |
michael@0 | 184 | content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote, |
michael@0 | 185 | nsGkAtoms::_true, eIgnoreCase)) { |
michael@0 | 186 | return true; |
michael@0 | 187 | } |
michael@0 | 188 | } else if (content->IsXUL()) { |
michael@0 | 189 | nsIAtom* tag = content->Tag(); |
michael@0 | 190 | // See nsCSSFrameConstructor::FindXULTagData. This code is not |
michael@0 | 191 | // really intended to be used with XUL, though. |
michael@0 | 192 | if (tag == nsGkAtoms::button || |
michael@0 | 193 | tag == nsGkAtoms::checkbox || |
michael@0 | 194 | tag == nsGkAtoms::radio || |
michael@0 | 195 | tag == nsGkAtoms::autorepeatbutton || |
michael@0 | 196 | tag == nsGkAtoms::menu || |
michael@0 | 197 | tag == nsGkAtoms::menubutton || |
michael@0 | 198 | tag == nsGkAtoms::menuitem || |
michael@0 | 199 | tag == nsGkAtoms::menulist || |
michael@0 | 200 | tag == nsGkAtoms::scrollbarbutton || |
michael@0 | 201 | tag == nsGkAtoms::resizer) { |
michael@0 | 202 | return true; |
michael@0 | 203 | } |
michael@0 | 204 | } |
michael@0 | 205 | static nsIContent::AttrValuesArray clickableRoles[] = |
michael@0 | 206 | { &nsGkAtoms::button, &nsGkAtoms::key, nullptr }; |
michael@0 | 207 | if (content->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role, |
michael@0 | 208 | clickableRoles, eIgnoreCase) >= 0) { |
michael@0 | 209 | return true; |
michael@0 | 210 | } |
michael@0 | 211 | if (content->IsEditable()) { |
michael@0 | 212 | return true; |
michael@0 | 213 | } |
michael@0 | 214 | nsCOMPtr<nsIURI> linkURI; |
michael@0 | 215 | if (content->IsLink(getter_AddRefs(linkURI))) { |
michael@0 | 216 | return true; |
michael@0 | 217 | } |
michael@0 | 218 | } |
michael@0 | 219 | return false; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | static nscoord |
michael@0 | 223 | AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM, bool aVertical) |
michael@0 | 224 | { |
michael@0 | 225 | nsPresContext* pc = aFrame->PresContext(); |
michael@0 | 226 | float result = float(aMM) * |
michael@0 | 227 | (pc->DeviceContext()->AppUnitsPerPhysicalInch() / MM_PER_INCH_FLOAT); |
michael@0 | 228 | return NSToCoordRound(result); |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | /** |
michael@0 | 232 | * Clip aRect with the bounds of aFrame in the coordinate system of |
michael@0 | 233 | * aRootFrame. aRootFrame is an ancestor of aFrame. |
michael@0 | 234 | */ |
michael@0 | 235 | static nsRect |
michael@0 | 236 | ClipToFrame(nsIFrame* aRootFrame, nsIFrame* aFrame, nsRect& aRect) |
michael@0 | 237 | { |
michael@0 | 238 | nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor( |
michael@0 | 239 | aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame); |
michael@0 | 240 | nsRect result = bound.Intersect(aRect); |
michael@0 | 241 | return result; |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | static nsRect |
michael@0 | 245 | GetTargetRect(nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame, |
michael@0 | 246 | nsIFrame* aRestrictToDescendants, const EventRadiusPrefs* aPrefs) |
michael@0 | 247 | { |
michael@0 | 248 | nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[0], true), |
michael@0 | 249 | AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[1], false), |
michael@0 | 250 | AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[2], true), |
michael@0 | 251 | AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[3], false)); |
michael@0 | 252 | nsRect r(aPointRelativeToRootFrame, nsSize(0,0)); |
michael@0 | 253 | r.Inflate(m); |
michael@0 | 254 | return ClipToFrame(aRootFrame, aRestrictToDescendants, r); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | static float |
michael@0 | 258 | ComputeDistanceFromRect(const nsPoint& aPoint, const nsRect& aRect) |
michael@0 | 259 | { |
michael@0 | 260 | nscoord dx = std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost())); |
michael@0 | 261 | nscoord dy = std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost())); |
michael@0 | 262 | return float(NS_hypot(dx, dy)); |
michael@0 | 263 | } |
michael@0 | 264 | |
michael@0 | 265 | static float |
michael@0 | 266 | ComputeDistanceFromRegion(const nsPoint& aPoint, const nsRegion& aRegion) |
michael@0 | 267 | { |
michael@0 | 268 | MOZ_ASSERT(!aRegion.IsEmpty(), "can't compute distance between point and empty region"); |
michael@0 | 269 | nsRegionRectIterator iter(aRegion); |
michael@0 | 270 | const nsRect* r; |
michael@0 | 271 | float minDist = -1; |
michael@0 | 272 | while ((r = iter.Next()) != nullptr) { |
michael@0 | 273 | float dist = ComputeDistanceFromRect(aPoint, *r); |
michael@0 | 274 | if (dist < minDist || minDist < 0) { |
michael@0 | 275 | minDist = dist; |
michael@0 | 276 | } |
michael@0 | 277 | } |
michael@0 | 278 | return minDist; |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | // Subtract aRegion from aExposedRegion as long as that doesn't make the |
michael@0 | 282 | // exposed region get too complex or removes a big chunk of the exposed region. |
michael@0 | 283 | static void |
michael@0 | 284 | SubtractFromExposedRegion(nsRegion* aExposedRegion, const nsRegion& aRegion) |
michael@0 | 285 | { |
michael@0 | 286 | if (aRegion.IsEmpty()) |
michael@0 | 287 | return; |
michael@0 | 288 | |
michael@0 | 289 | nsRegion tmp; |
michael@0 | 290 | tmp.Sub(*aExposedRegion, aRegion); |
michael@0 | 291 | // Don't let *aExposedRegion get too complex, but don't let it fluff out to |
michael@0 | 292 | // its bounds either. Do let aExposedRegion get more complex if by doing so |
michael@0 | 293 | // we reduce its area by at least half. |
michael@0 | 294 | if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area()/2) { |
michael@0 | 295 | *aExposedRegion = tmp; |
michael@0 | 296 | } |
michael@0 | 297 | } |
michael@0 | 298 | |
michael@0 | 299 | static nsIFrame* |
michael@0 | 300 | GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame, |
michael@0 | 301 | const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs, |
michael@0 | 302 | nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates) |
michael@0 | 303 | { |
michael@0 | 304 | nsIFrame* bestTarget = nullptr; |
michael@0 | 305 | // Lower is better; distance is in appunits |
michael@0 | 306 | float bestDistance = 1e6f; |
michael@0 | 307 | nsRegion exposedRegion(aTargetRect); |
michael@0 | 308 | for (uint32_t i = 0; i < aCandidates.Length(); ++i) { |
michael@0 | 309 | nsIFrame* f = aCandidates[i]; |
michael@0 | 310 | |
michael@0 | 311 | bool preservesAxisAlignedRectangles = false; |
michael@0 | 312 | nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f, |
michael@0 | 313 | nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles); |
michael@0 | 314 | nsRegion region; |
michael@0 | 315 | region.And(exposedRegion, borderBox); |
michael@0 | 316 | |
michael@0 | 317 | if (region.IsEmpty()) { |
michael@0 | 318 | continue; |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | if (preservesAxisAlignedRectangles) { |
michael@0 | 322 | // Subtract from the exposed region if we have a transform that won't make |
michael@0 | 323 | // the bounds include a bunch of area that we don't actually cover. |
michael@0 | 324 | SubtractFromExposedRegion(&exposedRegion, region); |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | if (!IsElementClickable(f)) { |
michael@0 | 328 | continue; |
michael@0 | 329 | } |
michael@0 | 330 | // If our current closest frame is a descendant of 'f', skip 'f' (prefer |
michael@0 | 331 | // the nested frame). |
michael@0 | 332 | if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) { |
michael@0 | 333 | continue; |
michael@0 | 334 | } |
michael@0 | 335 | if (!nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) { |
michael@0 | 336 | continue; |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | // distance is in appunits |
michael@0 | 340 | float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region); |
michael@0 | 341 | nsIContent* content = f->GetContent(); |
michael@0 | 342 | if (content && content->IsElement() && |
michael@0 | 343 | content->AsElement()->State().HasState( |
michael@0 | 344 | EventStates(NS_EVENT_STATE_VISITED))) { |
michael@0 | 345 | distance *= aPrefs->mVisitedWeight / 100.0f; |
michael@0 | 346 | } |
michael@0 | 347 | if (distance < bestDistance) { |
michael@0 | 348 | bestDistance = distance; |
michael@0 | 349 | bestTarget = f; |
michael@0 | 350 | } |
michael@0 | 351 | } |
michael@0 | 352 | return bestTarget; |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | nsIFrame* |
michael@0 | 356 | FindFrameTargetedByInputEvent(const WidgetGUIEvent* aEvent, |
michael@0 | 357 | nsIFrame* aRootFrame, |
michael@0 | 358 | const nsPoint& aPointRelativeToRootFrame, |
michael@0 | 359 | uint32_t aFlags) |
michael@0 | 360 | { |
michael@0 | 361 | uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ? |
michael@0 | 362 | nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0; |
michael@0 | 363 | nsIFrame* target = |
michael@0 | 364 | nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags); |
michael@0 | 365 | |
michael@0 | 366 | const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->eventStructType); |
michael@0 | 367 | if (!prefs || !prefs->mEnabled || (target && IsElementClickable(target, nsGkAtoms::body))) { |
michael@0 | 368 | return target; |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | // Do not modify targeting for actual mouse hardware; only for mouse |
michael@0 | 372 | // events generated by touch-screen hardware. |
michael@0 | 373 | if (aEvent->eventStructType == NS_MOUSE_EVENT && |
michael@0 | 374 | prefs->mTouchOnly && |
michael@0 | 375 | aEvent->AsMouseEvent()->inputSource != |
michael@0 | 376 | nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { |
michael@0 | 377 | return target; |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | // If the exact target is non-null, only consider candidate targets in the same |
michael@0 | 381 | // document as the exact target. Otherwise, if an ancestor document has |
michael@0 | 382 | // a mouse event handler for example, targets that are !IsElementClickable can |
michael@0 | 383 | // never be targeted --- something nsSubDocumentFrame in an ancestor document |
michael@0 | 384 | // would be targeted instead. |
michael@0 | 385 | nsIFrame* restrictToDescendants = target ? |
michael@0 | 386 | target->PresContext()->PresShell()->GetRootFrame() : aRootFrame; |
michael@0 | 387 | |
michael@0 | 388 | nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame, |
michael@0 | 389 | restrictToDescendants, prefs); |
michael@0 | 390 | nsAutoTArray<nsIFrame*,8> candidates; |
michael@0 | 391 | nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags); |
michael@0 | 392 | if (NS_FAILED(rv)) { |
michael@0 | 393 | return target; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | nsIFrame* closestClickable = |
michael@0 | 397 | GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs, |
michael@0 | 398 | restrictToDescendants, candidates); |
michael@0 | 399 | return closestClickable ? closestClickable : target; |
michael@0 | 400 | } |
michael@0 | 401 | |
michael@0 | 402 | } |