Bug 1774537 - Propagate snap target ids for async scroll operations triggered on the main-thread, but will be processed in APZ. r=botond

Ideally we can write a wpt for this case, but it doesn't work due to
bug 1674687 and ClampAndAlignWithPixels [1] in our code. In the wpt we can use
waitForScrollTo [2] to wait for the end of an async scroll operation, say
a scrollBy call with `behavior: smooth`, at some point where it's close to the
final scroll destination APZ reports a fractional scroll offset close to the
destination, it will be clamped and rounded to the final destination value, thus
waitForScrollTo considers it reached to the end of the scroll operation. At the
moment APZ considers the async scroll is still in progress, which mean the snap
target ids haven't yet been reported to back the main-thread unfortunately.

[1] https://searchfox.org/mozilla-central/rev/170f06a720ddabee44c728b05ad30b18b066acca/layout/generic/nsGfxScrollFrame.cpp#2847
[2] https://searchfox.org/mozilla-central/rev/170f06a720ddabee44c728b05ad30b18b066acca/testing/web-platform/tests/css/css-scroll-snap/support/common.js#84

Depends on D149496

Differential Revision: https://phabricator.services.mozilla.com/D149497
This commit is contained in:
Hiroyuki Ikezoe 2022-07-12 03:01:14 +00:00
parent 88f14ab50f
commit db52d38a69
8 changed files with 139 additions and 12 deletions

View File

@ -5334,9 +5334,9 @@ void AsyncPanZoomController::NotifyLayersUpdated(
}
if (scrollUpdate.GetMode() == ScrollMode::SmoothMsd) {
// FIXME: Need to use ScrollSnapTargetIds coming from the main-thread.
SmoothMsdScrollTo(CSSSnapTarget{destination},
scrollUpdate.GetScrollTriggeredByScript());
SmoothMsdScrollTo(
CSSSnapTarget{destination, scrollUpdate.GetSnapTargetIds()},
scrollUpdate.GetScrollTriggeredByScript());
} else {
MOZ_ASSERT(scrollUpdate.GetMode() == ScrollMode::Smooth);
MOZ_ASSERT(!scrollUpdate.WasTriggeredByScript());

View File

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Re-snapping to the last snapped element on APZ</title>
<script src="apz_test_utils.js"></script>
<script src="apz_test_native_event_utils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<style>
div {
position: absolute;
}
#scroller {
width: 100%;
height: 500px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.child {
width: 100%;
height: 100px;
background-color: blue;
scroll-snap-align: start;
}
</style>
</head>
<body>
<div id="scroller">
<div class="child" style="top: 0px;"></div>
<div class="child" style="top: 200px;"></div>
<div style="width: 100%; height: 2000px;"></div>
</div>
<script type="application/javascript">
async function test() {
let transformEndPromise = promiseTransformEnd();
scroller.scrollBy({left: 0, top: 100, behavior: "smooth"});
await transformEndPromise;
await promiseApzFlushedRepaints();
is(scroller.scrollTop, 200, "snap to 200px");
document.querySelectorAll(".child")[1].style.top = "400px";
is(scroller.scrollTop, 400, "re-snap to 400px");
}
waitUntilApzStable()
.then(test)
.then(subtestDone, subtestFailed);
</script>
</body>
</html>

View File

@ -22,6 +22,7 @@ const subtests = [
{"file": "helper_scroll_snap_resnap_after_async_scroll.html"},
{"file": "helper_scroll_snap_resnap_after_async_scroll.html",
"prefs": [["general.smoothScroll", false]]},
{"file": "helper_scroll_snap_resnap_after_async_scrollBy.html"},
];
if (isApzEnabled()) {

View File

@ -481,8 +481,58 @@ struct ParamTraits<mozilla::ScrollGeneration<T>>
: PlainOldDataSerializer<mozilla::ScrollGeneration<T>> {};
template <>
struct ParamTraits<mozilla::ScrollPositionUpdate>
: PlainOldDataSerializer<mozilla::ScrollPositionUpdate> {};
struct ParamTraits<mozilla::ScrollUpdateType>
: public ContiguousEnumSerializerInclusive<
mozilla::ScrollUpdateType, mozilla::ScrollUpdateType::Absolute,
mozilla::ScrollUpdateType::PureRelative> {};
template <>
struct ParamTraits<mozilla::ScrollMode>
: public ContiguousEnumSerializerInclusive<mozilla::ScrollMode,
mozilla::ScrollMode::Instant,
mozilla::ScrollMode::Normal> {};
template <>
struct ParamTraits<mozilla::ScrollOrigin>
: public ContiguousEnumSerializerInclusive<
mozilla::ScrollOrigin, mozilla::ScrollOrigin::None,
mozilla::ScrollOrigin::Scrollbars> {};
template <>
struct ParamTraits<mozilla::ScrollTriggeredByScript>
: public ContiguousEnumSerializerInclusive<
mozilla::ScrollTriggeredByScript,
mozilla::ScrollTriggeredByScript::No,
mozilla::ScrollTriggeredByScript::Yes> {};
template <>
struct ParamTraits<mozilla::ScrollPositionUpdate> {
typedef mozilla::ScrollPositionUpdate paramType;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.mScrollGeneration);
WriteParam(aWriter, aParam.mType);
WriteParam(aWriter, aParam.mScrollMode);
WriteParam(aWriter, aParam.mScrollOrigin);
WriteParam(aWriter, aParam.mDestination);
WriteParam(aWriter, aParam.mSource);
WriteParam(aWriter, aParam.mDelta);
WriteParam(aWriter, aParam.mTriggeredByScript);
WriteParam(aWriter, aParam.mSnapTargetIds);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mScrollGeneration) &&
ReadParam(aReader, &aResult->mType) &&
ReadParam(aReader, &aResult->mScrollMode) &&
ReadParam(aReader, &aResult->mScrollOrigin) &&
ReadParam(aReader, &aResult->mDestination) &&
ReadParam(aReader, &aResult->mSource) &&
ReadParam(aReader, &aResult->mDelta) &&
ReadParam(aReader, &aResult->mTriggeredByScript) &&
ReadParam(aReader, &aResult->mSnapTargetIds);
}
};
template <>
struct ParamTraits<mozilla::layers::ScrollMetadata>

