summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/util
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/layers/apz/util
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/apz/util')
-rw-r--r--gfx/layers/apz/util/APZCCallbackHelper.cpp925
-rw-r--r--gfx/layers/apz/util/APZCCallbackHelper.h204
-rw-r--r--gfx/layers/apz/util/APZEventState.cpp553
-rw-r--r--gfx/layers/apz/util/APZEventState.h124
-rw-r--r--gfx/layers/apz/util/APZThreadUtils.cpp118
-rw-r--r--gfx/layers/apz/util/APZThreadUtils.h117
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.cpp178
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.h97
-rw-r--r--gfx/layers/apz/util/CheckerboardReportService.cpp219
-rw-r--r--gfx/layers/apz/util/CheckerboardReportService.h138
-rw-r--r--gfx/layers/apz/util/ChromeProcessController.cpp335
-rw-r--r--gfx/layers/apz/util/ChromeProcessController.h97
-rw-r--r--gfx/layers/apz/util/ContentProcessController.cpp108
-rw-r--r--gfx/layers/apz/util/ContentProcessController.h87
-rw-r--r--gfx/layers/apz/util/DoubleTapToZoom.cpp188
-rw-r--r--gfx/layers/apz/util/DoubleTapToZoom.h35
-rw-r--r--gfx/layers/apz/util/InputAPZContext.cpp65
-rw-r--r--gfx/layers/apz/util/InputAPZContext.h69
-rw-r--r--gfx/layers/apz/util/ScrollInputMethods.h78
-rw-r--r--gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp47
-rw-r--r--gfx/layers/apz/util/ScrollLinkedEffectDetector.h46
-rw-r--r--gfx/layers/apz/util/TouchActionHelper.cpp106
-rw-r--r--gfx/layers/apz/util/TouchActionHelper.h39
-rw-r--r--gfx/layers/apz/util/TouchCounter.cpp74
-rw-r--r--gfx/layers/apz/util/TouchCounter.h36
25 files changed, 4083 insertions, 0 deletions
diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp
new file mode 100644
index 0000000000..8d5d38f0cd
--- /dev/null
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "APZCCallbackHelper.h"
+
+#include "TouchActionHelper.h"
+#include "gfxPlatform.h" // For gfxPlatform::UseTiling
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/layers/LayerTransactionChild.h"
+#include "mozilla/layers/RepaintRequest.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/ViewportUtils.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsIDOMWindowUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPrintfCString.h"
+#include "nsRefreshDriver.h"
+#include "nsString.h"
+#include "nsView.h"
+#include "Layers.h"
+
+static mozilla::LazyLogModule sApzHlpLog("apz.helper");
+#define APZCCH_LOG(...) MOZ_LOG(sApzHlpLog, LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule sDisplayportLog("apz.displayport");
+
+namespace mozilla {
+namespace layers {
+
+using dom::BrowserParent;
+
+uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock =
+ uint64_t(-1);
+
+static ScreenMargin RecenterDisplayPort(const ScreenMargin& aDisplayPort) {
+ ScreenMargin margins = aDisplayPort;
+ margins.right = margins.left = margins.LeftRight() / 2;
+ margins.top = margins.bottom = margins.TopBottom() / 2;
+ return margins;
+}
+
+static PresShell* GetPresShell(const nsIContent* aContent) {
+ if (dom::Document* doc = aContent->GetComposedDoc()) {
+ return doc->GetPresShell();
+ }
+ return nullptr;
+}
+
+static CSSPoint ScrollFrameTo(nsIScrollableFrame* aFrame,
+ const RepaintRequest& aRequest,
+ bool& aSuccessOut) {
+ aSuccessOut = false;
+ CSSPoint targetScrollPosition = aRequest.GetLayoutScrollOffset();
+
+ 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 (!aRequest.GetScrollOffsetUpdated()) {
+ return geckoScrollPosition;
+ }
+
+ // If this frame is overflow:hidden, then the expectation is that it was
+ // sized in a way that respects its scrollable boundaries. For the root
+ // frame, this means that it cannot be scrolled in such a way that it moves
+ // the layout viewport. For a non-root frame, this means that it cannot be
+ // scrolled at all.
+ //
+ // In either case, |targetScrollPosition| should be the same as
+ // |geckoScrollPosition| here.
+ //
+ // However, this is slightly racy. We query the overflow property of the
+ // scroll frame at the time the repaint request arrives at the main thread
+ // (i.e., right now), but APZ made the decision of whether or not to allow
+ // scrolling based on the information it had at the time it processed the
+ // scroll event. The overflow property could have changed at some time
+ // between the two events and so APZ may have computed a scrollable region
+ // that is larger than what is actually allowed.
+ //
+ // Currently, we allow the scroll position to change even though the frame is
+ // overflow:hidden (that is, we take |targetScrollPosition|). If this turns
+ // out to be problematic, an alternative solution would be to ignore the
+ // scroll position change (that is, use |geckoScrollPosition|).
+ if (aFrame->GetScrollStyles().mVertical == StyleOverflow::Hidden &&
+ targetScrollPosition.y != geckoScrollPosition.y) {
+ NS_WARNING(
+ nsPrintfCString(
+ "APZCCH: targetScrollPosition.y (%f) != geckoScrollPosition.y (%f)",
+ targetScrollPosition.y, geckoScrollPosition.y)
+ .get());
+ }
+ if (aFrame->GetScrollStyles().mHorizontal == StyleOverflow::Hidden &&
+ targetScrollPosition.x != geckoScrollPosition.x) {
+ NS_WARNING(
+ nsPrintfCString(
+ "APZCCH: targetScrollPosition.x (%f) != geckoScrollPosition.x (%f)",
+ targetScrollPosition.x, geckoScrollPosition.x)
+ .get());
+ }
+
+ // 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,
+ ScrollOrigin::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 |aRequest|.
+ *
+ * Any difference between the requested and actual scroll positions is used to
+ * update the callback-transform stored on the content, and return a new
+ * display port.
+ */
+static DisplayPortMargins ScrollFrame(nsIContent* aContent,
+ const RepaintRequest& aRequest) {
+ // Scroll the window to the desired spot
+ nsIScrollableFrame* sf =
+ nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
+ if (sf) {
+ sf->ResetScrollInfoIfNeeded(aRequest.GetScrollGeneration(),
+ aRequest.IsAnimationInProgress());
+ sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
+ if (sf->IsRootScrollFrameOfDocument()) {
+ if (!APZCCallbackHelper::IsScrollInProgress(sf)) {
+ APZCCH_LOG("Setting VV offset to %s\n",
+ ToString(aRequest.GetVisualScrollOffset()).c_str());
+ if (sf->SetVisualViewportOffset(
+ CSSPoint::ToAppUnits(aRequest.GetVisualScrollOffset()),
+ /* aRepaint = */ false)) {
+ // sf can't be destroyed if SetVisualViewportOffset returned true.
+ sf->MarkEverScrolled();
+ }
+ }
+ }
+ }
+ // sf might have been destroyed by the call to SetVisualViewportOffset, so
+ // re-get it.
+ sf = nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
+ bool scrollUpdated = false;
+ auto displayPortMargins = DisplayPortMargins::ForScrollFrame(
+ sf, aRequest.GetDisplayPortMargins(),
+ Some(aRequest.DisplayportPixelsPerCSSPixel()));
+ CSSPoint apzScrollOffset = aRequest.GetVisualScrollOffset();
+ CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated);
+ CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
+
+ if (scrollUpdated) {
+ if (aRequest.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 the requested
+ // and actual scroll offsets.
+ displayPortMargins = DisplayPortMargins::FromAPZ(
+ aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset,
+ aRequest.DisplayportPixelsPerCSSPixel());
+ }
+ } else if (aRequest.IsRootContent() &&
+ apzScrollOffset != aRequest.GetLayoutScrollOffset()) {
+ // APZ uses the visual viewport's offset to calculate where to place the
+ // display port, so the display port is misplaced when a pinch zoom occurs.
+ //
+ // We need to force a display port adjustment in the following paint to
+ // account for a difference between the requested and actual scroll
+ // offsets in repaints requested by
+ // AsyncPanZoomController::NotifyLayersUpdated.
+ displayPortMargins = DisplayPortMargins::FromAPZ(
+ aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset,
+ aRequest.DisplayportPixelsPerCSSPixel());
+ } 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.
+ displayPortMargins = DisplayPortMargins::ForScrollFrame(
+ sf, RecenterDisplayPort(aRequest.GetDisplayPortMargins()),
+ Some(aRequest.DisplayportPixelsPerCSSPixel()));
+ }
+
+ // 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() != aRequest.GetScrollGeneration() &&
+ nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
+ if (aContent && !mainThreadScrollChanged) {
+ aContent->SetProperty(nsGkAtoms::apzCallbackTransform,
+ new CSSPoint(scrollDelta),
+ nsINode::DeleteProperty<CSSPoint>);
+ }
+
+ return displayPortMargins;
+}
+
+static void SetDisplayPortMargins(PresShell* aPresShell, nsIContent* aContent,
+ const DisplayPortMargins& aDisplayPortMargins,
+ CSSSize aDisplayPortBase) {
+ if (!aContent) {
+ return;
+ }
+
+ bool hadDisplayPort = DisplayPortUtils::HasDisplayPort(aContent);
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
+ if (!hadDisplayPort) {
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsLayoutUtils::FindIDFor(aContent, &viewID);
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Debug,
+ ("APZCCH installing displayport margins %s on scrollId=%" PRIu64 "\n",
+ ToString(aDisplayPortMargins).c_str(), viewID));
+ }
+ }
+ DisplayPortUtils::SetDisplayPortMargins(aContent, aPresShell,
+ aDisplayPortMargins, 0);
+ if (!hadDisplayPort) {
+ DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ aContent->GetPrimaryFrame());
+ }
+
+ nsRect base(0, 0, aDisplayPortBase.width * AppUnitsPerCSSPixel(),
+ aDisplayPortBase.height * AppUnitsPerCSSPixel());
+ DisplayPortUtils::SetDisplayPortBaseIfNotSet(aContent, base);
+}
+
+static void SetPaintRequestTime(nsIContent* aContent,
+ const TimeStamp& aPaintRequestTime) {
+ aContent->SetProperty(nsGkAtoms::paintRequestTime,
+ new TimeStamp(aPaintRequestTime),
+ nsINode::DeleteProperty<TimeStamp>);
+}
+
+void APZCCallbackHelper::NotifyLayerTransforms(
+ const nsTArray<MatrixMessage>& aTransforms) {
+ MOZ_ASSERT(NS_IsMainThread());
+ for (const MatrixMessage& msg : aTransforms) {
+ BrowserParent* parent =
+ BrowserParent::GetBrowserParentFromLayersId(msg.GetLayersId());
+ if (parent) {
+ parent->SetChildToParentConversionMatrix(
+ ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>(
+ msg.GetMatrix(),
+ PixelCastJustification::ContentProcessIsLayerInUiProcess),
+ msg.GetTopLevelViewportVisibleRectInBrowserCoords());
+ }
+ }
+}
+
+void APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest) {
+ if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) {
+ return;
+ }
+ RefPtr<nsIContent> content =
+ nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
+ if (!content) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell(content);
+ if (!presShell || aRequest.GetPresShellId() != presShell->GetPresShellId()) {
+ return;
+ }
+
+ APZCCH_LOG("Handling request %s\n", ToString(aRequest).c_str());
+ if (nsLayoutUtils::AllowZoomingForDocument(presShell->GetDocument()) &&
+ aRequest.GetAsyncZoom().scale != 1.0) {
+ // 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 visual viewport size is bad because it can cause a
+ // stale value to be returned by window.innerWidth/innerHeight (see bug
+ // 1187792).
+
+ float presShellResolution = presShell->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,
+ aRequest.GetPresShellResolution())) {
+ return;
+ }
+
+ // The pres shell resolution is updated by the the async zoom since the
+ // last paint.
+ presShellResolution =
+ aRequest.GetPresShellResolution() * aRequest.GetAsyncZoom().scale;
+ presShell->SetResolutionAndScaleTo(presShellResolution,
+ ResolutionChangeOrigin::Apz);
+
+ // Changing the resolution will trigger a reflow which will cause the
+ // main-thread scroll position to be realigned in layer pixels. This
+ // (subpixel) scroll mutation can trigger a scroll update to APZ which
+ // is undesirable. Instead of having that happen as part of the post-reflow
+ // code, we force it to happen here with ScrollOrigin::Apz so that it
+ // doesn't trigger a scroll update to APZ.
+ nsIScrollableFrame* sf =
+ nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
+ CSSPoint currentScrollPosition =
+ CSSPoint::FromAppUnits(sf->GetScrollPosition());
+ sf->ScrollToCSSPixelsApproximate(currentScrollPosition, ScrollOrigin::Apz);
+ }
+
+ // 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.
+ DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest);
+
+ SetDisplayPortMargins(presShell, content, displayPortMargins,
+ aRequest.CalculateCompositedSizeInCssPixels());
+ SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
+}
+
+void APZCCallbackHelper::UpdateSubFrame(const RepaintRequest& aRequest) {
+ if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) {
+ return;
+ }
+ RefPtr<nsIContent> content =
+ nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
+ if (!content) {
+ return;
+ }
+
+ // We don't currently support zooming for subframes, so nothing extra
+ // needs to be done beyond the tasks common to this and UpdateRootFrame.
+ DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest);
+ if (RefPtr<PresShell> presShell = GetPresShell(content)) {
+ SetDisplayPortMargins(presShell, content, displayPortMargins,
+ aRequest.CalculateCompositedSizeInCssPixels());
+ }
+ SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
+}
+
+bool APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ nsIContent* aContent, uint32_t* aPresShellIdOut,
+ ScrollableLayerGuid::ViewID* aViewIdOut) {
+ if (!aContent) {
+ return false;
+ }
+ *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
+ if (PresShell* presShell = GetPresShell(aContent)) {
+ *aPresShellIdOut = presShell->GetPresShellId();
+ return true;
+ }
+ return false;
+}
+
+void APZCCallbackHelper::InitializeRootDisplayport(PresShell* 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;
+ ScrollableLayerGuid::ViewID viewId;
+ if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId,
+ &viewId)) {
+ nsPresContext* pc = aPresShell->GetPresContext();
+ // This code is only correct for root content or toplevel documents.
+ MOZ_ASSERT(!pc || pc->IsRootContentDocumentCrossProcess() ||
+ !pc->GetParentPresContext());
+ nsIFrame* frame = aPresShell->GetRootScrollFrame();
+ if (!frame) {
+ frame = aPresShell->GetRootFrame();
+ }
+ nsRect baseRect;
+ if (frame) {
+ baseRect = nsRect(nsPoint(0, 0),
+ nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
+ } else if (pc) {
+ baseRect = nsRect(nsPoint(0, 0), pc->GetVisibleArea().Size());
+ }
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Debug,
+ ("Initializing root displayport on scrollId=%" PRIu64 "\n", viewId));
+ DisplayPortUtils::SetDisplayPortBaseIfNotSet(content, baseRect);
+ // Note that we also set the base rect that goes with these margins in
+ // nsRootBoxFrame::BuildDisplayList.
+ DisplayPortUtils::SetDisplayPortMargins(
+ content, aPresShell, DisplayPortMargins::Empty(content), 0);
+ DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ content->GetPrimaryFrame());
+ }
+}
+
+nsPresContext* APZCCallbackHelper::GetPresContextForContent(
+ nsIContent* aContent) {
+ dom::Document* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetPresContext();
+}
+
+PresShell* APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
+ nsIContent* aContent) {
+ nsPresContext* context = GetPresContextForContent(aContent);
+ if (!context) {
+ return nullptr;
+ }
+ context = context->GetInProcessRootContentDocumentPresContext();
+ if (!context) {
+ return nullptr;
+ }
+ return context->PresShell();
+}
+
+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.mButton = MouseButton::ePrimary;
+ event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ if (aMsg == eMouseLongTap) {
+ event.mFlags.mOnlyChromeDispatch = 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(
+ PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint,
+ int32_t aButton, int32_t aClickCount, int32_t aModifiers,
+ unsigned short aInputSourceArg, uint32_t aPointerId) {
+ 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 = */ false, 0, aInputSourceArg, aPointerId,
+ 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",
+ ToString(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 (PresShell* presShell = view->GetPresShell()) {
+ MOZ_ASSERT(presShell->GetDocument());
+ return presShell->GetDocument()->GetDocumentElement();
+ }
+ }
+ return nullptr;
+}
+
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+// 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 the frame had a displayport that has already been
+// painted (in this case, the caller can send the SetTargetAPZC notification
+// right away, rather than waiting for a transaction to propagate the
+// displayport to APZ first).
+static bool PrepareForSetTargetAPZCNotification(
+ nsIWidget* aWidget, const LayersId& aLayersId, nsIFrame* aRootFrame,
+ const LayoutDeviceIntPoint& aRefPoint,
+ nsTArray<ScrollableLayerGuid>* aTargets) {
+ ScrollableLayerGuid guid(aLayersId, 0, ScrollableLayerGuid::NULL_SCROLL_ID);
+ RelativeTo relativeTo{aRootFrame, ViewportType::Visual};
+ nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aWidget, aRefPoint, relativeTo);
+ nsIFrame* target = nsLayoutUtils::GetFrameForPoint(relativeTo, point);
+ nsIScrollableFrame* scrollAncestor =
+ target ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
+ : aRootFrame->PresShell()->GetRootScrollFrameAsScrollable();
+
+ // Assuming that if there's no scrollAncestor, there's already a displayPort.
+ nsCOMPtr<dom::Element> dpElement =
+ scrollAncestor ? GetDisplayportElementFor(scrollAncestor)
+ : GetRootDocumentElementFor(aWidget);
+
+ if (MOZ_LOG_TEST(sApzHlpLog, LogLevel::Debug)) {
+ nsAutoString dpElementDesc;
+ if (dpElement) {
+ dpElement->Describe(dpElementDesc);
+ }
+ APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
+ ToString(aRefPoint).c_str(), dpElement.get(),
+ NS_LossyConvertUTF16toASCII(dpElementDesc).get());
+ }
+
+ bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ dpElement, &(guid.mPresShellId), &(guid.mScrollId));
+ aTargets->AppendElement(guid);
+
+ if (!guidIsValid) {
+ return false;
+ }
+ if (DisplayPortUtils::HasDisplayPort(dpElement)) {
+ // If the element has a displayport but it hasn't been painted yet,
+ // we want the caller to wait for the paint to happen, but we don't
+ // need to set the displayport here since it's already been set.
+ return !DisplayPortUtils::HasPaintedDisplayPort(dpElement);
+ }
+
+ if (!scrollAncestor) {
+ // This can happen if the document element gets swapped out after
+ // ChromeProcessController runs InitializeRootDisplayport. In this case
+ // let's try to set a displayport 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->PresShell());
+ return false;
+ }
+
+ APZCCH_LOG("%p didn't have a displayport, so setting one...\n",
+ dpElement.get());
+ MOZ_LOG(sDisplayportLog, LogLevel::Debug,
+ ("Activating displayport on scrollId=%" PRIu64 " for SetTargetAPZC\n",
+ guid.mScrollId));
+ bool activated = DisplayPortUtils::CalculateAndSetDisplayPortMargins(
+ scrollAncestor, DisplayPortUtils::RepaintMode::Repaint);
+ if (!activated) {
+ return false;
+ }
+
+ nsIFrame* frame = do_QueryFrame(scrollAncestor);
+ DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
+
+ return true;
+}
+
+static void SendLayersDependentApzcTargetConfirmation(
+ PresShell* aPresShell, uint64_t aInputBlockId,
+ nsTArray<ScrollableLayerGuid>&& aTargets) {
+ LayerManager* lm = aPresShell->GetLayerManager();
+ if (!lm) {
+ return;
+ }
+
+ if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
+ if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) {
+ wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
+ }
+ return;
+ }
+
+ ShadowLayerForwarder* lf = lm->AsShadowForwarder();
+ if (!lf) {
+ return;
+ }
+
+ LayerTransactionChild* shadow = lf->GetShadowManager();
+ if (!shadow) {
+ return;
+ }
+
+ shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
+}
+
+} // namespace
+
+DisplayportSetListener::DisplayportSetListener(
+ nsIWidget* aWidget, PresShell* aPresShell, const uint64_t& aInputBlockId,
+ nsTArray<ScrollableLayerGuid>&& aTargets)
+ : OneShotPostRefreshObserver(aPresShell),
+ mWidget(aWidget),
+ mInputBlockId(aInputBlockId),
+ mTargets(std::move(aTargets)) {
+ MOZ_ASSERT(!mAction, "Setting Action twice");
+ mAction = [](PresShell* aPresShell,
+ OneShotPostRefreshObserver* aThisObserver) {
+ OnPostRefresh(static_cast<DisplayportSetListener*>(aThisObserver),
+ aPresShell);
+ };
+}
+
+DisplayportSetListener::~DisplayportSetListener() = default;
+
+bool DisplayportSetListener::Register() {
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ presContext->RegisterOneShotPostRefreshObserver(this);
+ APZCCH_LOG("Successfully registered post-refresh observer\n");
+ return true;
+ }
+ // In case of failure just send the notification right away
+ APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n",
+ mInputBlockId);
+ mWidget->SetConfirmedTargetAPZC(mInputBlockId, mTargets);
+ return false;
+}
+
+/* static */
+void DisplayportSetListener::OnPostRefresh(DisplayportSetListener* aListener,
+ PresShell* aPresShell) {
+ APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n",
+ aListener->mInputBlockId);
+ SendLayersDependentApzcTargetConfirmation(
+ aPresShell, aListener->mInputBlockId, std::move(aListener->mTargets));
+}
+
+UniquePtr<DisplayportSetListener>
+APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
+ dom::Document* aDocument,
+ const WidgetGUIEvent& aEvent,
+ const LayersId& aLayersId,
+ uint64_t aInputBlockId) {
+ if (!aWidget || !aDocument) {
+ return nullptr;
+ }
+ 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 nullptr;
+ }
+ sLastTargetAPZCNotificationInputBlock = aInputBlockId;
+ if (PresShell* presShell = aDocument->GetPresShell()) {
+ if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
+ 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, aLayersId, rootFrame, touchEvent->mTouches[i]->mRefPoint,
+ &targets);
+ }
+ } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
+ waitForRefresh = PrepareForSetTargetAPZCNotification(
+ aWidget, aLayersId, rootFrame, wheelEvent->mRefPoint, &targets);
+ } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
+ waitForRefresh = PrepareForSetTargetAPZCNotification(
+ aWidget, aLayersId, rootFrame, mouseEvent->mRefPoint, &targets);
+ }
+ // TODO: Do other types of events need to be handled?
+
+ if (!targets.IsEmpty()) {
+ if (waitForRefresh) {
+ APZCCH_LOG(
+ "At least one target got a new displayport, need to wait for "
+ "refresh\n");
+ return MakeUnique<DisplayportSetListener>(
+ aWidget, presShell, aInputBlockId, std::move(targets));
+ }
+ APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n",
+ aInputBlockId);
+ aWidget->SetConfirmedTargetAPZC(aInputBlockId, targets);
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsTArray<TouchBehaviorFlags>
+APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
+ nsIWidget* aWidget, dom::Document* aDocument,
+ const WidgetTouchEvent& aEvent, uint64_t aInputBlockId,
+ const SetAllowedTouchBehaviorCallback& aCallback) {
+ nsTArray<TouchBehaviorFlags> flags;
+ if (!aWidget || !aDocument) {
+ return flags;
+ }
+ if (PresShell* presShell = aDocument->GetPresShell()) {
+ if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
+ for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
+ flags.AppendElement(TouchActionHelper::GetAllowedTouchBehavior(
+ aWidget, RelativeTo{rootFrame, ViewportType::Visual},
+ aEvent.mTouches[i]->mRefPoint));
+ }
+ aCallback(aInputBlockId, flags);
+ }
+ }
+ return flags;
+}
+
+void APZCCallbackHelper::NotifyMozMouseScrollEvent(
+ const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) {
+ nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
+ if (!targetContent) {
+ return;
+ }
+ RefPtr<dom::Document> ownerDoc = targetContent->OwnerDoc();
+ if (!ownerDoc) {
+ return;
+ }
+
+ nsContentUtils::DispatchEventOnlyToChrome(ownerDoc, targetContent, aEvent,
+ CanBubble::eYes, Cancelable::eYes);
+}
+
+void APZCCallbackHelper::NotifyFlushComplete(PresShell* aPresShell) {
+ 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 (aPresShell && aPresShell->GetRootFrame()) {
+ aPresShell->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+ observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
+}
+
+/* static */
+bool APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame) {
+ using IncludeApzAnimation = nsIScrollableFrame::IncludeApzAnimation;
+
+ return aFrame->IsScrollAnimating(IncludeApzAnimation::No) ||
+ nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin());
+}
+
+/* static */
+void APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
+ scrollFrame->AsyncScrollbarDragInitiated(aDragBlockId, aDirection);
+ }
+}
+
+/* static */
+void APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
+ scrollFrame->AsyncScrollbarDragRejected();
+ }
+}
+
+/* static */
+void APZCCallbackHelper::NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ nsAutoString data;
+ data.AppendInt(aScrollId);
+ observerService->NotifyObservers(nullptr, "autoscroll-rejected-by-apz",
+ data.get());
+}
+
+/* static */
+void APZCCallbackHelper::CancelAutoscroll(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ nsAutoString data;
+ data.AppendInt(aScrollId);
+ observerService->NotifyObservers(nullptr, "apz:cancel-autoscroll",
+ data.get());
+}
+
+/* static */
+void APZCCallbackHelper::NotifyPinchGesture(
+ PinchGestureInput::PinchGestureType aType,
+ const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers, const nsCOMPtr<nsIWidget>& aWidget) {
+ APZCCH_LOG("APZCCallbackHelper dispatching pinch gesture\n");
+ EventMessage msg;
+ switch (aType) {
+ case PinchGestureInput::PINCHGESTURE_START:
+ msg = eMagnifyGestureStart;
+ break;
+ case PinchGestureInput::PINCHGESTURE_SCALE:
+ msg = eMagnifyGestureUpdate;
+ break;
+ case PinchGestureInput::PINCHGESTURE_FINGERLIFTED:
+ case PinchGestureInput::PINCHGESTURE_END:
+ msg = eMagnifyGesture;
+ break;
+ }
+
+ WidgetSimpleGestureEvent event(true, msg, aWidget.get());
+ // XXX mDelta for the eMagnifyGesture event is supposed to be the
+ // cumulative magnification over the entire gesture (per docs in
+ // SimpleGestureEvent.webidl) but currently APZ just sends us a zero
+ // aSpanChange for that event, so the mDelta is wrong. Nothing relies
+ // on that currently, but we might want to fix it at some point.
+ event.mDelta = aSpanChange;
+ event.mModifiers = aModifiers;
+ event.mRefPoint = RoundedToInt(aFocusPoint);
+
+ 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 0000000000..4bf6806c76
--- /dev/null
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -0,0 +1,204 @@
+/* -*- 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 "InputData.h"
+#include "LayersTypes.h"
+#include "Units.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/layers/MatrixMessage.h"
+#include "nsRefreshObservers.h"
+
+#include <functional>
+
+class nsIContent;
+class nsIScrollableFrame;
+class nsIWidget;
+template <class T>
+struct already_AddRefed;
+template <class T>
+class nsCOMPtr;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace layers {
+
+struct RepaintRequest;
+
+typedef std::function<void(uint64_t, const nsTArray<TouchBehaviorFlags>&)>
+ SetAllowedTouchBehaviorCallback;
+
+/* Refer to documentation on SendSetTargetAPZCNotification for this class */
+class DisplayportSetListener : public OneShotPostRefreshObserver {
+ public:
+ DisplayportSetListener(nsIWidget* aWidget, PresShell* aPresShell,
+ const uint64_t& aInputBlockId,
+ nsTArray<ScrollableLayerGuid>&& aTargets);
+ virtual ~DisplayportSetListener();
+ bool Register();
+
+ private:
+ RefPtr<nsIWidget> mWidget;
+ uint64_t mInputBlockId;
+ nsTArray<ScrollableLayerGuid> mTargets;
+
+ static void OnPostRefresh(DisplayportSetListener* aListener,
+ PresShell* aPresShell);
+};
+
+/* 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::ScrollableLayerGuid ScrollableLayerGuid;
+
+ public:
+ static void NotifyLayerTransforms(const nsTArray<MatrixMessage>& aTransforms);
+
+ /* Applies the scroll and zoom parameters from the given RepaintRequest 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. */
+ static void UpdateRootFrame(const RepaintRequest& aRequest);
+
+ /* Applies the scroll parameters from the given RepaintRequest 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. */
+ static void UpdateSubFrame(const RepaintRequest& aRequest);
+
+ /* 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,
+ ScrollableLayerGuid::ViewID* aViewIdOut);
+
+ /* Initialize a zero-margin displayport on the root document element of the
+ given presShell. */
+ static void InitializeRootDisplayport(PresShell* 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 PresShell* GetRootContentDocumentPresShellForContent(
+ nsIContent* aContent);
+
+ /* Dispatch a widget event via the widget stored in the event, if any.
+ * In a child process, allows the BrowserParent 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.
+ * This is a lightweight wrapper around nsContentUtils::SendMouseEvent()
+ * and as such expects |aPoint| to be in layout coordinates. */
+ MOZ_CAN_RUN_SCRIPT
+ static bool DispatchMouseEvent(PresShell* aPresShell, const nsString& aType,
+ const CSSPoint& aPoint, int32_t aButton,
+ int32_t aClickCount, int32_t aModifiers,
+ unsigned short aInputSourceArg,
+ uint32_t aPointerId);
+
+ /* 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, this function returns a heap-allocated
+ * object. The caller is responsible for calling Register() on that object,
+ * and release()'ing the UniquePtr if that Register() call returns true.
+ * The object registers itself as a post-refresh observer on the presShell
+ * and ensures that notifications get sent to APZ correctly after the
+ * refresh.
+ *
+ * Having the caller manage this object is desirable in case they want to
+ * (a) know about the fact that a displayport needs to be set, and
+ * (b) register a post-refresh observer of their own that will run in
+ * a defined ordering relative to the APZ messages.
+ */
+ static UniquePtr<DisplayportSetListener> SendSetTargetAPZCNotification(
+ nsIWidget* aWidget, mozilla::dom::Document* aDocument,
+ const WidgetGUIEvent& aEvent, const LayersId& aLayersId,
+ uint64_t aInputBlockId);
+
+ /* Figure out the allowed touch behaviors of each touch point in |aEvent|
+ * and send that information to the provided callback. Also returns the
+ * allowed touch behaviors. */
+ static nsTArray<TouchBehaviorFlags> SendSetAllowedTouchBehaviorNotification(
+ nsIWidget* aWidget, mozilla::dom::Document* aDocument,
+ const WidgetTouchEvent& aEvent, uint64_t aInputBlockId,
+ const SetAllowedTouchBehaviorCallback& aCallback);
+
+ /* Notify content of a mouse scroll testing event. */
+ static void NotifyMozMouseScrollEvent(
+ const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent);
+
+ /* Notify content that the repaint flush is complete. */
+ static void NotifyFlushComplete(PresShell* aPresShell);
+
+ static void NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection);
+ static void NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId);
+ static void NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId);
+
+ static void CancelAutoscroll(const ScrollableLayerGuid::ViewID& aScrollId);
+
+ /*
+ * Check if the scrollable frame is currently in the middle of a main thread
+ * async or smooth scroll, or has already requested some other apz scroll that
+ * hasn't been acknowledged by apz.
+ *
+ * We want to discard apz updates to the main-thread scroll offset 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,
+ const LayoutDevicePoint& aFocusPoint,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers,
+ const nsCOMPtr<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 0000000000..159952f772
--- /dev/null
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -0,0 +1,553 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "APZEventState.h"
+
+#include <utility>
+
+#include "APZCCallbackHelper.h"
+#include "ActiveElementManager.h"
+#include "TouchManager.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/PositionedEventTargeting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/ToString.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/widget/nsAutoRollup.h"
+#include "nsCOMPtr.h"
+#include "nsDocShell.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsINamed.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsITimer.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWidget.h"
+#include "nsLayoutUtils.h"
+#include "nsQueryFrame.h"
+
+static mozilla::LazyLogModule sApzEvtLog("apz.eventstate");
+#define APZES_LOG(...) MOZ_LOG(sApzEvtLog, LogLevel::Debug, (__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 {
+
+APZEventState::APZEventState(nsIWidget* aWidget,
+ ContentReceivedInputBlockCallback&& aCallback)
+ : mWidget(nullptr) // initialized in constructor body
+ ,
+ mActiveElementManager(new ActiveElementManager()),
+ mContentReceivedInputBlockCallback(std::move(aCallback)),
+ mPendingTouchPreventedResponse(false),
+ mPendingTouchPreventedBlockId(0),
+ mEndTouchIsClick(false),
+ mFirstTouchCancelled(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!");
+}
+
+APZEventState::~APZEventState() = default;
+
+class DelayedFireSingleTapEvent final : public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+
+ DelayedFireSingleTapEvent(nsWeakPtr aWidget, LayoutDevicePoint& aPoint,
+ Modifiers aModifiers, int32_t aClickCount,
+ nsITimer* aTimer, RefPtr<nsIContent>& aTouchRollup)
+ : mWidget(aWidget),
+ mPoint(aPoint),
+ mModifiers(aModifiers),
+ mClickCount(aClickCount)
+ // Hold the reference count until we are called back.
+ ,
+ mTimer(aTimer),
+ mTouchRollup(aTouchRollup) {}
+
+ NS_IMETHOD Notify(nsITimer*) override {
+ if (nsCOMPtr<nsIWidget> widget = do_QueryReferent(mWidget)) {
+ widget::nsAutoRollup rollup(mTouchRollup.get());
+ APZCCallbackHelper::FireSingleTapEvent(mPoint, mModifiers, mClickCount,
+ widget);
+ }
+ mTimer = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetName(nsACString& aName) override {
+ aName.AssignLiteral("DelayedFireSingleTapEvent");
+ return NS_OK;
+ }
+
+ void ClearTimer() { mTimer = nullptr; }
+
+ private:
+ ~DelayedFireSingleTapEvent() = default;
+
+ nsWeakPtr mWidget;
+ LayoutDevicePoint mPoint;
+ Modifiers mModifiers;
+ int32_t mClickCount;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<nsIContent> mTouchRollup;
+};
+
+NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback, nsINamed)
+
+void APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ int32_t aClickCount) {
+ APZES_LOG("Handling single tap at %s with %d\n", ToString(aPoint).c_str(),
+ mTouchEndCancelled);
+
+ RefPtr<nsIContent> touchRollup = GetTouchRollup();
+ mTouchRollup = nullptr;
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return;
+ }
+
+ if (mTouchEndCancelled) {
+ return;
+ }
+
+ LayoutDevicePoint ldPoint = aPoint * aScale;
+
+ APZES_LOG("Scheduling timer for click event\n");
+ nsCOMPtr<nsITimer> timer = NS_NewTimer();
+ RefPtr<DelayedFireSingleTapEvent> callback = new DelayedFireSingleTapEvent(
+ mWidget, ldPoint, aModifiers, aClickCount, timer, touchRollup);
+ nsresult rv = timer->InitWithCallback(
+ callback, StaticPrefs::ui_touch_activation_duration_ms(),
+ 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(PresShell* aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const nsCOMPtr<nsIWidget>& aWidget) {
+ // Suppress retargeting for mouse events generated by a long-press
+ EventRetargetSuppression suppression;
+
+ // Synthesize mousemove event for allowing users to emulate to move mouse
+ // cursor over the element. As a result, users can open submenu UI which
+ // is opened when mouse cursor is moved over a link (i.e., it's a case that
+ // users cannot stay in the page after tapping it). So, this improves
+ // accessibility in websites which are designed for desktop.
+ // Note that we don't need to check whether mousemove event is consumed or
+ // not because Chrome also ignores the result.
+ APZCCallbackHelper::DispatchSynthesizedMouseEvent(
+ eMouseMove, 0 /* time */, aPoint * aScale, aModifiers, 0 /* clickCount */,
+ 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.
+ CSSPoint point = CSSPoint::FromAppUnits(
+ ViewportUtils::VisualToLayout(CSSPoint::ToAppUnits(aPoint), aPresShell));
+ bool eventHandled = APZCCallbackHelper::DispatchMouseEvent(
+ aPresShell, u"contextmenu"_ns, point, 2, 1,
+ WidgetModifiersToDOMModifiers(aModifiers),
+ dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
+ 0 /* Use the default value here. */);
+
+ 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(PresShell* aPresShell,
+ const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ uint64_t aInputBlockId) {
+ APZES_LOG("Handling long tap at %s\n", ToString(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(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(PresShell* 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,
+ nsTArray<TouchBehaviorFlags>&& aAllowedTouchBehaviors) {
+ if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) {
+ mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget());
+ mLastTouchIdentifier = aEvent.mTouches[0]->Identifier();
+ }
+ if (aEvent.mMessage == eTouchStart) {
+ // We get the allowed touch behaviors on a touchstart, but may not actually
+ // use them until the first touchmove, so we stash them in a member
+ // variable.
+ mTouchBlockAllowedBehaviors = std::move(aAllowedTouchBehaviors);
+ }
+
+ bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault;
+ bool sentContentResponse = false;
+ APZES_LOG("Handling event type %d isPrevented=%d\n", aEvent.mMessage,
+ isTouchPrevented);
+ switch (aEvent.mMessage) {
+ case eTouchStart: {
+ mTouchEndCancelled = false;
+ mTouchRollup = do_GetWeakReference(widget::nsAutoRollup::GetLastRollup());
+
+ SendPendingTouchPreventedResponse(false);
+ // The above call may have sent a message to APZ 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 the first touchstart event was preventDefaulted, ensure that any
+ // subsequent additional touchstart events also get preventDefaulted. This
+ // ensures that e.g. pinch zooming is prevented even if just the first
+ // touchstart was prevented by content.
+ if (mTouchCounter.GetActiveTouchCount() == 0) {
+ mFirstTouchCancelled = isTouchPrevented;
+ } else {
+ if (mFirstTouchCancelled && !isTouchPrevented) {
+ APZES_LOG(
+ "Propagating prevent-default from first-touch for block %" PRIu64
+ "\n",
+ aInputBlockId);
+ }
+ isTouchPrevented |= mFirstTouchCancelled;
+ }
+
+ if (isTouchPrevented) {
+ mContentReceivedInputBlockCallback(aInputBlockId, isTouchPrevented);
+ sentContentResponse = true;
+ } else {
+ APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n",
+ aInputBlockId, ToString(aGuid).c_str());
+ mPendingTouchPreventedResponse = true;
+ mPendingTouchPreventedGuid = aGuid;
+ mPendingTouchPreventedBlockId = aInputBlockId;
+ }
+ break;
+ }
+
+ case eTouchEnd:
+ if (isTouchPrevented) {
+ mTouchEndCancelled = true;
+ mEndTouchIsClick = false;
+ }
+ [[fallthrough]];
+ case eTouchCancel:
+ mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick);
+ [[fallthrough]];
+ case eTouchMove: {
+ if (mPendingTouchPreventedResponse) {
+ MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
+ }
+ sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented);
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown touch event type");
+ break;
+ }
+
+ mTouchCounter.Update(aEvent);
+ if (mTouchCounter.GetActiveTouchCount() == 0) {
+ mFirstTouchCancelled = false;
+ }
+
+ APZES_LOG("Pointercancel if %d %d %d %d %d\n", sentContentResponse,
+ !isTouchPrevented, aApzResponse == nsEventStatus_eConsumeDoDefault,
+ StaticPrefs::dom_w3c_pointer_events_enabled(),
+ MainThreadAgreesEventsAreConsumableByAPZ());
+ if (sentContentResponse && !isTouchPrevented &&
+ aApzResponse == nsEventStatus_eConsumeDoDefault &&
+ StaticPrefs::dom_w3c_pointer_events_enabled() &&
+ MainThreadAgreesEventsAreConsumableByAPZ()) {
+ WidgetTouchEvent cancelEvent(aEvent);
+ cancelEvent.mMessage = eTouchPointerCancel;
+ 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);
+ }
+}
+
+bool APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() const {
+ // APZ errs on the side of saying it can consume touch events to perform
+ // default user-agent behaviours. In particular it may say this if it hasn't
+ // received accurate touch-action information. Here we double-check using
+ // accurate touch-action information. This code is kinda-sorta the main
+ // thread equivalent of AsyncPanZoomController::ArePointerEventsConsumable().
+
+ switch (mTouchBlockAllowedBehaviors.Length()) {
+ case 0:
+ // If we don't have any touch-action (e.g. because it is disabled) then
+ // APZ has no restrictions.
+ return true;
+
+ case 1: {
+ // If there's one touch point in this touch block, then check the pan-x
+ // and pan-y flags. If neither is allowed, then we disagree with APZ and
+ // say that it can't do anything with this touch block. Note that it would
+ // be even better if we could check the allowed scroll directions of the
+ // scrollframe at this point and refine this further.
+ TouchBehaviorFlags flags = mTouchBlockAllowedBehaviors[0];
+ return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) ||
+ (flags & AllowedTouchBehavior::VERTICAL_PAN);
+ }
+
+ case 2: {
+ // If there's two touch points in this touch block, check that they both
+ // allow zooming.
+ for (const auto& allowed : mTouchBlockAllowedBehaviors) {
+ if (!(allowed & AllowedTouchBehavior::PINCH_ZOOM)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ default:
+ // More than two touch points? APZ shouldn't be doing anything with this,
+ // so APZ shouldn't be consuming them.
+ return false;
+ }
+}
+
+void APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent,
+ 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(aInputBlockId, defaultPrevented);
+}
+
+void APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent,
+ uint64_t aInputBlockId) {
+ bool defaultPrevented = false;
+ mContentReceivedInputBlockCallback(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);
+ dom::Document* 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);
+ dom::Document* 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;
+ }
+ }
+}
+
+bool APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault) {
+ if (mPendingTouchPreventedResponse) {
+ APZES_LOG("Sending response %d for pending guid: %s\n", aPreventDefault,
+ ToString(mPendingTouchPreventedGuid).c_str());
+ mContentReceivedInputBlockCallback(mPendingTouchPreventedBlockId,
+ aPreventDefault);
+ mPendingTouchPreventedResponse = false;
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<nsIWidget> APZEventState::GetWidget() const {
+ nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget);
+ return result.forget();
+}
+
+already_AddRefed<nsIContent> APZEventState::GetTouchRollup() const {
+ nsCOMPtr<nsIContent> result = do_QueryReferent(mTouchRollup);
+ 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 0000000000..60020f0f9a
--- /dev/null
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZEventState_h
+#define mozilla_layers_APZEventState_h
+
+#include <stdint.h>
+
+#include "Units.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/layers/GeckoContentControllerTypes.h" // for APZStateChange
+#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid
+#include "mozilla/layers/TouchCounter.h" // for TouchCounter
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+
+#include <functional>
+
+template <class>
+class nsCOMPtr;
+class nsIContent;
+class nsIWidget;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace layers {
+
+class ActiveElementManager;
+
+typedef std::function<void(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 final {
+ typedef GeckoContentController_APZStateChange APZStateChange;
+ typedef ScrollableLayerGuid::ViewID ViewID;
+
+ public:
+ APZEventState(nsIWidget* aWidget,
+ ContentReceivedInputBlockCallback&& aCallback);
+
+ NS_INLINE_DECL_REFCOUNTING(APZEventState);
+
+ void ProcessSingleTap(const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers, int32_t aClickCount);
+ MOZ_CAN_RUN_SCRIPT
+ void ProcessLongTap(PresShell* aPresShell, const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers, uint64_t aInputBlockId);
+ MOZ_CAN_RUN_SCRIPT
+ void ProcessLongTapUp(PresShell* aPresShell, const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers);
+ void ProcessTouchEvent(const WidgetTouchEvent& aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId, nsEventStatus aApzResponse,
+ nsEventStatus aContentResponse,
+ nsTArray<TouchBehaviorFlags>&& aAllowedTouchBehaviors);
+ void ProcessWheelEvent(const WidgetWheelEvent& aEvent,
+ uint64_t aInputBlockId);
+ void ProcessMouseEvent(const WidgetMouseEvent& aEvent,
+ uint64_t aInputBlockId);
+ void ProcessAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
+ void ProcessClusterHit();
+
+ private:
+ ~APZEventState();
+ bool SendPendingTouchPreventedResponse(bool aPreventDefault);
+ MOZ_CAN_RUN_SCRIPT
+ bool FireContextmenuEvents(PresShell* aPresShell, const CSSPoint& aPoint,
+ const CSSToLayoutDeviceScale& aScale,
+ Modifiers aModifiers,
+ const nsCOMPtr<nsIWidget>& aWidget);
+ already_AddRefed<nsIWidget> GetWidget() const;
+ already_AddRefed<nsIContent> GetTouchRollup() const;
+ bool MainThreadAgreesEventsAreConsumableByAPZ() const;
+
+ private:
+ nsWeakPtr mWidget;
+ RefPtr<ActiveElementManager> mActiveElementManager;
+ ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback;
+ TouchCounter mTouchCounter;
+ bool mPendingTouchPreventedResponse;
+ ScrollableLayerGuid mPendingTouchPreventedGuid;
+ uint64_t mPendingTouchPreventedBlockId;
+ bool mEndTouchIsClick;
+ bool mFirstTouchCancelled;
+ bool mTouchEndCancelled;
+ int32_t mLastTouchIdentifier;
+ nsTArray<TouchBehaviorFlags> mTouchBlockAllowedBehaviors;
+
+ // Because touch-triggered mouse events (e.g. mouse events from a tap
+ // gesture) happen asynchronously from the touch events themselves, we
+ // need to stash and replicate some of the state from the touch events
+ // to the mouse events. One piece of state is the rollup content, which
+ // is the content for which a popup window was recently closed. If we
+ // don't replicate this state properly during the mouse events, the
+ // synthetic click might reopen a popup window that was just closed by
+ // the touch event, which is undesirable. See also documentation in
+ // nsAutoRollup.h
+ // Note that in cases where we get multiple touch blocks interleaved with
+ // their single-tap event notifications, mTouchRollup may hold an incorrect
+ // value. This is kind of an edge case, and falls in the same category of
+ // problems as bug 1227241. I intend that fixing that bug will also take
+ // care of this potential problem.
+ nsWeakPtr mTouchRollup;
+};
+
+} // 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 0000000000..63db976834
--- /dev/null
+++ b/gfx/layers/apz/util/APZThreadUtils.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "APZThreadUtils.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticMutex.h"
+
+#include "nsISerialEventTarget.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace layers {
+
+static bool sThreadAssertionsEnabled = true;
+static StaticRefPtr<nsISerialEventTarget> sControllerThread;
+static StaticMutex sControllerThreadMutex;
+
+/*static*/
+void APZThreadUtils::SetThreadAssertionsEnabled(bool aEnabled) {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ sThreadAssertionsEnabled = aEnabled;
+}
+
+/*static*/
+bool APZThreadUtils::GetThreadAssertionsEnabled() {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ return sThreadAssertionsEnabled;
+}
+
+/*static*/
+void APZThreadUtils::SetControllerThread(nsISerialEventTarget* aThread) {
+ // We must either be setting the initial controller thread, or removing it,
+ // or re-using an existing controller thread.
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ MOZ_ASSERT(!sControllerThread || !aThread || sControllerThread == aThread);
+ if (aThread != sControllerThread) {
+ // This can only happen once, on startup.
+ sControllerThread = aThread;
+ ClearOnShutdown(&sControllerThread);
+ }
+}
+
+/*static*/
+void APZThreadUtils::AssertOnControllerThread() {
+#if DEBUG
+ if (!GetThreadAssertionsEnabled()) {
+ return;
+ }
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ MOZ_ASSERT(sControllerThread && sControllerThread->IsOnCurrentThread());
+#endif
+}
+
+/*static*/
+void APZThreadUtils::RunOnControllerThread(RefPtr<Runnable>&& aTask) {
+ RefPtr<nsISerialEventTarget> thread;
+ {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ thread = sControllerThread;
+ }
+ RefPtr<Runnable> task = std::move(aTask);
+
+ if (!thread) {
+ // Could happen on startup or if Shutdown() got called.
+ NS_WARNING("Dropping task posted to controller thread");
+ return;
+ }
+
+ if (thread->IsOnCurrentThread()) {
+ task->Run();
+ } else {
+ thread->Dispatch(task.forget());
+ }
+}
+
+/*static*/
+bool APZThreadUtils::IsControllerThread() {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ return sControllerThread && sControllerThread->IsOnCurrentThread();
+}
+
+/*static*/
+bool APZThreadUtils::IsControllerThreadAlive() {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ return !!sControllerThread;
+}
+
+/*static*/
+void APZThreadUtils::DelayedDispatch(already_AddRefed<Runnable> aRunnable,
+ int aDelayMs) {
+ MOZ_ASSERT(!XRE_IsContentProcess(),
+ "ContentProcessController should only be used remotely.");
+ RefPtr<nsISerialEventTarget> thread;
+ {
+ StaticMutexAutoLock lock(sControllerThreadMutex);
+ thread = sControllerThread;
+ }
+ if (!thread) {
+ // Could happen on startup
+ NS_WARNING("Dropping task posted to controller thread");
+ return;
+ }
+ if (aDelayMs) {
+ thread->DelayedDispatch(std::move(aRunnable), aDelayMs);
+ } else {
+ thread->Dispatch(std::move(aRunnable));
+ }
+}
+
+NS_IMPL_ISUPPORTS(GenericNamedTimerCallbackBase, nsITimerCallback, nsINamed)
+
+} // 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 0000000000..fb03ac10da
--- /dev/null
+++ b/gfx/layers/apz/util/APZThreadUtils.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZThreadUtils_h
+#define mozilla_layers_APZThreadUtils_h
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsString.h"
+
+class nsISerialEventTarget;
+
+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(nsISerialEventTarget* aThread);
+
+ /**
+ * 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();
+
+ /**
+ * 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(RefPtr<Runnable>&& aTask);
+
+ /**
+ * Returns true if currently on APZ "controller thread".
+ */
+ static bool IsControllerThread();
+
+ /**
+ * Returns true if the controller thread is still alive.
+ */
+ static bool IsControllerThreadAlive();
+
+ /**
+ * Schedules a runnable to run on the controller thread at some time
+ * in the future.
+ */
+ static void DelayedDispatch(already_AddRefed<Runnable> aRunnable,
+ int aDelayMs);
+};
+
+// A base class for GenericNamedTimerCallback<Function>.
+// This is necessary because NS_IMPL_ISUPPORTS doesn't work for a class
+// template.
+class GenericNamedTimerCallbackBase : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ protected:
+ virtual ~GenericNamedTimerCallbackBase() = default;
+};
+
+// An nsITimerCallback implementation with nsINamed that can be used with any
+// function object that's callable with no arguments.
+template <typename Function>
+class GenericNamedTimerCallback final : public GenericNamedTimerCallbackBase {
+ public:
+ GenericNamedTimerCallback(const Function& aFunction, const char* aName)
+ : mFunction(aFunction), mName(aName) {}
+
+ NS_IMETHOD Notify(nsITimer*) override {
+ mFunction();
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName = mName;
+ return NS_OK;
+ }
+
+ private:
+ Function mFunction;
+ nsCString mName;
+};
+
+// Convenience function for constructing a GenericNamedTimerCallback.
+// 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(NewNamedTimerCallback([](){ ... }, name), delay);
+template <typename Function>
+GenericNamedTimerCallback<Function>* NewNamedTimerCallback(
+ const Function& aFunction, const char* aName) {
+ return new GenericNamedTimerCallback<Function>(aFunction, aName);
+}
+
+} // 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 0000000000..ed485c3a1d
--- /dev/null
+++ b/gfx/layers/apz/util/ActiveElementManager.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ActiveElementManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Document.h"
+
+static mozilla::LazyLogModule sApzAemLog("apz.activeelement");
+#define AEM_LOG(...) MOZ_LOG(sApzAemLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+namespace layers {
+
+ActiveElementManager::ActiveElementManager()
+ : mCanBePan(false), mCanBePanSet(false), mSetActiveTask(nullptr) {}
+
+ActiveElementManager::~ActiveElementManager() = default;
+
+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>>(
+ "layers::ActiveElementManager::SetActiveTask", this,
+ &ActiveElementManager::SetActiveTask, mTarget);
+ mSetActiveTask = task;
+ NS_GetCurrentThread()->DelayedDispatch(
+ task.forget(), StaticPrefs::ui_touch_activation_delay_ms());
+ AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask.get());
+ }
+}
+
+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) {
+ // Scrollbar thumbs use a different mechanism for their active
+ // highlight (the "active" attribute), so don't set the active state
+ // on them because nothing will clear it.
+ if (!(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) {
+ 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;
+}
+
+static nsPresContext* GetPresContextFor(nsIContent* aContent) {
+ if (!aContent) {
+ return nullptr;
+ }
+ PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetPresContext();
+}
+
+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);
+ }
+}
+
+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.get());
+
+ // 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.get());
+
+ 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 0000000000..b783659962
--- /dev/null
+++ b/gfx/layers/apz/util/ActiveElementManager.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_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 final {
+ ~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();
+
+ 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;
+
+ // 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 0000000000..24668deecf
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CheckerboardReportService.h"
+
+#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/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_apz.h"
+#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 "nsIObserverService.h"
+#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(
+ "layers::CheckerboardEventStorage::Report",
+ [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::IsSystemCaller(aCtx) ||
+ nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard");
+}
+
+/*static*/
+already_AddRefed<CheckerboardReportService>
+CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal) {
+ 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 CheckerboardReportService_Binding::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 StaticPrefs::apz_record_checkerboarding();
+}
+
+void CheckerboardReportService::SetRecordingEnabled(bool aEnabled) {
+ Preferences::SetBool("apz.record_checkerboarding", 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 0000000000..3062255f95
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CheckerboardReportService_h
+#define mozilla_dom_CheckerboardReportService_h
+
+#include <string>
+
+#include "js/TypeDecls.h" // for JSContext, JSObject
+#include "mozilla/StaticPtr.h" // for StaticRefPtr
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsTArrayForwardDeclare.h" // for nsTArray
+#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() = default;
+ virtual ~CheckerboardEventStorage() = default;
+
+ 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);
+
+ 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() = default;
+
+ 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 0000000000..0f907f8049
--- /dev/null
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeProcessController.h"
+
+#include "MainThreadUtils.h" // for NS_IsMainThread()
+#include "base/task.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/DoubleTapToZoom.h"
+#include "mozilla/layers/RepaintRequest.h"
+#include "mozilla/dom/Document.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+
+static mozilla::LazyLogModule sApzChromeLog("apz.cc.chrome");
+
+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),
+ mUIThread(NS_GetCurrentThread()) {
+ // Otherwise we're initializing mUIThread incorrectly.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aAPZEventState);
+ MOZ_ASSERT(aAPZCTreeManager);
+
+ mUIThread->Dispatch(
+ NewRunnableMethod("layers::ChromeProcessController::InitializeRoot", this,
+ &ChromeProcessController::InitializeRoot));
+}
+
+ChromeProcessController::~ChromeProcessController() = default;
+
+void ChromeProcessController::InitializeRoot() {
+ APZCCallbackHelper::InitializeRootDisplayport(GetPresShell());
+}
+
+void ChromeProcessController::NotifyLayerTransforms(
+ nsTArray<MatrixMessage>&& aTransforms) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(
+ NewRunnableMethod<StoreCopyPassByRRef<nsTArray<MatrixMessage>>>(
+ "layers::ChromeProcessController::NotifyLayerTransforms", this,
+ &ChromeProcessController::NotifyLayerTransforms,
+ std::move(aTransforms)));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyLayerTransforms(aTransforms);
+}
+
+void ChromeProcessController::RequestContentRepaint(
+ const RepaintRequest& aRequest) {
+ MOZ_ASSERT(IsRepaintThread());
+
+ if (aRequest.IsRootContent()) {
+ APZCCallbackHelper::UpdateRootFrame(aRequest);
+ } else {
+ APZCCallbackHelper::UpdateSubFrame(aRequest);
+ }
+}
+
+bool ChromeProcessController::IsRepaintThread() { return NS_IsMainThread(); }
+
+void ChromeProcessController::DispatchToRepaintThread(
+ already_AddRefed<Runnable> aTask) {
+ NS_DispatchToMainThread(std::move(aTask));
+}
+
+void ChromeProcessController::Destroy() {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(
+ NewRunnableMethod("layers::ChromeProcessController::Destroy", this,
+ &ChromeProcessController::Destroy));
+ return;
+ }
+
+ MOZ_ASSERT(mUIThread->IsOnCurrentThread());
+ mWidget = nullptr;
+ mAPZEventState = nullptr;
+}
+
+PresShell* ChromeProcessController::GetPresShell() const {
+ if (!mWidget) {
+ return nullptr;
+ }
+ if (nsView* view = nsView::GetViewFor(mWidget)) {
+ return view->GetPresShell();
+ }
+ return nullptr;
+}
+
+dom::Document* ChromeProcessController::GetRootDocument() const {
+ if (PresShell* presShell = GetPresShell()) {
+ return presShell->GetDocument();
+ }
+ return nullptr;
+}
+
+dom::Document* ChromeProcessController::GetRootContentDocument(
+ const ScrollableLayerGuid::ViewID& aScrollId) const {
+ nsIContent* content = nsLayoutUtils::FindContentFor(aScrollId);
+ if (!content) {
+ return nullptr;
+ }
+ if (PresShell* presShell =
+ APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
+ content)) {
+ return presShell->GetDocument();
+ }
+ return nullptr;
+}
+
+void ChromeProcessController::HandleDoubleTap(
+ const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid) {
+ MOZ_ASSERT(mUIThread->IsOnCurrentThread());
+
+ RefPtr<dom::Document> document = GetRootContentDocument(aGuid.mScrollId);
+ if (!document.get()) {
+ return;
+ }
+
+ CSSRect zoomToRect = CalculateRectToZoomTo(document, aPoint);
+
+ uint32_t presShellId;
+ ScrollableLayerGuid::ViewID viewId;
+ if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ document->GetDocumentElement(), &presShellId, &viewId)) {
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<ScrollableLayerGuid, CSSRect, uint32_t>(
+ "IAPZCTreeManager::ZoomToRect", mAPZCTreeManager,
+ &IAPZCTreeManager::ZoomToRect,
+ ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId),
+ zoomToRect, ZoomToRectBehavior::DEFAULT_BEHAVIOR));
+ }
+}
+
+void ChromeProcessController::HandleTap(
+ TapType aType, const mozilla::LayoutDevicePoint& aPoint,
+ Modifiers aModifiers, const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId) {
+ MOZ_LOG(sApzChromeLog, LogLevel::Debug,
+ ("HandleTap called with %d\n", (int)aType));
+ if (!mUIThread->IsOnCurrentThread()) {
+ MOZ_LOG(sApzChromeLog, LogLevel::Debug, ("HandleTap redispatching\n"));
+ mUIThread->Dispatch(
+ NewRunnableMethod<TapType, mozilla::LayoutDevicePoint, Modifiers,
+ ScrollableLayerGuid, uint64_t>(
+ "layers::ChromeProcessController::HandleTap", this,
+ &ChromeProcessController::HandleTap, aType, aPoint, aModifiers,
+ aGuid, aInputBlockId));
+ return;
+ }
+
+ if (!mAPZEventState) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ if (!presShell->GetPresContext()) {
+ return;
+ }
+ CSSToLayoutDeviceScale scale(
+ presShell->GetPresContext()->CSSToDevPixelScale());
+
+ CSSPoint point = aPoint / scale;
+
+ // Stash the guid in InputAPZContext so that when the visual-to-layout
+ // transform is applied to the event's coordinates, we use the right transform
+ // based on the scroll frame being targeted.
+ // The other values don't really matter.
+ InputAPZContext context(aGuid, aInputBlockId, nsEventStatus_eSentinel);
+
+ switch (aType) {
+ case TapType::eSingleTap:
+ mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 1);
+ break;
+ case TapType::eDoubleTap:
+ HandleDoubleTap(point, aModifiers, aGuid);
+ break;
+ case TapType::eSecondTap:
+ mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 2);
+ break;
+ case TapType::eLongTap: {
+ RefPtr<APZEventState> eventState(mAPZEventState);
+ eventState->ProcessLongTap(presShell, point, scale, aModifiers,
+ aInputBlockId);
+ break;
+ }
+ case TapType::eLongTapUp: {
+ RefPtr<APZEventState> eventState(mAPZEventState);
+ eventState->ProcessLongTapUp(presShell, point, scale, aModifiers);
+ break;
+ }
+ }
+}
+
+void ChromeProcessController::NotifyPinchGesture(
+ PinchGestureInput::PinchGestureType aType, const ScrollableLayerGuid& aGuid,
+ const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(
+ NewRunnableMethod<PinchGestureInput::PinchGestureType,
+ ScrollableLayerGuid, LayoutDevicePoint,
+ LayoutDeviceCoord, Modifiers>(
+ "layers::ChromeProcessController::NotifyPinchGesture", this,
+ &ChromeProcessController::NotifyPinchGesture, aType, aGuid,
+ aFocusPoint, aSpanChange, aModifiers));
+ return;
+ }
+
+ if (mWidget) {
+ // Dispatch the call to APZCCallbackHelper::NotifyPinchGesture to the main
+ // thread so that it runs asynchronously from the current call. This is
+ // because the call can run arbitrary JS code, which can also spin the event
+ // loop and cause undesirable re-entrancy in APZ.
+ mUIThread->Dispatch(NewRunnableFunction(
+ "layers::ChromeProcessController::NotifyPinchGestureAsync",
+ &APZCCallbackHelper::NotifyPinchGesture, aType, aFocusPoint,
+ aSpanChange, aModifiers, mWidget));
+ }
+}
+
+void ChromeProcessController::NotifyAPZStateChange(
+ const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(
+ NewRunnableMethod<ScrollableLayerGuid, APZStateChange, int>(
+ "layers::ChromeProcessController::NotifyAPZStateChange", this,
+ &ChromeProcessController::NotifyAPZStateChange, aGuid, aChange,
+ aArg));
+ return;
+ }
+
+ if (!mAPZEventState) {
+ return;
+ }
+
+ mAPZEventState->ProcessAPZStateChange(aGuid.mScrollId, aChange, aArg);
+}
+
+void ChromeProcessController::NotifyMozMouseScrollEvent(
+ const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(
+ NewRunnableMethod<ScrollableLayerGuid::ViewID, nsString>(
+ "layers::ChromeProcessController::NotifyMozMouseScrollEvent", this,
+ &ChromeProcessController::NotifyMozMouseScrollEvent, aScrollId,
+ aEvent));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+}
+
+void ChromeProcessController::NotifyFlushComplete() {
+ MOZ_ASSERT(IsRepaintThread());
+
+ APZCCallbackHelper::NotifyFlushComplete(GetPresShell());
+}
+
+void ChromeProcessController::NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(NewRunnableMethod<uint64_t, ScrollableLayerGuid::ViewID,
+ ScrollDirection>(
+ "layers::ChromeProcessController::NotifyAsyncScrollbarDragInitiated",
+ this, &ChromeProcessController::NotifyAsyncScrollbarDragInitiated,
+ aDragBlockId, aScrollId, aDirection));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId,
+ aDirection);
+}
+
+void ChromeProcessController::NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(NewRunnableMethod<ScrollableLayerGuid::ViewID>(
+ "layers::ChromeProcessController::NotifyAsyncScrollbarDragRejected",
+ this, &ChromeProcessController::NotifyAsyncScrollbarDragRejected,
+ aScrollId));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
+}
+
+void ChromeProcessController::NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(NewRunnableMethod<ScrollableLayerGuid::ViewID>(
+ "layers::ChromeProcessController::NotifyAsyncAutoscrollRejected", this,
+ &ChromeProcessController::NotifyAsyncAutoscrollRejected, aScrollId));
+ return;
+ }
+
+ APZCCallbackHelper::NotifyAsyncAutoscrollRejected(aScrollId);
+}
+
+void ChromeProcessController::CancelAutoscroll(
+ const ScrollableLayerGuid& aGuid) {
+ if (!mUIThread->IsOnCurrentThread()) {
+ mUIThread->Dispatch(NewRunnableMethod<ScrollableLayerGuid>(
+ "layers::ChromeProcessController::CancelAutoscroll", this,
+ &ChromeProcessController::CancelAutoscroll, aGuid));
+ return;
+ }
+
+ APZCCallbackHelper::CancelAutoscroll(aGuid.mScrollId);
+}
diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h
new file mode 100644
index 0000000000..158a0d6eba
--- /dev/null
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_ChromeProcessController_h
+#define mozilla_layers_ChromeProcessController_h
+
+#include "mozilla/layers/GeckoContentController.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/layers/MatrixMessage.h"
+
+class nsIDOMWindowUtils;
+class nsISerialEventTarget;
+class nsIWidget;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+}
+
+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);
+ virtual ~ChromeProcessController();
+ void Destroy() override;
+
+ // GeckoContentController interface
+ void NotifyLayerTransforms(nsTArray<MatrixMessage>&& aTransforms) override;
+ void RequestContentRepaint(const RepaintRequest& aRequest) override;
+ bool IsRepaintThread() override;
+ void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
+ MOZ_CAN_RUN_SCRIPT
+ void HandleTap(TapType aType, const mozilla::LayoutDevicePoint& aPoint,
+ Modifiers aModifiers, const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId) override;
+ void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+ const ScrollableLayerGuid& aGuid,
+ const LayoutDevicePoint& aFocusPoint,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers) override;
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange, int aArg) override;
+ void NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID& aScrollId,
+ const nsString& aEvent) override;
+ void NotifyFlushComplete() override;
+ void NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection) override;
+ void NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) override;
+ void NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) override;
+ void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override;
+
+ private:
+ nsCOMPtr<nsIWidget> mWidget;
+ RefPtr<APZEventState> mAPZEventState;
+ RefPtr<IAPZCTreeManager> mAPZCTreeManager;
+ nsCOMPtr<nsISerialEventTarget> mUIThread;
+
+ void InitializeRoot();
+ PresShell* GetPresShell() const;
+ dom::Document* GetRootDocument() const;
+ dom::Document* GetRootContentDocument(
+ const ScrollableLayerGuid::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 0000000000..0c52890631
--- /dev/null
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentProcessController.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZChild.h"
+#include "nsIContentInlines.h"
+
+#include "InputData.h" // for InputData
+
+namespace mozilla {
+namespace layers {
+
+ContentProcessController::ContentProcessController(
+ const RefPtr<dom::BrowserChild>& aBrowser)
+ : mBrowser(aBrowser) {
+ MOZ_ASSERT(mBrowser);
+}
+
+void ContentProcessController::NotifyLayerTransforms(
+ nsTArray<MatrixMessage>&& aTransforms) {
+ // This should never get called
+ MOZ_ASSERT(false);
+}
+
+void ContentProcessController::RequestContentRepaint(
+ const RepaintRequest& aRequest) {
+ if (mBrowser) {
+ mBrowser->UpdateFrame(aRequest);
+ }
+}
+
+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,
+ const LayoutDevicePoint& aFocusPoint, 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 ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) {
+ if (mBrowser) {
+ APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+ }
+}
+
+void ContentProcessController::NotifyFlushComplete() {
+ if (mBrowser) {
+ RefPtr<PresShell> presShell = mBrowser->GetTopLevelPresShell();
+ APZCCallbackHelper::NotifyFlushComplete(presShell);
+ }
+}
+
+void ContentProcessController::NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection) {
+ APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId,
+ aDirection);
+}
+
+void ContentProcessController::NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
+}
+
+void ContentProcessController::NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) {
+ APZCCallbackHelper::NotifyAsyncAutoscrollRejected(aScrollId);
+}
+
+void ContentProcessController::CancelAutoscroll(
+ const ScrollableLayerGuid& aGuid) {
+ // This should never get called
+ MOZ_ASSERT_UNREACHABLE("Unexpected message to content process");
+}
+
+bool ContentProcessController::IsRepaintThread() { return NS_IsMainThread(); }
+
+void ContentProcessController::DispatchToRepaintThread(
+ already_AddRefed<Runnable> aTask) {
+ NS_DispatchToMainThread(std::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 0000000000..ab5522cd44
--- /dev/null
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_ContentProcessController_h
+#define mozilla_layers_ContentProcessController_h
+
+#include "mozilla/layers/GeckoContentController.h"
+
+class nsIObserver;
+
+namespace mozilla {
+
+namespace dom {
+class BrowserChild;
+} // namespace dom
+
+namespace layers {
+
+class APZChild;
+
+/**
+ * ContentProcessController is a GeckoContentController for a BrowserChild, 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:
+ explicit ContentProcessController(const RefPtr<dom::BrowserChild>& aBrowser);
+
+ // GeckoContentController
+
+ void NotifyLayerTransforms(nsTArray<MatrixMessage>&& aTransforms) override;
+
+ void RequestContentRepaint(const RepaintRequest& aRequest) 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,
+ const LayoutDevicePoint& aFocusPoint,
+ LayoutDeviceCoord aSpanChange,
+ Modifiers aModifiers) override;
+
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange, int aArg) override;
+
+ void NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID& aScrollId,
+ const nsString& aEvent) override;
+
+ void NotifyFlushComplete() override;
+
+ void NotifyAsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
+ ScrollDirection aDirection) override;
+ void NotifyAsyncScrollbarDragRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) override;
+
+ void NotifyAsyncAutoscrollRejected(
+ const ScrollableLayerGuid::ViewID& aScrollId) override;
+
+ void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override;
+
+ bool IsRepaintThread() override;
+
+ void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
+
+ private:
+ RefPtr<dom::BrowserChild> mBrowser;
+};
+
+} // 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 0000000000..6d4d3d7f0c
--- /dev/null
+++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DoubleTapToZoom.h"
+
+#include <algorithm> // for std::min, std::max
+
+#include "mozilla/PresShell.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/Element.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+
+namespace mozilla {
+namespace layers {
+
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+// Returns the DOM element found at |aPoint|, interpreted as being relative to
+// the root frame of |aPresShell| in visual coordinates. 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 DocumentOrShadowRoot::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 RefPtr<PresShell>& aPresShell, const CSSPoint& aPoint) {
+ nsIFrame* rootFrame = aPresShell->GetRootFrame();
+ if (!rootFrame) {
+ return nullptr;
+ }
+ nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(
+ RelativeTo{rootFrame, ViewportType::Visual}, CSSPoint::ToAppUnits(aPoint),
+ {{FrameForPointOption::IgnorePaintSuppression}});
+ while (frame && (!frame->GetContent() ||
+ frame->GetContent()->IsInNativeAnonymousSubtree())) {
+ frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+ }
+ if (!frame) {
+ return nullptr;
+ }
+ // FIXME(emilio): This should probably use the flattened tree, GetParent() is
+ // not guaranteed to be an element in presence of shadow DOM.
+ nsIContent* content = frame->GetContent();
+ if (!content) {
+ return nullptr;
+ }
+ if (dom::Element* element = content->GetAsElementOrParentElement()) {
+ return do_AddRef(element);
+ }
+ return nullptr;
+}
+
+static bool ShouldZoomToElement(const nsCOMPtr<dom::Element>& aElement) {
+ if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
+ if (frame->StyleDisplay()->IsInlineFlow()) {
+ 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);
+}
+
+} // namespace
+
+CSSRect CalculateRectToZoomTo(const RefPtr<dom::Document>& aRootContentDocument,
+ const CSSPoint& aPoint) {
+ // Ensure the layout information we get is up-to-date.
+ aRootContentDocument->FlushPendingNotifications(FlushType::Layout);
+
+ // An empty rect as return value is interpreted as "zoom out".
+ const CSSRect zoomOut;
+
+ RefPtr<PresShell> presShell = aRootContentDocument->GetPresShell();
+ if (!presShell) {
+ return zoomOut;
+ }
+
+ nsIScrollableFrame* rootScrollFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ if (!rootScrollFrame) {
+ return zoomOut;
+ }
+
+ nsCOMPtr<dom::Element> element = ElementFromPoint(presShell, aPoint);
+ if (!element) {
+ return zoomOut;
+ }
+
+ while (element && !ShouldZoomToElement(element)) {
+ element = element->GetParentElement();
+ }
+
+ if (!element) {
+ return zoomOut;
+ }
+
+ FrameMetrics metrics =
+ nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
+ CSSPoint visualScrollOffset = metrics.GetVisualScrollOffset();
+ CSSRect compositedArea(visualScrollOffset,
+ 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.YMost()) {
+ rect.MoveByY(rect.Height() - targetHeight);
+ } else if (newY > rect.Y()) {
+ rect.MoveToY(newY);
+ }
+ rect.SetHeight(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.SetWidth(
+ 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 = visualScrollOffset.y + aPoint.y;
+ if ((rect.Height() > rounded.Height()) &&
+ (cssTapY > rounded.Y() + (rounded.Height() * 1.2))) {
+ rounded.MoveToY(cssTapY - (rounded.Height() / 2));
+ }
+
+ return rounded;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/DoubleTapToZoom.h b/gfx/layers/apz/util/DoubleTapToZoom.h
new file mode 100644
index 0000000000..163481fe03
--- /dev/null
+++ b/gfx/layers/apz/util/DoubleTapToZoom.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_DoubleTapToZoom_h
+#define mozilla_layers_DoubleTapToZoom_h
+
+#include "Units.h"
+
+template <class T>
+class RefPtr;
+
+namespace mozilla {
+namespace dom {
+class Document;
+}
+
+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 RefPtr<mozilla::dom::Document>& aRootContentDocument,
+ const CSSPoint& aPoint);
+
+} // namespace layers
+} // namespace mozilla
+
+#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 0000000000..77573221ff
--- /dev/null
+++ b/gfx/layers/apz/util/InputAPZContext.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "InputAPZContext.h"
+
+namespace mozilla {
+namespace layers {
+
+ScrollableLayerGuid InputAPZContext::sGuid;
+uint64_t InputAPZContext::sBlockId = 0;
+nsEventStatus InputAPZContext::sApzResponse = nsEventStatus_eSentinel;
+bool InputAPZContext::sPendingLayerization = false;
+bool InputAPZContext::sRoutedToChildProcess = false;
+
+/*static*/
+ScrollableLayerGuid InputAPZContext::GetTargetLayerGuid() { return sGuid; }
+
+/*static*/
+uint64_t InputAPZContext::GetInputBlockId() { return sBlockId; }
+
+/*static*/
+nsEventStatus InputAPZContext::GetApzResponse() { return sApzResponse; }
+
+/*static*/
+bool InputAPZContext::HavePendingLayerization() { return sPendingLayerization; }
+
+/*static*/
+bool InputAPZContext::WasRoutedToChildProcess() {
+ return sRoutedToChildProcess;
+}
+
+InputAPZContext::InputAPZContext(const ScrollableLayerGuid& aGuid,
+ const uint64_t& aBlockId,
+ const nsEventStatus& aApzResponse,
+ bool aPendingLayerization)
+ : mOldGuid(sGuid),
+ mOldBlockId(sBlockId),
+ mOldApzResponse(sApzResponse),
+ mOldPendingLayerization(sPendingLayerization),
+ mOldRoutedToChildProcess(sRoutedToChildProcess) {
+ sGuid = aGuid;
+ sBlockId = aBlockId;
+ sApzResponse = aApzResponse;
+ sPendingLayerization = aPendingLayerization;
+ sRoutedToChildProcess = false;
+}
+
+InputAPZContext::~InputAPZContext() {
+ sGuid = mOldGuid;
+ sBlockId = mOldBlockId;
+ sApzResponse = mOldApzResponse;
+ sPendingLayerization = mOldPendingLayerization;
+ sRoutedToChildProcess = mOldRoutedToChildProcess;
+}
+
+/*static*/
+void InputAPZContext::SetRoutedToChildProcess() {
+ sRoutedToChildProcess = true;
+}
+
+} // 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 0000000000..928359ab1d
--- /dev/null
+++ b/gfx/layers/apz/util/InputAPZContext.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_InputAPZContext_h
+#define mozilla_layers_InputAPZContext_h
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+
+namespace mozilla {
+namespace layers {
+
+// InputAPZContext is used to communicate various pieces of information
+// around the codebase without having to plumb it through lots of functions
+// and codepaths. Conceptually it is attached to a WidgetInputEvent that is
+// relevant to APZ.
+//
+// There are two types of information bits propagated using this class. One
+// type is propagated "downwards" (from a process entry point like nsBaseWidget
+// or BrowserChild) into deeper code that is run during complicated operations
+// like event dispatch. The other type is information that is propagated
+// "upwards", from the deeper code back to the entry point.
+class MOZ_STACK_CLASS InputAPZContext {
+ private:
+ // State that is propagated downwards from InputAPZContext creation into
+ // "deeper" code.
+ static ScrollableLayerGuid sGuid;
+ static uint64_t sBlockId;
+ static nsEventStatus sApzResponse;
+ static bool sPendingLayerization;
+
+ // State that is set in deeper code and propagated upwards.
+ static bool sRoutedToChildProcess;
+
+ public:
+ // Functions to access downwards-propagated data
+ static ScrollableLayerGuid GetTargetLayerGuid();
+ static uint64_t GetInputBlockId();
+ static nsEventStatus GetApzResponse();
+ static bool HavePendingLayerization();
+
+ // Functions to access upwards-propagated data
+ static bool WasRoutedToChildProcess();
+
+ // Constructor sets the data to be propagated downwards
+ InputAPZContext(const ScrollableLayerGuid& aGuid, const uint64_t& aBlockId,
+ const nsEventStatus& aApzResponse,
+ bool aPendingLayerization = false);
+ ~InputAPZContext();
+
+ // Functions to set data to be propagated upwards
+ static void SetRoutedToChildProcess();
+
+ private:
+ ScrollableLayerGuid mOldGuid;
+ uint64_t mOldBlockId;
+ nsEventStatus mOldApzResponse;
+ bool mOldPendingLayerization;
+
+ 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 0000000000..999f2c6abf
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollInputMethods.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_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
+
+ // === Additional input methods implemented in APZ ===
+
+ // Async Keyboard
+ ApzScrollLine, // line scrolling
+ // (generally triggered by up/down arrow keys)
+ ApzScrollCharacter, // character scrolling
+ // (generally triggered by left/right arrow keys)
+ ApzScrollPage, // page scrolling
+ // (generally triggered by PageUp/PageDown keys)
+ ApzCompleteScroll, // scrolling to the end of the scroll range
+ // (generally triggered by Home/End keys)
+
+ // Autoscrolling
+ ApzAutoscrolling,
+
+ // 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 0000000000..6ef8749a9c
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollLinkedEffectDetector.h"
+
+#include "mozilla/dom/Document.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(dom::Document* 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 0000000000..81044ed614
--- /dev/null
+++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_ScrollLinkedEffectDetector_h
+#define mozilla_layers_ScrollLinkedEffectDetector_h
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+}
+
+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 final {
+ private:
+ static uint32_t sDepth;
+ static bool sFoundScrollLinkedEffect;
+
+ public:
+ static void PositioningPropertyMutated();
+
+ explicit ScrollLinkedEffectDetector(dom::Document*);
+ ~ScrollLinkedEffectDetector();
+
+ private:
+ RefPtr<dom::Document> 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 0000000000..1cac71467f
--- /dev/null
+++ b/gfx/layers/apz/util/TouchActionHelper.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TouchActionHelper.h"
+
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "nsContainerFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+namespace layers {
+
+static void UpdateAllowedBehavior(StyleTouchAction aTouchActionValue,
+ bool aConsiderPanning,
+ TouchBehaviorFlags& aOutBehavior) {
+ if (aTouchActionValue != StyleTouchAction::AUTO) {
+ // Double-tap-zooming need property value AUTO
+ aOutBehavior &= ~AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
+ if (aTouchActionValue != StyleTouchAction::MANIPULATION &&
+ !(aTouchActionValue & StyleTouchAction::PINCH_ZOOM)) {
+ // Pinch-zooming needs value AUTO or MANIPULATION, or the PINCH_ZOOM bit
+ // set
+ aOutBehavior &= ~AllowedTouchBehavior::PINCH_ZOOM;
+ }
+ }
+
+ if (aConsiderPanning) {
+ if (aTouchActionValue == StyleTouchAction::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 & StyleTouchAction::PAN_X) &&
+ !(aTouchActionValue & StyleTouchAction::PAN_Y)) {
+ aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN;
+ } else if ((aTouchActionValue & StyleTouchAction::PAN_Y) &&
+ !(aTouchActionValue & StyleTouchAction::PAN_X)) {
+ aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
+ }
+ }
+}
+
+TouchBehaviorFlags TouchActionHelper::GetAllowedTouchBehavior(
+ nsIWidget* aWidget, RelativeTo 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);
+ 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->GetInFlowParent()) {
+ 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 0000000000..723b276997
--- /dev/null
+++ b/gfx/layers/apz/util/TouchActionHelper.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_layers_TouchActionHelper_h__
+#define __mozilla_layers_TouchActionHelper_h__
+
+#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags
+#include "RelativeTo.h" // for RelativeTo
+
+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 {
+ 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, RelativeTo aRootFrame,
+ const LayoutDeviceIntPoint& aPoint);
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /*__mozilla_layers_TouchActionHelper_h__ */
diff --git a/gfx/layers/apz/util/TouchCounter.cpp b/gfx/layers/apz/util/TouchCounter.cpp
new file mode 100644
index 0000000000..9e4d6f1ba6
--- /dev/null
+++ b/gfx/layers/apz/util/TouchCounter.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TouchCounter.h"
+
+#include "InputData.h"
+#include "mozilla/TouchEvents.h"
+
+namespace mozilla {
+namespace layers {
+
+TouchCounter::TouchCounter() : mActiveTouchCount(0) {}
+
+void TouchCounter::Update(const MultiTouchInput& aInput) {
+ switch (aInput.mType) {
+ case MultiTouchInput::MULTITOUCH_START:
+ // touch-start event contains all active touches of the current session
+ mActiveTouchCount = aInput.mTouches.Length();
+ break;
+ case MultiTouchInput::MULTITOUCH_END:
+ if (mActiveTouchCount >= aInput.mTouches.Length()) {
+ // touch-end event contains only released touches
+ mActiveTouchCount -= aInput.mTouches.Length();
+ } else {
+ NS_WARNING("Got an unexpected touchend");
+ mActiveTouchCount = 0;
+ }
+ break;
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ mActiveTouchCount = 0;
+ break;
+ case MultiTouchInput::MULTITOUCH_MOVE:
+ break;
+ }
+}
+
+void TouchCounter::Update(const WidgetTouchEvent& aEvent) {
+ switch (aEvent.mMessage) {
+ case eTouchStart:
+ // touch-start event contains all active touches of the current session
+ mActiveTouchCount = aEvent.mTouches.Length();
+ break;
+ case eTouchEnd: {
+ // touch-end contains all touches, but ones being lifted are marked as
+ // changed
+ uint32_t liftedTouches = 0;
+ for (const auto& touch : aEvent.mTouches) {
+ if (touch->mChanged) {
+ liftedTouches++;
+ }
+ }
+ if (mActiveTouchCount >= liftedTouches) {
+ mActiveTouchCount -= liftedTouches;
+ } else {
+ NS_WARNING("Got an unexpected touchend");
+ mActiveTouchCount = 0;
+ }
+ break;
+ }
+ case eTouchCancel:
+ mActiveTouchCount = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+uint32_t TouchCounter::GetActiveTouchCount() const { return mActiveTouchCount; }
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/apz/util/TouchCounter.h b/gfx/layers/apz/util/TouchCounter.h
new file mode 100644
index 0000000000..c13f475355
--- /dev/null
+++ b/gfx/layers/apz/util/TouchCounter.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_TouchCounter_h
+#define mozilla_layers_TouchCounter_h
+
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+
+class MultiTouchInput;
+
+namespace layers {
+
+// TouchCounter simply tracks the number of active touch points. Feed it
+// your input events to update the internal state. Generally you should
+// only be calling one of the Update functions, depending on which type
+// of touch inputs you have access to.
+class TouchCounter {
+ public:
+ TouchCounter();
+ void Update(const MultiTouchInput& aInput);
+ void Update(const WidgetTouchEvent& aEvent);
+ uint32_t GetActiveTouchCount() const;
+
+ private:
+ uint32_t mActiveTouchCount;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* mozilla_layers_TouchCounter_h */