Bug 1188185 - Zoomed View appears when the two links are the same link. r=kats
authordominique vincent <domivinc@toitl.com>
Fri, 31 Jul 2015 15:22:46 +0200
changeset 287504 5ff82587c6603f54022b039ca96d29ec0dd44f2f
parent 287503 85afe1aa3e6fa57689fa096a192a4265e0c40d01
child 287505 83b8721b9b8c821e84245566a66fa75e73d2ca01
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1188185
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1188185 - Zoomed View appears when the two links are the same link. r=kats
layout/base/PositionedEventTargeting.cpp
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -29,31 +29,31 @@ namespace mozilla {
 /*
  * The basic goal of FindFrameTargetedByInputEvent() is to find a good
  * target element that can respond to mouse events. Both mouse events and touch
  * events are targeted at this element. Note that even for touch events, we
  * check responsiveness to mouse events. We assume Web authors
  * designing for touch events will take their own steps to account for
  * inaccurate touch events.
  *
- * IsElementClickable() encapsulates the heuristic that determines whether an
+ * GetClickableAncestor() encapsulates the heuristic that determines whether an
  * element is expected to respond to mouse events. An element is deemed
  * "clickable" if it has registered listeners for "click", "mousedown" or
  * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
  * <select>, <textarea>, <label>), or has role="button", or is a link, or
  * is a suitable XUL element.
  * Any descendant (in the same document) of a clickable element is also
  * deemed clickable since events will propagate to the clickable element from its
  * descendant.
  *
  * If the element directly under the event position is clickable (or
  * event radii are disabled), we always use that element. Otherwise we collect
  * all frames intersecting a rectangle around the event position (taking CSS
  * transforms into account) and choose the best candidate in GetClosest().
- * Only IsElementClickable() candidates are considered; if none are found,
+ * Only GetClickableAncestor() candidates are considered; if none are found,
  * then we revert to targeting the element under the event position.
  * We ignore candidates outside the document subtree rooted by the
  * document of the element directly under the event position. This ensures that
  * event listeners in ancestor documents don't make it completely impossible
  * to target a non-clickable element in a child document.
  *
  * When both a frame and its ancestor are in the candidate list, we ignore
  * the ancestor. Otherwise a large ancestor element with a mouse event listener
@@ -182,83 +182,83 @@ IsDescendant(nsIFrame* aFrame, nsIConten
     }
     if (content == aAncestor) {
       return true;
     }
   }
   return false;
 }
 
-static bool
-IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
+static nsIContent*
+GetClickableAncestor(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
 {
   // Input events propagate up the content tree so we'll follow the content
   // ancestors to look for elements accepting the click.
   for (nsIContent* content = aFrame->GetContent(); content;
        content = content->GetFlattenedTreeParent()) {
     if (stopAt && content->IsHTMLElement(stopAt)) {
       break;
     }
     if (HasTouchListener(content) || HasMouseListener(content)) {
-      return true;
+      return content;
     }
     if (content->IsAnyOfHTMLElements(nsGkAtoms::button,
                                      nsGkAtoms::input,
                                      nsGkAtoms::select,
                                      nsGkAtoms::textarea)) {
-      return true;
+      return content;
     }
     if (content->IsHTMLElement(nsGkAtoms::label)) {
       if (aLabelTargetId) {
         content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
       }
-      return true;
+      return content;
     }
 
     // Bug 921928: we don't have access to the content of remote iframe.
     // So fluffing won't go there. We do an optimistic assumption here:
     // that the content of the remote iframe needs to be a target.
     if (content->IsHTMLElement(nsGkAtoms::iframe) &&
         content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser,
                              nsGkAtoms::_true, eIgnoreCase) &&
         content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
                              nsGkAtoms::_true, eIgnoreCase)) {
-      return true;
+      return content;
     }
 
     // See nsCSSFrameConstructor::FindXULTagData. This code is not
     // really intended to be used with XUL, though.
     if (content->IsAnyOfXULElements(nsGkAtoms::button,
                                     nsGkAtoms::checkbox,
                                     nsGkAtoms::radio,
                                     nsGkAtoms::autorepeatbutton,
                                     nsGkAtoms::menu,
                                     nsGkAtoms::menubutton,
                                     nsGkAtoms::menuitem,
                                     nsGkAtoms::menulist,
                                     nsGkAtoms::scrollbarbutton,
                                     nsGkAtoms::resizer)) {
-      return true;
+      return content;
     }
 
     static nsIContent::AttrValuesArray clickableRoles[] =
       { &nsGkAtoms::button, &nsGkAtoms::key, nullptr };
     if (content->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
                                  clickableRoles, eIgnoreCase) >= 0) {
-      return true;
+      return content;
     }
     if (content->IsEditable()) {
-      return true;
+      return content;
     }
     nsCOMPtr<nsIURI> linkURI;
     if (content->IsLink(getter_AddRefs(linkURI))) {
-      return true;
+      return content;
     }
   }
-  return false;
+  return nullptr;
 }
 
 static nscoord
 AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM, bool aVertical)
 {
   nsPresContext* pc = aFrame->PresContext();
   float result = float(aMM) *
     (pc->DeviceContext()->AppUnitsPerPhysicalInch() / MM_PER_INCH_FLOAT);
@@ -357,84 +357,88 @@ static bool IsElementPresent(nsTArray<ns
 }
 
 static nsIFrame*
 GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
            const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
            nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
            nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster)
 {
+  std::vector<nsIContent*> mContentsInCluster;  // List of content elements in the cluster without duplicate
   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 (aClickableAncestor) {
-      if (!IsDescendant(f, aClickableAncestor, &labelTargetId)) {
-        PET_LOG("  candidate %p is not a descendant of required ancestor\n", f);
-        continue;
-      }
-    } else if (!IsElementClickable(f, nsGkAtoms::body, &labelTargetId)) {
+    if (aClickableAncestor && !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
+      PET_LOG("  candidate %p is not a descendant of required ancestor\n", f);
+      continue;
+    }
+
+    nsIContent* clickableContent = GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
+    if (!aClickableAncestor && !clickableContent) {
       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)) {
+    if (!aClickableAncestor && !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)++;
+      if (std::find(mContentsInCluster.begin(), mContentsInCluster.end(), clickableContent) == mContentsInCluster.end()) {
+        mContentsInCluster.push_back(clickableContent);
+      }
     }
 
     // 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;
     }
   }
+  *aElementsInCluster = mContentsInCluster.size();
   return bestTarget;
 }
 
 /*
  * Return always true when touch cluster detection is OFF.
  * When cluster detection is ON, return true:
  *   if the text inside the frame is readable (by human eyes)
  *   or
@@ -519,37 +523,39 @@ FindFrameTargetedByInputEvent(WidgetGUIE
     mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(), aRootFrame);
 
   const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass);
   if (!prefs || !prefs->mEnabled) {
     PET_LOG("Retargeting disabled\n");
     return target;
   }
   nsIContent* clickableAncestor = nullptr;
-  if (target && IsElementClickable(target, nsGkAtoms::body)) {
-    if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
-      aEvent->AsMouseEventBase()->hitCluster = true;
+  if (target) {
+    clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
+    if (clickableAncestor) {
+      if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
+        aEvent->AsMouseEventBase()->hitCluster = true;
+      }
+      PET_LOG("Target %p is clickable\n", target);
     }
-    PET_LOG("Target %p is clickable\n", target);
-    clickableAncestor = target->GetContent();
   }
 
   // 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
+  // a mouse event handler for example, targets that are !GetClickableAncestor 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",