Bug 1478776 - Part 6: Initial Visual Viewport event implementation. r=botond
authorJan Henning <jh+bugzilla@buttercookie.de>
Thu, 20 Dec 2018 21:35:26 +0000
changeset 451697 63749e66f2665bfb64988809fbd541874084cfe1
parent 451696 24951c9d732d81bc1747f089c683e2ffecd78ea0
child 451698 6f2e2faa3321fb36ac285310855c4bd3e25e8657
push id35251
push userccoroiu@mozilla.com
push dateFri, 21 Dec 2018 21:54:30 +0000
treeherdermozilla-central@74101900e7d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1478776
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1478776 - Part 6: Initial Visual Viewport event implementation. r=botond The event rate throttling mechanism is modelled on the logic for "scroll" events in nsGfxScrollFrame.cpp. That is 1. When a request to fire an event is posted to the VisualViewport, we create a new runnable for this and register it with the RefreshDriver. If we already have a pending runnable, calling VisualViewport->Post...Event() becomes a no-op. 2. When the RefreshDriver is ready, it executes the runnable, which in turn fires the actual event and then cleans itself up. To keep this patch manageable, we simply fire a scroll event every time the stored visual viewport offset is changed. Because we are storing the absolute offset of the viewport relative to the page, this behaviour doesn't match the spec, which demands that scroll events are fired only when the relative offset between visual and layout viewport changes. We'll fix this up in the next patch. Differential Revision: https://phabricator.services.mozilla.com/D14043
dom/base/VisualViewport.cpp
dom/base/VisualViewport.h
gfx/layers/apz/test/mochitest/helper_basic_pan.html
gfx/layers/apz/test/mochitest/helper_basic_zoom.html
layout/base/PresShell.cpp
layout/base/nsIPresShell.h
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
--- a/dom/base/VisualViewport.cpp
+++ b/dom/base/VisualViewport.cpp
@@ -1,25 +1,40 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VisualViewport.h"
+
+#include "mozilla/EventDispatcher.h"
 #include "nsIScrollableFrame.h"
 #include "nsIDocShell.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+
+#define VVP_LOG(...)
+// #define VVP_LOG(...) printf_stderr("VVP: " __VA_ARGS__)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow)
     : DOMEventTargetHelper(aWindow) {}
 
