Bug 805746 - Send CustomScroll domevent to browser.js. r=cjones
authorBenjamin Chen <bechen@mozilla.com>
Tue, 25 Dec 2012 14:09:34 +0800
changeset 117086 305236f284cfbf1927bd62d8f0b181e6bcd8b79e
parent 117085 c55e74c4118462870cc28b3292bf114307bdd906
child 117087 e7b858cfccc27e4eaeade03d294f7e24bddb48f5
push id24083
push userdholbert@mozilla.com
push dateThu, 27 Dec 2012 21:54:50 +0000
treeherdermozilla-central@65de3fdfe888 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs805746
milestone20.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 805746 - Send CustomScroll domevent to browser.js. r=cjones
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/browser-element/BrowserElementParent.cpp
dom/browser-element/BrowserElementParent.h
dom/browser-element/Makefile.in
dom/browser-element/nsAsyncScrollEventDetail.cpp
dom/browser-element/nsAsyncScrollEventDetail.h
dom/browser-element/nsIAsyncScrollEventDetail.idl
dom/tests/mochitest/general/test_interfaces.html
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/layers/ipc/GeckoContentController.h
layout/ipc/RenderFrameParent.cpp
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -491,16 +491,17 @@ using mozilla::dom::indexedDB::IDBWrappe
 
 #include "nsIDOMNavigatorSystemMessages.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraControl.h"
 #include "DOMCameraCapabilities.h"
 #include "DOMError.h"
 #include "DOMRequest.h"
 #include "nsIOpenWindowEventDetail.h"
+#include "nsIAsyncScrollEventDetail.h"
 #include "nsIDOMGlobalObjectConstructor.h"
 #include "nsIDOMCanvasRenderingContext2D.h"
 #include "DOMFileHandle.h"
 #include "FileRequest.h"
 #include "LockedFile.h"
 #include "GeneratedEvents.h"
 #include "nsDebug.h"
 
@@ -1539,16 +1540,18 @@ static nsDOMClassInfoData sClassInfoData
 
   NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DOMRequest, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(OpenWindowEventDetail, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(AsyncScrollEventDetail, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA_WITH_NAME(DOMFileHandle, FileHandle, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(FileRequest, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(LockedFile, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 #ifdef MOZ_SYS_MSG
@@ -4011,16 +4014,20 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(OpenWindowEventDetail, nsIOpenWindowEventDetail)
     DOM_CLASSINFO_MAP_ENTRY(nsIOpenWindowEventDetail)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(AsyncScrollEventDetail, nsIAsyncScrollEventDetail)
+    DOM_CLASSINFO_MAP_ENTRY(nsIAsyncScrollEventDetail)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(DOMFileHandle, nsIDOMFileHandle)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMFileHandle)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(FileRequest, nsIDOMFileRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMFileRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -448,16 +448,17 @@ DOMCI_CLASS(BluetoothPropertyEvent)
 
 DOMCI_CLASS(CameraManager)
 DOMCI_CLASS(CameraControl)
 DOMCI_CLASS(CameraCapabilities)
 
 DOMCI_CLASS(DOMError)
 DOMCI_CLASS(DOMRequest)
 DOMCI_CLASS(OpenWindowEventDetail)
+DOMCI_CLASS(AsyncScrollEventDetail)
 
 DOMCI_CLASS(DOMFileHandle)
 DOMCI_CLASS(FileRequest)
 DOMCI_CLASS(LockedFile)
 
 #ifdef MOZ_SYS_MSG
 DOMCI_CLASS(MozActivity)
 #endif
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -14,16 +14,17 @@
 
 #include "BrowserElementParent.h"
 #include "nsHTMLIFrameElement.h"
 #include "nsOpenWindowEventDetail.h"
 #include "nsEventDispatcher.h"
 #include "nsIDOMCustomEvent.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsVariant.h"
+#include "nsAsyncScrollEventDetail.h"
 
 using mozilla::dom::Element;
 using mozilla::dom::TabParent;
 
 namespace {
 
 /**
  * Create an <iframe mozbrowser> owned by the same document as
@@ -63,16 +64,50 @@ CreateIframe(Element* aOpenerFrameElemen
   popupFrameElement->SetAttr(kNameSpaceID_None, nsGkAtoms::Remote,
                              aRemote ? NS_LITERAL_STRING("true") :
                                        NS_LITERAL_STRING("false"),
                              /* aNotify = */ false);
 
   return popupFrameElement.forget();
 }
 
+bool
+DispatchCustomDOMEvent(Element* aFrameElement, const nsAString& aEventName,
+                       nsISupports *aDetailValue)
+{
+  NS_ENSURE_TRUE(aFrameElement, false);
+  nsIPresShell *shell = aFrameElement->OwnerDoc()->GetShell();
+  nsRefPtr<nsPresContext> presContext;
+  if (shell) {
+    presContext = shell->GetPresContext();
+  }
+
+  nsCOMPtr<nsIDOMEvent> domEvent;
+  nsEventDispatcher::CreateEvent(presContext, nullptr,
+                                 NS_LITERAL_STRING("customevent"),
+                                 getter_AddRefs(domEvent));
+  NS_ENSURE_TRUE(domEvent, false);
+
+  nsCOMPtr<nsIWritableVariant> detailVariant = new nsVariant();
+  nsresult rv = detailVariant->SetAsISupports(aDetailValue);
+  NS_ENSURE_SUCCESS(rv, false);
+  nsCOMPtr<nsIDOMCustomEvent> customEvent = do_QueryInterface(domEvent);
+  NS_ENSURE_TRUE(customEvent, false);
+  customEvent->InitCustomEvent(aEventName,
+                               /* bubbles = */ true,
+                               /* cancelable = */ false,
+                               detailVariant);
+  customEvent->SetTrusted(true);
+  // Dispatch the event.
+  nsEventStatus status = nsEventStatus_eIgnore;
+  rv = nsEventDispatcher::DispatchDOMEvent(aFrameElement, nullptr,
+                                           domEvent, presContext, &status);
+  return NS_SUCCEEDED(rv);
+}
+
 /**
  * Dispatch a mozbrowseropenwindow event to the given opener frame element.
  * The "popup iframe" (event.detail.frameElement) will be |aPopupFrameElement|.
  *
  * Returns true iff there were no unexpected failures and the window.open call
  * was accepted by the embedder.
  */
 bool
@@ -85,59 +120,34 @@ DispatchOpenWindowEvent(Element* aOpener
   // Dispatch a CustomEvent at aOpenerFrameElement with a detail object
   // (nsIOpenWindowEventDetail) containing aPopupFrameElement, aURL, aName, and
   // aFeatures.
 
   // Create the event's detail object.
   nsRefPtr<nsOpenWindowEventDetail> detail =
     new nsOpenWindowEventDetail(aURL, aName, aFeatures,
                                 aPopupFrameElement->AsDOMNode());
-  nsCOMPtr<nsIWritableVariant> detailVariant = new nsVariant();
-  nsresult rv = detailVariant->SetAsISupports(detail);
-  NS_ENSURE_SUCCESS(rv, false);
 
-  // Create the CustomEvent.
-  nsIPresShell *shell = aOpenerFrameElement->OwnerDoc()->GetShell();
-  nsRefPtr<nsPresContext> presContext;
-  if (shell) {
-    presContext = shell->GetPresContext();
-  }
-
-  nsCOMPtr<nsIDOMEvent> domEvent;
-  nsEventDispatcher::CreateEvent(presContext, nullptr,
-                                 NS_LITERAL_STRING("customevent"),
-                                 getter_AddRefs(domEvent));
-  NS_ENSURE_TRUE(domEvent, false);
-
-  nsCOMPtr<nsIDOMCustomEvent> customEvent = do_QueryInterface(domEvent);
-  NS_ENSURE_TRUE(customEvent, false);
-  customEvent->InitCustomEvent(NS_LITERAL_STRING("mozbrowseropenwindow"),
-                               /* bubbles = */ true,
-                               /* cancelable = */ false,
-                               detailVariant);
-  customEvent->SetTrusted(true);
-
-  // Dispatch the event.
-  nsEventStatus status = nsEventStatus_eIgnore;
-  rv = nsEventDispatcher::DispatchDOMEvent(aOpenerFrameElement, nullptr,
-                                           domEvent, presContext, &status);
-  NS_ENSURE_SUCCESS(rv, false);
+  bool dispatchSucceeded =
+    DispatchCustomDOMEvent(aOpenerFrameElement,
+                           NS_LITERAL_STRING("mozbrowseropenwindow"),
+                           detail);
 
   // If the iframe is not in some document's DOM at this point, the embedder
   // has "blocked" the popup.
-  return aPopupFrameElement->IsInDoc();
+  return (dispatchSucceeded && aPopupFrameElement->IsInDoc());
 }
 
 } // anonymous namespace
 
 namespace mozilla {
 
 /*static*/ bool
-BrowserElementParent::OpenWindowOOP(mozilla::dom::TabParent* aOpenerTabParent,
-                                    mozilla::dom::TabParent* aPopupTabParent,
+BrowserElementParent::OpenWindowOOP(TabParent* aOpenerTabParent,
+                                    TabParent* aPopupTabParent,
                                     const nsAString& aURL,
                                     const nsAString& aName,
                                     const nsAString& aFeatures)
 {
   // Create an iframe owned by the same document which owns openerFrameElement.
   nsCOMPtr<Element> openerFrameElement =
     do_QueryInterface(aOpenerTabParent->GetOwnerElement());
   NS_ENSURE_TRUE(openerFrameElement, false);
@@ -225,9 +235,26 @@ BrowserElementParent::OpenWindowInProces
   frameLoader->GetDocShell(getter_AddRefs(docshell));
   NS_ENSURE_TRUE(docshell, false);
 
   nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docshell);
   window.forget(aReturnWindow);
   return !!*aReturnWindow;
 }
 
+bool
+BrowserElementParent::DispatchAsyncScrollEvent(TabParent* aTabParent,
+                                               const gfx::Rect& aContentRect,
+                                               const gfx::Size& aContentSize)
+{
+  nsIDOMElement* element = aTabParent->GetOwnerElement();
+  nsCOMPtr<Element> frameElement = do_QueryInterface(element);
+  // Create the event's detail object.
+  nsRefPtr<nsAsyncScrollEventDetail> detail =
+    new nsAsyncScrollEventDetail(aContentRect.x, aContentRect.y,
+                                 aContentRect.width, aContentRect.height,
+                                 aContentSize.width, aContentSize.height);
+  return DispatchCustomDOMEvent(frameElement,
+                                NS_LITERAL_STRING("mozbrowserasyncscroll"),
+                                detail);
+}
+
 } // namespace mozilla
--- a/dom/browser-element/BrowserElementParent.h
+++ b/dom/browser-element/BrowserElementParent.h
@@ -11,16 +11,21 @@ class nsIDOMWindow;
 class nsIURI;
 
 namespace mozilla {
 
 namespace dom {
 class TabParent;
 }
 
+namespace gfx{
+struct Rect;
+struct Size;
+}
+
 /**
  * BrowserElementParent implements a portion of the parent-process side of
  * <iframe mozbrowser>.
  *
  * Most of the parent-process side of <iframe mozbrowser> is implemented in
  * BrowserElementParent.js.  This file implements the few parts of this
  * functionality which must be written in C++.
  *
@@ -58,18 +63,18 @@ public:
    * @param aOpenerTabParent the TabParent whose TabChild called window.open.
    * @param aPopupTabParent the TabParent inside which the opened window will
    *                        live.
    * @return true on success, false otherwise.  Failure is not (necessarily)
    *         an error; it may indicate that the embedder simply rejected the
    *         window.open request.
    */
   static bool
-  OpenWindowOOP(mozilla::dom::TabParent* aOpenerTabParent,
-                mozilla::dom::TabParent* aPopupTabParent,
+  OpenWindowOOP(dom::TabParent* aOpenerTabParent,
+                dom::TabParent* aPopupTabParent,
                 const nsAString& aURL,
                 const nsAString& aName,
                 const nsAString& aFeatures);
 
   /**
    * Handle a window.open call from an in-process <iframe mozbrowser>.
    *
    * As with OpenWindowOOP, we return true if the window.open request
@@ -81,13 +86,33 @@ public:
    * @param aURI the URI the new window should load.  May be null.
    */
   static bool
   OpenWindowInProcess(nsIDOMWindow* aOpenerWindow,
                       nsIURI* aURI,
                       const nsAString& aName,
                       const nsACString& aFeatures,
                       nsIDOMWindow** aReturnWindow);
+
+  /**
+   * Fire a mozbrowserasyncscroll CustomEvent on the given TabParent's frame element.
+   * This event's detail is an instance of nsIAsyncScrollEventDetail.
+   *
+   * @param aContentRect: The portion of the page which is currently visible
+   *                      onscreen in CSS pixels.
+   *
+   * @param aContentSize: The content width/height in CSS pixels.
+   *
+   * aContentRect.top + aContentRect.height may be larger than aContentSize.height.
+   * This indicates that the content is over-scrolled, which occurs when the
+   * page "rubber-bands" after being scrolled all the way to the bottom.
+   * Similarly, aContentRect.left + aContentRect.width may be greater than
+   * contentSize.width, and both left and top may be negative.
+   */
+  static bool
+  DispatchAsyncScrollEvent(dom::TabParent* aTabParent,
+                           const gfx::Rect& aContentRect,
+                           const gfx::Size& aContentSize);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/browser-element/Makefile.in
+++ b/dom/browser-element/Makefile.in
@@ -17,29 +17,32 @@ FORCE_STATIC_LIB = 1
 FAIL_ON_WARNINGS := 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
 TEST_DIRS += mochitest
 
 XPIDLSRCS = \
   nsIOpenWindowEventDetail.idl \
+  nsIAsyncScrollEventDetail.idl \
   $(NULL)
 
 EXPORTS = \
   nsOpenWindowEventDetail.h \
+  nsAsyncScrollEventDetail.h \
   $(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 EXPORTS_mozilla = \
   BrowserElementParent.h \
   $(NULL)
 
 CPPSRCS = \
   nsOpenWindowEventDetail.cpp \
+  nsAsyncScrollEventDetail.cpp \
   BrowserElementParent.cpp \
   $(NULL)
 
 EXTRA_COMPONENTS = \
   BrowserElementParent.js \
   BrowserElementParent.manifest \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/nsAsyncScrollEventDetail.cpp
@@ -0,0 +1,62 @@
+/* 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 "nsAsyncScrollEventDetail.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIDOMClassInfo.h"
+#include "nsIClassInfo.h"
+#include "nsDOMClassInfo.h"
+
+NS_IMPL_ADDREF(nsAsyncScrollEventDetail)
+NS_IMPL_RELEASE(nsAsyncScrollEventDetail)
+NS_INTERFACE_MAP_BEGIN(nsAsyncScrollEventDetail)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncScrollEventDetail)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(AsyncScrollEventDetail)
+NS_INTERFACE_MAP_END
+
+DOMCI_DATA(AsyncScrollEventDetail, nsAsyncScrollEventDetail)
+
+/* readonly attribute float top; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetTop(float *aTop)
+{
+  *aTop = mTop;
+  return NS_OK;
+}
+
+/* readonly attribute float left; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetLeft(float *aLeft)
+{
+  *aLeft = mLeft;
+  return NS_OK;
+}
+
+/* readonly attribute float width; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetWidth(float *aWidth)
+{
+  *aWidth = mWidth;
+  return NS_OK;
+}
+
+/* readonly attribute float height; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetHeight(float *aHeight)
+{
+  *aHeight = mHeight;
+  return NS_OK;
+}
+
+/* readonly attribute float scrollWidth; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetScrollWidth(float *aScrollWidth)
+{
+  *aScrollWidth = mScrollWidth;
+  return NS_OK;
+}
+
+/* readonly attribute float scrollHeight; */
+NS_IMETHODIMP nsAsyncScrollEventDetail::GetScrollHeight(float *aScrollHeight)
+{
+  *aScrollHeight = mScrollHeight;
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/nsAsyncScrollEventDetail.h
@@ -0,0 +1,36 @@
+/* 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 "nsIAsyncScrollEventDetail.h"
+
+/**
+ * When we send a mozbrowserasyncscroll event (an instance of CustomEvent), we
+ * use an instance of this class as the event's detail.
+ */
+class nsAsyncScrollEventDetail : public nsIAsyncScrollEventDetail
+{
+public:
+  nsAsyncScrollEventDetail(const float left, const float top,
+                           const float width, const float height,
+                           const float contentWidth, const float contentHeigh)
+    : mTop(top)
+    , mLeft(left)
+    , mWidth(width)
+    , mHeight(height)
+    , mScrollWidth(contentWidth)
+    , mScrollHeight(contentHeigh)
+  {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIASYNCSCROLLEVENTDETAIL
+
+private:
+  virtual ~nsAsyncScrollEventDetail() {}
+  const float mTop;
+  const float mLeft;
+  const float mWidth;
+  const float mHeight;
+  const float mScrollWidth;
+  const float mScrollHeight;
+};
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/nsIAsyncScrollEventDetail.idl
@@ -0,0 +1,29 @@
+/* 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 "nsISupports.idl"
+
+/**
+ * When we send a mozbrowserasyncscroll event (an instance of CustomEvent), we
+ * use an instance of this interface as the event's detail.
+ * [left, top, width, height]: The portion of the page which is currently 
+ * visible onscreen in CSS pixels.
+ * [scrollWidth, scrollHeight]: The content width/height in CSS pixels.
+ *
+ * top + height may be larger than scrollHeight.
+ * This indicates that the content is over-scrolled, which occurs when the
+ * page "rubber-bands" after being scrolled all the way to the bottom.
+ * Similarly, left + width may be greater than scrollWidth, 
+ * and both left and top may be negative.
+ */
+[scriptable, uuid(d0c13577-31e6-4701-b9b7-3535bbe19fe6)]
+interface nsIAsyncScrollEventDetail : nsISupports
+{
+  readonly attribute float top;
+  readonly attribute float left;
+  readonly attribute float width;
+  readonly attribute float height;
+  readonly attribute float scrollWidth;
+  readonly attribute float scrollHeight;
+};
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -523,17 +523,18 @@ var interfaceNamesInGlobalScope =
     "MozNetworkStatsManager",
     "MozNetworkStats",
     "MozNetworkStatsData",
     "RTCSessionDescription",
     "RTCIceCandidate",
     "RTCPeerConnection",
     "LocalMediaStream",
     "CSSConditionRule",
-    "CSSGroupingRule"
+    "CSSGroupingRule",
+    "AsyncScrollEventDetail"
   ]
 
 for (var i in SpecialPowers.Components.interfaces) {
   var s = i.toString();
   var name = null;
   if (s.indexOf("nsIDOM") == 0) {
     name = s.substring("nsIDOM".length);
   } else if (s.indexOf("nsI") == 0) {
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -87,33 +87,43 @@ AsyncPanZoomController::AsyncPanZoomCont
      mX(this),
      mY(this),
      mAllowZoom(true),
      mMinZoom(MIN_ZOOM),
      mMaxZoom(MAX_ZOOM),
      mMonitor("AsyncPanZoomController"),
      mLastSampleTime(TimeStamp::Now()),
      mState(NOTHING),
+     mLastAsyncScrollTime(TimeStamp::Now()),
+     mLastAsyncScrollOffset(0, 0),
+     mCurrentAsyncScrollOffset(0, 0),
+     mAsyncScrollTimeoutTask(nullptr),
+     mAsyncScrollThrottleTime(100),
+     mAsyncScrollTimeout(300),
      mDPI(72),
      mWaitingForContentToPaint(false),
      mDisableNextTouchBatch(false),
      mHandlingTouchQueue(false)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 
   SetDPI(mDPI);
 
   if (!gComputedTimingFunction) {
     gComputedTimingFunction = new ComputedTimingFunction();
     gComputedTimingFunction->Init(
       nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
     ClearOnShutdown(&gComputedTimingFunction);
   }
+
+  Preferences::GetUint("apzc.asyncscroll.throttle", &mAsyncScrollThrottleTime);
+  Preferences::GetUint("apzc.asyncscroll.timeout", &mAsyncScrollTimeout);
 }
 
 AsyncPanZoomController::~AsyncPanZoomController() {
 
 }
 
 static gfx::Point
 WidgetSpaceToCompensatedViewportSpace(const gfx::Point& aPoint,
@@ -362,16 +372,21 @@ nsEventStatus AsyncPanZoomController::On
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
   if (mDisableNextTouchBatch) {
     mDisableNextTouchBatch = false;
     return nsEventStatus_eIgnore;
   }
 
+  {
+    MonitorAutoLock monitor(mMonitor);
+    SendAsyncScrollEvent();
+  }
+
   switch (mState) {
   case FLING:
     // Should never happen.
     NS_WARNING("Received impossible touch end in OnTouchEnd.");
     // Fall through.
   case ANIMATING_ZOOM:
   case NOTHING:
     // May happen if the user double-taps and drags without lifting after the
@@ -674,16 +689,17 @@ bool AsyncPanZoomController::DoFling(con
 
   bool shouldContinueFlingX = mX.FlingApplyFrictionOrCancel(aDelta),
        shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta);
   // If we shouldn't continue the fling, let's just stop and repaint.
   if (!shouldContinueFlingX && !shouldContinueFlingY) {
     // Bring the resolution back in sync with the zoom, in case we scaled down
     // the zoom while accelerating.
     SetZoomAndResolution(mFrameMetrics.mZoom.width);
+    SendAsyncScrollEvent();
     RequestContentRepaint();
     mState = NOTHING;
     return false;
   }
 
   // We want to inversely scale it because when you're zoomed further in, a
   // larger swipe should move you a shorter distance.
   gfxFloat inverseResolution = 1 / CalculateResolution(mFrameMetrics).width;
@@ -948,16 +964,18 @@ void AsyncPanZoomController::RequestCont
   if (fabsf(oldDisplayPort.x - newDisplayPort.x) < EPSILON &&
       fabsf(oldDisplayPort.y - newDisplayPort.y) < EPSILON &&
       fabsf(oldDisplayPort.width - newDisplayPort.width) < EPSILON &&
       fabsf(oldDisplayPort.height - newDisplayPort.height) < EPSILON &&
       mFrameMetrics.mResolution.width == mLastPaintRequestMetrics.mResolution.width) {
     return;
   }
 
+  SendAsyncScrollEvent();
+
   // Cache the zoom since we're temporarily changing it for
   // acceleration-scaled painting.
   gfxFloat actualZoom = mFrameMetrics.mZoom.width;
   // Calculate the factor of acceleration based on the faster of the two axes.
   float accelerationFactor =
     clamped(NS_MAX(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()),
             float(MIN_ZOOM) / 2.0f, float(MAX_ZOOM));
   // Scale down the resolution a bit based on acceleration.
@@ -974,16 +992,26 @@ void AsyncPanZoomController::RequestCont
                       mFrameMetrics));
   mLastPaintRequestMetrics = mFrameMetrics;
   mWaitingForContentToPaint = true;
 
   // Set the zoom back to what it was for the purpose of logic control.
   mFrameMetrics.mZoom = gfxSize(actualZoom, actualZoom);
 }
 
+void
+AsyncPanZoomController::FireAsyncScrollOnTimeout()
+{
+  if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
+    MonitorAutoLock monitor(mMonitor);
+    SendAsyncScrollEvent();
+  }
+  mAsyncScrollTimeoutTask = nullptr;
+}
+
 bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
                                                             ContainerLayer* aLayer,
                                                             ViewTransform* aNewTransform) {
   // The eventual return value of this function. The compositor needs to know
   // whether or not to advance by a frame as soon as it can. For example, if a
   // fling is happening, it has to keep compositing so that the animation is
   // smooth. If an animation frame is requested, it is the compositor's
   // responsibility to schedule a composite.
@@ -1031,16 +1059,17 @@ bool AsyncPanZoomController::SampleConte
       );
 
       requestAnimationFrame = true;
 
       if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) {
         // Bring the resolution in sync with the zoom.
         SetZoomAndResolution(mFrameMetrics.mZoom.width);
         mState = NOTHING;
+        SendAsyncScrollEvent();
         RequestContentRepaint();
       }
 
       break;
     }
     default:
       break;
     }
@@ -1051,16 +1080,44 @@ bool AsyncPanZoomController::SampleConte
     // during runtime, but we must wait for Gecko to repaint.
     localScale = CalculateResolution(mFrameMetrics);
 
     if (frame.IsScrollable()) {
       metricsScrollOffset = frame.GetScrollOffsetInLayerPixels();
     }
 
     scrollOffset = gfxPoint(mFrameMetrics.mScrollOffset.x, mFrameMetrics.mScrollOffset.y);
+    mCurrentAsyncScrollOffset = mFrameMetrics.mScrollOffset;
+  }
+
+  // Cancel the mAsyncScrollTimeoutTask because we will fire a
+  // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again.
+  if (mAsyncScrollTimeoutTask) {
+    mAsyncScrollTimeoutTask->Cancel();
+    mAsyncScrollTimeoutTask = nullptr;
+  }
+  // Fire the mozbrowserasyncscroll event immediately if it's been
+  // sAsyncScrollThrottleTime ms since the last time we fired the event and the
+  // current scroll offset is different than the mLastAsyncScrollOffset we sent
+  // with the last event.
+  // Otherwise, start a timer to fire the event sAsyncScrollTimeout ms from now.
+  TimeDuration delta = aSampleTime - mLastAsyncScrollTime;
+  if (delta.ToMilliseconds() > mAsyncScrollThrottleTime &&
+      mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
+    MonitorAutoLock monitor(mMonitor);
+    mLastAsyncScrollTime = aSampleTime;
+    mLastAsyncScrollOffset = mCurrentAsyncScrollOffset;
+    SendAsyncScrollEvent();
+  }
+  else {
+    mAsyncScrollTimeoutTask =
+      NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout);
+    MessageLoop::current()->PostDelayedTask(FROM_HERE,
+                                            mAsyncScrollTimeoutTask,
+                                            mAsyncScrollTimeout);
   }
 
   nsIntPoint scrollCompensation(
     ((scrollOffset / rootScale - metricsScrollOffset) * localScale)
     .RoundedAwayFromZero());
   *aNewTransform = ViewTransform(-scrollCompensation, localScale);
 
   mLastSampleTime = aSampleTime;
@@ -1307,10 +1364,27 @@ void AsyncPanZoomController::SetZoomAndR
 void AsyncPanZoomController::UpdateZoomConstraints(bool aAllowZoom,
                                                    float aMinZoom,
                                                    float aMaxZoom) {
   mAllowZoom = aAllowZoom;
   mMinZoom = aMinZoom;
   mMaxZoom = aMaxZoom;
 }
 
+void AsyncPanZoomController::SendAsyncScrollEvent() {
+  if (!mGeckoContentController) {
+    return;
+  }
+
+  gfx::Rect contentRect;
+  gfx::Size scrollableSize;
+  {
+    scrollableSize = gfx::Size(mFrameMetrics.mScrollableRect.width,
+                               mFrameMetrics.mScrollableRect.height);
+    contentRect =
+      AsyncPanZoomController::CalculateCompositedRectInCssPixels(mFrameMetrics);
+    contentRect.MoveTo(mCurrentAsyncScrollOffset);
+  }
+
+  mGeckoContentController->SendAsyncScrollDOMEvent(contentRect, scrollableSize);
 }
 }
