summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/util
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz/util')
-rw-r--r--gfx/layers/apz/util/APZCCallbackHelper.cpp942
-rw-r--r--gfx/layers/apz/util/APZCCallbackHelper.h209
-rw-r--r--gfx/layers/apz/util/APZEventState.cpp512
-rw-r--r--gfx/layers/apz/util/APZEventState.h103
-rw-r--r--gfx/layers/apz/util/APZThreadUtils.cpp96
-rw-r--r--gfx/layers/apz/util/APZThreadUtils.h104
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.cpp237
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.h104
-rw-r--r--gfx/layers/apz/util/CheckerboardReportService.cpp228
-rw-r--r--gfx/layers/apz/util/CheckerboardReportService.h144
-rw-r--r--gfx/layers/apz/util/ChromeProcessController.cpp276
-rw-r--r--gfx/layers/apz/util/ChromeProcessController.h83
-rw-r--r--gfx/layers/apz/util/ContentProcessController.cpp207
-rw-r--r--gfx/layers/apz/util/ContentProcessController.h90
-rw-r--r--gfx/layers/apz/util/DoubleTapToZoom.cpp178
-rw-r--r--gfx/layers/apz/util/DoubleTapToZoom.h29
-rw-r--r--gfx/layers/apz/util/InputAPZContext.cpp69
-rw-r--r--gfx/layers/apz/util/InputAPZContext.h50
-rw-r--r--gfx/layers/apz/util/ScrollInputMethods.h62
-rw-r--r--gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp49
-rw-r--r--gfx/layers/apz/util/ScrollLinkedEffectDetector.h43
-rw-r--r--gfx/layers/apz/util/TouchActionHelper.cpp96
-rw-r--r--gfx/layers/apz/util/TouchActionHelper.h42
23 files changed, 3953 insertions, 0 deletions
diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp
new file mode 100644
index 000000000..3f33a59e4
--- /dev/null
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -0,0 +1,942 @@
+/* -*- Mode: C++; 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/. */
+
+#include "APZCCallbackHelper.h"
+
+#include "TouchActionHelper.h"
+#include "gfxPlatform.h" // For gfxPlatform::UseTiling
+#include "gfxPrefs.h"
+#include "LayersLogging.h" // For Stringify
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/layers/LayerTransactionChild.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/TouchEvents.h"
+#include "nsContentUtils.h"
+#include "nsContainerFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsRefreshDriver.h"
+#include "nsString.h"
+#include "nsView.h"
+#include "Layers.h"
+
+#define APZCCH_LOG(...)
+// #define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
+
+namespace mozilla {
+namespace layers {
+
+using dom::TabParent;
+
+uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
+
+void
+APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
+ mozilla::layers::FrameMetrics& aFrameMetrics,
+ const CSSPoint& aActualScrollOffset)
+{
+ // Correct the display-port by the difference between the requested scroll
+ // offset and the resulting scroll offset after setting the requested value.
+ ScreenPoint shift =
+ (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
+ aFrameMetrics.DisplayportPixelsPerCSSPixel();
+ ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+ margins.left -= shift.x;
+ margins.right += shift.x;
+ margins.top -= shift.y;
+ margins.bottom += shift.y;
+ aFrameMetrics.SetDisplayPortMargins(margins);
+}
+
+static void
+RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
+{
+ ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+ margins.right = margins.left = margins.LeftRight() / 2;
+ margins.top = margins.bottom = margins.TopBottom() / 2;
+ aFrameMetrics.SetDisplayPortMargins(margins);
+}
+
+static CSSPoint
+ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
+{
+ aSuccessOut = false;
+ CSSPoint targetScrollPosition = aMetrics.GetScrollOffset();
+
+ if (!aFrame) {
+ return targetScrollPosition;
+ }
+
+ CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
+
+ // If the repaint request was triggered due to a previous main-thread scroll
+ // offset update sent to the APZ, then we don't need to do another scroll here
+ // and we can just return.
+ if (!aMetrics.GetScrollOffsetUpdated()) {
+ return geckoScrollPosition;
+ }
+
+ // If the frame is overflow:hidden on a particular axis, we don't want to allow
+ // user-driven scroll on that axis. Simply set the scroll position on that axis
+ // to whatever it already is. Note that this will leave the APZ's async scroll
+ // position out of sync with the gecko scroll position, but APZ can deal with that
+ // (by design). Note also that when we run into this case, even if both axes
+ // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport
+ // follows the async scroll position rather than the gecko scroll position.
+ if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
+ targetScrollPosition.y = geckoScrollPosition.y;
+ }
+ if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
+ targetScrollPosition.x = geckoScrollPosition.x;
+ }
+
+ // If the scrollable frame is currently in the middle of an async or smooth
+ // scroll then we don't want to interrupt it (see bug 961280).
+ // Also if the scrollable frame got a scroll request from a higher priority origin
+ // since the last layers update, then we don't want to push our scroll request
+ // because we'll clobber that one, which is bad.
+ bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
+ if (!scrollInProgress) {
+ aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
+ geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
+ aSuccessOut = true;
+ }
+ // Return the final scroll position after setting it so that anything that relies
+ // on it can have an accurate value. Note that even if we set it above re-querying it
+ // is a good idea because it may have gotten clamped or rounded.
+ return geckoScrollPosition;
+}
+
+/**
+ * Scroll the scroll frame associated with |aContent| to the scroll position
+ * requested in |aMetrics|.
+ * The scroll offset in |aMetrics| is updated to reflect the actual scroll
+ * position.
+ * The displayport stored in |aMetrics| and the callback-transform stored on
+ * the content are updated to reflect any difference between the requested
+ * and actual scroll positions.
+ */
+static void
+ScrollFrame(nsIContent* aContent,
+ FrameMetrics& aMetrics)
+{
+ // Scroll the window to the desired spot
+ nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+ if (sf) {
+ sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
+ sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
+ }
+ bool scrollUpdated = false;
+ CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
+ CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
+
+ if (scrollUpdated) {
+ if (aMetrics.IsScrollInfoLayer()) {
+ // In cases where the APZ scroll offset is different from the content scroll
+ // offset, we want to interpret the margins as relative to the APZ scroll
+ // offset except when the frame is not scrollable by APZ. Therefore, if the
+ // layer is a scroll info layer, we leave the margins as-is and they will
+ // be interpreted as relative to the content scroll offset.
+ if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
+ frame->SchedulePaint();
+ }
+ } else {
+ // Correct the display port due to the difference between mScrollOffset and the
+ // actual scroll offset.
+ APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+ }
+ } else {
+ // For whatever reason we couldn't update the scroll offset on the scroll frame,
+ // which means the data APZ used for its displayport calculation is stale. Fall
+ // back to a sane default behaviour. Note that we don't tile-align the recentered
+ // displayport because tile-alignment depends on the scroll position, and the
+ // scroll position here is out of our control. See bug 966507 comment 21 for a
+ // more detailed explanation.
+ RecenterDisplayPort(aMetrics);
+ }
+
+ aMetrics.SetScrollOffset(actualScrollOffset);
+
+ // APZ transforms inputs assuming we applied the exact scroll offset it
+ // requested (|apzScrollOffset|). Since we may not have, record the difference
+ // between what APZ asked for and what we actually applied, and apply it to
+ // input events to compensate.
+ // Note that if the main-thread had a change in its scroll position, we don't
+ // want to record that difference here, because it can be large and throw off
+ // input events by a large amount. It is also going to be transient, because
+ // any main-thread scroll position change will be synced to APZ and we will
+ // get another repaint request when APZ confirms. In the interval while this
+ // is happening we can just leave the callback transform as it was.
+ bool mainThreadScrollChanged =
+ sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
+ if (aContent && !mainThreadScrollChanged) {
+ CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
+ aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
+ nsINode::DeleteProperty<CSSPoint>);
+ }
+}
+
+static void
+SetDisplayPortMargins(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ const FrameMetrics& aMetrics)
+{
+ if (!aContent) {
+ return;
+ }
+
+ bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
+ ScreenMargin margins = aMetrics.GetDisplayPortMargins();
+ nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
+ if (!hadDisplayPort) {
+ nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
+ }
+
+ CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels();
+ nsRect base(0, 0,
+ baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
+ nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
+}
+
+static already_AddRefed<nsIPresShell>
+GetPresShell(const nsIContent* aContent)
+{
+ nsCOMPtr<nsIPresShell> result;
+ if (nsIDocument* doc = aContent->GetComposedDoc()) {
+ result = doc->GetShell();
+ }
+ return result.forget();
+}
+
+static void
+SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
+{
+ aContent->SetProperty(nsGkAtoms::paintRequestTime,
+ new TimeStamp(aPaintRequestTime),
+ nsINode::DeleteProperty<TimeStamp>);
+}
+
+void
+APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
+{
+ if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+ return;
+ }
+ nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+ if (!content) {
+ return;
+ }
+
+ nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
+ if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
+ return;
+ }
+
+ MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+
+ if (gfxPrefs::APZAllowZooming()) {
+ // If zooming is disabled then we don't really want to let APZ fiddle
+ // with these things. In theory setting the resolution here should be a
+ // no-op, but setting the SPCSPS is bad because it can cause a stale value
+ // to be returned by window.innerWidth/innerHeight (see bug 1187792).
+
+ float presShellResolution = shell->GetResolution();
+
+ // If the pres shell resolution has changed on the content side side
+ // the time this repaint request was fired, consider this request out of date
+ // and drop it; setting a zoom based on the out-of-date resolution can have
+ // the effect of getting us stuck with the stale resolution.
+ if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
+ return;
+ }
+
+ // The pres shell resolution is updated by the the async zoom since the
+ // last paint.
+ presShellResolution = aMetrics.GetPresShellResolution()
+ * aMetrics.GetAsyncZoom().scale;
+ shell->SetResolutionAndScaleTo(presShellResolution);
+ }
+
+ // Do this as late as possible since scrolling can flush layout. It also
+ // adjusts the display port margins, so do it before we set those.
+ ScrollFrame(content, aMetrics);
+
+ SetDisplayPortMargins(shell, content, aMetrics);
+ SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+}
+
+void
+APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
+{
+ if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+ return;
+ }
+ nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+ if (!content) {
+ return;
+ }
+
+ MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+
+ // We don't currently support zooming for subframes, so nothing extra
+ // needs to be done beyond the tasks common to this and UpdateRootFrame.
+ ScrollFrame(content, aMetrics);
+ if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
+ SetDisplayPortMargins(shell, content, aMetrics);
+ }
+ SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+}
+
+bool
+APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
+ uint32_t* aPresShellIdOut,
+ FrameMetrics::ViewID* aViewIdOut)
+{
+ if (!aContent) {
+ return false;
+ }
+ *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
+ if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
+ *aPresShellIdOut = shell->GetPresShellId();
+ return true;
+ }
+ return false;
+}
+
+void
+APZCCallbackHelper::InitializeRootDisplayport(nsIPresShell* aPresShell)
+{
+ // Create a view-id and set a zero-margin displayport for the root element
+ // of the root document in the chrome process. This ensures that the scroll
+ // frame for this element gets an APZC, which in turn ensures that all content
+ // in the chrome processes is covered by an APZC.
+ // The displayport is zero-margin because this element is generally not
+ // actually scrollable (if it is, APZC will set proper margins when it's
+ // scrolled).
+ if (!aPresShell) {
+ return;
+ }
+
+ MOZ_ASSERT(aPresShell->GetDocument());
+ nsIContent* content = aPresShell->GetDocument()->GetDocumentElement();
+ if (!content) {
+ return;
+ }
+
+ uint32_t presShellId;
+ FrameMetrics::ViewID viewId;
+ if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, &viewId)) {
+ // Note that the base rect that goes with these margins is set in
+ // nsRootBoxFrame::BuildDisplayList.
+ nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(), 0,
+ nsLayoutUtils::RepaintMode::DoNotRepaint);
+ nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ content->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::DoNotRepaint);
+ }
+}
+
+nsPresContext*
+APZCCallbackHelper::GetPresContextForContent(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+ return shell->GetPresContext();
+}
+
+nsIPresShell*
+APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent)
+{
+ nsPresContext* context = GetPresContextForContent(aContent);
+ if (!context) {
+ return nullptr;
+ }
+ context = context->GetToplevelContentDocumentPresContext();
+ if (!context) {
+ return nullptr;
+ }
+ return context->PresShell();
+}
+
+static nsIPresShell*
+GetRootDocumentPresShell(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+ nsPresContext* context = shell->GetPresContext();
+ if (!context) {
+ return nullptr;
+ }
+ context = context->GetRootPresContext();
+ if (!context) {
+ return nullptr;
+ }
+ return context->PresShell();
+}
+
+CSSPoint
+APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
+ const ScrollableLayerGuid& aGuid)
+{
+ CSSPoint input = aInput;
+ if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
+ return input;
+ }
+ nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
+ if (!content) {
+ return input;
+ }
+
+ // First, scale inversely by the root content document's pres shell
+ // resolution to cancel the scale-to-resolution transform that the
+ // compositor adds to the layer with the pres shell resolution. The points
+ // sent to Gecko by APZ don't have this transform unapplied (unlike other
+ // compositor-side transforms) because APZ doesn't know about it.
+ if (nsIPresShell* shell = GetRootDocumentPresShell(content)) {
+ input = input / shell->GetResolution();
+ }
+
+ // This represents any resolution on the Root Content Document (RCD)
+ // that's not on the Root Document (RD). That is, on platforms where
+ // RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
+ // resolution. 'input' has this resolution applied, but the scroll
+ // delta retrieved below do not, so we need to apply them to the
+ // delta before adding the delta to 'input'. (Technically, deltas
+ // from scroll frames outside the RCD would already have this
+ // resolution applied, but we don't have such scroll frames in
+ // practice.)
+ float nonRootResolution = 1.0f;
+ if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
+ nonRootResolution = shell->GetCumulativeNonRootScaleResolution();
+ }
+ // Now apply the callback-transform. This is only approximately correct,
+ // see the comment on GetCumulativeApzCallbackTransform for details.
+ CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(content->GetPrimaryFrame());
+ return input + transform * nonRootResolution;
+}
+
+LayoutDeviceIntPoint
+APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
+ const ScrollableLayerGuid& aGuid,
+ const CSSToLayoutDeviceScale& aScale)
+{
+ LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
+ point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
+ return LayoutDeviceIntPoint::Round(point);
+}
+
+void
+APZCCallbackHelper::ApplyCallbackTransform(WidgetEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ const CSSToLayoutDeviceScale& aScale)
+{
+ if (aEvent.AsTouchEvent()) {
+ WidgetTouchEvent& event = *(aEvent.AsTouchEvent());
+ for (size_t i = 0; i < event.mTouches.Length(); i++) {
+ event.mTouches[i]->mRefPoint = ApplyCallbackTransform(
+ event.mTouches[i]->mRefPoint, aGuid, aScale);
+ }
+ } else {
+ aEvent.mRefPoint = ApplyCallbackTransform(aEvent.mRefPoint, aGuid, aScale);
+ }
+}
+
+nsEventStatus
+APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
+{
+ nsEventStatus status = nsEventStatus_eConsumeNoDefault;
+ if (aEvent.mWidget) {
+ aEvent.mWidget->DispatchEvent(&aEvent, status);
+ }
+ return status;
+}
+
+nsEventStatus
+APZCCallbackHelper::DispatchSynthesizedMouseEvent(EventMessage aMsg,
+ uint64_t aTime,
+ const LayoutDevicePoint& aRefPoint,
+ Modifiers aModifiers,
+ int32_t aClickCount,
+ nsIWidget* aWidget)
+{
+ MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown ||
+ aMsg == eMouseUp || aMsg == eMouseLongTap);
+
+ WidgetMouseEvent event(true, aMsg, aWidget,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+ event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
+ event.mTime = aTime;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ if (aMsg == eMouseLongTap) {
+ event.mFlags.mOnlyChromeDispatch = true;
+ }
+ event.mIgnoreRootScrollFrame = true;
+ if (aMsg != eMouseMove) {
+ event.mClickCount = aClickCount;
+ }
+ event.mModifiers = aModifiers;
+ // Real touch events will generate corresponding pointer events. We set
+ // convertToPointer to false to prevent the synthesized mouse events generate
+ // pointer events again.
+ event.convertToPointer = false;
+ return DispatchWidgetEvent(event);
+}
+
+bool
+APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const nsString& aType,
+ const CSSPoint& aPoint,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ unsigned short aInputSourceArg)
+{
+ NS_ENSURE_TRUE(aPresShell, true);
+
+ bool defaultPrevented = false;
+ nsContentUtils::SendMouseEvent(aPresShell, aType, aPoint.x, aPoint.y,
+ aButton, nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount,
+ aModifiers, aIgnoreRootScrollFrame, 0, aInputSourceArg, false,
+ &defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false);
+ return defaultPrevented;
+}
+
+
+void
+APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ int32_t aClickCount,
+ nsIWidget* aWidget)
+{
+ if (aWidget->Destroyed()) {
+ return;
+ }
+ APZCCH_LOG("Dispatching single-tap component events to %s\n",
+ Stringify(aPoint).c_str());
+ int time = 0;
+ DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, aClickCount, aWidget);
+ DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aClickCount, aWidget);
+ DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, aWidget);
+}
+
+static dom::Element*
+GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame)
+{
+ if (!aScrollableFrame) {
+ return nullptr;
+ }
+ nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
+ if (!scrolledFrame) {
+ return nullptr;
+ }
+ // |scrolledFrame| should at this point be the root content frame of the
+ // nearest ancestor scrollable frame. The element corresponding to this
+ // frame should be the one with the displayport set on it, so find that
+ // element and return it.
+ nsIContent* content = scrolledFrame->GetContent();
+ MOZ_ASSERT(content->IsElement()); // roc says this must be true
+ return content->AsElement();
+}
+
+
+static dom::Element*
+GetRootDocumentElementFor(nsIWidget* aWidget)
+{
+ // This returns the root element that ChromeProcessController sets the
+ // displayport on during initialization.
+ if (nsView* view = nsView::GetViewFor(aWidget)) {
+ if (nsIPresShell* shell = view->GetPresShell()) {
+ MOZ_ASSERT(shell->GetDocument());
+ return shell->GetDocument()->GetDocumentElement();
+ }
+ }
+ return nullptr;
+}
+
+static nsIFrame*
+UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+ // Re-target so that the hit test is performed relative to the frame for the
+ // Root Content Document instead of the Root Document which are different in
+ // Android. See bug 1229752 comment 16 for an explanation of why this is necessary.
+ if (nsIDocument* doc = aRootFrame->PresContext()->PresShell()->GetTouchEventTargetDocument()) {
+ if (nsIPresShell* shell = doc->GetShell()) {
+ if (nsIFrame* frame = shell->GetRootFrame()) {
+ return frame;
+ }
+ }
+ }
+#endif
+ return aRootFrame;
+}
+
+// Determine the scrollable target frame for the given point and add it to
+// the target list. If the frame doesn't have a displayport, set one.
+// Return whether or not a displayport was set.
+static bool
+PrepareForSetTargetAPZCNotification(nsIWidget* aWidget,
+ const ScrollableLayerGuid& aGuid,
+ nsIFrame* aRootFrame,
+ const LayoutDeviceIntPoint& aRefPoint,
+ nsTArray<ScrollableLayerGuid>* aTargets)
+{
+ ScrollableLayerGuid guid(aGuid.mLayersId, 0, FrameMetrics::NULL_SCROLL_ID);
+ nsPoint point =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aRefPoint, aRootFrame);
+ nsIFrame* target =
+ nsLayoutUtils::GetFrameForPoint(aRootFrame, point, nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
+ nsIScrollableFrame* scrollAncestor = target
+ ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
+ : aRootFrame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
+
+ // Assuming that if there's no scrollAncestor, there's already a displayPort.
+ nsCOMPtr<dom::Element> dpElement = scrollAncestor
+ ? GetDisplayportElementFor(scrollAncestor)
+ : GetRootDocumentElementFor(aWidget);
+
+ nsAutoString dpElementDesc;
+ if (dpElement) {
+ dpElement->Describe(dpElementDesc);
+ }
+ APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
+ Stringify(aRefPoint).c_str(), dpElement.get(),
+ NS_LossyConvertUTF16toASCII(dpElementDesc).get());
+
+ bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ dpElement, &(guid.mPresShellId), &(guid.mScrollId));
+ aTargets->AppendElement(guid);
+
+ if (!guidIsValid || nsLayoutUtils::HasDisplayPort(dpElement)) {
+ return false;
+ }
+
+ if (!scrollAncestor) {
+ MOZ_ASSERT(false); // If you hit this, please file a bug with STR.
+
+ // Attempt some sort of graceful handling based on a theory as to why we
+ // reach this point...
+ // If we get here, the document element is non-null, valid, but doesn't have
+ // a displayport. It's possible that the init code in ChromeProcessController
+ // failed for some reason, or the document element got swapped out at some
+ // later time. In this case let's try to set a displayport on the document
+ // element again and bail out on this operation.
+ APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n",
+ aWidget, dpElement.get());
+ APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresContext()->PresShell());
+ return false;
+ }
+
+ APZCCH_LOG("%p didn't have a displayport, so setting one...\n", dpElement.get());
+ bool activated = nsLayoutUtils::CalculateAndSetDisplayPortMargins(
+ scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
+ if (!activated) {
+ return false;
+ }
+
+ nsIFrame* frame = do_QueryFrame(scrollAncestor);
+ nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame,
+ nsLayoutUtils::RepaintMode::Repaint);
+
+ return true;
+}
+
+static void
+SendLayersDependentApzcTargetConfirmation(nsIPresShell* aShell, uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets)
+{
+ LayerManager* lm = aShell->GetLayerManager();
+ if (!lm) {
+ return;
+ }
+
+ LayerTransactionChild* shadow = lm->AsShadowForwarder()->GetShadowManager();
+ if (!shadow) {
+ return;
+ }
+
+ shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
+}
+
+class DisplayportSetListener : public nsAPostRefreshObserver {
+public:
+ DisplayportSetListener(nsIPresShell* aPresShell,
+ const uint64_t& aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets)
+ : mPresShell(aPresShell)
+ , mInputBlockId(aInputBlockId)
+ , mTargets(aTargets)
+ {
+ }
+
+ virtual ~DisplayportSetListener()
+ {
+ }
+
+ void DidRefresh() override {
+ if (!mPresShell) {
+ MOZ_ASSERT_UNREACHABLE("Post-refresh observer fired again after failed attempt at unregistering it");
+ return;
+ }
+
+ APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n", mInputBlockId);
+ SendLayersDependentApzcTargetConfirmation(mPresShell, mInputBlockId, Move(mTargets));
+
+ if (!mPresShell->RemovePostRefreshObserver(this)) {
+ MOZ_ASSERT_UNREACHABLE("Unable to unregister post-refresh observer! Leaking it instead of leaving garbage registered");
+ // Graceful handling, just in case...
+ mPresShell = nullptr;
+ return;
+ }
+
+ delete this;
+ }
+
+private:
+ RefPtr<nsIPresShell> mPresShell;
+ uint64_t mInputBlockId;
+ nsTArray<ScrollableLayerGuid> mTargets;
+};
+
+// Sends a SetTarget notification for APZC, given one or more previous
+// calls to PrepareForAPZCSetTargetNotification().
+static void
+SendSetTargetAPZCNotificationHelper(nsIWidget* aWidget,
+ nsIPresShell* aShell,
+ const uint64_t& aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets,
+ bool aWaitForRefresh)
+{
+ bool waitForRefresh = aWaitForRefresh;
+ if (waitForRefresh) {
+ APZCCH_LOG("At least one target got a new displayport, need to wait for refresh\n");
+ waitForRefresh = aShell->AddPostRefreshObserver(
+ new DisplayportSetListener(aShell, aInputBlockId, Move(aTargets)));
+ }
+ if (!waitForRefresh) {
+ APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", aInputBlockId);
+ aWidget->SetConfirmedTargetAPZC(aInputBlockId, aTargets);
+ } else {
+ APZCCH_LOG("Successfully registered post-refresh observer\n");
+ }
+}
+
+void
+APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
+ nsIDocument* aDocument,
+ const WidgetGUIEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ if (!aWidget || !aDocument) {
+ return;
+ }
+ if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) {
+ // We have already confirmed the target APZC for a previous event of this
+ // input block. If we activated a scroll frame for this input block,
+ // sending another target APZC confirmation would be harmful, as it might
+ // race the original confirmation (which needs to go through a layers
+ // transaction).
+ APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64 "\n", aInputBlockId);
+ return;
+ }
+ sLastTargetAPZCNotificationInputBlock = aInputBlockId;
+ if (nsIPresShell* shell = aDocument->GetShell()) {
+ if (nsIFrame* rootFrame = shell->GetRootFrame()) {
+ rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
+
+ bool waitForRefresh = false;
+ nsTArray<ScrollableLayerGuid> targets;
+
+ if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) {
+ for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) {
+ waitForRefresh |= PrepareForSetTargetAPZCNotification(aWidget, aGuid,
+ rootFrame, touchEvent->mTouches[i]->mRefPoint, &targets);
+ }
+ } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
+ waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
+ rootFrame, wheelEvent->mRefPoint, &targets);
+ } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
+ waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
+ rootFrame, mouseEvent->mRefPoint, &targets);
+ }
+ // TODO: Do other types of events need to be handled?
+
+ if (!targets.IsEmpty()) {
+ SendSetTargetAPZCNotificationHelper(
+ aWidget,
+ shell,
+ aInputBlockId,
+ Move(targets),
+ waitForRefresh);
+ }
+ }
+ }
+}
+
+void
+APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
+ nsIWidget* aWidget,
+ nsIDocument* aDocument,
+ const WidgetTouchEvent& aEvent,
+ uint64_t aInputBlockId,
+ const SetAllowedTouchBehaviorCallback& aCallback)
+{
+ if (nsIPresShell* shell = aDocument->GetShell()) {
+ if (nsIFrame* rootFrame = shell->GetRootFrame()) {
+ rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
+
+ nsTArray<TouchBehaviorFlags> flags;
+ for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
+ flags.AppendElement(
+ TouchActionHelper::GetAllowedTouchBehavior(aWidget,
+ rootFrame, aEvent.mTouches[i]->mRefPoint));
+ }
+ aCallback(aInputBlockId, Move(flags));
+ }
+ }
+}
+
+void
+APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+{
+ nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
+ if (!targetContent) {
+ return;
+ }
+ nsCOMPtr<nsIDocument> ownerDoc = targetContent->OwnerDoc();
+ if (!ownerDoc) {
+ return;
+ }
+
+ nsContentUtils::DispatchTrustedEvent(
+ ownerDoc, targetContent,
+ aEvent,
+ true, true);
+}
+
+void
+APZCCallbackHelper::NotifyFlushComplete(nsIPresShell* aShell)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // In some cases, flushing the APZ state to the main thread doesn't actually
+ // trigger a flush and repaint (this is an intentional optimization - the stuff
+ // visible to the user is still correct). However, reftests update their
+ // snapshot based on invalidation events that are emitted during paints,
+ // so we ensure that we kick off a paint when an APZ flush is done. Note that
+ // only chrome/testing code can trigger this behaviour.
+ if (aShell && aShell->GetRootFrame()) {
+ aShell->GetRootFrame()->SchedulePaint();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+ observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
+}
+
+static int32_t sActiveSuppressDisplayport = 0;
+static bool sDisplayPortSuppressionRespected = true;
+
+void
+APZCCallbackHelper::SuppressDisplayport(const bool& aEnabled,
+ const nsCOMPtr<nsIPresShell>& aShell)
+{
+ if (aEnabled) {
+ sActiveSuppressDisplayport++;
+ } else {
+ bool isSuppressed = IsDisplayportSuppressed();
+ sActiveSuppressDisplayport--;
+ if (isSuppressed && !IsDisplayportSuppressed() &&
+ aShell && aShell->GetRootFrame()) {
+ // We unsuppressed the displayport, trigger a paint
+ aShell->GetRootFrame()->SchedulePaint();
+ }
+ }
+
+ MOZ_ASSERT(sActiveSuppressDisplayport >= 0);
+}
+
+void
+APZCCallbackHelper::RespectDisplayPortSuppression(bool aEnabled,
+ const nsCOMPtr<nsIPresShell>& aShell)
+{
+ bool isSuppressed = IsDisplayportSuppressed();
+ sDisplayPortSuppressionRespected = aEnabled;
+ if (isSuppressed && !IsDisplayportSuppressed() &&
+ aShell && aShell->GetRootFrame()) {
+ // We unsuppressed the displayport, trigger a paint
+ aShell->GetRootFrame()->SchedulePaint();
+ }
+}
+
+bool
+APZCCallbackHelper::IsDisplayportSuppressed()
+{
+ return sDisplayPortSuppressionRespected
+ && sActiveSuppressDisplayport > 0;
+}
+
+/* static */ bool
+APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
+{
+ return aFrame->IsProcessingAsyncScroll()
+ || nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
+ || aFrame->LastSmoothScrollOrigin();
+}
+
+/* static */ void
+APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers,
+ nsIWidget* aWidget)
+{
+ EventMessage msg;
+ switch (aType) {
+ case PinchGestureInput::PINCHGESTURE_START:
+ msg = eMagnifyGestureStart;
+ break;
+ case PinchGestureInput::PINCHGESTURE_SCALE:
+ msg = eMagnifyGestureUpdate;
+ break;
+ case PinchGestureInput::PINCHGESTURE_END:
+ msg = eMagnifyGesture;
+ break;
+ case PinchGestureInput::PINCHGESTURE_SENTINEL:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid gesture type");
+ return;
+ }
+
+ WidgetSimpleGestureEvent event(true, msg, aWidget);
+ event.mDelta = aSpanChange;
+ event.mModifiers = aModifiers;
+ DispatchWidgetEvent(event);
+}
+
+} // namespace layers
+} // namespace mozilla
+
diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h
new file mode 100644
index 000000000..8e888805c
--- /dev/null
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_APZCCallbackHelper_h
+#define mozilla_layers_APZCCallbackHelper_h
+
+#include "FrameMetrics.h"
+#include "InputData.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Function.h"
+#include "mozilla/layers/APZUtils.h"
+#include "nsIDOMWindowUtils.h"
+
+class nsIContent;
+class nsIDocument;
+class nsIPresShell;
+class nsIScrollableFrame;
+class nsIWidget;
+template<class T> struct already_AddRefed;
+template<class T> class nsCOMPtr;
+
+namespace mozilla {
+namespace layers {
+
+typedef function<void(uint64_t, const nsTArray<TouchBehaviorFlags>&)>
+ SetAllowedTouchBehaviorCallback;
+
+/* This class contains some helper methods that facilitate implementing the
+ GeckoContentController callback interface required by the AsyncPanZoomController.
+ Since different platforms need to implement this interface in similar-but-
+ not-quite-the-same ways, this utility class provides some helpful methods
+ to hold code that can be shared across the different platform implementations.
+ */
+class APZCCallbackHelper
+{
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
+
+public:
+ /* Applies the scroll and zoom parameters from the given FrameMetrics object
+ to the root frame for the given metrics' scrollId. If tiled thebes layers
+ are enabled, this will align the displayport to tile boundaries. Setting
+ the scroll position can cause some small adjustments to be made to the
+ actual scroll position. aMetrics' display port and scroll position will
+ be updated with any modifications made. */
+ static void UpdateRootFrame(FrameMetrics& aMetrics);
+
+ /* Applies the scroll parameters from the given FrameMetrics object to the
+ subframe corresponding to given metrics' scrollId. If tiled thebes
+ layers are enabled, this will align the displayport to tile boundaries.
+ Setting the scroll position can cause some small adjustments to be made
+ to the actual scroll position. aMetrics' display port and scroll position
+ will be updated with any modifications made. */
+ static void UpdateSubFrame(FrameMetrics& aMetrics);
+
+ /* Get the presShellId and view ID for the given content element.
+ * If the view ID does not exist, one is created.
+ * The pres shell ID should generally already exist; if it doesn't for some
+ * reason, false is returned. */
+ static bool GetOrCreateScrollIdentifiers(nsIContent* aContent,
+ uint32_t* aPresShellIdOut,
+ FrameMetrics::ViewID* aViewIdOut);
+
+ /* Initialize a zero-margin displayport on the root document element of the
+ given presShell. */
+ static void InitializeRootDisplayport(nsIPresShell* aPresShell);
+
+ /* Get the pres context associated with the document enclosing |aContent|. */
+ static nsPresContext* GetPresContextForContent(nsIContent* aContent);
+
+ /* Get the pres shell associated with the root content document enclosing |aContent|. */
+ static nsIPresShell* GetRootContentDocumentPresShellForContent(nsIContent* aContent);
+
+ /* Apply an "input transform" to the given |aInput| and return the transformed value.
+ The input transform applied is the one for the content element corresponding to
+ |aGuid|; this is populated in a previous call to UpdateCallbackTransform. See that
+ method's documentations for details.
+ This method additionally adjusts |aInput| by inversely scaling by the provided
+ pres shell resolution, to cancel out a compositor-side transform (added in
+ bug 1076241) that APZ doesn't unapply. */
+ static CSSPoint ApplyCallbackTransform(const CSSPoint& aInput,
+ const ScrollableLayerGuid& aGuid);
+
+ /* Same as above, but operates on LayoutDeviceIntPoint.
+ Requires an additonal |aScale| parameter to convert between CSS and
+ LayoutDevice space. */
+ static mozilla::LayoutDeviceIntPoint
+ ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
+ const ScrollableLayerGuid& aGuid,
+ const CSSToLayoutDeviceScale& aScale);
+
+ /* Convenience function for applying a callback transform to all refpoints
+ * in the input event. */
+ static void ApplyCallbackTransform(WidgetEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ const CSSToLayoutDeviceScale& aScale);
+
+ /* Dispatch a widget event via the widget stored in the event, if any.
+ * In a child process, allows the TabParent event-capture mechanism to
+ * intercept the event. */
+ static nsEventStatus DispatchWidgetEvent(WidgetGUIEvent& aEvent);
+
+ /* Synthesize a mouse event with the given parameters, and dispatch it
+ * via the given widget. */
+ static nsEventStatus DispatchSynthesizedMouseEvent(EventMessage aMsg,
+ uint64_t aTime,
+ const LayoutDevicePoint& aRefPoint,
+ Modifiers aModifiers,
+ int32_t aClickCount,
+ nsIWidget* aWidget);
+
+ /* Dispatch a mouse event with the given parameters.
+ * Return whether or not any listeners have called preventDefault on the event. */
+ static bool DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const nsString& aType,
+ const CSSPoint& aPoint,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ unsigned short aInputSourceArg);
+
+ /* Fire a single-tap event at the given point. The event is dispatched
+ * via the given widget. */
+ static void FireSingleTapEvent(const LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ int32_t aClickCount,
+ nsIWidget* aWidget);
+
+ /* Perform hit-testing on the touch points of |aEvent| to determine
+ * which scrollable frames they target. If any of these frames don't have
+ * a displayport, set one.
+ *
+ * If any displayports need to be set, the actual notification to APZ is
+ * sent to the compositor, which will then post a message back to APZ's
+ * controller thread. Otherwise, the provided widget's SetConfirmedTargetAPZC
+ * method is invoked immediately.
+ */
+ static void SendSetTargetAPZCNotification(nsIWidget* aWidget,
+ nsIDocument* aDocument,
+ const WidgetGUIEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId);
+
+ /* Figure out the allowed touch behaviors of each touch point in |aEvent|
+ * and send that information to the provided callback. */
+ static void SendSetAllowedTouchBehaviorNotification(nsIWidget* aWidget,
+ nsIDocument* aDocument,
+ const WidgetTouchEvent& aEvent,
+ uint64_t aInputBlockId,
+ const SetAllowedTouchBehaviorCallback& aCallback);
+
+ /* Notify content of a mouse scroll testing event. */
+ static void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent);
+
+ /* Notify content that the repaint flush is complete. */
+ static void NotifyFlushComplete(nsIPresShell* aShell);
+
+ /* Temporarily ignore the Displayport for better paint performance. If at
+ * all possible, pass in a presShell if you have one at the call site, we
+ * use it to trigger a repaint once suppression is disabled. Without that
+ * the displayport may get left at the suppressed size for an extended
+ * period of time and result in unnecessary checkerboarding (see bug
+ * 1255054). */
+ static void SuppressDisplayport(const bool& aEnabled,
+ const nsCOMPtr<nsIPresShell>& aShell);
+
+ /* Whether or not displayport suppression should be turned on. Note that
+ * this only affects the return value of |IsDisplayportSuppressed()|, and
+ * doesn't change the value of the internal counter. As with
+ * SuppressDisplayport, this function should be passed a presShell to trigger
+ * a repaint if suppression is being turned off.
+ */
+ static void RespectDisplayPortSuppression(bool aEnabled,
+ const nsCOMPtr<nsIPresShell>& aShell);
+
+ /* Whether or not the displayport is currently suppressed. */
+ static bool IsDisplayportSuppressed();
+
+ static void
+ AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
+ const CSSPoint& aActualScrollOffset);
+
+ /*
+ * Check if the scrollable frame is currently in the middle of an async
+ * or smooth scroll. We want to discard certain scroll input if this is
+ * true to prevent clobbering higher priority origins.
+ */
+ static bool
+ IsScrollInProgress(nsIScrollableFrame* aFrame);
+
+ /* Notify content of the progress of a pinch gesture that APZ won't do
+ * zooming for (because the apz.allow_zooming pref is false). This function
+ * will dispatch appropriate WidgetSimpleGestureEvent events to gecko.
+ */
+ static void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers,
+ nsIWidget* aWidget);
+private:
+ static uint64_t sLastTargetAPZCNotificationInputBlock;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_APZCCallbackHelper_h */
diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp
new file mode 100644
index 000000000..20a41eed5
--- /dev/null
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -0,0 +1,512 @@
+/* -*- Mode: C++; 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/. */
+
+#include "APZEventState.h"
+
+#include "ActiveElementManager.h"
+#include "APZCCallbackHelper.h"
+#include "gfxPrefs.h"
+#include "LayersLogging.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "nsCOMPtr.h"
+#include "nsDocShell.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsITimer.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWidget.h"
+#include "nsLayoutUtils.h"
+#include "nsQueryFrame.h"
+#include "TouchManager.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "mozilla/TouchEvents.h"
+
+#define APZES_LOG(...)
+// #define APZES_LOG(...) printf_stderr("APZES: " __VA_ARGS__)
+
+// Static helper functions
+namespace {
+
+int32_t
+WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers)
+{
+ int32_t result = 0;
+ if (aModifiers & mozilla::MODIFIER_SHIFT) {
+ result |= nsIDOMWindowUtils::MODIFIER_SHIFT;
+ }
+ if (aModifiers & mozilla::MODIFIER_CONTROL) {
+ result |= nsIDOMWindowUtils::MODIFIER_CONTROL;
+ }
+ if (aModifiers & mozilla::MODIFIER_ALT) {
+ result |= nsIDOMWindowUtils::MODIFIER_ALT;
+ }
+ if (aModifiers & mozilla::MODIFIER_META) {
+ result |= nsIDOMWindowUtils::MODIFIER_META;
+ }
+ if (aModifiers & mozilla::MODIFIER_ALTGRAPH) {
+ result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH;
+ }
+ if (aModifiers & mozilla::MODIFIER_CAPSLOCK) {
+ result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK;
+ }
+ if (aModifiers & mozilla::MODIFIER_FN) {
+ result |= nsIDOMWindowUtils::MODIFIER_FN;
+ }
+ if (aModifiers & mozilla::MODIFIER_FNLOCK) {
+ result |= nsIDOMWindowUtils::MODIFIER_FNLOCK;
+ }
+ if (aModifiers & mozilla::MODIFIER_NUMLOCK) {
+ result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK;
+ }
+ if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) {
+ result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK;
+ }
+ if (aModifiers & mozilla::MODIFIER_SYMBOL) {
+ result |= nsIDOMWindowUtils::MODIFIER_SYMBOL;
+ }
+ if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) {
+ result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK;
+ }
+ if (aModifiers & mozilla::MODIFIER_OS) {
+ result |= nsIDOMWindowUtils::MODIFIER_OS;
+ }
+ return result;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace layers {
+
+static int32_t sActiveDurationMs = 10;
+static bool sActiveDurationMsSet = false;
+
+APZEventState::APZEventState(nsIWidget* aWidget,
+ ContentReceivedInputBlockCallback&& aCallback)
+ : mWidget(nullptr) // initialized in constructor body
+ , mActiveElementManager(new ActiveElementManager())
+ , mContentReceivedInputBlockCallback(Move(aCallback))
+ , mPendingTouchPreventedResponse(false)
+ , mPendingTouchPreventedBlockId(0)
+ , mEndTouchIsClick(false)
+ , mTouchEndCancelled(false)
+ , mLastTouchIdentifier(0)
+{
+ nsresult rv;
+ mWidget = do_GetWeakReference(aWidget, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that"
+ " does not support weak references. APZ will NOT work!");
+
+ if (!sActiveDurationMsSet) {
+ Preferences::AddIntVarCache(&sActiveDurationMs,
+ "ui.touch_activation.duration_ms",
+ sActiveDurationMs);
+ sActiveDurationMsSet = true;
+ }
+}
+
+APZEventState::~APZEventState()
+{}
+
+class DelayedFireSingleTapEvent final : public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ DelayedFireSingleTapEvent(nsWeakPtr aWidget,
+ LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ int32_t aClickCount,
+ nsITimer* aTimer)
+ : mWidget(aWidget)
+ , mPoint(aPoint)
+ , mModifiers(aModifiers)
+ , mClickCount(aClickCount)
+ // Hold the reference count until we are called back.
+ , mTimer(aTimer)
+ {
+ }
+
+ NS_IMETHOD Notify(nsITimer*) override
+ {
+ if (nsCOMPtr<nsIWidget> widget = do_QueryReferent(mWidget)) {
+ APZCCallbackHelper::FireSingleTapEvent(mPoint, mModifiers, mClickCount, widget);
+ }
+ mTimer = nullptr;
+ return NS_OK;
+ }
+
+ void ClearTimer() {
+ mTimer = nullptr;
+ }
+
+private:
+ ~DelayedFireSingleTapEvent()
+ {
+ }
+
+ nsWeakPtr mWidget;
+ LayoutDevicePoint mPoint;
+ Modifiers mModifiers;
+ int32_t mClickCount;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback)
+
+void
+APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ int32_t aClickCount)
+{
+ APZES_LOG("Handling single tap at %s on %s with %d\n",
+ Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mTouchEndCancelled);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return;
+ }
+
+ if (mTouchEndCancelled) {
+ return;
+ }
+
+ LayoutDevicePoint ldPoint = aPoint * aScale;
+ if (!mActiveElementManager->ActiveElementUsesStyle()) {
+ // If the active element isn't visually affected by the :active style, we
+ // have no need to wait the extra sActiveDurationMs to make the activation
+ // visually obvious to the user.
+ APZCCallbackHelper::FireSingleTapEvent(ldPoint, aModifiers, aClickCount, widget);
+ return;
+ }
+
+ APZES_LOG("Active element uses style, scheduling timer for click event\n");
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ RefPtr<DelayedFireSingleTapEvent> callback =
+ new DelayedFireSingleTapEvent(mWidget, ldPoint, aModifiers, aClickCount, timer);
+ nsresult rv = timer->InitWithCallback(callback,
+ sActiveDurationMs,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ // Make |callback| not hold the timer, so they will both be destructed when
+ // we leave the scope of this function.
+ callback->ClearTimer();
+ }
+}
+
+bool
+APZEventState::FireContextmenuEvents(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const nsCOMPtr<nsIWidget>& aWidget)
+{
+ // Converting the modifiers to DOM format for the DispatchMouseEvent call
+ // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
+ // just converts them back to widget format, but that API has many callers,
+ // including in JS code, so it's not trivial to change.
+ bool eventHandled =
+ APZCCallbackHelper::DispatchMouseEvent(aPresShell, NS_LITERAL_STRING("contextmenu"),
+ aPoint, 2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
+ nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
+
+ APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
+ if (eventHandled) {
+ // If the contextmenu event was handled then we're showing a contextmenu,
+ // and so we should remove any activation
+ mActiveElementManager->ClearActivation();
+#ifndef XP_WIN
+ } else {
+ // If the contextmenu wasn't consumed, fire the eMouseLongTap event.
+ nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
+ eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers,
+ /*clickCount*/ 1, aWidget);
+ eventHandled = (status == nsEventStatus_eConsumeNoDefault);
+ APZES_LOG("eMouseLongTap event handled: %d\n", eventHandled);
+#endif
+ }
+
+ return eventHandled;
+}
+
+void
+APZEventState::ProcessLongTap(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ APZES_LOG("Handling long tap at %s\n", Stringify(aPoint).c_str());
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return;
+ }
+
+ SendPendingTouchPreventedResponse(false);
+
+#ifdef XP_WIN
+ // On Windows, we fire the contextmenu events when the user lifts their
+ // finger, in keeping with the platform convention. This happens in the
+ // ProcessLongTapUp function. However, we still fire the eMouseLongTap event
+ // at this time, because things like text selection or dragging may want
+ // to know about it.
+ nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
+ eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, /*clickCount*/ 1,
+ widget);
+
+ bool eventHandled = (status == nsEventStatus_eConsumeNoDefault);
+#else
+ bool eventHandled = FireContextmenuEvents(aPresShell, aPoint, aScale,
+ aModifiers, widget);
+#endif
+ mContentReceivedInputBlockCallback(aGuid, aInputBlockId, eventHandled);
+
+ if (eventHandled) {
+ // Also send a touchcancel to content, so that listeners that might be
+ // waiting for a touchend don't trigger.
+ WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get());
+ cancelTouchEvent.mModifiers = aModifiers;
+ auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale);
+ cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch(mLastTouchIdentifier,
+ ldPoint, LayoutDeviceIntPoint(), 0, 0));
+ APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent);
+ }
+}
+
+void
+APZEventState::ProcessLongTapUp(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers)
+{
+#ifdef XP_WIN
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (widget) {
+ FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget);
+ }
+#endif
+}
+
+void
+APZEventState::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId,
+ nsEventStatus aApzResponse,
+ nsEventStatus aContentResponse)
+{
+ if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) {
+ mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget());
+ mLastTouchIdentifier = aEvent.mTouches[0]->Identifier();
+ }
+
+ bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault;
+ bool sentContentResponse = false;
+ APZES_LOG("Handling event type %d\n", aEvent.mMessage);
+ switch (aEvent.mMessage) {
+ case eTouchStart: {
+ mTouchEndCancelled = false;
+ sentContentResponse = SendPendingTouchPreventedResponse(false);
+ // sentContentResponse can be true here if we get two TOUCH_STARTs in a row
+ // and just responded to the first one.
+
+ // We're about to send a response back to APZ, but we should only do it
+ // for events that went through APZ (which should be all of them).
+ MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ);
+
+ if (isTouchPrevented) {
+ mContentReceivedInputBlockCallback(aGuid, aInputBlockId, isTouchPrevented);
+ sentContentResponse = true;
+ } else {
+ APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n",
+ aInputBlockId, Stringify(aGuid).c_str());
+ mPendingTouchPreventedResponse = true;
+ mPendingTouchPreventedGuid = aGuid;
+ mPendingTouchPreventedBlockId = aInputBlockId;
+ }
+ break;
+ }
+
+ case eTouchEnd:
+ if (isTouchPrevented) {
+ mTouchEndCancelled = true;
+ mEndTouchIsClick = false;
+ }
+ MOZ_FALLTHROUGH;
+ case eTouchCancel:
+ mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick);
+ MOZ_FALLTHROUGH;
+ case eTouchMove: {
+ if (mPendingTouchPreventedResponse) {
+ MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
+ }
+ sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented);
+ break;
+ }
+
+ default:
+ NS_WARNING("Unknown touch event type");
+ }
+
+ if (sentContentResponse &&
+ aApzResponse == nsEventStatus_eConsumeDoDefault &&
+ gfxPrefs::PointerEventsEnabled()) {
+ WidgetTouchEvent cancelEvent(aEvent);
+ cancelEvent.mMessage = eTouchCancel;
+ cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel;
+ for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
+ if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
+ touch->convertToPointer = true;
+ }
+ }
+ nsEventStatus status;
+ cancelEvent.mWidget->DispatchEvent(&cancelEvent, status);
+ }
+}
+
+void
+APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ // If this event starts a swipe, indicate that it shouldn't result in a
+ // scroll by setting defaultPrevented to true.
+ bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe();
+ mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
+}
+
+void
+APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ // If we get here and the drag block has not been confirmed by the code in
+ // nsSliderFrame, then no scrollbar reacted to the event thus APZC will
+ // ignore this drag block. We can send defaultPrevented as either true or
+ // false, it doesn't matter, because APZ won't have the scrollbar metrics
+ // anyway, and will know to drop the block.
+ bool defaultPrevented = false;
+ mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
+}
+
+void
+APZEventState::ProcessAPZStateChange(ViewID aViewId,
+ APZStateChange aChange,
+ int aArg)
+{
+ switch (aChange)
+ {
+ case APZStateChange::eTransformBegin:
+ {
+ nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
+ if (sf) {
+ sf->SetTransformingByAPZ(true);
+ }
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
+ if (scrollbarMediator) {
+ scrollbarMediator->ScrollbarActivityStarted();
+ }
+
+ nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
+ nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
+ nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
+ if (docshell && sf) {
+ nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
+ nsdocshell->NotifyAsyncPanZoomStarted();
+ }
+ break;
+ }
+ case APZStateChange::eTransformEnd:
+ {
+ nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
+ if (sf) {
+ sf->SetTransformingByAPZ(false);
+ }
+ nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
+ if (scrollbarMediator) {
+ scrollbarMediator->ScrollbarActivityStopped();
+ }
+
+ nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
+ nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
+ nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
+ if (docshell && sf) {
+ nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
+ nsdocshell->NotifyAsyncPanZoomStopped();
+ }
+ break;
+ }
+ case APZStateChange::eStartTouch:
+ {
+ mActiveElementManager->HandleTouchStart(aArg);
+ break;
+ }
+ case APZStateChange::eStartPanning:
+ {
+ // The user started to pan, so we don't want anything to be :active.
+ mActiveElementManager->ClearActivation();
+ break;
+ }
+ case APZStateChange::eEndTouch:
+ {
+ mEndTouchIsClick = aArg;
+ mActiveElementManager->HandleTouchEnd();
+ break;
+ }
+ case APZStateChange::eSentinel:
+ // Should never happen, but we want this case branch to stop the compiler
+ // whining about unhandled values.
+ MOZ_ASSERT(false);
+ break;
+ }
+}
+
+void
+APZEventState::ProcessClusterHit()
+{
+ // If we hit a cluster of links then we shouldn't activate any of them,
+ // as we will be showing the zoomed view. (This is only called on Fennec).
+#ifndef MOZ_WIDGET_ANDROID
+ MOZ_ASSERT(false);
+#endif
+ mActiveElementManager->ClearActivation();
+}
+
+bool
+APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault)
+{
+ if (mPendingTouchPreventedResponse) {
+ APZES_LOG("Sending response %d for pending guid: %s\n", aPreventDefault,
+ Stringify(mPendingTouchPreventedGuid).c_str());
+ mContentReceivedInputBlockCallback(mPendingTouchPreventedGuid,
+ mPendingTouchPreventedBlockId, aPreventDefault);
+ mPendingTouchPreventedResponse = false;
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<nsIWidget>
+APZEventState::GetWidget() const
+{
+ nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget);
+ return result.forget();
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/APZEventState.h b/gfx/layers/apz/util/APZEventState.h
new file mode 100644
index 000000000..44188eaa7
--- /dev/null
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_APZEventState_h
+#define mozilla_layers_APZEventState_h
+
+#include <stdint.h>
+
+#include "FrameMetrics.h" // for ScrollableLayerGuid
+#include "Units.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Function.h"
+#include "mozilla/layers/GeckoContentController.h" // for APZStateChange
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+
+template <class> class nsCOMPtr;
+class nsIDocument;
+class nsIPresShell;
+class nsIWidget;
+
+namespace mozilla {
+namespace layers {
+
+class ActiveElementManager;
+
+typedef function<void(const ScrollableLayerGuid&,
+ uint64_t /* input block id */,
+ bool /* prevent default */)>
+ ContentReceivedInputBlockCallback;
+
+/**
+ * A content-side component that keeps track of state for handling APZ
+ * gestures and sending APZ notifications.
+ */
+class APZEventState {
+ typedef GeckoContentController::APZStateChange APZStateChange;
+ typedef FrameMetrics::ViewID ViewID;
+public:
+ APZEventState(nsIWidget* aWidget,
+ ContentReceivedInputBlockCallback&& aCallback);
+
+ NS_INLINE_DECL_REFCOUNTING(APZEventState);
+
+ void ProcessSingleTap(const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ int32_t aClickCount);
+ void ProcessLongTap(const nsCOMPtr<nsIPresShell>& aUtils,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId);
+ void ProcessLongTapUp(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers);
+ void ProcessTouchEvent(const WidgetTouchEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId,
+ nsEventStatus aApzResponse,
+ nsEventStatus aContentResponse);
+ void ProcessWheelEvent(const WidgetWheelEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId);
+ void ProcessMouseEvent(const WidgetMouseEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId);
+ void ProcessAPZStateChange(ViewID aViewId,
+ APZStateChange aChange,
+ int aArg);
+ void ProcessClusterHit();
+private:
+ ~APZEventState();
+ bool SendPendingTouchPreventedResponse(bool aPreventDefault);
+ bool FireContextmenuEvents(const nsCOMPtr<nsIPresShell>& aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const nsCOMPtr<nsIWidget>& aWidget);
+ already_AddRefed<nsIWidget> GetWidget() const;
+private:
+ nsWeakPtr mWidget;
+ RefPtr<ActiveElementManager> mActiveElementManager;
+ ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback;
+ bool mPendingTouchPreventedResponse;
+ ScrollableLayerGuid mPendingTouchPreventedGuid;
+ uint64_t mPendingTouchPreventedBlockId;
+ bool mEndTouchIsClick;
+ bool mTouchEndCancelled;
+ int32_t mLastTouchIdentifier;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_APZEventState_h */
diff --git a/gfx/layers/apz/util/APZThreadUtils.cpp b/gfx/layers/apz/util/APZThreadUtils.cpp
new file mode 100644
index 000000000..46f67d010
--- /dev/null
+++ b/gfx/layers/apz/util/APZThreadUtils.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; 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/. */
+
+#include "mozilla/layers/APZThreadUtils.h"
+
+#include "mozilla/layers/Compositor.h"
+#ifdef MOZ_WIDGET_ANDROID
+#include "AndroidBridge.h"
+#endif
+
+namespace mozilla {
+namespace layers {
+
+static bool sThreadAssertionsEnabled = true;
+static MessageLoop* sControllerThread;
+
+/*static*/ void
+APZThreadUtils::SetThreadAssertionsEnabled(bool aEnabled) {
+ sThreadAssertionsEnabled = aEnabled;
+}
+
+/*static*/ bool
+APZThreadUtils::GetThreadAssertionsEnabled() {
+ return sThreadAssertionsEnabled;
+}
+
+/*static*/ void
+APZThreadUtils::SetControllerThread(MessageLoop* aLoop)
+{
+ // We must either be setting the initial controller thread, or removing it,
+ // or re-using an existing controller thread.
+ MOZ_ASSERT(!sControllerThread || !aLoop || sControllerThread == aLoop);
+ sControllerThread = aLoop;
+}
+
+/*static*/ void
+APZThreadUtils::AssertOnControllerThread() {
+ if (!GetThreadAssertionsEnabled()) {
+ return;
+ }
+
+ MOZ_ASSERT(sControllerThread == MessageLoop::current());
+}
+
+/*static*/ void
+APZThreadUtils::AssertOnCompositorThread()
+{
+ if (GetThreadAssertionsEnabled()) {
+ Compositor::AssertOnCompositorThread();
+ }
+}
+
+/*static*/ void
+APZThreadUtils::RunOnControllerThread(already_AddRefed<Runnable> aTask)
+{
+ RefPtr<Runnable> task = aTask;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // This is needed while nsWindow::ConfigureAPZControllerThread is not propper
+ // implemented.
+ if (AndroidBridge::IsJavaUiThread()) {
+ task->Run();
+ } else {
+ AndroidBridge::Bridge()->PostTaskToUiThread(task.forget(), 0);
+ }
+#else
+ if (!sControllerThread) {
+ // Could happen on startup
+ NS_WARNING("Dropping task posted to controller thread");
+ return;
+ }
+
+ if (sControllerThread == MessageLoop::current()) {
+ task->Run();
+ } else {
+ sControllerThread->PostTask(task.forget());
+ }
+#endif
+}
+
+/*static*/ bool
+APZThreadUtils::IsControllerThread()
+{
+#ifdef MOZ_WIDGET_ANDROID
+ return AndroidBridge::IsJavaUiThread();
+#else
+ return sControllerThread == MessageLoop::current();
+#endif
+}
+
+NS_IMPL_ISUPPORTS(GenericTimerCallbackBase, nsITimerCallback)
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/APZThreadUtils.h b/gfx/layers/apz/util/APZThreadUtils.h
new file mode 100644
index 000000000..4b9b2c0d0
--- /dev/null
+++ b/gfx/layers/apz/util/APZThreadUtils.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_APZThreadUtils_h
+#define mozilla_layers_APZThreadUtils_h
+
+#include "base/message_loop.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+
+class Runnable;
+
+namespace layers {
+
+class APZThreadUtils
+{
+public:
+ /**
+ * In the gtest environment everything runs on one thread, so we
+ * shouldn't assert that we're on a particular thread. This enables
+ * that behaviour.
+ */
+ static void SetThreadAssertionsEnabled(bool aEnabled);
+ static bool GetThreadAssertionsEnabled();
+
+ /**
+ * Set the controller thread.
+ */
+ static void SetControllerThread(MessageLoop* aLoop);
+
+ /**
+ * This can be used to assert that the current thread is the
+ * controller/UI thread (on which input events are received).
+ * This does nothing if thread assertions are disabled.
+ */
+ static void AssertOnControllerThread();
+
+ /**
+ * This can be used to assert that the current thread is the
+ * compositor thread (which applies the async transform).
+ * This does nothing if thread assertions are disabled.
+ */
+ static void AssertOnCompositorThread();
+
+ /**
+ * Run the given task on the APZ "controller thread" for this platform. If
+ * this function is called from the controller thread itself then the task is
+ * run immediately without getting queued.
+ */
+ static void RunOnControllerThread(already_AddRefed<Runnable> aTask);
+
+ /**
+ * Returns true if currently on APZ "controller thread".
+ */
+ static bool IsControllerThread();
+};
+
+// A base class for GenericTimerCallback<Function>.
+// This is necessary because NS_IMPL_ISUPPORTS doesn't work for a class
+// template.
+class GenericTimerCallbackBase : public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+protected:
+ virtual ~GenericTimerCallbackBase() {}
+};
+
+// An nsITimerCallback implementation that can be used with any function
+// object that's callable with no arguments.
+template <typename Function>
+class GenericTimerCallback final : public GenericTimerCallbackBase
+{
+public:
+ explicit GenericTimerCallback(const Function& aFunction) : mFunction(aFunction) {}
+
+ NS_IMETHOD Notify(nsITimer*) override
+ {
+ mFunction();
+ return NS_OK;
+ }
+private:
+ Function mFunction;
+};
+
+// Convenience function for constructing a GenericTimerCallback.
+// Returns a raw pointer, suitable for passing directly as an argument to
+// nsITimer::InitWithCallback(). The intention is to enable the following
+// terse inline usage:
+// timer->InitWithCallback(NewTimerCallback([](){ ... }), delay);
+template <typename Function>
+GenericTimerCallback<Function>* NewTimerCallback(const Function& aFunction)
+{
+ return new GenericTimerCallback<Function>(aFunction);
+}
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_APZThreadUtils_h */
diff --git a/gfx/layers/apz/util/ActiveElementManager.cpp b/gfx/layers/apz/util/ActiveElementManager.cpp
new file mode 100644
index 000000000..20d34aa2b
--- /dev/null
+++ b/gfx/layers/apz/util/ActiveElementManager.cpp
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; 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/. */
+
+#include "ActiveElementManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/Preferences.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocument.h"
+#include "nsStyleSet.h"
+
+#define AEM_LOG(...)
+// #define AEM_LOG(...) printf_stderr("AEM: " __VA_ARGS__)
+
+namespace mozilla {
+namespace layers {
+
+static int32_t sActivationDelayMs = 100;
+static bool sActivationDelayMsSet = false;
+
+ActiveElementManager::ActiveElementManager()
+ : mCanBePan(false),
+ mCanBePanSet(false),
+ mSetActiveTask(nullptr),
+ mActiveElementUsesStyle(false)
+{
+ if (!sActivationDelayMsSet) {
+ Preferences::AddIntVarCache(&sActivationDelayMs,
+ "ui.touch_activation.delay_ms",
+ sActivationDelayMs);
+ sActivationDelayMsSet = true;
+ }
+}
+
+ActiveElementManager::~ActiveElementManager() {}
+
+void
+ActiveElementManager::SetTargetElement(dom::EventTarget* aTarget)
+{
+ if (mTarget) {
+ // Multiple fingers on screen (since HandleTouchEnd clears mTarget).
+ AEM_LOG("Multiple fingers on-screen, clearing target element\n");
+ CancelTask();
+ ResetActive();
+ ResetTouchBlockState();
+ return;
+ }
+
+ mTarget = do_QueryInterface(aTarget);
+ AEM_LOG("Setting target element to %p\n", mTarget.get());
+ TriggerElementActivation();
+}
+
+void
+ActiveElementManager::HandleTouchStart(bool aCanBePan)
+{
+ AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan);
+ if (mCanBePanSet) {
+ // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet).
+ AEM_LOG("Multiple fingers on-screen, clearing touch block state\n");
+ CancelTask();
+ ResetActive();
+ ResetTouchBlockState();
+ return;
+ }
+
+ mCanBePan = aCanBePan;
+ mCanBePanSet = true;
+ TriggerElementActivation();
+}
+
+void
+ActiveElementManager::TriggerElementActivation()
+{
+ // Both HandleTouchStart() and SetTargetElement() call this. They can be
+ // called in either order. One will set mCanBePanSet, and the other, mTarget.
+ // We want to actually trigger the activation once both are set.
+ if (!(mTarget && mCanBePanSet)) {
+ return;
+ }
+
+ // If the touch cannot be a pan, make mTarget :active right away.
+ // Otherwise, wait a bit to see if the user will pan or not.
+ if (!mCanBePan) {
+ SetActive(mTarget);
+ } else {
+ CancelTask(); // this is only needed because of bug 1169802. Fixing that
+ // bug properly should make this unnecessary.
+ MOZ_ASSERT(mSetActiveTask == nullptr);
+
+ RefPtr<CancelableRunnable> task =
+ NewCancelableRunnableMethod<nsCOMPtr<dom::Element>>(this,
+ &ActiveElementManager::SetActiveTask,
+ mTarget);
+ mSetActiveTask = task;
+ MessageLoop::current()->PostDelayedTask(task.forget(), sActivationDelayMs);
+ AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask);
+ }
+}
+
+void
+ActiveElementManager::ClearActivation()
+{
+ AEM_LOG("Clearing element activation\n");
+ CancelTask();
+ ResetActive();
+}
+
+void
+ActiveElementManager::HandleTouchEndEvent(bool aWasClick)
+{
+ AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick);
+
+ // If the touch was a click, make mTarget :active right away.
+ // nsEventStateManager will reset the active element when processing
+ // the mouse-down event generated by the click.
+ CancelTask();
+ if (aWasClick) {
+ SetActive(mTarget);
+ } else {
+ // We might reach here if mCanBePan was false on touch-start and
+ // so we set the element active right away. Now it turns out the
+ // action was not a click so we need to reset the active element.
+ ResetActive();
+ }
+
+ ResetTouchBlockState();
+}
+
+void
+ActiveElementManager::HandleTouchEnd()
+{
+ AEM_LOG("Touch end, clearing pan state\n");
+ mCanBePanSet = false;
+}
+
+bool
+ActiveElementManager::ActiveElementUsesStyle() const
+{
+ return mActiveElementUsesStyle;
+}
+
+static nsPresContext*
+GetPresContextFor(nsIContent* aContent)
+{
+ if (!aContent) {
+ return nullptr;
+ }
+ nsIPresShell* shell = aContent->OwnerDoc()->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+ return shell->GetPresContext();
+}
+
+static bool
+ElementHasActiveStyle(dom::Element* aElement)
+{
+ nsPresContext* pc = GetPresContextFor(aElement);
+ if (!pc) {
+ return false;
+ }
+ StyleSetHandle styleSet = pc->StyleSet();
+ for (dom::Element* e = aElement; e; e = e->GetParentElement()) {
+ if (styleSet->HasStateDependentStyle(e, NS_EVENT_STATE_ACTIVE)) {
+ AEM_LOG("Element %p's style is dependent on the active state\n", e);
+ return true;
+ }
+ }
+ AEM_LOG("Element %p doesn't use active styles\n", aElement);
+ return false;
+}
+
+void
+ActiveElementManager::SetActive(dom::Element* aTarget)
+{
+ AEM_LOG("Setting active %p\n", aTarget);
+
+ if (nsPresContext* pc = GetPresContextFor(aTarget)) {
+ pc->EventStateManager()->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE);
+ mActiveElementUsesStyle = ElementHasActiveStyle(aTarget);
+ }
+}
+
+void
+ActiveElementManager::ResetActive()
+{
+ AEM_LOG("Resetting active from %p\n", mTarget.get());
+
+ // Clear the :active flag from mTarget by setting it on the document root.
+ if (mTarget) {
+ dom::Element* root = mTarget->OwnerDoc()->GetDocumentElement();
+ if (root) {
+ AEM_LOG("Found root %p, making active\n", root);
+ SetActive(root);
+ }
+ }
+}
+
+void
+ActiveElementManager::ResetTouchBlockState()
+{
+ mTarget = nullptr;
+ mCanBePanSet = false;
+}
+
+void
+ActiveElementManager::SetActiveTask(const nsCOMPtr<dom::Element>& aTarget)
+{
+ AEM_LOG("mSetActiveTask %p running\n", mSetActiveTask);
+
+ // This gets called from mSetActiveTask's Run() method. The message loop
+ // deletes the task right after running it, so we need to null out
+ // mSetActiveTask to make sure we're not left with a dangling pointer.
+ mSetActiveTask = nullptr;
+ SetActive(aTarget);
+}
+
+void
+ActiveElementManager::CancelTask()
+{
+ AEM_LOG("Cancelling task %p\n", mSetActiveTask);
+
+ if (mSetActiveTask) {
+ mSetActiveTask->Cancel();
+ mSetActiveTask = nullptr;
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/ActiveElementManager.h b/gfx/layers/apz/util/ActiveElementManager.h
new file mode 100644
index 000000000..83d0cb29f
--- /dev/null
+++ b/gfx/layers/apz/util/ActiveElementManager.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_ActiveElementManager_h
+#define mozilla_layers_ActiveElementManager_h
+
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+
+class CancelableRunnable;
+
+namespace dom {
+class Element;
+class EventTarget;
+} // namespace dom
+
+namespace layers {
+
+/**
+ * Manages setting and clearing the ':active' CSS pseudostate in the presence
+ * of touch input.
+ */
+class ActiveElementManager {
+ ~ActiveElementManager();
+public:
+ NS_INLINE_DECL_REFCOUNTING(ActiveElementManager)
+
+ ActiveElementManager();
+
+ /**
+ * Specify the target of a touch. Typically this should be called right
+ * after HandleTouchStart(), but in cases where the APZ needs to wait for
+ * a content response the HandleTouchStart() may be delayed, in which case
+ * this function can be called first.
+ * |aTarget| may be nullptr.
+ */
+ void SetTargetElement(dom::EventTarget* aTarget);
+ /**
+ * Handle a touch-start state notification from APZ. This notification
+ * may be delayed until after touch listeners have responded to the APZ.
+ * @param aCanBePan whether the touch can be a pan
+ */
+ void HandleTouchStart(bool aCanBePan);
+ /**
+ * Clear the active element.
+ */
+ void ClearActivation();
+ /**
+ * Handle a touch-end or touch-cancel event.
+ * @param aWasClick whether the touch was a click
+ */
+ void HandleTouchEndEvent(bool aWasClick);
+ /**
+ * Handle a touch-end state notification from APZ. This notification may be
+ * delayed until after touch listeners have responded to the APZ.
+ */
+ void HandleTouchEnd();
+ /**
+ * @return true iff the currently active element (or one of its ancestors)
+ * actually had a style for the :active pseudo-class. The currently active
+ * element is the root element if no other elements are active.
+ */
+ bool ActiveElementUsesStyle() const;
+private:
+ /**
+ * The target of the first touch point in the current touch block.
+ */
+ nsCOMPtr<dom::Element> mTarget;
+ /**
+ * Whether the current touch block can be a pan. Set in HandleTouchStart().
+ */
+ bool mCanBePan;
+ /**
+ * Whether mCanBePan has been set for the current touch block.
+ * We need to keep track of this to allow HandleTouchStart() and
+ * SetTargetElement() to be called in either order.
+ */
+ bool mCanBePanSet;
+ /**
+ * A task for calling SetActive() after a timeout.
+ */
+ RefPtr<CancelableRunnable> mSetActiveTask;
+ /**
+ * See ActiveElementUsesStyle() documentation.
+ */
+ bool mActiveElementUsesStyle;
+
+ // Helpers
+ void TriggerElementActivation();
+ void SetActive(dom::Element* aTarget);
+ void ResetActive();
+ void ResetTouchBlockState();
+ void SetActiveTask(const nsCOMPtr<dom::Element>& aTarget);
+ void CancelTask();
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_ActiveElementManager_h */
diff --git a/gfx/layers/apz/util/CheckerboardReportService.cpp b/gfx/layers/apz/util/CheckerboardReportService.cpp
new file mode 100644
index 000000000..0924fe92d
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; 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/. */
+
+#include "CheckerboardReportService.h"
+
+#include "gfxPrefs.h" // for gfxPrefs
+#include "jsapi.h" // for JS_Now
+#include "MainThreadUtils.h" // for NS_IsMainThread
+#include "mozilla/Assertions.h" // for MOZ_ASSERT
+#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown
+#include "mozilla/Unused.h"
+#include "mozilla/dom/CheckerboardReportServiceBinding.h" // for dom::CheckerboardReports
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "nsContentUtils.h" // for nsContentUtils
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace layers {
+
+/*static*/ StaticRefPtr<CheckerboardEventStorage> CheckerboardEventStorage::sInstance;
+
+/*static*/ already_AddRefed<CheckerboardEventStorage>
+CheckerboardEventStorage::GetInstance()
+{
+ // The instance in the parent process does all the work, so if this is getting
+ // called in the child process something is likely wrong.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sInstance) {
+ sInstance = new CheckerboardEventStorage();
+ ClearOnShutdown(&sInstance);
+ }
+ RefPtr<CheckerboardEventStorage> instance = sInstance.get();
+ return instance.forget();
+}
+
+void
+CheckerboardEventStorage::Report(uint32_t aSeverity, const std::string& aLog)
+{
+ if (!NS_IsMainThread()) {
+ RefPtr<Runnable> task = NS_NewRunnableFunction([aSeverity, aLog] () -> void {
+ CheckerboardEventStorage::Report(aSeverity, aLog);
+ });
+ NS_DispatchToMainThread(task.forget());
+ return;
+ }
+
+ if (XRE_IsGPUProcess()) {
+ if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) {
+ nsCString log(aLog.c_str());
+ Unused << gpu->SendReportCheckerboard(aSeverity, log);
+ }
+ return;
+ }
+
+ RefPtr<CheckerboardEventStorage> storage = GetInstance();
+ storage->ReportCheckerboard(aSeverity, aLog);
+}
+
+void
+CheckerboardEventStorage::ReportCheckerboard(uint32_t aSeverity, const std::string& aLog)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aSeverity == 0) {
+ // This code assumes all checkerboard reports have a nonzero severity.
+ return;
+ }
+
+ CheckerboardReport severe(aSeverity, JS_Now(), aLog);
+ CheckerboardReport recent;
+
+ // First look in the "severe" reports to see if the new one belongs in that
+ // list.
+ for (int i = 0; i < SEVERITY_MAX_INDEX; i++) {
+ if (mCheckerboardReports[i].mSeverity >= severe.mSeverity) {
+ continue;
+ }
+ // The new one deserves to be in the "severe" list. Take the one getting
+ // bumped off the list, and put it in |recent| for possible insertion into
+ // the recents list.
+ recent = mCheckerboardReports[SEVERITY_MAX_INDEX - 1];
+
+ // Shuffle the severe list down, insert the new one.
+ for (int j = SEVERITY_MAX_INDEX - 1; j > i; j--) {
+ mCheckerboardReports[j] = mCheckerboardReports[j - 1];
+ }
+ mCheckerboardReports[i] = severe;
+ severe.mSeverity = 0; // mark |severe| as inserted
+ break;
+ }
+
+ // If |severe.mSeverity| is nonzero, the incoming report didn't get inserted
+ // into the severe list; put it into |recent| for insertion into the recent
+ // list.
+ if (severe.mSeverity) {
+ MOZ_ASSERT(recent.mSeverity == 0, "recent should be empty here");
+ recent = severe;
+ } // else |recent| may hold a report that got knocked out of the severe list.
+
+ if (recent.mSeverity == 0) {
+ // Nothing to be inserted into the recent list.
+ return;
+ }
+
+ // If it wasn't in the "severe" list, add it to the "recent" list.
+ for (int i = SEVERITY_MAX_INDEX; i < RECENT_MAX_INDEX; i++) {
+ if (mCheckerboardReports[i].mTimestamp >= recent.mTimestamp) {
+ continue;
+ }
+ // |recent| needs to be inserted at |i|. Shuffle the remaining ones down
+ // and insert it.
+ for (int j = RECENT_MAX_INDEX - 1; j > i; j--) {
+ mCheckerboardReports[j] = mCheckerboardReports[j - 1];
+ }
+ mCheckerboardReports[i] = recent;
+ break;
+ }
+}
+
+void
+CheckerboardEventStorage::GetReports(nsTArray<dom::CheckerboardReport>& aOutReports)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (int i = 0; i < RECENT_MAX_INDEX; i++) {
+ CheckerboardReport& r = mCheckerboardReports[i];
+ if (r.mSeverity == 0) {
+ continue;
+ }
+ dom::CheckerboardReport report;
+ report.mSeverity.Construct() = r.mSeverity;
+ report.mTimestamp.Construct() = r.mTimestamp / 1000; // micros to millis
+ report.mLog.Construct() = NS_ConvertUTF8toUTF16(r.mLog.c_str(), r.mLog.size());
+ report.mReason.Construct() = (i < SEVERITY_MAX_INDEX)
+ ? dom::CheckerboardReason::Severe
+ : dom::CheckerboardReason::Recent;
+ aOutReports.AppendElement(report);
+ }
+}
+
+} // namespace layers
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CheckerboardReportService, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CheckerboardReportService, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CheckerboardReportService, Release)
+
+/*static*/ bool
+CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal)
+{
+ // Only allow this in the parent process
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+ // Allow privileged code or about:checkerboard (unprivileged) to access this.
+ return nsContentUtils::IsCallerChrome()
+ || nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard");
+}
+
+/*static*/ already_AddRefed<CheckerboardReportService>
+CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<CheckerboardReportService> ces = new CheckerboardReportService(aGlobal.GetAsSupports());
+ return ces.forget();
+}
+
+CheckerboardReportService::CheckerboardReportService(nsISupports* aParent)
+ : mParent(aParent)
+{
+}
+
+JSObject*
+CheckerboardReportService::WrapObject(JSContext* aCtx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CheckerboardReportServiceBinding::Wrap(aCtx, this, aGivenProto);
+}
+
+nsISupports*
+CheckerboardReportService::GetParentObject()
+{
+ return mParent;
+}
+
+void
+CheckerboardReportService::GetReports(nsTArray<dom::CheckerboardReport>& aOutReports)
+{
+ RefPtr<mozilla::layers::CheckerboardEventStorage> instance =
+ mozilla::layers::CheckerboardEventStorage::GetInstance();
+ MOZ_ASSERT(instance);
+ instance->GetReports(aOutReports);
+}
+
+bool
+CheckerboardReportService::IsRecordingEnabled() const
+{
+ return gfxPrefs::APZRecordCheckerboarding();
+}
+
+void
+CheckerboardReportService::SetRecordingEnabled(bool aEnabled)
+{
+ gfxPrefs::SetAPZRecordCheckerboarding(aEnabled);
+}
+
+void
+CheckerboardReportService::FlushActiveReports()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get();
+ if (gpu && gpu->NotifyGpuObservers("APZ:FlushActiveCheckerboard")) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsSvc);
+ if (obsSvc) {
+ obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard", nullptr);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/CheckerboardReportService.h b/gfx/layers/apz/util/CheckerboardReportService.h
new file mode 100644
index 000000000..743b29825
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_dom_CheckerboardReportService_h
+#define mozilla_dom_CheckerboardReportService_h
+
+#include <string>
+
+#include "js/TypeDecls.h" // for JSContext, JSObject
+#include "mozilla/ErrorResult.h" // for ErrorResult
+#include "mozilla/StaticPtr.h" // for StaticRefPtr
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsWrapperCache.h" // for nsWrapperCache
+
+namespace mozilla {
+
+namespace dom {
+struct CheckerboardReport;
+}
+
+namespace layers {
+
+// CheckerboardEventStorage is a singleton that stores info on checkerboard
+// events, so that they can be accessed from about:checkerboard and visualized.
+// Note that this class is NOT threadsafe, and all methods must be called on
+// the main thread.
+class CheckerboardEventStorage
+{
+ NS_INLINE_DECL_REFCOUNTING(CheckerboardEventStorage)
+
+public:
+ /**
+ * Get the singleton instance.
+ */
+ static already_AddRefed<CheckerboardEventStorage> GetInstance();
+
+ /**
+ * Get the stored checkerboard reports.
+ */
+ void GetReports(nsTArray<dom::CheckerboardReport>& aOutReports);
+
+ /**
+ * Save a checkerboard event log, optionally dropping older ones that were
+ * less severe or less recent. Zero-severity reports may be ignored entirely.
+ */
+ static void Report(uint32_t aSeverity, const std::string& aLog);
+
+private:
+ /* Stuff for refcounted singleton */
+ CheckerboardEventStorage() {}
+ virtual ~CheckerboardEventStorage() {}
+
+ static StaticRefPtr<CheckerboardEventStorage> sInstance;
+
+ void ReportCheckerboard(uint32_t aSeverity, const std::string& aLog);
+
+private:
+ /**
+ * Struct that this class uses internally to store a checkerboard report.
+ */
+ struct CheckerboardReport {
+ uint32_t mSeverity; // if 0, this report is empty
+ int64_t mTimestamp; // microseconds since epoch, as from JS_Now()
+ std::string mLog;
+
+ CheckerboardReport()
+ : mSeverity(0)
+ , mTimestamp(0)
+ {}
+
+ CheckerboardReport(uint32_t aSeverity, int64_t aTimestamp,
+ const std::string& aLog)
+ : mSeverity(aSeverity)
+ , mTimestamp(aTimestamp)
+ , mLog(aLog)
+ {}
+ };
+
+ // The first 5 (indices 0-4) are the most severe ones in decreasing order
+ // of severity; the next 5 (indices 5-9) are the most recent ones that are
+ // not already in the "severe" list.
+ static const int SEVERITY_MAX_INDEX = 5;
+ static const int RECENT_MAX_INDEX = 10;
+ CheckerboardReport mCheckerboardReports[RECENT_MAX_INDEX];
+};
+
+} // namespace layers
+
+namespace dom {
+
+class GlobalObject;
+
+/**
+ * CheckerboardReportService is a wrapper object that allows access to the
+ * stuff in CheckerboardEventStorage (above). We need this wrapper for proper
+ * garbage/cycle collection, since this can be accessed from JS.
+ */
+class CheckerboardReportService : public nsWrapperCache
+{
+public:
+ /**
+ * Check if the given page is allowed to access this object via the WebIDL
+ * bindings. It only returns true if the page is about:checkerboard.
+ */
+ static bool IsEnabled(JSContext* aCtx, JSObject* aGlobal);
+
+ /*
+ * Other standard WebIDL binding glue.
+ */
+
+ static already_AddRefed<CheckerboardReportService>
+ Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv);
+
+ explicit CheckerboardReportService(nsISupports* aSupports);
+
+ JSObject* WrapObject(JSContext* aCtx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject();
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CheckerboardReportService)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CheckerboardReportService)
+
+public:
+ /*
+ * The methods exposed via the webidl.
+ */
+ void GetReports(nsTArray<dom::CheckerboardReport>& aOutReports);
+ bool IsRecordingEnabled() const;
+ void SetRecordingEnabled(bool aEnabled);
+ void FlushActiveReports();
+
+private:
+ virtual ~CheckerboardReportService() {}
+
+ nsCOMPtr<nsISupports> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_layers_CheckerboardReportService_h */
diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp
new file mode 100644
index 000000000..ac8b3824f
--- /dev/null
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; 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/. */
+
+#include "ChromeProcessController.h"
+
+#include "MainThreadUtils.h" // for NS_IsMainThread()
+#include "base/message_loop.h" // for MessageLoop
+#include "mozilla/dom/Element.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/DoubleTapToZoom.h"
+#include "nsIDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+ChromeProcessController::ChromeProcessController(nsIWidget* aWidget,
+ APZEventState* aAPZEventState,
+ IAPZCTreeManager* aAPZCTreeManager)
+ : mWidget(aWidget)
+ , mAPZEventState(aAPZEventState)
+ , mAPZCTreeManager(aAPZCTreeManager)
+ , mUILoop(MessageLoop::current())
+{
+ // Otherwise we're initializing mUILoop incorrectly.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aAPZEventState);
+ MOZ_ASSERT(aAPZCTreeManager);
+
+ mUILoop->PostTask(NewRunnableMethod(this, &ChromeProcessController::InitializeRoot));
+}
+
+ChromeProcessController::~ChromeProcessController() {}
+
+void
+ChromeProcessController::InitializeRoot()
+{
+ APZCCallbackHelper::InitializeRootDisplayport(GetPresShell());
+}
+
+void
+ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+{
+ MOZ_ASSERT(IsRepaintThread());
+
+ FrameMetrics metrics = aFrameMetrics;
+ if (metrics.IsRootContent()) {
+ APZCCallbackHelper::UpdateRootFrame(metrics);
+ } else {
+ APZCCallbackHelper::UpdateSubFrame(metrics);
+ }
+}
+
+void
+ChromeProcessController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
+{
+ MessageLoop::current()->PostDelayedTask(Move(aTask), aDelayMs);
+}
+
+bool
+ChromeProcessController::IsRepaintThread()
+{
+ return NS_IsMainThread();
+}
+
+void
+ChromeProcessController::DispatchToRepaintThread(already_AddRefed<Runnable> aTask)
+{
+ NS_DispatchToMainThread(Move(aTask));
+}
+
+void
+ChromeProcessController::Destroy()
+{
+ if (MessageLoop::current() != mUILoop) {
+ mUILoop->PostTask(NewRunnableMethod(this, &ChromeProcessController::Destroy));
+ return;
+ }
+
+ MOZ_ASSERT(MessageLoop::current() == mUILoop);
+ mWidget = nullptr;
+ mAPZEventState = nullptr;
+}
+
+nsIPresShell*
+ChromeProcessController::GetPresShell() const
+{
+ if (!mWidget) {
+ return nullptr;
+ }
+ if (nsView* view = nsView::GetViewFor(mWidget)) {
+ return view->GetPresShell();
+ }
+ return nullptr;
+}
+
+nsIDocument*
+ChromeProcessController::GetRootDocument() const
+{
+ if (nsIPresShell* presShell = GetPresShell()) {
+ return presShell->GetDocument();
+ }
+ return nullptr;
+}
+
+nsIDocument*
+ChromeProcessController::GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const
+{
+ nsIContent* content = nsLayoutUtils::FindContentFor(aScrollId);
+ if (!content) {
+ return nullptr;
+ }
+ nsIPresShell* presShell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(content);
+ if (presShell) {
+ return presShell->GetDocument();
+ }
+ return nullptr;
+}
+
+void
+ChromeProcessController::HandleDoubleTap(const mozilla::CSSPoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid)
+{
+ MOZ_ASSERT(MessageLoop::current() == mUILoop);
+
+ nsCOMPtr<nsIDocument> document = GetRootContentDocument(aGuid.mScrollId);
+ if (!document.get()) {
+ return;
+ }
+
+ // CalculateRectToZoomTo performs a hit test on the frame associated with the
+ // Root Content Document. Unfortunately that frame does not know about the
+ // resolution of the document and so we must remove it before calculating
+ // the zoomToRect.
+ nsIPresShell* presShell = document->GetShell();
+ const float resolution = presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f;
+ CSSPoint point(aPoint.x / resolution, aPoint.y / resolution);
+ CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
+
+ uint32_t presShellId;
+ FrameMetrics::ViewID viewId;
+ if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ document->GetDocumentElement(), &presShellId, &viewId)) {
+ mAPZCTreeManager->ZoomToRect(
+ ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId), zoomToRect);
+ }
+}
+
+void
+ChromeProcessController::HandleTap(TapType aType,
+ const mozilla::LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ if (MessageLoop::current() != mUILoop) {
+ mUILoop->PostTask(NewRunnableMethod<TapType, mozilla::LayoutDevicePoint, Modifiers,
+ ScrollableLayerGuid, uint64_t>(this,
+ &ChromeProcessController::HandleTap,
+ aType, aPoint, aModifiers, aGuid, aInputBlockId));
+ return;
+ }
+
+ if (!mAPZEventState) {
+ return;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ if (!presShell->GetPresContext()) {
+ return;
+ }
+ CSSToLayoutDeviceScale scale(presShell->GetPresContext()->CSSToDevPixelScale());
+ CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint / scale, aGuid);
+
+ switch (aType) {
+ case TapType::eSingleTap:
+ mAPZEventState->ProcessSingleTap(point, scale, aModifiers, aGuid, 1);
+ break;
+ case TapType::eDoubleTap:
+ HandleDoubleTap(point, aModifiers, aGuid);
+ break;
+ case TapType::eSecondTap:
+ mAPZEventState->ProcessSingleTap(point, scale, aModifiers, aGuid, 2);
+ break;
+ case TapType::eLongTap:
+ mAPZEventState->ProcessLongTap(presShell, point, scale, aModifiers, aGuid,
+ aInputBlockId);
+ break;
+ case TapType::eLongTapUp:
+ mAPZEventState->ProcessLongTapUp(presShell, point, scale, aModifiers);
+ break;
+ case TapType::eSentinel:
+ // Should never happen, but we need to handle this case branch for the
+ // compiler to be happy.
+ MOZ_ASSERT(false);
+ break;
+ }
+}
+
+void
+ChromeProcessController::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ const ScrollableLayerGuid& aGuid,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers)
+{
+ if (MessageLoop::current() != mUILoop) {
+ mUILoop->PostTask(NewRunnableMethod
+ <PinchGestureInput::PinchGestureType,
+ ScrollableLayerGuid,
+ LayoutDeviceCoord,
+ Modifiers>(this,
+ &ChromeProcessController::NotifyPinchGesture,
+ aType, aGuid, aSpanChange, aModifiers));
+ return;
+ }
+
+ if (mWidget) {
+ APZCCallbackHelper::NotifyPinchGesture(aType, aSpanChange, aModifiers, mWidget.get());
+ }
+}
+
+void
+ChromeProcessController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange,
+ int aArg)
+{
+ if (MessageLoop::current() != mUILoop) {
+ mUILoop->PostTask(NewRunnableMethod
+ <ScrollableLayerGuid,
+ APZStateChange,
+ int>(this, &ChromeProcessController::NotifyAPZStateChange,
+ aGuid, aChange, aArg));
+ return;
+ }
+
+ if (!mAPZEventState) {
+ return;
+ }
+
+ mAPZEventState->ProcessAPZStateChange(aGuid.mScrollId, aChange, aArg);
+}
+
+void
+ChromeProcessController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+{
+ if (MessageLoop::current() != mUILoop) {
+ mUILoop->PostTask(NewRunnableMethod
+ <FrameMetrics::ViewID,
+ nsString>(this, &ChromeProcessController::NotifyMozMouseScrollEvent,
+ aScrollId, aEvent));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+}
+
+void
+ChromeProcessController::NotifyFlushComplete()
+{
+ MOZ_ASSERT(IsRepaintThread());
+
+ APZCCallbackHelper::NotifyFlushComplete(GetPresShell());
+}
diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h
new file mode 100644
index 000000000..9a43297d4
--- /dev/null
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_ChromeProcessController_h
+#define mozilla_layers_ChromeProcessController_h
+
+#include "mozilla/layers/GeckoContentController.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+
+class nsIDOMWindowUtils;
+class nsIDocument;
+class nsIPresShell;
+class nsIWidget;
+
+class MessageLoop;
+
+namespace mozilla {
+
+namespace layers {
+
+class IAPZCTreeManager;
+class APZEventState;
+
+/**
+ * ChromeProcessController is a GeckoContentController attached to the root of
+ * a compositor's layer tree. It's used directly by APZ by default, and remoted
+ * using PAPZ if there is a gpu process.
+ *
+ * If ChromeProcessController needs to implement a new method on GeckoContentController
+ * PAPZ, APZChild, and RemoteContentController must be updated to handle it.
+ */
+class ChromeProcessController : public mozilla::layers::GeckoContentController
+{
+protected:
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
+
+public:
+ explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, IAPZCTreeManager* aAPZCTreeManager);
+ ~ChromeProcessController();
+ virtual void Destroy() override;
+
+ // GeckoContentController interface
+ virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
+ virtual void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
+ virtual bool IsRepaintThread() override;
+ virtual void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
+ virtual void HandleTap(TapType aType,
+ const mozilla::LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId) override;
+ virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ const ScrollableLayerGuid& aGuid,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers) override;
+ virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange,
+ int aArg) override;
+ virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
+ const nsString& aEvent) override;
+ virtual void NotifyFlushComplete() override;
+private:
+ nsCOMPtr<nsIWidget> mWidget;
+ RefPtr<APZEventState> mAPZEventState;
+ RefPtr<IAPZCTreeManager> mAPZCTreeManager;
+ MessageLoop* mUILoop;
+
+ void InitializeRoot();
+ nsIPresShell* GetPresShell() const;
+ nsIDocument* GetRootDocument() const;
+ nsIDocument* GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const;
+ void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid);
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_ChromeProcessController_h */
diff --git a/gfx/layers/apz/util/ContentProcessController.cpp b/gfx/layers/apz/util/ContentProcessController.cpp
new file mode 100644
index 000000000..eccd4179f
--- /dev/null
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 ts=8 et 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 "ContentProcessController.h"
+
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZChild.h"
+
+#include "InputData.h" // for InputData
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * There are cases where we try to create the APZChild before the corresponding
+ * TabChild has been created, we use an observer for the "tab-child-created"
+ * topic to set the TabChild in the APZChild when it has been created.
+ */
+class TabChildCreatedObserver : public nsIObserver
+{
+public:
+ TabChildCreatedObserver(ContentProcessController* aController, const dom::TabId& aTabId)
+ : mController(aController),
+ mTabId(aTabId)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+private:
+ virtual ~TabChildCreatedObserver()
+ {}
+
+ // TabChildCreatedObserver is owned by mController, and mController outlives its
+ // TabChildCreatedObserver, so the raw pointer is fine.
+ ContentProcessController* mController;
+ dom::TabId mTabId;
+};
+
+NS_IMPL_ISUPPORTS(TabChildCreatedObserver, nsIObserver)
+
+NS_IMETHODIMP
+TabChildCreatedObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(strcmp(aTopic, "tab-child-created") == 0);
+
+ nsCOMPtr<nsITabChild> tabChild(do_QueryInterface(aSubject));
+ NS_ENSURE_TRUE(tabChild, NS_ERROR_FAILURE);
+
+ dom::TabChild* browser = static_cast<dom::TabChild*>(tabChild.get());
+
+ if (browser->GetTabId() == mTabId) {
+ mController->SetBrowser(browser);
+ }
+ return NS_OK;
+}
+
+APZChild*
+ContentProcessController::Create(const dom::TabId& aTabId)
+{
+ RefPtr<dom::TabChild> browser = dom::TabChild::FindTabChild(aTabId);
+
+ ContentProcessController* controller = new ContentProcessController();
+
+ nsAutoPtr<APZChild> apz(new APZChild(controller));
+
+ if (browser) {
+
+ controller->SetBrowser(browser);
+
+ } else {
+
+ RefPtr<TabChildCreatedObserver> observer =
+ new TabChildCreatedObserver(controller, aTabId);
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os ||
+ NS_FAILED(os->AddObserver(observer, "tab-child-created", false))) {
+ return nullptr;
+ }
+ controller->SetObserver(observer);
+
+ }
+
+ return apz.forget();
+}
+
+ContentProcessController::ContentProcessController()
+ : mBrowser(nullptr)
+{
+}
+ContentProcessController::~ContentProcessController()
+{
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ os->RemoveObserver(mObserver, "tab-child-created");
+ }
+}
+
+void
+ContentProcessController::SetObserver(nsIObserver* aObserver)
+{
+ MOZ_ASSERT(!mBrowser);
+ mObserver = aObserver;
+}
+
+void
+ContentProcessController::SetBrowser(dom::TabChild* aBrowser)
+{
+ MOZ_ASSERT(!mBrowser);
+ mBrowser = aBrowser;
+
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ os->RemoveObserver(mObserver, "tab-child-created");
+ mObserver = nullptr;
+ }
+}
+void
+ContentProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+{
+ if (mBrowser) {
+ mBrowser->UpdateFrame(aFrameMetrics);
+ }
+}
+
+void
+ContentProcessController::HandleTap(
+ TapType aType,
+ const LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ // This should never get called
+ MOZ_ASSERT(false);
+}
+
+void
+ContentProcessController::NotifyPinchGesture(
+ PinchGestureInput::PinchGestureType aType,
+ const ScrollableLayerGuid& aGuid,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers)
+{
+ // This should never get called
+ MOZ_ASSERT_UNREACHABLE("Unexpected message to content process");
+}
+
+void
+ContentProcessController::NotifyAPZStateChange(
+ const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange,
+ int aArg)
+{
+ if (mBrowser) {
+ mBrowser->NotifyAPZStateChange(aGuid.mScrollId, aChange, aArg);
+ }
+}
+
+void
+ContentProcessController::NotifyMozMouseScrollEvent(
+ const FrameMetrics::ViewID& aScrollId,
+ const nsString& aEvent)
+{
+ if (mBrowser) {
+ APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+ }
+}
+
+void
+ContentProcessController::NotifyFlushComplete()
+{
+ if (mBrowser) {
+ nsCOMPtr<nsIPresShell> shell;
+ if (nsCOMPtr<nsIDocument> doc = mBrowser->GetDocument()) {
+ shell = doc->GetShell();
+ }
+ APZCCallbackHelper::NotifyFlushComplete(shell.get());
+ }
+}
+
+void
+ContentProcessController::PostDelayedTask(already_AddRefed<Runnable> aRunnable, int aDelayMs)
+{
+ MOZ_ASSERT_UNREACHABLE("ContentProcessController should only be used remotely.");
+}
+
+bool
+ContentProcessController::IsRepaintThread()
+{
+ return NS_IsMainThread();
+}
+
+void
+ContentProcessController::DispatchToRepaintThread(already_AddRefed<Runnable> aTask)
+{
+ NS_DispatchToMainThread(Move(aTask));
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/ContentProcessController.h b/gfx/layers/apz/util/ContentProcessController.h
new file mode 100644
index 000000000..07d113c9e
--- /dev/null
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 ts=8 et 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_layers_ContentProcessController_h
+#define mozilla_layers_ContentProcessController_h
+
+#include "mozilla/layers/GeckoContentController.h"
+
+class nsIObserver;
+
+namespace mozilla {
+
+namespace dom {
+class TabChild;
+} // namespace dom
+
+namespace layers {
+
+class APZChild;
+
+/**
+ * ContentProcessController is a GeckoContentController for a TabChild, and is always
+ * remoted using PAPZ/APZChild.
+ *
+ * ContentProcessController is created in ContentChild when a layer tree id has
+ * been allocated for a PBrowser that lives in that content process, and is destroyed
+ * when the Destroy message is received, or when the tab dies.
+ *
+ * If ContentProcessController needs to implement a new method on GeckoContentController
+ * PAPZ, APZChild, and RemoteContentController must be updated to handle it.
+ */
+class ContentProcessController final
+ : public GeckoContentController
+{
+public:
+ ~ContentProcessController();
+
+ static APZChild* Create(const dom::TabId& aTabId);
+
+ // ContentProcessController
+
+ void SetBrowser(dom::TabChild* aBrowser);
+
+ // GeckoContentController
+
+ void RequestContentRepaint(const FrameMetrics& frame) override;
+
+ void HandleTap(TapType aType,
+ const LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId) override;
+
+ void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ const ScrollableLayerGuid& aGuid,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers) override;
+
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange,
+ int aArg) override;
+
+ void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
+ const nsString& aEvent) override;
+
+ void NotifyFlushComplete() override;
+
+ void PostDelayedTask(already_AddRefed<Runnable> aRunnable, int aDelayMs) override;
+
+ bool IsRepaintThread() override;
+
+ void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
+
+private:
+ ContentProcessController();
+
+ void SetObserver(nsIObserver* aObserver);
+
+ RefPtr<dom::TabChild> mBrowser;
+ RefPtr<nsIObserver> mObserver;
+};
+
+} // namespace layers
+
+} // namespace mozilla
+
+#endif // mozilla_layers_ContentProcessController_h
diff --git a/gfx/layers/apz/util/DoubleTapToZoom.cpp b/gfx/layers/apz/util/DoubleTapToZoom.cpp
new file mode 100644
index 000000000..62dd5feaa
--- /dev/null
+++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; 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/. */
+
+#include "DoubleTapToZoom.h"
+
+#include <algorithm> // for std::min, std::max
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/Element.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLLIElement.h"
+#include "nsIDOMHTMLQuoteElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIPresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+
+namespace mozilla {
+namespace layers {
+
+// Returns the DOM element found at |aPoint|, interpreted as being relative to
+// the root frame of |aShell|. If the point is inside a subdocument, returns
+// an element inside the subdocument, rather than the subdocument element
+// (and does so recursively).
+// The implementation was adapted from nsDocument::ElementFromPoint(), with
+// the notable exception that we don't pass nsLayoutUtils::IGNORE_CROSS_DOC
+// to GetFrameForPoint(), so as to get the behaviour described above in the
+// presence of subdocuments.
+static already_AddRefed<dom::Element>
+ElementFromPoint(const nsCOMPtr<nsIPresShell>& aShell,
+ const CSSPoint& aPoint)
+{
+ if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
+ if (nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(rootFrame,
+ CSSPoint::ToAppUnits(aPoint),
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
+ nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME)) {
+ while (frame && (!frame->GetContent() || frame->GetContent()->IsInAnonymousSubtree())) {
+ frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+ }
+ nsIContent* content = frame->GetContent();
+ if (content && !content->IsElement()) {
+ content = content->GetParent();
+ }
+ if (content) {
+ nsCOMPtr<dom::Element> result = content->AsElement();
+ return result.forget();
+ }
+ }
+ }
+ return nullptr;
+}
+
+static bool
+ShouldZoomToElement(const nsCOMPtr<dom::Element>& aElement) {
+ if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
+ if (frame->GetDisplay() == StyleDisplay::Inline) {
+ return false;
+ }
+ }
+ if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) {
+ return false;
+ }
+ return true;
+}
+
+static bool
+IsRectZoomedIn(const CSSRect& aRect, const CSSRect& aCompositedArea)
+{
+ // This functions checks to see if the area of the rect visible in the
+ // composition bounds (i.e. the overlapArea variable below) is approximately
+ // the max area of the rect we can show.
+ CSSRect overlap = aCompositedArea.Intersect(aRect);
+ float overlapArea = overlap.width * overlap.height;
+ float availHeight = std::min(aRect.width * aCompositedArea.height / aCompositedArea.width,
+ aRect.height);
+ float showing = overlapArea / (aRect.width * availHeight);
+ float ratioW = aRect.width / aCompositedArea.width;
+ float ratioH = aRect.height / aCompositedArea.height;
+
+ return showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9);
+}
+
+CSSRect
+CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
+ const CSSPoint& aPoint)
+{
+ // Ensure the layout information we get is up-to-date.
+ aRootContentDocument->FlushPendingNotifications(Flush_Layout);
+
+ // An empty rect as return value is interpreted as "zoom out".
+ const CSSRect zoomOut;
+
+ nsCOMPtr<nsIPresShell> shell = aRootContentDocument->GetShell();
+ if (!shell) {
+ return zoomOut;
+ }
+
+ nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable();
+ if (!rootScrollFrame) {
+ return zoomOut;
+ }
+
+ nsCOMPtr<dom::Element> element = ElementFromPoint(shell, aPoint);
+ if (!element) {
+ return zoomOut;
+ }
+
+ while (element && !ShouldZoomToElement(element)) {
+ element = element->GetParentElement();
+ }
+
+ if (!element) {
+ return zoomOut;
+ }
+
+ FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
+ CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels());
+ const CSSCoord margin = 15;
+ CSSRect rect = nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame);
+
+ // If the element is taller than the visible area of the page scale
+ // the height of the |rect| so that it has the same aspect ratio as
+ // the root frame. The clipped |rect| is centered on the y value of
+ // the touch point. This allows tall narrow elements to be zoomed.
+ if (!rect.IsEmpty() && compositedArea.width > 0.0f) {
+ const float widthRatio = rect.width / compositedArea.width;
+ float targetHeight = compositedArea.height * widthRatio;
+ if (widthRatio < 0.9 && targetHeight < rect.height) {
+ const CSSPoint scrollPoint = CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition());
+ float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f);
+ if ((newY + targetHeight) > (rect.y + rect.height)) {
+ rect.y += rect.height - targetHeight;
+ } else if (newY > rect.y) {
+ rect.y = newY;
+ }
+ rect.height = targetHeight;
+ }
+ }
+
+ rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin),
+ rect.y,
+ rect.width + 2 * margin,
+ rect.height);
+ // Constrict the rect to the screen's right edge
+ rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x);
+
+ // If the rect is already taking up most of the visible area and is
+ // stretching the width of the page, then we want to zoom out instead.
+ if (IsRectZoomedIn(rect, compositedArea)) {
+ return zoomOut;
+ }
+
+ CSSRect rounded(rect);
+ rounded.Round();
+
+ // If the block we're zooming to is really tall, and the user double-tapped
+ // more than a screenful of height from the top of it, then adjust the
+ // y-coordinate so that we center the actual point the user double-tapped
+ // upon. This prevents flying to the top of the page when double-tapping
+ // to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to
+ // compensate for 'rect' including horizontal margins but not vertical ones.
+ CSSCoord cssTapY = metrics.GetScrollOffset().y + aPoint.y;
+ if ((rect.height > rounded.height) && (cssTapY > rounded.y + (rounded.height * 1.2))) {
+ rounded.y = cssTapY - (rounded.height / 2);
+ }
+
+ return rounded;
+}
+
+}
+}
diff --git a/gfx/layers/apz/util/DoubleTapToZoom.h b/gfx/layers/apz/util/DoubleTapToZoom.h
new file mode 100644
index 000000000..7b8723865
--- /dev/null
+++ b/gfx/layers/apz/util/DoubleTapToZoom.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_DoubleTapToZoom_h
+#define mozilla_layers_DoubleTapToZoom_h
+
+#include "Units.h"
+
+class nsIDocument;
+template<class T> class nsCOMPtr;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * For a double tap at |aPoint|, return the rect to which the browser
+ * should zoom in response, or an empty rect if the browser should zoom out.
+ * |aDocument| should be the root content document for the content that was
+ * tapped.
+ */
+CSSRect CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
+ const CSSPoint& aPoint);
+
+}
+}
+
+#endif /* mozilla_layers_DoubleTapToZoom_h */
diff --git a/gfx/layers/apz/util/InputAPZContext.cpp b/gfx/layers/apz/util/InputAPZContext.cpp
new file mode 100644
index 000000000..af5bd9f0f
--- /dev/null
+++ b/gfx/layers/apz/util/InputAPZContext.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; 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/. */
+
+#include "InputAPZContext.h"
+
+namespace mozilla {
+namespace layers {
+
+ScrollableLayerGuid InputAPZContext::sGuid;
+uint64_t InputAPZContext::sBlockId = 0;
+nsEventStatus InputAPZContext::sApzResponse = nsEventStatus_eIgnore;
+bool InputAPZContext::sRoutedToChildProcess = false;
+
+/*static*/ ScrollableLayerGuid
+InputAPZContext::GetTargetLayerGuid()
+{
+ return sGuid;
+}
+
+/*static*/ uint64_t
+InputAPZContext::GetInputBlockId()
+{
+ return sBlockId;
+}
+
+/*static*/ nsEventStatus
+InputAPZContext::GetApzResponse()
+{
+ return sApzResponse;
+}
+
+/*static*/ void
+InputAPZContext::SetRoutedToChildProcess()
+{
+ sRoutedToChildProcess = true;
+}
+
+InputAPZContext::InputAPZContext(const ScrollableLayerGuid& aGuid,
+ const uint64_t& aBlockId,
+ const nsEventStatus& aApzResponse)
+ : mOldGuid(sGuid)
+ , mOldBlockId(sBlockId)
+ , mOldApzResponse(sApzResponse)
+ , mOldRoutedToChildProcess(sRoutedToChildProcess)
+{
+ sGuid = aGuid;
+ sBlockId = aBlockId;
+ sApzResponse = aApzResponse;
+ sRoutedToChildProcess = false;
+}
+
+InputAPZContext::~InputAPZContext()
+{
+ sGuid = mOldGuid;
+ sBlockId = mOldBlockId;
+ sApzResponse = mOldApzResponse;
+ sRoutedToChildProcess = mOldRoutedToChildProcess;
+}
+
+bool
+InputAPZContext::WasRoutedToChildProcess()
+{
+ return sRoutedToChildProcess;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/InputAPZContext.h b/gfx/layers/apz/util/InputAPZContext.h
new file mode 100644
index 000000000..0f232e1cb
--- /dev/null
+++ b/gfx/layers/apz/util/InputAPZContext.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_InputAPZContext_h
+#define mozilla_layers_InputAPZContext_h
+
+#include "FrameMetrics.h"
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace layers {
+
+// InputAPZContext is used to communicate the ScrollableLayerGuid,
+// input block ID, APZ response from nsIWidget to RenderFrameParent.
+// It is conceptually attached to any WidgetInputEvent
+// that has been processed by APZ directly from a widget.
+class MOZ_STACK_CLASS InputAPZContext
+{
+private:
+ static ScrollableLayerGuid sGuid;
+ static uint64_t sBlockId;
+ static nsEventStatus sApzResponse;
+ static bool sRoutedToChildProcess;
+
+public:
+ static ScrollableLayerGuid GetTargetLayerGuid();
+ static uint64_t GetInputBlockId();
+ static nsEventStatus GetApzResponse();
+ static void SetRoutedToChildProcess();
+
+ InputAPZContext(const ScrollableLayerGuid& aGuid,
+ const uint64_t& aBlockId,
+ const nsEventStatus& aApzResponse);
+ ~InputAPZContext();
+
+ bool WasRoutedToChildProcess();
+
+private:
+ ScrollableLayerGuid mOldGuid;
+ uint64_t mOldBlockId;
+ nsEventStatus mOldApzResponse;
+ bool mOldRoutedToChildProcess;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_InputAPZContext_h */
diff --git a/gfx/layers/apz/util/ScrollInputMethods.h b/gfx/layers/apz/util/ScrollInputMethods.h
new file mode 100644
index 000000000..ba599cd8b
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollInputMethods.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_ScrollInputMethods_h
+#define mozilla_layers_ScrollInputMethods_h
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * An enumeration that lists various input methods used to trigger scrolling.
+ * Used as the values for the SCROLL_INPUT_METHODS telemetry histogram.
+ */
+enum class ScrollInputMethod {
+
+ // === Driven by APZ ===
+
+ ApzTouch, // touch events
+ ApzWheelPixel, // wheel events, pixel scrolling mode
+ ApzWheelLine, // wheel events, line scrolling mode
+ ApzWheelPage, // wheel events, page scrolling mode
+ ApzPanGesture, // pan gesture events (generally triggered by trackpad)
+ ApzScrollbarDrag, // dragging the scrollbar
+
+ // === Driven by the main thread ===
+
+ // Keyboard
+ MainThreadScrollLine, // line scrolling
+ // (generally triggered by up/down arrow keys)
+ MainThreadScrollCharacter, // character scrolling
+ // (generally triggered by left/right arrow keys)
+ MainThreadScrollPage, // page scrolling
+ // (generally triggered by PageUp/PageDown keys)
+ MainThreadCompleteScroll, // scrolling to the end of the scroll range
+ // (generally triggered by Home/End keys)
+ MainThreadScrollCaretIntoView, // scrolling to bring the caret into view
+ // after moving the caret via the keyboard
+
+ // Touch
+ MainThreadTouch, // touch events
+
+ // Scrollbar
+ MainThreadScrollbarDrag, // dragging the scrollbar
+ MainThreadScrollbarButtonClick, // clicking the buttons at the ends of the
+ // scrollback track
+ MainThreadScrollbarTrackClick, // clicking the scrollbar track above or
+ // below the thumb
+
+ // Autoscrolling
+ MainThreadAutoscrolling, // autoscrolling
+
+ // New input methods can be added at the end, up to a maximum of 64.
+ // They should only be added at the end, to preserve the numerical values
+ // of the existing enumerators.
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_ScrollInputMethods_h */
diff --git a/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp b/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp
new file mode 100644
index 000000000..758b705a3
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; 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/. */
+
+#include "ScrollLinkedEffectDetector.h"
+
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace layers {
+
+uint32_t ScrollLinkedEffectDetector::sDepth = 0;
+bool ScrollLinkedEffectDetector::sFoundScrollLinkedEffect = false;
+
+/* static */ void
+ScrollLinkedEffectDetector::PositioningPropertyMutated()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sDepth > 0) {
+ // We are inside a scroll event dispatch
+ sFoundScrollLinkedEffect = true;
+ }
+}
+
+ScrollLinkedEffectDetector::ScrollLinkedEffectDetector(nsIDocument* aDoc)
+ : mDocument(aDoc)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ sDepth++;
+}
+
+ScrollLinkedEffectDetector::~ScrollLinkedEffectDetector()
+{
+ sDepth--;
+ if (sDepth == 0) {
+ // We have exited all (possibly-nested) scroll event dispatches,
+ // record whether or not we found an effect, and reset state
+ if (sFoundScrollLinkedEffect) {
+ mDocument->ReportHasScrollLinkedEffect();
+ sFoundScrollLinkedEffect = false;
+ }
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/ScrollLinkedEffectDetector.h b/gfx/layers/apz/util/ScrollLinkedEffectDetector.h
new file mode 100644
index 000000000..f792586cf
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_layers_ScrollLinkedEffectDetector_h
+#define mozilla_layers_ScrollLinkedEffectDetector_h
+
+#include "mozilla/RefPtr.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace layers {
+
+// ScrollLinkedEffectDetector is used to detect the existence of a scroll-linked
+// effect on a webpage. Generally speaking, a scroll-linked effect is something
+// on the page that animates or changes with respect to the scroll position.
+// Content authors usually rely on running some JS in response to the scroll
+// event in order to implement such effects, and therefore it tends to be laggy
+// or work improperly with APZ enabled. This class helps us detect such an
+// effect so that we can warn the author and/or take other preventative
+// measures.
+class MOZ_STACK_CLASS ScrollLinkedEffectDetector
+{
+private:
+ static uint32_t sDepth;
+ static bool sFoundScrollLinkedEffect;
+
+public:
+ static void PositioningPropertyMutated();
+
+ explicit ScrollLinkedEffectDetector(nsIDocument* aDoc);
+ ~ScrollLinkedEffectDetector();
+
+private:
+ RefPtr<nsIDocument> mDocument;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_ScrollLinkedEffectDetector_h */
diff --git a/gfx/layers/apz/util/TouchActionHelper.cpp b/gfx/layers/apz/util/TouchActionHelper.cpp
new file mode 100644
index 000000000..b35fd2ec7
--- /dev/null
+++ b/gfx/layers/apz/util/TouchActionHelper.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; 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/. */
+
+#include "TouchActionHelper.h"
+
+#include "mozilla/layers/APZCTreeManager.h"
+#include "nsContainerFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+namespace layers {
+
+void
+TouchActionHelper::UpdateAllowedBehavior(uint32_t aTouchActionValue,
+ bool aConsiderPanning,
+ TouchBehaviorFlags& aOutBehavior)
+{
+ if (aTouchActionValue != NS_STYLE_TOUCH_ACTION_AUTO) {
+ // Double-tap-zooming need property value AUTO
+ aOutBehavior &= ~AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
+ if (aTouchActionValue != NS_STYLE_TOUCH_ACTION_MANIPULATION) {
+ // Pinch-zooming need value AUTO or MANIPULATION
+ aOutBehavior &= ~AllowedTouchBehavior::PINCH_ZOOM;
+ }
+ }
+
+ if (aConsiderPanning) {
+ if (aTouchActionValue == NS_STYLE_TOUCH_ACTION_NONE) {
+ aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN;
+ aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
+ }
+
+ // Values pan-x and pan-y set at the same time to the same element do not affect panning constraints.
+ // Therefore we need to check whether pan-x is set without pan-y and the same for pan-y.
+ if ((aTouchActionValue & NS_STYLE_TOUCH_ACTION_PAN_X) && !(aTouchActionValue & NS_STYLE_TOUCH_ACTION_PAN_Y)) {
+ aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN;
+ } else if ((aTouchActionValue & NS_STYLE_TOUCH_ACTION_PAN_Y) && !(aTouchActionValue & NS_STYLE_TOUCH_ACTION_PAN_X)) {
+ aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
+ }
+ }
+}
+
+TouchBehaviorFlags
+TouchActionHelper::GetAllowedTouchBehavior(nsIWidget* aWidget,
+ nsIFrame* aRootFrame,
+ const LayoutDeviceIntPoint& aPoint)
+{
+ TouchBehaviorFlags behavior = AllowedTouchBehavior::VERTICAL_PAN | AllowedTouchBehavior::HORIZONTAL_PAN |
+ AllowedTouchBehavior::PINCH_ZOOM | AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
+
+ nsPoint relativePoint =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aPoint, aRootFrame);
+
+ nsIFrame *target = nsLayoutUtils::GetFrameForPoint(aRootFrame, relativePoint, nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
+ if (!target) {
+ return behavior;
+ }
+ nsIScrollableFrame *nearestScrollableParent = nsLayoutUtils::GetNearestScrollableFrame(target, 0);
+ nsIFrame* nearestScrollableFrame = do_QueryFrame(nearestScrollableParent);
+
+ // We're walking up the DOM tree until we meet the element with touch behavior and accumulating
+ // touch-action restrictions of all elements in this chain.
+ // The exact quote from the spec, that clarifies more:
+ // To determine the effect of a touch, find the nearest ancestor (starting from the element itself)
+ // that has a default touch behavior. Then examine the touch-action property of each element between
+ // the hit tested element and the element with the default touch behavior (including both the hit
+ // tested element and the element with the default touch behavior). If the touch-action property of
+ // any of those elements disallows the default touch behavior, do nothing. Otherwise allow the element
+ // to start considering the touch for the purposes of executing a default touch behavior.
+
+ // Currently we support only two touch behaviors: panning and zooming.
+ // For panning we walk up until we meet the first scrollable element (the element that supports panning)
+ // or root element.
+ // For zooming we walk up until the root element since Firefox currently supports only zooming of the
+ // root frame but not the subframes.
+
+ bool considerPanning = true;
+
+ for (nsIFrame *frame = target; frame && frame->GetContent() && behavior; frame = frame->GetParent()) {
+ UpdateAllowedBehavior(nsLayoutUtils::GetTouchActionFromFrame(frame), considerPanning, behavior);
+
+ if (frame == nearestScrollableFrame) {
+ // We met the scrollable element, after it we shouldn't consider touch-action
+ // values for the purpose of panning but only for zooming.
+ considerPanning = false;
+ }
+ }
+
+ return behavior;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/TouchActionHelper.h b/gfx/layers/apz/util/TouchActionHelper.h
new file mode 100644
index 000000000..1dacfd4c0
--- /dev/null
+++ b/gfx/layers/apz/util/TouchActionHelper.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef __mozilla_layers_TouchActionHelper_h__
+#define __mozilla_layers_TouchActionHelper_h__
+
+#include "mozilla/layers/APZUtils.h" // for TouchBehaviorFlags
+
+class nsIFrame;
+class nsIWidget;
+
+namespace mozilla {
+namespace layers {
+
+/*
+ * Helper class to figure out the allowed touch behavior for frames, as per
+ * the touch-action spec.
+ */
+class TouchActionHelper
+{
+private:
+ static void UpdateAllowedBehavior(uint32_t aTouchActionValue,
+ bool aConsiderPanning,
+ TouchBehaviorFlags& aOutBehavior);
+
+public:
+ /*
+ * Performs hit testing on content, finds frame that corresponds to the aPoint and retrieves
+ * touch-action css property value from it according the rules specified in the spec:
+ * http://www.w3.org/TR/pointerevents/#the-touch-action-css-property.
+ */
+ static TouchBehaviorFlags GetAllowedTouchBehavior(nsIWidget* aWidget,
+ nsIFrame* aRootFrame,
+ const LayoutDeviceIntPoint& aPoint);
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /*__mozilla_layers_TouchActionHelper_h__ */