Bug 1478776 - Part 7: Tune scroll events to only fire when the *relative* offset changes. r=botond

Internally, Gecko stores and updates the *absolute* offset between the visual
viewport and the page, however the spec demands that the scroll event be fired
whenever the *relative* offset between visual and layout viewport changes.

Differential Revision: https://phabricator.services.mozilla.com/D14044

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan Henning 2018-12-20 21:35:38 +00:00
Родитель 6011bbb22c
Коммит 29e0e9331a
7 изменённых файлов: 67 добавлений и 18 удалений

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

@ -7,6 +7,7 @@
#include "VisualViewport.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/ToString.h"
#include "nsIScrollableFrame.h"
#include "nsIDocShell.h"
#include "nsPresContext.h"
@ -175,23 +176,27 @@ void VisualViewport::FireResizeEvent() {
/* ================= Scroll event handling ================= */
void VisualViewport::PostScrollEvent() {
VVP_LOG("%p: PostScrollEvent\n", this);
void VisualViewport::PostScrollEvent(const nsPoint& aPrevRelativeOffset) {
VVP_LOG("%p: PostScrollEvent, prevRelativeOffset %s\n", this,
ToString(aPrevRelativeOffset).c_str());
if (mScrollEvent) {
return;
}
// The event constructor will register itself with the refresh driver.
if (nsPresContext* presContext = GetPresContext()) {
mScrollEvent = new VisualViewportScrollEvent(this, presContext);
mScrollEvent =
new VisualViewportScrollEvent(this, presContext, aPrevRelativeOffset);
VVP_LOG("%p: PostScrollEvent, created new event\n", this);
}
}
VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
VisualViewport* aViewport, nsPresContext* aPresContext)
VisualViewport* aViewport, nsPresContext* aPresContext,
const nsPoint& aPrevRelativeOffset)
: Runnable("VisualViewport::VisualViewportScrollEvent"),
mViewport(aViewport) {
mViewport(aViewport),
mPrevRelativeOffset(aPrevRelativeOffset) {
aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this);
}
@ -205,12 +210,28 @@ VisualViewport::VisualViewportScrollEvent::Run() {
void VisualViewport::FireScrollEvent() {
MOZ_ASSERT(mScrollEvent);
nsPoint prevRelativeOffset = mScrollEvent->PrevRelativeOffset();
mScrollEvent->Revoke();
mScrollEvent = nullptr;
VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
WidgetGUIEvent event(true, eScroll, nullptr);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
EventDispatcher::Dispatch(this, GetPresContext(), &event);
nsIPresShell* presShell = GetPresShell();
// Check whether the relative visual viewport offset actually changed - maybe
// both visual and layout viewport scrolled together and there was no change
// after all.
if (presShell) {
nsPoint curRelativeOffset =
presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
VVP_LOG(
"%p: FireScrollEvent, curRelativeOffset %s, "
"prevRelativeOffset %s\n",
this, ToString(curRelativeOffset).c_str(),
ToString(prevRelativeOffset).c_str());
if (curRelativeOffset != prevRelativeOffset) {
VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
WidgetGUIEvent event(true, eScroll, nullptr);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
EventDispatcher::Dispatch(this, GetPresContext(), &event);
}
}
}

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

@ -35,7 +35,7 @@ class VisualViewport final : public mozilla::DOMEventTargetHelper {
JS::Handle<JSObject*> aGivenProto) override;
void PostResizeEvent();
void PostScrollEvent();
void PostScrollEvent(const nsPoint& aPrevRelativeOffset);
// These two events are modelled after the ScrollEvent class in
// nsGfxScrollFrame.h.
@ -54,11 +54,23 @@ class VisualViewport final : public mozilla::DOMEventTargetHelper {
public:
NS_DECL_NSIRUNNABLE
VisualViewportScrollEvent(VisualViewport* aViewport,
nsPresContext* aPresContext);
nsPresContext* aPresContext,
const nsPoint& aPrevRelativeOffset);
void Revoke() { mViewport = nullptr; }
nsPoint PrevRelativeOffset() const { return mPrevRelativeOffset; }
private:
VisualViewport* mViewport;
// The VisualViewport "scroll" event is supposed to be fired only when the
// *relative* offset between visual and layout viewport changes. The two
// viewports are updated independently from each other, though, so the only
// thing we can do is note the fact that one of the inputs into the relative
// visual viewport offset changed and then check the offset again at the
// next refresh driver tick, just before the event is going to fire.
// Hopefully, at this point both visual and layout viewport positions have
// been updated, so that we're able to tell whether the relative offset did
// in fact change or not.
const nsPoint mPrevRelativeOffset;
};
private:

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

@ -55,7 +55,7 @@ function* test(testDriver) {
// a constant zero and therefore not cause any visual viewport scroll events
// to fire.
visScrEvt.unregister();
todo_is(visScrEvt.count, 0, "Got no visual viewport scroll events");
is(visScrEvt.count, 0, "Got no visual viewport scroll events");
visScrEvtInternal.unregister();
// Our internal visual viewport scroll event on the other hand only cares
// about the absolute offset of the visual viewport and should therefore

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

@ -175,7 +175,8 @@ static ScreenMargin ScrollFrame(nsIContent* aContent,
if (sf->IsRootScrollFrameOfDocument()) {
if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
shell->SetVisualViewportOffset(
CSSPoint::ToAppUnits(aRequest.GetScrollOffset()));
CSSPoint::ToAppUnits(aRequest.GetScrollOffset()),
shell->GetVisualViewportOffsetRelativeToLayoutViewport());
}
}
}

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

@ -10042,12 +10042,13 @@ void nsIPresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
}
}
void nsIPresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset) {
void nsIPresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
const nsPoint& aPrevRelativeOffset) {
if (mVisualViewportOffset != aScrollOffset) {
mVisualViewportOffset = aScrollOffset;
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
window->VisualViewport()->PostScrollEvent();
window->VisualViewport()->PostScrollEvent(aPrevRelativeOffset);
}
}
}

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

@ -1647,7 +1647,8 @@ class nsIPresShell : public nsStubDocumentObserver {
return mVisualViewportSize;
}
void SetVisualViewportOffset(const nsPoint& aScrollOffset);
void SetVisualViewportOffset(const nsPoint& aScrollOffset,
const nsPoint& aPrevRelativeOffset);
nsPoint GetVisualViewportOffset() const { return mVisualViewportOffset; }

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

@ -74,6 +74,7 @@
#include "mozilla/layers/LayerTransactionChild.h"
#include "mozilla/layers/ScrollLinkedEffectDetector.h"
#include "mozilla/Unused.h"
#include "VisualViewport.h"
#include "LayersLogging.h" // for Stringify
#include <algorithm>
#include <cstdlib> // for std::abs(int/long)
@ -2675,6 +2676,9 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
needFrameVisibilityUpdate = true;
}
nsPoint prevVVRelativeOffset =
presContext->PresShell()
->GetVisualViewportOffsetRelativeToLayoutViewport();
// notify the listeners.
for (uint32_t i = 0; i < mListeners.Length(); i++) {
@ -2736,7 +2740,7 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
// offset (e.g. by using nsIDOMWindowUtils.getVisualViewportOffset()
// in chrome JS code) before it's updated by the next APZ repaint,
// we could get incorrect results.
presContext->PresShell()->SetVisualViewportOffset(pt);
presContext->PresShell()->SetVisualViewportOffset(pt, prevVVRelativeOffset);
}
ScrollVisual();
@ -2856,6 +2860,15 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
nsPresContext::InteractionType::eScrollInteraction, TimeStamp::Now());
PostScrollEvent();
// If this is a viewport scroll, this could affect the relative offset
// between layout and visual viewport, so we might have to fire a visual
// viewport scroll event as well.
if (mIsRoot) {
if (auto* window = nsGlobalWindowInner::Cast(
mOuter->PresContext()->Document()->GetInnerWindow())) {
window->VisualViewport()->PostScrollEvent(prevVVRelativeOffset);
}
}
// notify the listeners.
for (uint32_t i = 0; i < mListeners.Length(); i++) {