+}
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -222,16 +222,22 @@ public:
    * the configuration in aFrameMetrics: viewport dimensions, zoom
    * factor, etc.  (The mResolution member of aFrameMetrics is
    * ignored.)
    */
   static gfxSize CalculateResolution(const FrameMetrics& aMetrics);
 
   static gfx::Rect CalculateCompositedRectInCssPixels(const FrameMetrics& aMetrics);
 
+  /**
+   * Send an mozbrowserasyncscroll event.
+   * *** The monitor must be held while calling this.
+   */
+  void SendAsyncScrollEvent();
+
 protected:
   /**
    * Internal handler for ReceiveInputEvent(). Does all the actual work.
    */
   nsEventStatus HandleInputEvent(const InputData& aEvent);
 
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
@@ -434,16 +440,24 @@ protected:
   /**
    * Utility function that sets the zoom and resolution simultaneously. This is
    * useful when we want to repaint at the current zoom level.
    *
    * *** The monitor must be held while calling this.
    */
   void SetZoomAndResolution(float aScale);
 
+  /**
+   * Timeout function for mozbrowserasyncscroll event. Because we throttle
+   * mozbrowserasyncscroll events in some conditions, this function ensures
+   * that the last mozbrowserasyncscroll event will be fired after a period of
+   * time.
+   */
+  void FireAsyncScrollOnTimeout();
+
 private:
   enum PanZoomState {
     NOTHING,        /* no touch-start events received */
     FLING,          /* all touches removed, but we're still scrolling page */
     TOUCHING,       /* one touch-start event received */
     PANNING,        /* panning the frame */
     PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
     ANIMATING_ZOOM, /* animated zoom to a new rect */
@@ -527,16 +541,37 @@ private:
 
   // How long it took in the past to paint after a series of previous requests.
   nsTArray<TimeDuration> mPreviousPaintDurations;
 
   // When the last paint request started. Used to determine the duration of
   // previous paints.
   TimeStamp mPreviousPaintStartTime;
 
+  // The last time and offset we fire the mozbrowserasyncscroll event when
+  // compositor has sampled the content transform for this frame.
+  TimeStamp mLastAsyncScrollTime;
+  gfx::Point mLastAsyncScrollOffset;
+
+  // The current offset drawn on the screen, it may not be sent since we have
+  // throttling policy for mozbrowserasyncscroll event.
+  gfx::Point mCurrentAsyncScrollOffset;
+
+  // The delay task triggered by the throttling mozbrowserasyncscroll event
+  // ensures the last mozbrowserasyncscroll event is always been fired.
+  CancelableTask* mAsyncScrollTimeoutTask;
+
+  // The time period in ms that throttles mozbrowserasyncscroll event.
+  // Default is 100ms if there is no "apzc.asyncscroll.throttle" in preference.
+  uint32_t mAsyncScrollThrottleTime;
+
+  // The timeout in ms for mAsyncScrollTimeoutTask delay task.
+  // Default is 300ms if there is no "apzc.asyncscroll.timeout" in preference.
+  uint32_t mAsyncScrollTimeout;
+
   int mDPI;
 
   // Stores the current paint status of the frame that we're managing. Repaints
   // may be triggered by other things (like content doing things), in which case
   // this status will not be updated. It is only changed when this class
   // requests a repaint.
   bool mWaitingForContentToPaint;
 
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -39,16 +39,24 @@ public:
   virtual void HandleSingleTap(const nsIntPoint& aPoint) = 0;
 
   /**
    * Requests handling a long tap. |aPoint| is in CSS pixels, relative to the
    * current scroll offset.
    */
   virtual void HandleLongTap(const nsIntPoint& aPoint) = 0;
 
+  /**
+   * Requests sending a mozbrowserasyncscroll domevent to embedder.
+   * |aContentRect| is in CSS pixels, relative to the current cssPage.
+   * |aScrollableSize| is the current content width/height in CSS pixels.
+   */
+  virtual void SendAsyncScrollDOMEvent(const gfx::Rect &aContentRect,
+                                       const gfx::Size &aScrollableSize) = 0;
+
   GeckoContentController() {}
   virtual ~GeckoContentController() {}
 };
 
 }
 }
 
 #endif // mozilla_layers_GeckoContentController_h
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -8,16 +8,17 @@
 #include "base/basictypes.h"
 
 #include "BasicLayers.h"
 #include "gfx3DMatrix.h"
 #include "LayerManagerOGL.h"
 #ifdef MOZ_ENABLE_D3D9_LAYER
 # include "LayerManagerD3D9.h"
 #endif //MOZ_ENABLE_D3D9_LAYER
+#include "mozilla/BrowserElementParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ShadowLayersParent.h"
 #include "nsContentUtils.h"
 #include "nsFrameLoader.h"
 #include "nsIObserver.h"
 #include "nsSubDocumentFrame.h"
@@ -545,16 +546,34 @@ public:
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
       browser->HandleLongTap(aPoint);
     }
   }
 
   void ClearRenderFrame() { mRenderFrame = nullptr; }
 
+  virtual void SendAsyncScrollDOMEvent(const gfx::Rect& aContentRect,
+                                       const gfx::Size& aContentSize) MOZ_OVERRIDE
+  {
+    if (MessageLoop::current() != mUILoop) {
+      mUILoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this,
+                          &RemoteContentController::SendAsyncScrollDOMEvent,
+                          aContentRect, aContentSize));
+      return;
+    }
+    if (mRenderFrame) {
+      TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
+      BrowserElementParent::DispatchAsyncScrollEvent(browser, aContentRect,
+                                                     aContentSize);
+    }
+  }
+
 private:
   void DoRequestContentRepaint(const FrameMetrics& aFrameMetrics)
   {
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
       browser->UpdateFrame(aFrameMetrics);
     }
   }