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:
Dan Robertson 2023-01-19 13:36:44 +00:00
Родитель 06aad1d639
Коммит 4fbf0efd95
4 изменённых файлов: 149 добавлений и 18 удалений

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

@ -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},