forked from mirrors/gecko-dev
1012 lines
39 KiB
C++
1012 lines
39 KiB
C++
/* -*- 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 "gfxPrefs.h"
|
|
#include "LayersLogging.h" // For Stringify
|
|
#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/ShadowLayers.h"
|
|
#include "mozilla/layers/WebRenderLayerManager.h"
|
|
#include "mozilla/layers/WebRenderBridgeChild.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/TouchEvents.h"
|
|
#include "nsContainerFrame.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIDOMWindow.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"
|
|
|
|
// #define APZCCH_LOGGING 1
|
|
#ifdef APZCCH_LOGGING
|
|
# define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
|
|
#else
|
|
# define APZCCH_LOG(...)
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
using dom::BrowserParent;
|
|
|
|
uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock =
|
|
uint64_t(-1);
|
|
|
|
ScreenMargin APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
|
|
const RepaintRequest& aRequest, const CSSPoint& aActualScrollOffset) {
|
|
// Correct the display-port by the difference between the requested scroll
|
|
// offset and the resulting scroll offset after setting the requested value.
|
|
ScreenPoint shift = (aRequest.GetScrollOffset() - aActualScrollOffset) *
|
|
aRequest.DisplayportPixelsPerCSSPixel();
|
|
ScreenMargin margins = aRequest.GetDisplayPortMargins();
|
|
margins.left -= shift.x;
|
|
margins.right += shift.x;
|
|
margins.top -= shift.y;
|
|
margins.bottom += shift.y;
|
|
return margins;
|
|
}
|
|
|
|
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.IsRootContent()
|
|
? aRequest.GetLayoutViewport().TopLeft()
|
|
: aRequest.GetScrollOffset();
|
|
|
|
if (!aFrame) {
|
|
return targetScrollPosition;
|
|
}
|
|
|
|
CSSPoint geckoScrollPosition =
|
|
CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
|
|
|
|
// If the repaint request was triggered due to a previous main-thread scroll
|
|
// offset update sent to the APZ, then we don't need to do another scroll here
|
|
// and we can just return.
|
|
if (!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, nsGkAtoms::apz);
|
|
geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
|
|
aSuccessOut = true;
|
|
}
|
|
// Return the final scroll position after setting it so that anything that
|
|
// relies on it can have an accurate value. Note that even if we set it above
|
|
// re-querying it is a good idea because it may have gotten clamped or
|
|
// rounded.
|
|
return geckoScrollPosition;
|
|
}
|
|
|
|
/**
|
|
* Scroll the scroll frame associated with |aContent| to the scroll position
|
|
* requested in |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 ScreenMargin ScrollFrame(nsIContent* aContent,
|
|
const RepaintRequest& aRequest) {
|
|
// Scroll the window to the desired spot
|
|
nsIScrollableFrame* sf =
|
|
nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
|
|
if (sf) {
|
|
sf->ResetScrollInfoIfGeneration(aRequest.GetScrollGeneration());
|
|
sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
|
|
if (sf->IsRootScrollFrameOfDocument()) {
|
|
if (!APZCCallbackHelper::IsScrollInProgress(sf)) {
|
|
if (RefPtr<PresShell> presShell = GetPresShell(aContent)) {
|
|
if (presShell->SetVisualViewportOffset(
|
|
CSSPoint::ToAppUnits(aRequest.GetScrollOffset()),
|
|
presShell->GetLayoutViewportOffset())) {
|
|
sf->MarkEverScrolled();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bool scrollUpdated = false;
|
|
ScreenMargin displayPortMargins = aRequest.GetDisplayPortMargins();
|
|
CSSPoint apzScrollOffset = aRequest.GetScrollOffset();
|
|
CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated);
|
|
|
|
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 mScrollOffset
|
|
// and the actual scroll offset.
|
|
displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
|
|
aRequest, actualScrollOffset);
|
|
}
|
|
} else if (aRequest.IsRootContent() &&
|
|
aRequest.GetScrollOffset() !=
|
|
aRequest.GetLayoutViewport().TopLeft()) {
|
|
// 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 mScrollOffset and the actual scroll
|
|
// offset in repaints requested by
|
|
// AsyncPanZoomController::NotifyLayersUpdated.
|
|
displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
|
|
aRequest, actualScrollOffset);
|
|
} else {
|
|
// For whatever reason we couldn't update the scroll offset on the scroll
|
|
// frame, which means the data APZ used for its displayport calculation is
|
|
// stale. Fall back to a sane default behaviour. Note that we don't
|
|
// tile-align the recentered displayport because tile-alignment depends on
|
|
// the scroll position, and the scroll position here is out of our control.
|
|
// See bug 966507 comment 21 for a more detailed explanation.
|
|
displayPortMargins = RecenterDisplayPort(aRequest.GetDisplayPortMargins());
|
|
}
|
|
|
|
// 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) {
|
|
CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
|
|
aContent->SetProperty(nsGkAtoms::apzCallbackTransform,
|
|
new CSSPoint(scrollDelta),
|
|
nsINode::DeleteProperty<CSSPoint>);
|
|
}
|
|
|
|
return displayPortMargins;
|
|
}
|
|
|
|
static void SetDisplayPortMargins(PresShell* aPresShell, nsIContent* aContent,
|
|
ScreenMargin aDisplayPortMargins,
|
|
CSSSize aDisplayPortBase) {
|
|
if (!aContent) {
|
|
return;
|
|
}
|
|
|
|
bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
|
|
nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell,
|
|
aDisplayPortMargins, 0);
|
|
if (!hadDisplayPort) {
|
|
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
|
|
aContent->GetPrimaryFrame());
|
|
}
|
|
|
|
nsRect base(0, 0, aDisplayPortBase.width * AppUnitsPerCSSPixel(),
|
|
aDisplayPortBase.height * AppUnitsPerCSSPixel());
|
|
nsLayoutUtils::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));
|
|
}
|
|
}
|
|
}
|
|
|
|
void APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest) {
|
|
if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) {
|
|
return;
|
|
}
|
|
nsIContent* content = nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
|
|
if (!content) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresShell> presShell = GetPresShell(content);
|
|
if (!presShell || aRequest.GetPresShellId() != presShell->GetPresShellId()) {
|
|
return;
|
|
}
|
|
|
|
if (nsLayoutUtils::AllowZoomingForDocument(presShell->GetDocument()) &&
|
|
aRequest.GetScrollOffsetUpdated()) {
|
|
// 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).
|
|
//
|
|
// We also skip this codepath unless the metrics has a scroll offset update
|
|
// type other eNone, because eNone just means that this repaint request
|
|
// was triggered by APZ in response to a main-thread update. In this
|
|
// scenario we don't want to update the main-thread resolution because
|
|
// it can trigger unnecessary reflows.
|
|
|
|
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);
|
|
}
|
|
|
|
// 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.
|
|
ScreenMargin 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;
|
|
}
|
|
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.
|
|
ScreenMargin 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->IsRootContentDocument() ||
|
|
!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());
|
|
}
|
|
nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, baseRect);
|
|
// Note that we also set the base rect that goes with these margins in
|
|
// nsRootBoxFrame::BuildDisplayList.
|
|
nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(),
|
|
0);
|
|
nsLayoutUtils::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->GetToplevelContentDocumentPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
return context->PresShell();
|
|
}
|
|
|
|
static PresShell* GetRootDocumentPresShell(nsIContent* aContent) {
|
|
dom::Document* doc = aContent->GetComposedDoc();
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
PresShell* presShell = doc->GetPresShell();
|
|
if (!presShell) {
|
|
return nullptr;
|
|
}
|
|
nsPresContext* context = presShell->GetPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
context = context->GetRootPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
return context->PresShell();
|
|
}
|
|
|
|
CSSPoint APZCCallbackHelper::ApplyCallbackTransform(
|
|
const CSSPoint& aInput, const ScrollableLayerGuid& aGuid) {
|
|
CSSPoint input = aInput;
|
|
if (aGuid.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
|
|
return input;
|
|
}
|
|
nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
|
|
if (!content) {
|
|
return input;
|
|
}
|
|
|
|
// First, scale inversely by the root content document's pres shell
|
|
// resolution to cancel the scale-to-resolution transform that the
|
|
// compositor adds to the layer with the pres shell resolution. The points
|
|
// sent to Gecko by APZ don't have this transform unapplied (unlike other
|
|
// compositor-side transforms) because APZ doesn't know about it.
|
|
if (PresShell* presShell = GetRootDocumentPresShell(content)) {
|
|
input = input / presShell->GetResolution();
|
|
}
|
|
|
|
// This represents any resolution on the Root Content Document (RCD)
|
|
// that's not on the Root Document (RD). That is, on platforms where
|
|
// RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
|
|
// resolution. 'input' has this resolution applied, but the scroll
|
|
// delta retrieved below do not, so we need to apply them to the
|
|
// delta before adding the delta to 'input'. (Technically, deltas
|
|
// from scroll frames outside the RCD would already have this
|
|
// resolution applied, but we don't have such scroll frames in
|
|
// practice.)
|
|
float nonRootResolution = 1.0f;
|
|
if (PresShell* presShell =
|
|
GetRootContentDocumentPresShellForContent(content)) {
|
|
nonRootResolution = presShell->GetCumulativeNonRootScaleResolution();
|
|
}
|
|
// Now apply the callback-transform. This is only approximately correct,
|
|
// see the comment on GetCumulativeApzCallbackTransform for details.
|
|
CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(
|
|
content->GetPrimaryFrame());
|
|
return input + transform * nonRootResolution;
|
|
}
|
|
|
|
LayoutDeviceIntPoint APZCCallbackHelper::ApplyCallbackTransform(
|
|
const LayoutDeviceIntPoint& aPoint, const ScrollableLayerGuid& aGuid,
|
|
const CSSToLayoutDeviceScale& aScale) {
|
|
LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
|
|
point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
|
|
return LayoutDeviceIntPoint::Round(point);
|
|
}
|
|
|
|
void APZCCallbackHelper::ApplyCallbackTransform(
|
|
WidgetEvent& aEvent, const ScrollableLayerGuid& aGuid,
|
|
const CSSToLayoutDeviceScale& aScale) {
|
|
if (aEvent.AsTouchEvent()) {
|
|
WidgetTouchEvent& event = *(aEvent.AsTouchEvent());
|
|
for (size_t i = 0; i < event.mTouches.Length(); i++) {
|
|
event.mTouches[i]->mRefPoint =
|
|
ApplyCallbackTransform(event.mTouches[i]->mRefPoint, aGuid, aScale);
|
|
}
|
|
} else {
|
|
aEvent.mRefPoint = ApplyCallbackTransform(aEvent.mRefPoint, aGuid, aScale);
|
|
}
|
|
}
|
|
|
|
nsEventStatus APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent) {
|
|
nsEventStatus status = nsEventStatus_eConsumeNoDefault;
|
|
if (aEvent.mWidget) {
|
|
aEvent.mWidget->DispatchEvent(&aEvent, status);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
|
EventMessage aMsg, uint64_t aTime, const LayoutDevicePoint& aRefPoint,
|
|
Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget) {
|
|
MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown || aMsg == eMouseUp ||
|
|
aMsg == eMouseLongTap);
|
|
|
|
WidgetMouseEvent event(true, aMsg, aWidget, WidgetMouseEvent::eReal,
|
|
WidgetMouseEvent::eNormal);
|
|
event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
|
|
event.mTime = aTime;
|
|
event.mButton = MouseButton::eLeft;
|
|
event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH;
|
|
if (aMsg == eMouseLongTap) {
|
|
event.mFlags.mOnlyChromeDispatch = true;
|
|
}
|
|
event.mIgnoreRootScrollFrame = true;
|
|
if (aMsg != eMouseMove) {
|
|
event.mClickCount = aClickCount;
|
|
}
|
|
event.mModifiers = aModifiers;
|
|
// Real touch events will generate corresponding pointer events. We set
|
|
// convertToPointer to false to prevent the synthesized mouse events generate
|
|
// pointer events again.
|
|
event.convertToPointer = false;
|
|
return DispatchWidgetEvent(event);
|
|
}
|
|
|
|
bool APZCCallbackHelper::DispatchMouseEvent(
|
|
PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint,
|
|
int32_t aButton, int32_t aClickCount, int32_t aModifiers,
|
|
bool aIgnoreRootScrollFrame, 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, 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",
|
|
Stringify(aPoint).c_str());
|
|
int time = 0;
|
|
DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers,
|
|
aClickCount, aWidget);
|
|
DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers,
|
|
aClickCount, aWidget);
|
|
DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount,
|
|
aWidget);
|
|
}
|
|
|
|
static dom::Element* GetDisplayportElementFor(
|
|
nsIScrollableFrame* aScrollableFrame) {
|
|
if (!aScrollableFrame) {
|
|
return nullptr;
|
|
}
|
|
nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
|
|
if (!scrolledFrame) {
|
|
return nullptr;
|
|
}
|
|
// |scrolledFrame| should at this point be the root content frame of the
|
|
// nearest ancestor scrollable frame. The element corresponding to this
|
|
// frame should be the one with the displayport set on it, so find that
|
|
// element and return it.
|
|
nsIContent* content = scrolledFrame->GetContent();
|
|
MOZ_ASSERT(content->IsElement()); // roc says this must be true
|
|
return content->AsElement();
|
|
}
|
|
|
|
static dom::Element* GetRootDocumentElementFor(nsIWidget* aWidget) {
|
|
// This returns the root element that ChromeProcessController sets the
|
|
// displayport on during initialization.
|
|
if (nsView* view = nsView::GetViewFor(aWidget)) {
|
|
if (PresShell* presShell = view->GetPresShell()) {
|
|
MOZ_ASSERT(presShell->GetDocument());
|
|
return presShell->GetDocument()->GetDocumentElement();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static nsIFrame* UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame) {
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
// Re-target so that the hit test is performed relative to the frame for the
|
|
// Root Content Document instead of the Root Document which are different in
|
|
// Android. See bug 1229752 comment 16 for an explanation of why this is
|
|
// necessary.
|
|
if (dom::Document* doc =
|
|
aRootFrame->PresShell()->GetPrimaryContentDocument()) {
|
|
if (PresShell* presShell = doc->GetPresShell()) {
|
|
if (nsIFrame* frame = presShell->GetRootFrame()) {
|
|
return frame;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return aRootFrame;
|
|
}
|
|
|
|
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 a displayport was set.
|
|
static bool PrepareForSetTargetAPZCNotification(
|
|
nsIWidget* aWidget, const LayersId& aLayersId, nsIFrame* aRootFrame,
|
|
const LayoutDeviceIntPoint& aRefPoint,
|
|
nsTArray<SLGuidAndRenderRoot>* aTargets) {
|
|
SLGuidAndRenderRoot guid(aLayersId, 0,
|
|
ScrollableLayerGuid::NULL_SCROLL_ID,
|
|
wr::RenderRoot::Default);
|
|
nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
|
|
aWidget, aRefPoint, aRootFrame);
|
|
EnumSet<FrameForPointOption> options;
|
|
if (nsLayoutUtils::AllowZoomingForDocument(
|
|
aRootFrame->PresShell()->GetDocument())) {
|
|
// If zooming is enabled, we need IgnoreRootScrollFrame for correct
|
|
// hit testing. Otherwise, don't use it because it interferes with
|
|
// hit testing for some purposes such as scrollbar dragging (this will
|
|
// need to be fixed before enabling zooming by default on desktop).
|
|
options += FrameForPointOption::IgnoreRootScrollFrame;
|
|
}
|
|
nsIFrame* target =
|
|
nsLayoutUtils::GetFrameForPoint(aRootFrame, point, options);
|
|
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 (XRE_IsContentProcess()) {
|
|
guid.mRenderRoot = gfxUtils::GetContentRenderRoot();
|
|
} else {
|
|
guid.mRenderRoot = gfxUtils::RecursivelyGetRenderRootForElement(dpElement);
|
|
}
|
|
|
|
#ifdef APZCCH_LOGGING
|
|
nsAutoString dpElementDesc;
|
|
if (dpElement) {
|
|
dpElement->Describe(dpElementDesc);
|
|
}
|
|
APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
|
|
Stringify(aRefPoint).c_str(), dpElement.get(),
|
|
NS_LossyConvertUTF16toASCII(dpElementDesc).get());
|
|
#endif
|
|
|
|
bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
|
|
dpElement, &(guid.mScrollableLayerGuid.mPresShellId),
|
|
&(guid.mScrollableLayerGuid.mScrollId));
|
|
aTargets->AppendElement(guid);
|
|
|
|
if (!guidIsValid || nsLayoutUtils::HasDisplayPort(dpElement)) {
|
|
return false;
|
|
}
|
|
|
|
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());
|
|
bool activated = nsLayoutUtils::CalculateAndSetDisplayPortMargins(
|
|
scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
|
|
if (!activated) {
|
|
return false;
|
|
}
|
|
|
|
nsIFrame* frame = do_QueryFrame(scrollAncestor);
|
|
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void SendLayersDependentApzcTargetConfirmation(
|
|
PresShell* aPresShell, uint64_t aInputBlockId,
|
|
const nsTArray<SLGuidAndRenderRoot>& 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,
|
|
const nsTArray<SLGuidAndRenderRoot>& aTargets)
|
|
: mWidget(aWidget),
|
|
mPresShell(aPresShell),
|
|
mInputBlockId(aInputBlockId),
|
|
mTargets(aTargets) {}
|
|
|
|
DisplayportSetListener::~DisplayportSetListener() {}
|
|
|
|
bool DisplayportSetListener::Register() {
|
|
if (mPresShell->AddPostRefreshObserver(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;
|
|
}
|
|
|
|
void DisplayportSetListener::DidRefresh() {
|
|
if (!mPresShell) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Post-refresh observer fired again after failed attempt at "
|
|
"unregistering it");
|
|
return;
|
|
}
|
|
|
|
APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n",
|
|
mInputBlockId);
|
|
SendLayersDependentApzcTargetConfirmation(mPresShell, mInputBlockId,
|
|
std::move(mTargets));
|
|
|
|
if (!mPresShell->RemovePostRefreshObserver(this)) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Unable to unregister post-refresh observer! Leaking it instead of "
|
|
"leaving garbage registered");
|
|
// Graceful handling, just in case...
|
|
mPresShell = nullptr;
|
|
return;
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
|
|
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()) {
|
|
rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
|
|
|
|
bool waitForRefresh = false;
|
|
nsTArray<SLGuidAndRenderRoot> 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;
|
|
}
|
|
|
|
void APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
|
|
nsIWidget* aWidget, dom::Document* aDocument,
|
|
const WidgetTouchEvent& aEvent, uint64_t aInputBlockId,
|
|
const SetAllowedTouchBehaviorCallback& aCallback) {
|
|
if (!aWidget || !aDocument) {
|
|
return;
|
|
}
|
|
if (PresShell* presShell = aDocument->GetPresShell()) {
|
|
if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
|
|
rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
|
|
|
|
nsTArray<TouchBehaviorFlags> flags;
|
|
for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
|
|
flags.AppendElement(TouchActionHelper::GetAllowedTouchBehavior(
|
|
aWidget, rootFrame, aEvent.mTouches[i]->mRefPoint));
|
|
}
|
|
aCallback(aInputBlockId, std::move(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::DispatchTrustedEvent(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) {
|
|
return aFrame->IsProcessingAsyncScroll() ||
|
|
nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin()) ||
|
|
aFrame->LastSmoothScrollOrigin();
|
|
}
|
|
|
|
/* 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, LayoutDeviceCoord aSpanChange,
|
|
Modifiers aModifiers, nsIWidget* aWidget) {
|
|
EventMessage msg;
|
|
switch (aType) {
|
|
case PinchGestureInput::PINCHGESTURE_START:
|
|
msg = eMagnifyGestureStart;
|
|
break;
|
|
case PinchGestureInput::PINCHGESTURE_SCALE:
|
|
msg = eMagnifyGestureUpdate;
|
|
break;
|
|
case PinchGestureInput::PINCHGESTURE_END:
|
|
msg = eMagnifyGesture;
|
|
break;
|
|
}
|
|
|
|
WidgetSimpleGestureEvent event(true, msg, aWidget);
|
|
event.mDelta = aSpanChange;
|
|
event.mModifiers = aModifiers;
|
|
DispatchWidgetEvent(event);
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|