зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
13a8848d29
Коммит
e3c8c5a562
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче