зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1809574 - Use hit-testing tree to determine fixed position handoff. r=botond
Use the hit-testing tree to determine the APZC to handoff overscroll to if the current APZC is in a fixed position subtree. Differential Revision: https://phabricator.services.mozilla.com/D166787
This commit is contained in:
Родитель
06aad1d639
Коммит
4fbf0efd95
|
@ -2834,20 +2834,21 @@ APZCTreeManager::HitTestResult APZCTreeManager::GetTargetAPZC(
|
|||
return mHitTester->GetAPZCAtPoint(aPoint, lock);
|
||||
}
|
||||
|
||||
AsyncPanZoomController* APZCTreeManager::FindHandoffParent(
|
||||
APZCTreeManager::TargetApzcForNodeResult APZCTreeManager::FindHandoffParent(
|
||||
const AsyncPanZoomController* aApzc) {
|
||||
RefPtr<HitTestingTreeNode> node = GetTargetNode(aApzc->GetGuid(), nullptr);
|
||||
while (node) {
|
||||
if (auto* apzc = GetTargetApzcForNode(node->GetParent())) {
|
||||
auto result = GetTargetApzcForNode(node->GetParent());
|
||||
if (result.mApzc) {
|
||||
// avoid infinite recursion in the overscroll handoff chain.
|
||||
if (apzc != aApzc) {
|
||||
return apzc;
|
||||
if (result.mApzc != aApzc) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
node = node->GetParent();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
RefPtr<const OverscrollHandoffChain>
|
||||
|
@ -2881,11 +2882,17 @@ APZCTreeManager::BuildOverscrollHandoffChain(
|
|||
// This probably indicates a bug or missed case in layout code
|
||||
NS_WARNING("Found a non-root APZ with no handoff parent");
|
||||
}
|
||||
}
|
||||
|
||||
// Find the parent APZC by using HitTestingTree (i.e. by using
|
||||
// GetTargetApzcForNode) so that we can properly find the parent APZC for
|
||||
// cases where cross-process iframes are inside position:fixed subtree.
|
||||
apzc = FindHandoffParent(apzc);
|
||||
APZCTreeManager::TargetApzcForNodeResult handoffResult =
|
||||
FindHandoffParent(apzc);
|
||||
|
||||
// If `apzc` is inside fixed content, we want to hand off to the document's
|
||||
// root APZC next. The scroll parent id wouldn't give us this because it's
|
||||
// based on ASRs.
|
||||
if (handoffResult.mIsFixed || apzc->GetScrollHandoffParentId() ==
|
||||
ScrollableLayerGuid::NULL_SCROLL_ID) {
|
||||
apzc = handoffResult.mApzc;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2949,23 +2956,26 @@ void APZCTreeManager::FindScrollThumbNode(
|
|||
}
|
||||
}
|
||||
|
||||
AsyncPanZoomController* APZCTreeManager::GetTargetApzcForNode(
|
||||
APZCTreeManager::TargetApzcForNodeResult APZCTreeManager::GetTargetApzcForNode(
|
||||
const HitTestingTreeNode* aNode) {
|
||||
for (const HitTestingTreeNode* n = aNode;
|
||||
n && n->GetLayersId() == aNode->GetLayersId(); n = n->GetParent()) {
|
||||
if (n->GetApzc()) {
|
||||
APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc());
|
||||
return n->GetApzc();
|
||||
}
|
||||
// For a fixed node, GetApzc() may return an APZC for content in the
|
||||
// enclosing document, so we need to check GetFixedPosTarget() before
|
||||
// GetApzc().
|
||||
if (n->GetFixedPosTarget() != ScrollableLayerGuid::NULL_SCROLL_ID) {
|
||||
RefPtr<AsyncPanZoomController> fpTarget =
|
||||
GetTargetAPZC(n->GetLayersId(), n->GetFixedPosTarget());
|
||||
APZCTM_LOG("Found target APZC %p using fixed-pos lookup on %" PRIu64 "\n",
|
||||
fpTarget.get(), n->GetFixedPosTarget());
|
||||
return fpTarget.get();
|
||||
return {fpTarget.get(), true};
|
||||
}
|
||||
if (n->GetApzc()) {
|
||||
APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc());
|
||||
return {n->GetApzc(), false};
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
HitTestingTreeNode* APZCTreeManager::FindRootNodeForLayersId(
|
||||
|
|
|
@ -110,6 +110,16 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge {
|
|||
typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics;
|
||||
using HitTestResult = IAPZHitTester::HitTestResult;
|
||||
|
||||
/**
|
||||
* A result from APZCTreeManager::FindHandoffParent.
|
||||
*/
|
||||
struct TargetApzcForNodeResult {
|
||||
// The APZC to handoff overscroll to.
|
||||
AsyncPanZoomController* mApzc;
|
||||
// Targeting a document's root APZC from content fixed to the document.
|
||||
bool mIsFixed;
|
||||
};
|
||||
|
||||
// Helper struct to hold some state while we build the hit-testing tree. The
|
||||
// sole purpose of this struct is to shorten the argument list to
|
||||
// UpdateHitTestingTree. All the state that we don't need to
|
||||
|
@ -604,8 +614,8 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge {
|
|||
HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
GuidComparator aComparator);
|
||||
AsyncPanZoomController* GetTargetApzcForNode(const HitTestingTreeNode* aNode);
|
||||
AsyncPanZoomController* FindHandoffParent(
|
||||
TargetApzcForNodeResult GetTargetApzcForNode(const HitTestingTreeNode* aNode);
|
||||
TargetApzcForNodeResult FindHandoffParent(
|
||||
const AsyncPanZoomController* aApzc);
|
||||
HitTestingTreeNode* FindRootNodeForLayersId(LayersId aLayersId) const;
|
||||
AsyncPanZoomController* FindRootContentApzcForLayersId(
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>APZ overscroll handoff for fixed elements in a subdoc</title>
|
||||
<script type="application/javascript" src="apz_test_utils.js"></script>
|
||||
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
|
||||
<script src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
<style>
|
||||
iframe {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border: solid 2px black;
|
||||
}
|
||||
#rootcontent {
|
||||
height: 200vh;
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="subdoc" srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: 100px;
|
||||
width: 80%;
|
||||
overflow: scroll;
|
||||
}
|
||||
#fixed-content {
|
||||
background: red;
|
||||
}
|
||||
#rootcontent {
|
||||
background: green;
|
||||
}
|
||||
.spacer {
|
||||
height: 200vh;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='fixed'>
|
||||
<div id='fixed-content' class='spacer'></div>
|
||||
</div>
|
||||
<div id='rootcontent' class='spacer'></div>
|
||||
</body>
|
||||
</html>
|
||||
"></iframe>
|
||||
<div id="rootcontent"></div>
|
||||
</body>
|
||||
<script>
|
||||
async function test() {
|
||||
// Scroll to the bottom of the fixed position element to ensure that the following
|
||||
// scroll does trigger overscroll handoff to the subdoc root scrollable element.
|
||||
subdoc.contentWindow.fixed.scrollTop = subdoc.contentWindow.fixed.scrollHeight;
|
||||
|
||||
// After scrolling to bottom tick the refresh driver.
|
||||
await promiseFrame();
|
||||
|
||||
let firstTransformEnd = promiseTransformEnd();
|
||||
|
||||
info("start scroll #1");
|
||||
|
||||
// Async scroll the fixed element by 200 pixels using the mouse-wheel. This should
|
||||
// handoff the overscroll to the subdoc's root scrollable element.
|
||||
await promiseMoveMouseAndScrollWheelOver(subdoc.contentWindow.fixed, 50, 50, false, 200);
|
||||
|
||||
info("After scroll #1: fixed=" + subdoc.contentWindow.fixed.scrollTop +
|
||||
" subdoc window=" + subdoc.contentWindow.scrollY + " window=" + window.scrollY);
|
||||
|
||||
info("wait scroll #1");
|
||||
await firstTransformEnd;
|
||||
|
||||
// Do not attempt the second scroll if we did scroll the root document.
|
||||
// A scroll in this case would likely cause the test to timeout. The assertions at the
|
||||
// end of the test will catch this.
|
||||
|
||||
// If we triggered a scroll handoff to the _root_ document from the subframe, do not
|
||||
// make another attempt at a second scroll. The test has already failed.
|
||||
if (window.scrollY == 0) {
|
||||
let secondTransformEnd = promiseTransformEnd();
|
||||
|
||||
info("start scroll #2");
|
||||
|
||||
await promiseMoveMouseAndScrollWheelOver(subdoc.contentWindow.fixed, 50, 50, false, 200);
|
||||
|
||||
info("After scroll #2: fixed=" + subdoc.contentWindow.fixed.scrollTop +
|
||||
" subdoc window=" + subdoc.contentWindow.scrollY + " window=" + window.scrollY);
|
||||
|
||||
info("wait scroll #2");
|
||||
await secondTransformEnd;
|
||||
}
|
||||
|
||||
// Ensure that the main element has not scrolled and overscroll was handed off to
|
||||
// the subdocument root scrollable element.
|
||||
is(window.scrollY, 0, "The overscroll should not handoff to the root window");
|
||||
isnot(subdoc.contentWindow.scrollY, 0,
|
||||
"The overscroll should handoff to the subdocument's root scrollable element");
|
||||
}
|
||||
|
||||
waitUntilApzStable()
|
||||
.then(test)
|
||||
.then(subtestDone, subtestFailed);
|
||||
</script>
|
||||
</html>
|
|
@ -25,6 +25,7 @@ var subtests = [
|
|||
{"file": "helper_position_fixed_scroll_handoff-2.html", prefs},
|
||||
{"file": "helper_position_fixed_scroll_handoff-3.html", prefs},
|
||||
{"file": "helper_position_fixed_scroll_handoff-4.html", prefs},
|
||||
{"file": "helper_position_fixed_scroll_handoff-5.html", prefs},
|
||||
{"file": "helper_position_sticky_scroll_handoff.html", prefs},
|
||||
{"file": "helper_wheelevents_handoff_on_iframe.html", "prefs": prefs},
|
||||
{"file": "helper_wheelevents_handoff_on_non_scrollable_iframe.html", "prefs": prefs},
|
||||
|
|
Загрузка…
Ссылка в новой задаче