Initial prototype by Mantaroh draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Mon, 11 Jul 2016 14:03:59 +0900
changeset 394550 ffcb8e876a6d5e3a64f42425e5249d2285cf3bc4
parent 394512 859eb50fc1bc471f34f26e17cd9128690ac7bf87
child 394551 cf9177244f5930a4290cdc457ef701e2862f447c
push id24603
push userbballo@mozilla.com
push dateFri, 29 Jul 2016 23:28:33 +0000
milestone50.0a1
Initial prototype by Mantaroh MozReview-Commit-ID: D7OGRO2TT7X
dom/animation/Animation.h
dom/animation/ScrollTimeline.cpp
dom/animation/ScrollTimeline.h
dom/animation/moz.build
dom/base/Element.h
dom/webidl/ScrollTimeline.webidl
dom/webidl/moz.build
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsIScrollableFrame.h
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -311,16 +311,17 @@ public:
    * if any.
    * Any properties already contained in |aSetProperties| are not changed. Any
    * properties that are changed are added to |aSetProperties|.
    */
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
 
   void NotifyEffectTimingUpdated();
+  StickyTimeDuration EffectEnd() const;
 
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   void CancelNoUpdate();
   void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void PauseNoUpdate(ErrorResult& aRv);
   void ResumeAt(const TimeDuration& aReadyTime);
@@ -366,17 +367,16 @@ protected:
   /**
    * Remove this animation from the pending animation tracker and reset
    * mPendingState as necessary. The caller is responsible for resolving or
    * aborting the mReady promise as necessary.
    */
   void CancelPendingTasks();
 
   bool IsPossiblyOrphanedPendingAnimation() const;
-  StickyTimeDuration EffectEnd() const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
 
   RefPtr<AnimationTimeline> mTimeline;
   RefPtr<KeyframeEffectReadOnly> mEffect;
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimeline.cpp
@@ -0,0 +1,261 @@
+/* -*- 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 "ScrollTimeline.h"
+#include "mozilla/dom/ScrollTimelineBinding.h"
+#include "AnimationUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h" // For CSSPseudoElementType
+#include "nsDOMMutationObserver.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ScrollTimeline, AnimationTimeline,
+                                   mDocument)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ScrollTimeline,
+                                               AnimationTimeline)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ScrollTimeline)
+NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline)
+
+NS_IMPL_ADDREF_INHERITED(ScrollTimeline, AnimationTimeline)
+NS_IMPL_RELEASE_INHERITED(ScrollTimeline, AnimationTimeline)
+
+JSObject*
+ScrollTimeline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ScrollTimelineBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<ScrollTimeline>
+ScrollTimeline::Constructor(const GlobalObject& aGlobal,
+                            Element& scrollElement,
+                            const Orientation orientation,
+                            const Optional<double>& maxTime,
+                            ErrorResult& aRv)
+{
+  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
+  if (!doc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<ScrollTimeline> timeline = new ScrollTimeline(doc,
+                                                       &scrollElement,
+                                                       orientation,
+                                                       maxTime);
+  return timeline.forget();
+}
+
+Nullable<TimeDuration>
+ScrollTimeline::GetCurrentTime() const
+{
+  // Memo: We should retrun current time which related with scroll value.
+  return ToTimelineTime(GetCurrentTimeStamp());
+}
+
+TimeStamp
+ScrollTimeline::GetCurrentTimeStamp() const
+{
+  // Memo: We should return current timestamp wich related with scroll value.
+  //       The following implementation is DocumentTimeline. So we should remove
+  //       this implementation, and return new concept time.
+  //       Maybe, we don't need to use RefreshDriver.
+  // Idea: Perhaps, We will return 
+  nsRefreshDriver* refreshDriver = GetRefreshDriver();
+  TimeStamp refreshTime = refreshDriver
+                          ? refreshDriver->MostRecentRefresh()
+                          : TimeStamp();
+
+  // Always return the same object to benefit from return-value optimization.
+  TimeStamp result = !refreshTime.IsNull()
+                     ? refreshTime
+                     : mLastRefreshDriverTime;
+
+  // If we don't have a refresh driver and we've never had one use the
+  // timeline's zero time.
+  if (result.IsNull()) {
+    RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+    if (timing) {
+      result = timing->GetNavigationStartTimeStamp();
+      // Also, let this time represent the current refresh time. This way
+      // we'll save it as the last refresh time and skip looking up
+      // navigation timing each time.
+      refreshTime = result;
+    }
+  }
+
+  if (!refreshTime.IsNull()) {
+    mLastRefreshDriverTime = refreshTime;
+  }
+
+  return result;
+}
+
+Nullable<TimeDuration>
+ScrollTimeline::ToTimelineTime(const TimeStamp& aTimeStamp) const
+{
+  // Memo: We don't concern about clock time and zero time.
+  Nullable<TimeDuration> result; // Initializes to null
+  if (aTimeStamp.IsNull()) {
+    return result;
+  }
+
+  RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+  if (MOZ_UNLIKELY(!timing)) {
+    return result;
+  }
+
+  result.SetValue(aTimeStamp
+                  - timing->GetNavigationStartTimeStamp());
+  return result;
+}
+
+// memo: This function is member function of AnimationTimeline.
+void
+ScrollTimeline::NotifyAnimationUpdated(Animation& aAnimation)
+{
+  // MEMO: Should I pause animation when setting animation?
+  AnimationTimeline::NotifyAnimationUpdated(aAnimation);
+
+  if (!mIsObservingRefreshDriver) {
+    nsRefreshDriver* refreshDriver = GetRefreshDriver();
+    if (refreshDriver) {
+      refreshDriver->AddRefreshObserver(this, Flush_Style);
+      mIsObservingRefreshDriver = true;
+    }
+  }
+}
+
+void
+ScrollTimeline::WillRefresh(mozilla::TimeStamp aTime)
+{
+  MOZ_ASSERT(mIsObservingRefreshDriver);
+  MOZ_ASSERT(GetRefreshDriver(),
+             "Should be able to reach refresh driver from within WillRefresh");
+
+  bool needsTicks = false;
+  nsTArray<Animation*> animationsToRemove(mAnimations.Count());
+
+  nsAutoAnimationMutationBatch mb(mDocument);
+
+  for (Animation* animation = mAnimationOrder.getFirst(); animation;
+       animation = animation->getNext()) {
+    // Skip any animations that are longer need associated with this timeline.
+    if (animation->GetTimeline() != this) {
+      // If animation has some other timeline, it better not be also in the
+      // animation list of this timeline object!
+      MOZ_ASSERT(!animation->GetTimeline());
+      animationsToRemove.AppendElement(animation);
+      continue;
+    }
+
+    needsTicks |= animation->NeedsTicks();
+
+    // Even if |animation| doesn't need future ticks, we should still
+    // Tick it this time around since it might just need a one-off tick in
+    // order to dispatch events.
+    animation->Tick();
+
+    if (!animation->IsRelevant() && !animation->NeedsTicks()) {
+      animationsToRemove.AppendElement(animation);
+    }
+  }
+
+  for (Animation* animation : animationsToRemove) {
+    RemoveAnimation(animation);
+  }
+
+  if (!needsTicks) {
+    // We already assert that GetRefreshDriver() is non-null at the beginning
+    // of this function but we check it again here to be sure that ticking
+    // animations does not have any side effects that cause us to lose the
+    // connection with the refresh driver, such as triggering the destruction
+    // of mDocument's PresShell.
+    MOZ_ASSERT(GetRefreshDriver(),
+               "Refresh driver should still be valid at end of WillRefresh");
+    GetRefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
+    mIsObservingRefreshDriver = false;
+  }
+}
+
+void
+ScrollTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
+{
+  MOZ_ASSERT(!mIsObservingRefreshDriver,
+             "Timeline should not be observing the refresh driver before"
+             " it is created");
+
+  if (!mAnimationOrder.isEmpty()) {
+    aDriver->AddRefreshObserver(this, Flush_Style);
+    mIsObservingRefreshDriver = true;
+  }
+}
+
+void
+ScrollTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
+{
+  if (!mIsObservingRefreshDriver) {
+    return;
+  }
+
+  aDriver->RemoveRefreshObserver(this, Flush_Style);
+  mIsObservingRefreshDriver = false;
+}
+
+void
+ScrollTimeline::RemoveAnimation(Animation* aAnimation)
+{
+  AnimationTimeline::RemoveAnimation(aAnimation);
+
+  if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
+    MOZ_ASSERT(GetRefreshDriver(),
+               "Refresh driver should still be valid when "
+               "mIsObservingRefreshDriver is true");
+    GetRefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
+    mIsObservingRefreshDriver = false;
+  }
+}
+
+TimeStamp
+ScrollTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
+{
+  TimeStamp result;
+  RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+  if (MOZ_UNLIKELY(!timing)) {
+    return result;
+  }
+
+  result =
+    timing->GetNavigationStartTimeStamp() + aTimeDuration;
+  return result;
+}
+
+nsRefreshDriver*
+ScrollTimeline::GetRefreshDriver() const
+{
+  nsIPresShell* presShell = mDocument->GetShell();
+  if (MOZ_UNLIKELY(!presShell)) {
+    return nullptr;
+  }
+
+  nsPresContext* presContext = presShell->GetPresContext();
+  if (MOZ_UNLIKELY(!presContext)) {
+    return nullptr;
+  }
+
+  return presContext->RefreshDriver();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimeline.h
@@ -0,0 +1,144 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_ScrollTimeline_h
+#define mozilla_dom_ScrollTimeline_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/dom/ScrollTimelineBinding.h"
+#include "AnimationTimeline.h"
+#include "nsIDocument.h"
+#include "nsRefreshDriver.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/LinkedList.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+class ScrollObserver;
+
+class ScrollObserverImpl : public ScrollObserver {
+public:
+  ScrollObserverImpl(Element* elem, LinkedList<dom::Animation>* animationOrder) {
+    mElement = elem;
+    mAnimationOrder = animationOrder;
+  }
+  
+  void notify() override {
+    if (mElement) {
+      mCurrent = mElement->ScrollLeft();
+      mMin = mElement->ScrollLeftMin();
+      mMax = mElement->ScrollLeftMax();
+    }
+
+    // We will change the currentTime according to scroll volumes.
+    for (Animation* animation = mAnimationOrder->getFirst(); animation;
+         animation = animation->getNext()) {
+      StickyTimeDuration endTime = animation->EffectEnd();
+      TimeDuration time =
+        TimeDuration(endTime.MultDouble(mCurrent /(mMax - mMin)));
+
+      ErrorResult rv;
+      animation->SetCurrentTimeAsDouble(Nullable<double>(time.ToMilliseconds()), rv);
+    }
+  }
+private:
+  double mCurrent, mMin, mMax;
+  Element* mElement;
+  LinkedList<dom::Animation>* mAnimationOrder;
+};
+
+class ScrollTimeline final
+  : public AnimationTimeline
+  , public nsARefreshObserver
+{
+public:
+  ScrollTimeline(nsIDocument* aDocument,
+                 Element* aTarget,
+                 Orientation orientation,
+                 const Optional<double>& maxTime)
+    : AnimationTimeline(aDocument->GetParentObject())
+    , mDocument(aDocument)
+    , mIsObservingRefreshDriver(false)
+    , mElement(aTarget)
+  {
+    mScrollObserver = new ScrollObserverImpl(aTarget, &mAnimationOrder);
+    aTarget->RegistScrollTimelineObserver(mScrollObserver);
+  }
+
+protected:
+  virtual ~ScrollTimeline()
+  {
+    MOZ_ASSERT(!mIsObservingRefreshDriver, "Timeline should have disassociated"
+               " from the refresh driver before being destroyed");
+    if (mScrollObserver) {
+      mElement.get()->UnregistScrollTimelineObserver();
+    }
+  }
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ScrollTimeline,
+                                                         AnimationTimeline)
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  static already_AddRefed<ScrollTimeline>
+  Constructor(const GlobalObject& aGlobal,
+              Element& scrollElement,
+              const Orientation orientation,
+              const Optional<double>& maxTime,
+              ErrorResult& aRv);
+
+  virtual Nullable<TimeDuration> GetCurrentTime() const override;
+
+  bool TracksWallclockTime() const override
+  {
+    nsRefreshDriver* refreshDriver = GetRefreshDriver();
+    return !refreshDriver ||
+           !refreshDriver->IsTestControllingRefreshesEnabled();
+  }
+  Nullable<TimeDuration> ToTimelineTime(const TimeStamp& aTimeStamp) const
+                                                                     override;
+  TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override;
+
+  void NotifyAnimationUpdated(Animation& aAnimation) override;
+
+  void RemoveAnimation(Animation* aAnimation) override;
+
+  // nsARefreshObserver methods
+  void WillRefresh(TimeStamp aTime) override;
+
+  void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
+  void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
+
+protected:
+  TimeStamp GetCurrentTimeStamp() const;
+  nsRefreshDriver* GetRefreshDriver() const;
+
+  nsCOMPtr<nsIDocument> mDocument;
+
+  // The most recently used refresh driver time. This is used in cases where
+  // we don't have a refresh driver (e.g. because we are in a display:none
+  // iframe).
+  mutable TimeStamp mLastRefreshDriverTime;
+  bool mIsObservingRefreshDriver;
+
+  
+  Orientation mOrientation;
+  double mMaxTime;
+  ScrollObserverImpl *mScrollObserver;
+  RefPtr<Element> mElement;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScrollTimeline_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -11,16 +11,17 @@ EXPORTS.mozilla.dom += [
     'Animation.h',
     'AnimationEffectReadOnly.h',
     'AnimationEffectTiming.h',
     'AnimationEffectTimingReadOnly.h',
     'AnimationTimeline.h',
     'CSSPseudoElement.h',
     'DocumentTimeline.h',
     'KeyframeEffect.h',
+    'ScrollTimeline.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
     'AnimationPerformanceWarning.h',
     'AnimationTarget.h',
     'AnimationUtils.h',
     'AnimValuesStyleRule.h',
@@ -48,16 +49,17 @@ UNIFIED_SOURCES += [
     'CSSPseudoElement.cpp',
     'DocumentTimeline.cpp',
     'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
     'KeyframeEffectParams.cpp',
     'KeyframeUtils.cpp',
     'PendingAnimationTracker.cpp',
+    'ScrollTimeline.cpp',
     'TimingParams.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/layout/base',
     '/layout/style',
 ]
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -136,25 +136,73 @@ namespace dom {
 
 class Animation;
 class Link;
 class UndoManager;
 class DOMRect;
 class DOMRectList;
 class DestinationInsertionPointList;
 class Grid;
+class ScrollHandler;
 
 // IID for the dom::Element interface
 #define NS_ELEMENT_IID \
 { 0xc67ed254, 0xfd3b, 0x4b10, \
   { 0x96, 0xa2, 0xc5, 0x8b, 0x7b, 0x64, 0x97, 0xd1 } }
 
+class ScrollObserver {
+public:
+  ScrollObserver() {};
+  virtual void notify() { };
+};
+
+class ScrollHandlerImpl : public ScrollHandler{
+public:
+  ScrollHandlerImpl(ScrollObserver* observer)
+    : ScrollHandler()
+    , scrollObserver(observer) {
+  };
+
+  // Implemented after Element definitions.
+  void notify() override {
+    scrollObserver->notify();
+  }
+
+private:
+  ScrollObserver* scrollObserver;
+};
+
 class Element : public FragmentOrElement
 {
 public:
+
+  ScrollObserver* scrollObserver;
+  ScrollHandlerImpl* mScrollHandler;
+  void RegistScrollTimelineObserver(ScrollObserver* obs) {
+    scrollObserver = obs;
+    nsIScrollableFrame* sf = GetScrollFrame(nullptr, false);
+    if (!sf) {
+      printf("Element:%s, ScrollableFrame is not found.\n", __func__);
+      return;
+    }
+    mScrollHandler = new ScrollHandlerImpl(scrollObserver);
+    sf->AddScrollHandler(mScrollHandler);
+  }
+
+  void UnregistScrollTimelineObserver() {
+    scrollObserver = nullptr;
+
+    nsIScrollableFrame* sf = GetScrollFrame(nullptr, false);
+    if (!sf) {
+      printf("Element:%s, ScrollableFrame is not found.\n", __func__);
+      return;
+    }
+    sf->RemoveScrollHandler();
+  }
+
 #ifdef MOZILLA_INTERNAL_API
   explicit Element(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) :
     FragmentOrElement(aNodeInfo),
     mState(NS_EVENT_STATE_MOZ_READONLY)
   {
     MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::ELEMENT_NODE,
                "Bad NodeType in aNodeInfo");
     SetIsElement();
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ScrollTimeline.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://w3c.github.io/web-animations/#documenttimeline
+ *
+ * Copyright © 2016 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+enum Orientation { "vertical", "horizontal" };
+
+[Func="nsDocument::IsWebAnimationsEnabled",
+ Constructor (Element scrollSource,
+              Orientation orientation,
+              optional double maxTime)]
+interface ScrollTimeline : AnimationTimeline {
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -400,16 +400,17 @@ WEBIDL_FILES = [
     'Response.webidl',
     'RGBColor.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScreenOrientation.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'ScrollBoxObject.webidl',
+    'ScrollTimeline.webidl',
     'Selection.webidl',
     'ServiceWorker.webidl',
     'ServiceWorkerContainer.webidl',
     'ServiceWorkerGlobalScope.webidl',
     'ServiceWorkerMessageEvent.webidl',
     'SettingChangeNotification.webidl',
     'SettingsManager.webidl',
     'ShadowRoot.webidl',
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2815,16 +2815,20 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
     UpdateScrollbarPosition();
     if (!weakFrame.IsAlive()) {
       return;
     }
   }
 
   PostScrollEvent();
 
+  // Notify the scroll volume to ScrollTimeline.
+  nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
+  scrollable->NotifyScroll();
+
   // notify the listeners.
   for (uint32_t i = 0; i < mListeners.Length(); i++) {
     mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
   }
 
   nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
   if (docShell) {
     docShell->NotifyScrollObservers();
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -31,25 +31,46 @@ class nsRenderingContext;
 class nsIAtom;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 struct ContainerLayerParameters;
 namespace layers {
 class Layer;
 } // namespace layers
+namespace dom {
+class ScrollHandler
+{
+public:
+  ScrollHandler() {};
+  virtual void notify() { };
+};
+} // namespace dom
 } // namespace mozilla
 
 /**
  * Interface for frames that are scrollable. This interface exposes
  * APIs for examining scroll state, observing changes to scroll state,
  * and triggering scrolling.
  */
 class nsIScrollableFrame : public nsIScrollbarMediator {
 public:
+  mozilla::dom::ScrollHandler* scrollHandler = nullptr;
+  void AddScrollHandler(mozilla::dom::ScrollHandler* handler) {
+    scrollHandler = handler;
+  }
+  void RemoveScrollHandler() {
+    scrollHandler = nullptr;
+  }
+  void NotifyScroll() {
+    if (scrollHandler) {
+      scrollHandler->notify();
+    }
+  }
+  
   typedef mozilla::CSSIntPoint CSSIntPoint;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::layers::FrameMetrics FrameMetrics;
   typedef mozilla::layers::ScrollSnapInfo ScrollSnapInfo;
 
   NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
 
   /**
@@ -463,9 +484,10 @@ public:
   /**
    * Returns information required to determine where to snap to after a scroll.
    */
   virtual ScrollSnapInfo GetScrollSnapInfo() const = 0;
 
   virtual void SetScrollsClipOnUnscrolledOutOfFlow() = 0;
 };
 
+
 #endif