Bug 1181763 - Allow the target fluffing code to fluff even when directly hitting something clickable. r=roc

There is a common pattern on the web where a click listener is registered on a
container element high up in the DOM tree, and based on the target of the click
events, it performs the appropriate action. In such cases, our existing fluffing
code was not getting activated anywhere inside the container, because the entire
container was considered clickable. However, this is not user-friendly because
often the actual targets inside the container are small and hard to hit. Also,
the fluffing code will often take the container element itself as the target,
even if the user actually hit something inside the container.

This patch changes this behaviour so when an event hits inside a clickable
container, fluffing still occurs, but is restricted to DOM descendants of the
container. This allows fluffing to work in the above scenarios, and since the
events will bubble up to the container, the listeners on the container are
guaranteed to still trigger.
This commit is contained in:
Kartikaya Gupta 2015-07-17 08:36:00 -04:00
Родитель 13a8848d29
Коммит e3c8c5a562
2 изменённых файлов: 36 добавлений и 7 удалений

Просмотреть файл

@ -172,6 +172,21 @@ HasTouchListener(nsIContent* aContent)
elm->HasListenersFor(nsGkAtoms::ontouchend);
}
static bool
IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor, nsAutoString* aLabelTargetId)
{
for (nsIContent* content = aFrame->GetContent(); content;
content = content->GetFlattenedTreeParent()) {
if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
}
if (content == aAncestor) {
return true;
}
}
return false;
}
static bool
IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
{
@ -344,8 +359,8 @@ static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates, const nsAutoStrin
static nsIFrame*
GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates,
int32_t* aElementsInCluster)
nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster)
{
nsIFrame* bestTarget = nullptr;
// Lower is better; distance is in appunits
@ -373,7 +388,12 @@ GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
}
nsAutoString labelTargetId;
if (!IsElementClickable(f, nsGkAtoms::body, &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)) {
PET_LOG(" candidate %p was not clickable\n", f);
continue;
}
@ -503,12 +523,13 @@ FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
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;
}
PET_LOG("Target %p is clickable\n", target);
return target;
clickableAncestor = target->GetContent();
}
// Do not modify targeting for actual mouse hardware; only for mouse
@ -543,7 +564,8 @@ FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
nsIFrame* closestClickable =
GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
restrictToDescendants, candidates, &elementsInCluster);
restrictToDescendants, clickableAncestor, candidates,
&elementsInCluster);
if (closestClickable) {
if ((!prefs->mTouchClusterDetectionDisabled && elementsInCluster > 1) ||
(!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {

Просмотреть файл

@ -40,6 +40,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=780847
<div class="target" id="t6" onmousedown="x=1" hidden>
<div id="t6_inner" style="position:absolute; left:-20px; top:20px; width:60px; height:60px; background:yellow;"></div>
</div>
<div id="t6_outer" style="position:absolute; left:160px; top:120px; width:60px; height:60px; background:green;" onmousedown="x=1" hidden></div>
<div class="target" id="t7" onmousedown="x=1" hidden></div>
<div class="target" id="t7_over" hidden></div>
@ -187,13 +188,19 @@ function test3() {
// Test behavior of nested elements.
// The following behaviors are questionable and may need to be changed.
setShowing("t6", true);
setShowing("t6_outer", true);
testMouseClick("t6_inner", -1, 10, "t6_inner",
"inner element is clickable because its parent is, even when it sticks outside parent");
testMouseClick("t6_inner", 19, -1, "t6_inner",
"when outside both inner and parent, but in range of both, the inner is selected");
testMouseClick("t6_inner", 25, -1, "t6",
"clicking in clickable parent close to inner activates parent, not inner");
testMouseClick("t6_inner", 25, -1, "t6_inner",
"clicking in clickable parent close to inner activates inner, not parent");
testMouseClick("t6_outer", 35, -1, "t6",
"clicking in clickable container close to outer activates parent, not outer");
testMouseClick("t6_outer", 1, 1, "t6_outer",
"clicking directly on the outer activates it");
setShowing("t6", false);
setShowing("t6_outer", false);
setShowing("t7", true);
setShowing("t7_over", true);