-VisualViewport::~VisualViewport() {}
+VisualViewport::~VisualViewport() {
+  if (mResizeEvent) {
+    mResizeEvent->Revoke();
+  }
+
+  if (mScrollEvent) {
+    mScrollEvent->Revoke();
+  }
+}
 
 /* virtual */
 JSObject* VisualViewport::WrapObject(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
   return VisualViewport_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 CSSSize VisualViewport::VisualViewportSize() const {
@@ -101,8 +116,101 @@ nsIPresShell* VisualViewport::GetPresShe
 
   nsIDocShell* docShell = window->GetDocShell();
   if (!docShell) {
     return nullptr;
   }
 
   return docShell->GetPresShell();
 }
+
+nsPresContext* VisualViewport::GetPresContext() const {
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return nullptr;
+  }
+
+  return presShell->GetPresContext();
+}
+
+/* ================= Resize event handling ================= */
+
+void VisualViewport::PostResizeEvent() {
+  VVP_LOG("%p: PostResizeEvent\n", this);
+  if (mResizeEvent) {
+    return;
+  }
+
+  // The event constructor will register itself with the refresh driver.
+  if (nsPresContext* presContext = GetPresContext()) {
+    mResizeEvent = new VisualViewportResizeEvent(this, presContext);
+    VVP_LOG("%p: PostResizeEvent, created new event\n", this);
+  }
+}
+
+VisualViewport::VisualViewportResizeEvent::VisualViewportResizeEvent(
+    VisualViewport* aViewport, nsPresContext* aPresContext)
+    : Runnable("VisualViewport::VisualViewportResizeEvent"),
+      mViewport(aViewport) {
+  aPresContext->RefreshDriver()->PostVisualViewportResizeEvent(this);
+}
+
+NS_IMETHODIMP
+VisualViewport::VisualViewportResizeEvent::Run() {
+  if (mViewport) {
+    mViewport->FireResizeEvent();
+  }
+  return NS_OK;
+}
+
+void VisualViewport::FireResizeEvent() {
+  MOZ_ASSERT(mResizeEvent);
+  mResizeEvent->Revoke();
+  mResizeEvent = nullptr;
+
+  VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this);
+  WidgetEvent event(true, eResize);
+  event.mFlags.mBubbles = false;
+  event.mFlags.mCancelable = false;
+  EventDispatcher::Dispatch(this, GetPresContext(), &event);
+}
+
+/* ================= Scroll event handling ================= */
+
+void VisualViewport::PostScrollEvent() {
+  VVP_LOG("%p: PostScrollEvent\n", this);
+  if (mScrollEvent) {
+    return;
+  }
+
+  // The event constructor will register itself with the refresh driver.
+  if (nsPresContext* presContext = GetPresContext()) {
+    mScrollEvent = new VisualViewportScrollEvent(this, presContext);
+    VVP_LOG("%p: PostScrollEvent, created new event\n", this);
+  }
+}
+
+VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
+    VisualViewport* aViewport, nsPresContext* aPresContext)
+    : Runnable("VisualViewport::VisualViewportScrollEvent"),
+      mViewport(aViewport) {
+  aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this);
+}
+
+NS_IMETHODIMP
+VisualViewport::VisualViewportScrollEvent::Run() {
+  if (mViewport) {
+    mViewport->FireScrollEvent();
+  }
+  return NS_OK;
+}
+
+void VisualViewport::FireScrollEvent() {
+  MOZ_ASSERT(mScrollEvent);
+  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);
+}
--- a/dom/base/VisualViewport.h
+++ b/dom/base/VisualViewport.h
@@ -29,21 +29,55 @@ class VisualViewport final : public mozi
   double Height() const;
   double Scale() const;
   IMPL_EVENT_HANDLER(resize)
   IMPL_EVENT_HANDLER(scroll)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  void PostResizeEvent();
+  void PostScrollEvent();
+
+  // These two events are modelled after the ScrollEvent class in
+  // nsGfxScrollFrame.h.
+  class VisualViewportResizeEvent : public Runnable {
+   public:
+    NS_DECL_NSIRUNNABLE
+    VisualViewportResizeEvent(VisualViewport* aViewport,
+                              nsPresContext* aPresContext);
+    void Revoke() { mViewport = nullptr; }
+
+   private:
+    VisualViewport* mViewport;
+  };
+
+  class VisualViewportScrollEvent : public Runnable {
+   public:
+    NS_DECL_NSIRUNNABLE
+    VisualViewportScrollEvent(VisualViewport* aViewport,
+                              nsPresContext* aPresContext);
+    void Revoke() { mViewport = nullptr; }
+
+   private:
+    VisualViewport* mViewport;
+  };
+
  private:
   virtual ~VisualViewport();
 
   CSSSize VisualViewportSize() const;
   CSSPoint VisualViewportOffset() const;
   CSSPoint LayoutViewportOffset() const;
   nsIPresShell* GetPresShell() const;
+  nsPresContext* GetPresContext() const;
+
+  void FireResizeEvent();
+  void FireScrollEvent();
+
+  RefPtr<VisualViewportResizeEvent> mResizeEvent;
+  RefPtr<VisualViewportScrollEvent> mScrollEvent;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_VisualViewport_h
--- a/gfx/layers/apz/test/mochitest/helper_basic_pan.html
+++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html
@@ -50,17 +50,17 @@ function* test(testDriver) {
   // changes. Even when they're both scrolling together, we may update their
   // positions independently, though, leading to some jitter in the offset and
   // triggering the event after all.
   // At least for the case here, where both viewports are the same size and we
   // have a freshly loaded page, we should however be able to keep the offset at
   // a constant zero and therefore not cause any visual viewport scroll events
   // to fire.
   visScrEvt.unregister();
-  is(visScrEvt.count, 0, "Got no visual viewport scroll events");
+  todo_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
   // definitively fire.
   todo(visScrEvtInternal.count > 0, "Got some mozvisualscroll events");
 }
 
 waitUntilApzStable()
--- a/gfx/layers/apz/test/mochitest/helper_basic_zoom.html
+++ b/gfx/layers/apz/test/mochitest/helper_basic_zoom.html
@@ -53,26 +53,26 @@ function* test(testDriver) {
   // Flush state and get the resolution we're at now
   yield waitForApzFlushedRepaints(testDriver);
   let final_resolution = getResolution();
   ok(final_resolution > initial_resolution, 'The final resolution (' + final_resolution + ') is greater after zooming in');
 
   // Check we've got the expected events.
   // Pinch-zooming the page should fire visual viewport resize events:
   visResEvt.unregister();
-  todo(visResEvt.count > 0, "Got some visual viewport resize events");
+  ok(visResEvt.count > 0, "Got some visual viewport resize events");
   visResEvtInternal.unregister();
   todo(visResEvtInternal.count > 0, "Got some mozvisualresize events");
 
   // We're pinch-zooming somewhere in the middle of the page, so the visual
   // viewport's coordinates change, too.
   // This is true both relative to the page (mozvisualscroll), as well as
   // relative to the layout viewport (visual viewport "scroll" event).
   visScrEvt.unregister();
-  todo(visScrEvt.count > 0, "Got some visual viewport scroll events");
+  ok(visScrEvt.count > 0, "Got some visual viewport scroll events");
   visScrEvtInternal.unregister();
   todo(visScrEvtInternal.count > 0, "Got some mozvisualscroll events");
 
   // Our internal events shouldn't leak to normal content.
   visResEvtContent.unregister();
   is(visResEvtContent.count, 0, "Got no mozvisualresize events in content");
   visScrEvtContent.unregister();
   is(visScrEvtContent.count, 0, "Got no mozvisualscroll events in content");
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -182,16 +182,17 @@
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/dom/ImageTracker.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsBindingManager.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
+#include "VisualViewport.h"
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 using namespace mozilla::tasktracer;
 #endif
 
 #define ANCHOR_SCROLL_FLAGS \
   (nsIPresShell::SCROLL_OVERFLOW_HIDDEN | nsIPresShell::SCROLL_NO_PARENT_FRAMES)
@@ -5168,16 +5169,19 @@ nsresult PresShell::SetResolutionAndScal
   state.mResolution = Some(aResolution);
   SetRenderingState(state);
   if (mMobileViewportManager) {
     mMobileViewportManager->ResolutionUpdated();
   }
   if (aOrigin != nsGkAtoms::apz) {
     mResolutionUpdated = true;
   }
+  if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+    window->VisualViewport()->PostResizeEvent();
+  }
 
   return NS_OK;
 }
 
 float PresShell::GetCumulativeResolution() {
   float resolution = GetResolution();
   nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
   if (parentCtx) {
@@ -10026,16 +10030,30 @@ void nsIPresShell::SetVisualViewportSize
     mVisualViewportSize.width = aWidth;
     mVisualViewportSize.height = aHeight;
 
     if (nsIScrollableFrame* rootScrollFrame =
             GetRootScrollFrameAsScrollable()) {
       rootScrollFrame->MarkScrollbarsDirtyForReflow();
     }
     MarkFixedFramesForReflow(nsIPresShell::eResize);
+
+    if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+      window->VisualViewport()->PostResizeEvent();
+    }
+  }
+}
+
+void nsIPresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset) {
+  if (mVisualViewportOffset != aScrollOffset) {
+    mVisualViewportOffset = aScrollOffset;
+
+    if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+      window->VisualViewport()->PostScrollEvent();
+    }
   }
 }
 
 nsPoint nsIPresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
   nsPoint result;
   if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
     result = GetVisualViewportOffset() - sf->GetScrollPosition();
   }
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1642,19 +1642,17 @@ class nsIPresShell : public nsStubDocume
   void SetVisualViewportSize(nscoord aWidth, nscoord aHeight);
   bool IsVisualViewportSizeSet() { return mVisualViewportSizeSet; }
   nsSize GetVisualViewportSize() {
     NS_ASSERTION(mVisualViewportSizeSet,
                  "asking for visual viewport size when its not set?");
     return mVisualViewportSize;
   }
 