View File

@ -59,7 +59,8 @@ ScrollPositionUpdate ScrollPositionUpdate::NewRelativeScroll(
/*static*/
ScrollPositionUpdate ScrollPositionUpdate::NewSmoothScroll(
ScrollOrigin aOrigin, nsPoint aDestination,
ScrollTriggeredByScript aTriggeredByScript) {
ScrollTriggeredByScript aTriggeredByScript,
UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) {
MOZ_ASSERT(aOrigin != ScrollOrigin::NotSpecified);
MOZ_ASSERT(aOrigin != ScrollOrigin::None);
@ -70,6 +71,9 @@ ScrollPositionUpdate ScrollPositionUpdate::NewSmoothScroll(
ret.mScrollOrigin = aOrigin;
ret.mDestination = CSSPoint::FromAppUnits(aDestination);
ret.mTriggeredByScript = aTriggeredByScript;
if (aSnapTargetIds) {
ret.mSnapTargetIds = *aSnapTargetIds;
}
return ret;
}

View File

@ -11,9 +11,15 @@
#include "nsPoint.h"
#include "mozilla/ScrollGeneration.h"
#include "mozilla/ScrollOrigin.h"
#include "mozilla/ScrollSnapTargetId.h"
#include "mozilla/ScrollTypes.h"
#include "Units.h"
namespace IPC {
template <typename T>
struct ParamTraits;
} // namespace IPC
namespace mozilla {
enum class ScrollUpdateType {
@ -41,6 +47,8 @@ enum class ScrollTriggeredByScript : bool { No, Yes };
* occurred independently on the compositor side.
*/
class ScrollPositionUpdate {
friend struct IPC::ParamTraits<mozilla::ScrollPositionUpdate>;
public:
// Constructor for IPC use only.
explicit ScrollPositionUpdate();
@ -59,9 +67,15 @@ class ScrollPositionUpdate {
// Create a ScrollPositionUpdate for a new absolute/smooth scroll, which
// animates smoothly to the given destination from whatever the current
// scroll position is in the receiver.
// If the smooth operation involves snapping to |aDestination|,
// |aSnapTargetIds| has snap-target-ids for snapping. Once after this smooth
// scroll finished on the target APZC, the ids will be reported back to the
// main-thread as the last snap target ids which will be used for re-snapping
// to the same snapped element(s).
static ScrollPositionUpdate NewSmoothScroll(
ScrollOrigin aOrigin, nsPoint aDestination,
ScrollTriggeredByScript aTriggeredByScript);
ScrollTriggeredByScript aTriggeredByScript,
UniquePtr<ScrollSnapTargetIds> aSnapTargetIds);
// Create a ScrollPositionUpdate for a new pure-relative scroll. The
// aMode parameter controls whether or not this is a smooth animation or
// instantaneous scroll.
@ -89,6 +103,7 @@ class ScrollPositionUpdate {
bool WasTriggeredByScript() const {
return mTriggeredByScript == ScrollTriggeredByScript::Yes;
}
const ScrollSnapTargetIds& GetSnapTargetIds() const { return mSnapTargetIds; }
friend std::ostream& operator<<(std::ostream& aStream,
const ScrollPositionUpdate& aUpdate);
@ -107,6 +122,7 @@ class ScrollPositionUpdate {
// mDelta is not populated when mType == Absolute || mType == Relative.
CSSPoint mDelta;
ScrollTriggeredByScript mTriggeredByScript;
ScrollSnapTargetIds mSnapTargetIds;
};
} // namespace mozilla

View File

@ -2634,7 +2634,7 @@ void ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
ApzSmoothScrollTo(mDestination, aParams.mOrigin,
aParams.mTriggeredByScript);
aParams.mTriggeredByScript, std::move(snapTargetIds));
return;
}
@ -8177,7 +8177,8 @@ void ScrollFrameHelper::AsyncScrollbarDragRejected() {
void ScrollFrameHelper::ApzSmoothScrollTo(
const nsPoint& aDestination, ScrollOrigin aOrigin,
ScrollTriggeredByScript aTriggeredByScript) {
ScrollTriggeredByScript aTriggeredByScript,
UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) {
if (mApzSmoothScrollDestination == Some(aDestination)) {
// If we already sent APZ a smooth-scroll request to this
// destination (i.e. it was the last request
@ -8199,7 +8200,7 @@ void ScrollFrameHelper::ApzSmoothScrollTo(
MOZ_ASSERT(aOrigin != ScrollOrigin::None);
mApzSmoothScrollDestination = Some(aDestination);
AppendScrollUpdate(ScrollPositionUpdate::NewSmoothScroll(
aOrigin, aDestination, aTriggeredByScript));
aOrigin, aDestination, aTriggeredByScript, std::move(aSnapTargetIds)));
nsIContent* content = mOuter->GetContent();
if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) {
@ -8250,12 +8251,13 @@ bool ScrollFrameHelper::SmoothScrollVisual(
// smooth scroll destination to send to APZ.
mDestination = GetVisualScrollRange().ClampPoint(aVisualViewportOffset);
UniquePtr<ScrollSnapTargetIds> snapTargetIds;
// Perform the scroll.
ApzSmoothScrollTo(mDestination,
aUpdateType == FrameMetrics::eRestore
? ScrollOrigin::Restore
: ScrollOrigin::Other,
ScrollTriggeredByScript::No);
ScrollTriggeredByScript::No, std::move(snapTargetIds));
return true;
}

View File

@ -876,7 +876,8 @@ class ScrollFrameHelper : public nsIReflowCallback {
// either the layout or the visual scroll range (APZ will happily smooth
// scroll to either).
void ApzSmoothScrollTo(const nsPoint& aDestination, ScrollOrigin aOrigin,
ScrollTriggeredByScript aTriggeredByScript);
ScrollTriggeredByScript aTriggeredByScript,
UniquePtr<ScrollSnapTargetIds> aSnapTargetIds);
// Removes any RefreshDriver observers we might have registered.
void RemoveObservers();