static nsIFrame* GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame, const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs, nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates) { nsIFrame* bestTarget = nullptr; // Lower is better; distance is in appunits float bestDistance = 1e6f; nsRegion exposedRegion(aTargetRect); for (uint32_t i = 0; i < aCandidates.Length(); ++i) { nsIFrame* f = aCandidates[i]; bool preservesAxisAlignedRectangles = false; nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles); nsRegion region; region.And(exposedRegion, borderBox); if (region.IsEmpty()) { continue; } if (preservesAxisAlignedRectangles) { // Subtract from the exposed region if we have a transform that won't make // the bounds include a bunch of area that we don't actually cover. SubtractFromExposedRegion(&exposedRegion, region); } if (!IsElementClickable(f)) { continue; } // If our current closest frame is a descendant of 'f', skip 'f' (prefer // the nested frame). if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) { continue; } if (!nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) { continue; } // distance is in appunits float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region); nsIContent* content = f->GetContent(); if (content && content->IsElement() && content->AsElement()->State().HasState( EventStates(NS_EVENT_STATE_VISITED))) { distance *= aPrefs->mVisitedWeight / 100.0f; } if (distance < bestDistance) { bestDistance = distance; bestTarget = f; } } return bestTarget; }
nsIFrame* FindFrameTargetedByInputEvent(const WidgetGUIEvent* aEvent, nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) { uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0; nsIFrame* target = nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags); const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->eventStructType); if (!prefs || !prefs->mEnabled || (target && IsElementClickable(target, nsGkAtoms::body))) { return target; } // Do not modify targeting for actual mouse hardware; only for mouse // events generated by touch-screen hardware. if (aEvent->eventStructType == NS_MOUSE_EVENT && prefs->mTouchOnly && aEvent->AsMouseEvent()->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { return target; } // If the exact target is non-null, only consider candidate targets in the same // document as the exact target. Otherwise, if an ancestor document has // a mouse event handler for example, targets that are !IsElementClickable can // never be targeted --- something nsSubDocumentFrame in an ancestor document // would be targeted instead. nsIFrame* restrictToDescendants = target ? target->PresContext()->PresShell()->GetRootFrame() : aRootFrame; nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame, restrictToDescendants, prefs); nsAutoTArray<nsIFrame*,8> candidates; nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags); if (NS_FAILED(rv)) { return target; } nsIFrame* closestClickable = GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs, restrictToDescendants, candidates); return closestClickable ? closestClickable : target; }
nsIFrame* FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent, nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) { uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0; nsIFrame* target = nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags); PET_LOG("Found initial target %p for event class %s point %s relative to root frame %p\n", target, (aEvent->mClass == eMouseEventClass ? "mouse" : (aEvent->mClass == eTouchEventClass ? "touch" : "other")), mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(), aRootFrame); const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass); if (!prefs || !prefs->mEnabled) { PET_LOG("Retargeting disabled\n"); return target; } if (target && IsElementClickable(target, nsGkAtoms::body)) { if (!IsElementClickableAndReadable(target, aEvent, prefs)) { aEvent->AsMouseEventBase()->hitCluster = true; } PET_LOG("Target %p is clickable\n", target); return target; } // Do not modify targeting for actual mouse hardware; only for mouse // events generated by touch-screen hardware. if (aEvent->mClass == eMouseEventClass && prefs->mTouchOnly && aEvent->AsMouseEvent()->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { PET_LOG("Mouse input event is not from a touch source\n"); return target; } // If the exact target is non-null, only consider candidate targets in the same // document as the exact target. Otherwise, if an ancestor document has // a mouse event handler for example, targets that are !IsElementClickable can // never be targeted --- something nsSubDocumentFrame in an ancestor document // would be targeted instead. nsIFrame* restrictToDescendants = target ? target->PresContext()->PresShell()->GetRootFrame() : aRootFrame; nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame, restrictToDescendants, prefs, aFlags); PET_LOG("Expanded point to target rect %s\n", mozilla::layers::Stringify(targetRect).c_str()); nsAutoTArray<nsIFrame*,8> candidates; nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags); if (NS_FAILED(rv)) { return target; } int32_t elementsInCluster = 0; nsIFrame* closestClickable = GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs, restrictToDescendants, candidates, &elementsInCluster); if (closestClickable) { if ((!prefs->mTouchClusterDetectionDisabled && elementsInCluster > 1) || (!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) { if (aEvent->mClass == eMouseEventClass) { WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase(); mouseEventBase->hitCluster = true; } } target = closestClickable; } PET_LOG("Final target is %p\n", target); // Uncomment this to dump the frame tree to help with debugging. // Note that dumping the frame tree at the top of the function may flood // logcat on Android devices and cause the PET_LOGs to get dropped. // aRootFrame->DumpFrameTree(); if (!target || !prefs->mRepositionEventCoords) { // No repositioning required for this event return target; } // Take the point relative to the root frame, make it relative to the target, // clamp it to the bounds, and then make it relative to the root frame again. nsPoint point = aPointRelativeToRootFrame; if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(aRootFrame, target, point)) { return target; } point = target->GetRectRelativeToSelf().ClampPoint(point); if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(target, aRootFrame, point)) { return target; } // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to // get back the (now-clamped) coordinates in the event's widget's space. nsView* view = aRootFrame->GetView(); if (!view) { return target; } LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget( aRootFrame->PresContext(), view, point, aEvent->widget); if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) { // If that succeeded, we update the point in the event aEvent->refPoint = widgetPoint; } return target; }
static nsIFrame* GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame, const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs, nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster) { nsIFrame* bestTarget = nullptr; // Lower is better; distance is in appunits float bestDistance = 1e6f; nsRegion exposedRegion(aTargetRect); for (uint32_t i = 0; i < aCandidates.Length(); ++i) { nsIFrame* f = aCandidates[i]; PET_LOG("Checking candidate %p\n", f); bool preservesAxisAlignedRectangles = false; nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles); nsRegion region; region.And(exposedRegion, borderBox); if (region.IsEmpty()) { PET_LOG(" candidate %p had empty hit region\n", f); continue; } if (preservesAxisAlignedRectangles) { // Subtract from the exposed region if we have a transform that won't make // the bounds include a bunch of area that we don't actually cover. SubtractFromExposedRegion(&exposedRegion, region); } nsAutoString labelTargetId; if (!IsElementClickable(f, nsGkAtoms::body, &labelTargetId)) { PET_LOG(" candidate %p was not clickable\n", f); continue; } // If our current closest frame is a descendant of 'f', skip 'f' (prefer // the nested frame). if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) { PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget); continue; } if (!nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) { PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f, aRestrictToDescendants); continue; } // If the first clickable ancestor of f is a label element // and "for" attribute is present in label element, search the frame list for the "for" element // If this element is present in the current list, do not count the frame in // the cluster elements counter if (labelTargetId.IsEmpty() || !IsElementPresent(aCandidates, labelTargetId)) { (*aElementsInCluster)++; } // distance is in appunits float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region); nsIContent* content = f->GetContent(); if (content && content->IsElement() && content->AsElement()->State().HasState( EventStates(NS_EVENT_STATE_VISITED))) { distance *= aPrefs->mVisitedWeight / 100.0f; } if (distance < bestDistance) { PET_LOG(" candidate %p is the new best\n", f); bestDistance = distance; bestTarget = f; } } return bestTarget; }