-  void SetVisualViewportOffset(const nsPoint& aScrollOffset) {
-    mVisualViewportOffset = aScrollOffset;
-  }
+  void SetVisualViewportOffset(const nsPoint& aScrollOffset);
 
   nsPoint GetVisualViewportOffset() const { return mVisualViewportOffset; }
 
   nsPoint GetVisualViewportOffsetRelativeToLayoutViewport() const;
 
   virtual void WindowSizeMoveDone() = 0;
   virtual void SysColorChanged() = 0;
   virtual void ThemeChanged() = 0;
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1139,16 +1139,33 @@ void nsRefreshDriver::AddTimerAdjustment
 }
 
 void nsRefreshDriver::RemoveTimerAdjustmentObserver(
     nsATimerAdjustmentObserver* aObserver) {
   MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
   mTimerAdjustmentObservers.RemoveElement(aObserver);
 }
 
+void nsRefreshDriver::PostVisualViewportResizeEvent(
+    VVPResizeEvent* aResizeEvent) {
+  mVisualViewportResizeEvents.AppendElement(aResizeEvent);
+  EnsureTimerStarted();
+}
+
+void nsRefreshDriver::DispatchVisualViewportResizeEvents() {
+  // We're taking a hint from scroll events and only dispatch the current set
+  // of queued resize events. If additional events are posted in response to
+  // the current events being dispatched, we'll dispatch them on the next tick.
+  VisualViewportResizeEventArray events;
+  events.SwapElements(mVisualViewportResizeEvents);
+  for (auto& event : events) {
+    event->Run();
+  }
+}
+
 void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed) {
   if (aDelayed) {
     mDelayedScrollEvents.AppendElement(aScrollEvent);
   } else {
     mScrollEvents.AppendElement(aScrollEvent);
     EnsureTimerStarted();
   }
 }
@@ -1160,16 +1177,34 @@ void nsRefreshDriver::DispatchScrollEven
   // first. (Newly posted scroll events will be dispatched on the next tick.)
   ScrollEventArray events;
   events.SwapElements(mScrollEvents);
   for (auto& event : events) {
     event->Run();
   }
 }
 
+void nsRefreshDriver::PostVisualViewportScrollEvent(
+    VVPScrollEvent* aScrollEvent) {
+  mVisualViewportScrollEvents.AppendElement(aScrollEvent);
+  EnsureTimerStarted();
+}
+
+void nsRefreshDriver::DispatchVisualViewportScrollEvents() {
+  // Scroll events are one-shot, so after running them we can drop them.
+  // However, dispatching a scroll event can potentially cause more scroll
+  // events to be posted, so we move the initial set into a temporary array
+  // first. (Newly posted scroll events will be dispatched on the next tick.)
+  VisualViewportScrollEventArray events;
+  events.SwapElements(mVisualViewportScrollEvents);
+  for (auto& event : events) {
+    event->Run();
+  }
+}
+
 void nsRefreshDriver::AddPostRefreshObserver(
     nsAPostRefreshObserver* aObserver) {
   mPostRefreshObservers.AppendElement(aObserver);
 }
 
 void nsRefreshDriver::RemovePostRefreshObserver(
     nsAPostRefreshObserver* aObserver) {
   mPostRefreshObservers.RemoveElement(aObserver);
@@ -1729,16 +1764,17 @@ void nsRefreshDriver::Tick(VsyncId aId, 
     }
     // Make sure to not process observers which might have been removed
     // during previous iterations.
     if (!mResizeEventFlushObservers.RemoveElement(shell)) {
       continue;
     }
     shell->FireResizeEvent();
   }
+  DispatchVisualViewportResizeEvents();
 
   /*
    * The timer holds a reference to |this| while calling |Notify|.
    * However, implementations of |WillRefresh| are permitted to destroy
    * the pres context, which will cause our |mPresContext| to become
    * null.  If this happens, we must stop notifying observers.
    */
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
@@ -1752,16 +1788,17 @@ void nsRefreshDriver::Tick(VsyncId aId, 
         return;
       }
     }
 
     if (i == 1) {
       // This is the FlushType::Style case.
 
       DispatchScrollEvents();
+      DispatchVisualViewportScrollEvents();
       DispatchAnimationEvents();
       RunFullscreenSteps();
       RunFrameRequestCallbacks(aNowTime);
 
       if (mPresContext && mPresContext->GetPresShell()) {
         AutoTArray<nsIPresShell*, 16> observers;
         observers.AppendElements(mStyleFlushObservers);
         for (uint32_t j = observers.Length();
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -19,16 +19,17 @@
 #include "mozilla/WeakPtr.h"
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/dom/VisualViewport.h"
 #include "mozilla/layers/TransactionIdAllocator.h"
 #include "mozilla/VsyncDispatcher.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
 class nsINode;
@@ -87,16 +88,20 @@ class nsATimerAdjustmentObserver {
 class nsAPostRefreshObserver {
  public:
   virtual void DidRefresh() = 0;
 };
 
 class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
                               public nsARefreshObserver {
   using TransactionId = mozilla::layers::TransactionId;
+  using VVPResizeEvent =
+      mozilla::dom::VisualViewport::VisualViewportResizeEvent;
+  using VVPScrollEvent =
+      mozilla::dom::VisualViewport::VisualViewportScrollEvent;
 
  public:
   explicit nsRefreshDriver(nsPresContext* aPresContext);
   ~nsRefreshDriver();
 
   /**
    * Methods for testing, exposed via nsIDOMWindowUtils.  See
    * nsIDOMWindowUtils.advanceTimeAndRefresh for description.
@@ -141,19 +146,25 @@ class nsRefreshDriver final : public moz
                              mozilla::FlushType aFlushType);
   /**
    * Add / remove an observer wants to know the time when the refresh driver
    * updated the most recent refresh time due to its active timer changes.
    */
   void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
   void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
 
+  void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
+  void DispatchVisualViewportResizeEvents();
+
   void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
   void DispatchScrollEvents();
 
+  void PostVisualViewportScrollEvent(VVPScrollEvent* aScrollEvent);
+  void DispatchVisualViewportScrollEvents();
+
   /**
    * Add an observer that will be called after each refresh. The caller
    * must remove the observer before it is deleted. This does not trigger
    * refresh driver ticks.
    */
   void AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
   void RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
 
@@ -406,17 +417,19 @@ class nsRefreshDriver final : public moz
 
   void NotifyDOMContentLoaded();
 
   // Schedule a refresh so that any delayed events will run soon.
   void RunDelayedEventsSoon();
 
  private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
+  typedef nsTArray<RefPtr<VVPResizeEvent>> VisualViewportResizeEventArray;
   typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
+  typedef nsTArray<RefPtr<VVPScrollEvent>> VisualViewportScrollEventArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
   struct ImageStartData {
     ImageStartData() {}
 
     mozilla::Maybe<mozilla::TimeStamp> mStartTime;
     RequestTable mEntries;
   };
   typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
@@ -528,17 +541,19 @@ class nsRefreshDriver final : public moz
   // is used to determine whether or not to stop the timer, or restore it when
   // thawing the refresh driver. On the other hand these observers are intended
   // to be called when the timer is re-started and should not influence its
   // starting or stopping.
   nsTObserverArray<nsATimerAdjustmentObserver*> mTimerAdjustmentObservers;
   RequestTable mRequests;
   ImageStartTable mStartTable;
   AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
+  VisualViewportResizeEventArray mVisualViewportResizeEvents;
   ScrollEventArray mScrollEvents;
+  VisualViewportScrollEventArray mVisualViewportScrollEvents;
 
   // Scroll events on documents that might have events suppressed.
   ScrollEventArray mDelayedScrollEvents;
 
   AutoTArray<nsIPresShell*, 16> mResizeEventFlushObservers;
   AutoTArray<nsIPresShell*, 16> mDelayedResizeEventFlushObservers;
   AutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;