mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-10 02:08:38 +09:00
ScrollbarStyles contains values of overflow, (over)scroll-behavior, etc. The only one which is marginally related to scroll _bars_ is overflow, which can be used to hide scrollbar (by making an element not scrollable) or enforce the scrollbar to display. It makes more sense to be called ScrollStyles as it's mainly concerning behavior of scrolling, not scrollbars. Also, with the addition of scrollbar width properties, the current name can be confusing.
931 lines
34 KiB
C++
931 lines
34 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "APZCCallbackHelper.h"
|
|
|
|
#include "TouchActionHelper.h"
|
|
#include "gfxPlatform.h" // For gfxPlatform::UseTiling
|
|
#include "gfxPrefs.h"
|
|
#include "LayersLogging.h" // For Stringify
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/TabParent.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/layers/LayerTransactionChild.h"
|
|
#include "mozilla/layers/ShadowLayers.h"
|
|
#include "mozilla/TouchEvents.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsContainerFrame.h"
|
|
#include "nsIScrollableFrame.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMWindow.h"
|
|
#include "nsIDOMWindowUtils.h"
|
|
#include "nsRefreshDriver.h"
|
|
#include "nsString.h"
|
|
#include "nsView.h"
|
|
#include "Layers.h"
|
|
|
|
#define APZCCH_LOG(...)
|
|
// #define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
using dom::TabParent;
|
|
|
|
uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
|
|
|
|
void
|
|
APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
|
|
mozilla::layers::FrameMetrics& aFrameMetrics,
|
|
const CSSPoint& aActualScrollOffset)
|
|
{
|
|
// Correct the display-port by the difference between the requested scroll
|
|
// offset and the resulting scroll offset after setting the requested value.
|
|
ScreenPoint shift =
|
|
(aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
|
|
aFrameMetrics.DisplayportPixelsPerCSSPixel();
|
|
ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
|
|
margins.left -= shift.x;
|
|
margins.right += shift.x;
|
|
margins.top -= shift.y;
|
|
margins.bottom += shift.y;
|
|
aFrameMetrics.SetDisplayPortMargins(margins);
|
|
}
|
|
|
|
static void
|
|
RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
|
|
{
|
|
ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
|
|
margins.right = margins.left = margins.LeftRight() / 2;
|
|
margins.top = margins.bottom = margins.TopBottom() / 2;
|
|
aFrameMetrics.SetDisplayPortMargins(margins);
|
|
}
|
|
|
|
static CSSPoint
|
|
ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
|
|
{
|
|
aSuccessOut = false;
|
|
CSSPoint targetScrollPosition = aMetrics.GetScrollOffset();
|
|
|
|
if (!aFrame) {
|
|
return targetScrollPosition;
|
|
}
|
|
|
|
CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
|
|
|
|
// If the repaint request was triggered due to a previous main-thread scroll
|
|
// offset update sent to the APZ, then we don't need to do another scroll here
|
|
// and we can just return.
|
|
if (!aMetrics.GetScrollOffsetUpdated()) {
|
|
return geckoScrollPosition;
|
|
}
|
|
|
|
// If the frame is overflow:hidden on a particular axis, we don't want to allow
|
|
// user-driven scroll on that axis. Simply set the scroll position on that axis
|
|
// to whatever it already is. Note that this will leave the APZ's async scroll
|
|
// position out of sync with the gecko scroll position, but APZ can deal with that
|
|
// (by design). Note also that when we run into this case, even if both axes
|
|
// have overflow:hidden, we want to set aSuccessOut to true, so that the displayport
|
|
// follows the async scroll position rather than the gecko scroll position.
|
|
if (aFrame->GetScrollStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
|
|
targetScrollPosition.y = geckoScrollPosition.y;
|
|
}
|
|
if (aFrame->GetScrollStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
|
|
targetScrollPosition.x = geckoScrollPosition.x;
|
|
}
|
|
|
|
// If the scrollable frame is currently in the middle of an async or smooth
|
|
// scroll then we don't want to interrupt it (see bug 961280).
|
|
// Also if the scrollable frame got a scroll request from a higher priority origin
|
|
// since the last layers update, then we don't want to push our scroll request
|
|
// because we'll clobber that one, which is bad.
|
|
bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
|
|
if (!scrollInProgress) {
|
|
aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
|
|
geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
|
|
aSuccessOut = true;
|
|
}
|
|
// Return the final scroll position after setting it so that anything that relies
|
|
// on it can have an accurate value. Note that even if we set it above re-querying it
|
|
// is a good idea because it may have gotten clamped or rounded.
|
|
return geckoScrollPosition;
|
|
}
|
|
|
|
/**
|
|
* Scroll the scroll frame associated with |aContent| to the scroll position
|
|
* requested in |aMetrics|.
|
|
* The scroll offset in |aMetrics| is updated to reflect the actual scroll
|
|
* position.
|
|
* The displayport stored in |aMetrics| and the callback-transform stored on
|
|
* the content are updated to reflect any difference between the requested
|
|
* and actual scroll positions.
|
|
*/
|
|
static void
|
|
ScrollFrame(nsIContent* aContent,
|
|
FrameMetrics& aMetrics)
|
|
{
|
|
// Scroll the window to the desired spot
|
|
nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
|
|
if (sf) {
|
|
sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
|
|
sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
|
|
}
|
|
bool scrollUpdated = false;
|
|
CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
|
|
CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
|
|
|
|
if (scrollUpdated) {
|
|
if (aMetrics.IsScrollInfoLayer()) {
|
|
// In cases where the APZ scroll offset is different from the content scroll
|
|
// offset, we want to interpret the margins as relative to the APZ scroll
|
|
// offset except when the frame is not scrollable by APZ. Therefore, if the
|
|
// layer is a scroll info layer, we leave the margins as-is and they will
|
|
// be interpreted as relative to the content scroll offset.
|
|
if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
|
|
frame->SchedulePaint();
|
|
}
|
|
} else {
|
|
// Correct the display port due to the difference between mScrollOffset and the
|
|
// actual scroll offset.
|
|
APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
|
|
}
|
|
} else {
|
|
// For whatever reason we couldn't update the scroll offset on the scroll frame,
|
|
// which means the data APZ used for its displayport calculation is stale. Fall
|
|
// back to a sane default behaviour. Note that we don't tile-align the recentered
|
|
// displayport because tile-alignment depends on the scroll position, and the
|
|
// scroll position here is out of our control. See bug 966507 comment 21 for a
|
|
// more detailed explanation.
|
|
RecenterDisplayPort(aMetrics);
|
|
}
|
|
|
|
aMetrics.SetScrollOffset(actualScrollOffset);
|
|
|
|
// APZ transforms inputs assuming we applied the exact scroll offset it
|
|
// requested (|apzScrollOffset|). Since we may not have, record the difference
|
|
// between what APZ asked for and what we actually applied, and apply it to
|
|
// input events to compensate.
|
|
// Note that if the main-thread had a change in its scroll position, we don't
|
|
// want to record that difference here, because it can be large and throw off
|
|
// input events by a large amount. It is also going to be transient, because
|
|
// any main-thread scroll position change will be synced to APZ and we will
|
|
// get another repaint request when APZ confirms. In the interval while this
|
|
// is happening we can just leave the callback transform as it was.
|
|
bool mainThreadScrollChanged =
|
|
sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
|
|
if (aContent && !mainThreadScrollChanged) {
|
|
CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
|
|
aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
|
|
nsINode::DeleteProperty<CSSPoint>);
|
|
}
|
|
}
|
|
|
|
static void
|
|
SetDisplayPortMargins(nsIPresShell* aPresShell,
|
|
nsIContent* aContent,
|
|
const FrameMetrics& aMetrics)
|
|
{
|
|
if (!aContent) {
|
|
return;
|
|
}
|
|
|
|
bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
|
|
ScreenMargin margins = aMetrics.GetDisplayPortMargins();
|
|
nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
|
|
if (!hadDisplayPort) {
|
|
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
|
|
aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
|
|
}
|
|
|
|
CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels();
|
|
nsRect base(0, 0,
|
|
baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
|
|
baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
|
|
nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
|
|
}
|
|
|
|
static already_AddRefed<nsIPresShell>
|
|
GetPresShell(const nsIContent* aContent)
|
|
{
|
|
nsCOMPtr<nsIPresShell> result;
|
|
if (nsIDocument* doc = aContent->GetComposedDoc()) {
|
|
result = doc->GetShell();
|
|
}
|
|
return result.forget();
|
|
}
|
|
|
|
static void
|
|
SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
|
|
{
|
|
aContent->SetProperty(nsGkAtoms::paintRequestTime,
|
|
new TimeStamp(aPaintRequestTime),
|
|
nsINode::DeleteProperty<TimeStamp>);
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
|
|
{
|
|
if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
|
|
return;
|
|
}
|
|
nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
|
|
if (!content) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
|
|
if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
|
|
|
|
if (gfxPrefs::APZAllowZooming()) {
|
|
// If zooming is disabled then we don't really want to let APZ fiddle
|
|
// with these things. In theory setting the resolution here should be a
|
|
// no-op, but setting the SPCSPS is bad because it can cause a stale value
|
|
// to be returned by window.innerWidth/innerHeight (see bug 1187792).
|
|
|
|
float presShellResolution = shell->GetResolution();
|
|
|
|
// If the pres shell resolution has changed on the content side side
|
|
// the time this repaint request was fired, consider this request out of date
|
|
// and drop it; setting a zoom based on the out-of-date resolution can have
|
|
// the effect of getting us stuck with the stale resolution.
|
|
if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
|
|
return;
|
|
}
|
|
|
|
// The pres shell resolution is updated by the the async zoom since the
|
|
// last paint.
|
|
presShellResolution = aMetrics.GetPresShellResolution()
|
|
* aMetrics.GetAsyncZoom().scale;
|
|
shell->SetResolutionAndScaleTo(presShellResolution);
|
|
}
|
|
|
|
// Do this as late as possible since scrolling can flush layout. It also
|
|
// adjusts the display port margins, so do it before we set those.
|
|
ScrollFrame(content, aMetrics);
|
|
|
|
SetDisplayPortMargins(shell, content, aMetrics);
|
|
SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
|
|
{
|
|
if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
|
|
return;
|
|
}
|
|
nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
|
|
if (!content) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
|
|
|
|
// We don't currently support zooming for subframes, so nothing extra
|
|
// needs to be done beyond the tasks common to this and UpdateRootFrame.
|
|
ScrollFrame(content, aMetrics);
|
|
if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
|
|
SetDisplayPortMargins(shell, content, aMetrics);
|
|
}
|
|
SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
|
|
}
|
|
|
|
bool
|
|
APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
|
|
uint32_t* aPresShellIdOut,
|
|
FrameMetrics::ViewID* aViewIdOut)
|
|
{
|
|
if (!aContent) {
|
|
return false;
|
|
}
|
|
*aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
|
|
if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
|
|
*aPresShellIdOut = shell->GetPresShellId();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::InitializeRootDisplayport(nsIPresShell* aPresShell)
|
|
{
|
|
// Create a view-id and set a zero-margin displayport for the root element
|
|
// of the root document in the chrome process. This ensures that the scroll
|
|
// frame for this element gets an APZC, which in turn ensures that all content
|
|
// in the chrome processes is covered by an APZC.
|
|
// The displayport is zero-margin because this element is generally not
|
|
// actually scrollable (if it is, APZC will set proper margins when it's
|
|
// scrolled).
|
|
if (!aPresShell) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aPresShell->GetDocument());
|
|
nsIContent* content = aPresShell->GetDocument()->GetDocumentElement();
|
|
if (!content) {
|
|
return;
|
|
}
|
|
|
|
uint32_t presShellId;
|
|
FrameMetrics::ViewID viewId;
|
|
if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, &viewId)) {
|
|
// Note that the base rect that goes with these margins is set in
|
|
// nsRootBoxFrame::BuildDisplayList.
|
|
nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(), 0,
|
|
nsLayoutUtils::RepaintMode::DoNotRepaint);
|
|
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
|
|
content->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::DoNotRepaint);
|
|
}
|
|
}
|
|
|
|
nsPresContext*
|
|
APZCCallbackHelper::GetPresContextForContent(nsIContent* aContent)
|
|
{
|
|
nsIDocument* doc = aContent->GetComposedDoc();
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
nsIPresShell* shell = doc->GetShell();
|
|
if (!shell) {
|
|
return nullptr;
|
|
}
|
|
return shell->GetPresContext();
|
|
}
|
|
|
|
nsIPresShell*
|
|
APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent)
|
|
{
|
|
nsPresContext* context = GetPresContextForContent(aContent);
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
context = context->GetToplevelContentDocumentPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
return context->PresShell();
|
|
}
|
|
|
|
static nsIPresShell*
|
|
GetRootDocumentPresShell(nsIContent* aContent)
|
|
{
|
|
nsIDocument* doc = aContent->GetComposedDoc();
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
nsIPresShell* shell = doc->GetShell();
|
|
if (!shell) {
|
|
return nullptr;
|
|
}
|
|
nsPresContext* context = shell->GetPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
context = context->GetRootPresContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
return context->PresShell();
|
|
}
|
|
|
|
CSSPoint
|
|
APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
|
|
const ScrollableLayerGuid& aGuid)
|
|
{
|
|
CSSPoint input = aInput;
|
|
if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
|
|
return input;
|
|
}
|
|
nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
|
|
if (!content) {
|
|
return input;
|
|
}
|
|
|
|
// First, scale inversely by the root content document's pres shell
|
|
// resolution to cancel the scale-to-resolution transform that the
|
|
// compositor adds to the layer with the pres shell resolution. The points
|
|
// sent to Gecko by APZ don't have this transform unapplied (unlike other
|
|
// compositor-side transforms) because APZ doesn't know about it.
|
|
if (nsIPresShell* shell = GetRootDocumentPresShell(content)) {
|
|
input = input / shell->GetResolution();
|
|
}
|
|
|
|
// This represents any resolution on the Root Content Document (RCD)
|
|
// that's not on the Root Document (RD). That is, on platforms where
|
|
// RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
|
|
// resolution. 'input' has this resolution applied, but the scroll
|
|
// delta retrieved below do not, so we need to apply them to the
|
|
// delta before adding the delta to 'input'. (Technically, deltas
|
|
// from scroll frames outside the RCD would already have this
|
|
// resolution applied, but we don't have such scroll frames in
|
|
// practice.)
|
|
float nonRootResolution = 1.0f;
|
|
if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
|
|
nonRootResolution = shell->GetCumulativeNonRootScaleResolution();
|
|
}
|
|
// Now apply the callback-transform. This is only approximately correct,
|
|
// see the comment on GetCumulativeApzCallbackTransform for details.
|
|
CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(content->GetPrimaryFrame());
|
|
return input + transform * nonRootResolution;
|
|
}
|
|
|
|
LayoutDeviceIntPoint
|
|
APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
|
|
const ScrollableLayerGuid& aGuid,
|
|
const CSSToLayoutDeviceScale& aScale)
|
|
{
|
|
LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
|
|
point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
|
|
return LayoutDeviceIntPoint::Round(point);
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::ApplyCallbackTransform(WidgetEvent& aEvent,
|
|
const ScrollableLayerGuid& aGuid,
|
|
const CSSToLayoutDeviceScale& aScale)
|
|
{
|
|
if (aEvent.AsTouchEvent()) {
|
|
WidgetTouchEvent& event = *(aEvent.AsTouchEvent());
|
|
for (size_t i = 0; i < event.mTouches.Length(); i++) {
|
|
event.mTouches[i]->mRefPoint = ApplyCallbackTransform(
|
|
event.mTouches[i]->mRefPoint, aGuid, aScale);
|
|
}
|
|
} else {
|
|
aEvent.mRefPoint = ApplyCallbackTransform(aEvent.mRefPoint, aGuid, aScale);
|
|
}
|
|
}
|
|
|
|
nsEventStatus
|
|
APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
|
|
{
|
|
nsEventStatus status = nsEventStatus_eConsumeNoDefault;
|
|
if (aEvent.mWidget) {
|
|
aEvent.mWidget->DispatchEvent(&aEvent, status);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
nsEventStatus
|
|
APZCCallbackHelper::DispatchSynthesizedMouseEvent(EventMessage aMsg,
|
|
uint64_t aTime,
|
|
const LayoutDevicePoint& aRefPoint,
|
|
Modifiers aModifiers,
|
|
int32_t aClickCount,
|
|
nsIWidget* aWidget)
|
|
{
|
|
MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown ||
|
|
aMsg == eMouseUp || aMsg == eMouseLongTap);
|
|
|
|
WidgetMouseEvent event(true, aMsg, aWidget,
|
|
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
|
|
event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
|
|
event.mTime = aTime;
|
|
event.button = WidgetMouseEvent::eLeftButton;
|
|
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
|
|
if (aMsg == eMouseLongTap) {
|
|
event.mFlags.mOnlyChromeDispatch = true;
|
|
}
|
|
event.mIgnoreRootScrollFrame = true;
|
|
if (aMsg != eMouseMove) {
|
|
event.mClickCount = aClickCount;
|
|
}
|
|
event.mModifiers = aModifiers;
|
|
// Real touch events will generate corresponding pointer events. We set
|
|
// convertToPointer to false to prevent the synthesized mouse events generate
|
|
// pointer events again.
|
|
event.convertToPointer = false;
|
|
return DispatchWidgetEvent(event);
|
|
}
|
|
|
|
bool
|
|
APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
|
|
const nsString& aType,
|
|
const CSSPoint& aPoint,
|
|
int32_t aButton,
|
|
int32_t aClickCount,
|
|
int32_t aModifiers,
|
|
bool aIgnoreRootScrollFrame,
|
|
unsigned short aInputSourceArg)
|
|
{
|
|
NS_ENSURE_TRUE(aPresShell, true);
|
|
|
|
bool defaultPrevented = false;
|
|
nsContentUtils::SendMouseEvent(aPresShell, aType, aPoint.x, aPoint.y,
|
|
aButton, nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount,
|
|
aModifiers, aIgnoreRootScrollFrame, 0, aInputSourceArg, false,
|
|
&defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false);
|
|
return defaultPrevented;
|
|
}
|
|
|
|
|
|
void
|
|
APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
|
|
Modifiers aModifiers,
|
|
int32_t aClickCount,
|
|
nsIWidget* aWidget)
|
|
{
|
|
if (aWidget->Destroyed()) {
|
|
return;
|
|
}
|
|
APZCCH_LOG("Dispatching single-tap component events to %s\n",
|
|
Stringify(aPoint).c_str());
|
|
int time = 0;
|
|
DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, aClickCount, aWidget);
|
|
DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aClickCount, aWidget);
|
|
DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, aWidget);
|
|
}
|
|
|
|
static dom::Element*
|
|
GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame)
|
|
{
|
|
if (!aScrollableFrame) {
|
|
return nullptr;
|
|
}
|
|
nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
|
|
if (!scrolledFrame) {
|
|
return nullptr;
|
|
}
|
|
// |scrolledFrame| should at this point be the root content frame of the
|
|
// nearest ancestor scrollable frame. The element corresponding to this
|
|
// frame should be the one with the displayport set on it, so find that
|
|
// element and return it.
|
|
nsIContent* content = scrolledFrame->GetContent();
|
|
MOZ_ASSERT(content->IsElement()); // roc says this must be true
|
|
return content->AsElement();
|
|
}
|
|
|
|
|
|
static dom::Element*
|
|
GetRootDocumentElementFor(nsIWidget* aWidget)
|
|
{
|
|
// This returns the root element that ChromeProcessController sets the
|
|
// displayport on during initialization.
|
|
if (nsView* view = nsView::GetViewFor(aWidget)) {
|
|
if (nsIPresShell* shell = view->GetPresShell()) {
|
|
MOZ_ASSERT(shell->GetDocument());
|
|
return shell->GetDocument()->GetDocumentElement();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static nsIFrame*
|
|
UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame)
|
|
{
|
|
// No retargeting needed on desktop.
|
|
return aRootFrame;
|
|
}
|
|
|
|
// Determine the scrollable target frame for the given point and add it to
|
|
// the target list. If the frame doesn't have a displayport, set one.
|
|
// Return whether or not a displayport was set.
|
|
static bool
|
|
PrepareForSetTargetAPZCNotification(nsIWidget* aWidget,
|
|
const ScrollableLayerGuid& aGuid,
|
|
nsIFrame* aRootFrame,
|
|
const LayoutDeviceIntPoint& aRefPoint,
|
|
nsTArray<ScrollableLayerGuid>* aTargets)
|
|
{
|
|
ScrollableLayerGuid guid(aGuid.mLayersId, 0, FrameMetrics::NULL_SCROLL_ID);
|
|
nsPoint point =
|
|
nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aRefPoint, aRootFrame);
|
|
nsIFrame* target =
|
|
nsLayoutUtils::GetFrameForPoint(aRootFrame, point, nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
|
|
nsIScrollableFrame* scrollAncestor = target
|
|
? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
|
|
: aRootFrame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
|
|
|
|
// Assuming that if there's no scrollAncestor, there's already a displayPort.
|
|
nsCOMPtr<dom::Element> dpElement = scrollAncestor
|
|
? GetDisplayportElementFor(scrollAncestor)
|
|
: GetRootDocumentElementFor(aWidget);
|
|
|
|
nsAutoString dpElementDesc;
|
|
if (dpElement) {
|
|
dpElement->Describe(dpElementDesc);
|
|
}
|
|
APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
|
|
Stringify(aRefPoint).c_str(), dpElement.get(),
|
|
NS_LossyConvertUTF16toASCII(dpElementDesc).get());
|
|
|
|
bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
|
|
dpElement, &(guid.mPresShellId), &(guid.mScrollId));
|
|
aTargets->AppendElement(guid);
|
|
|
|
if (!guidIsValid || nsLayoutUtils::HasDisplayPort(dpElement)) {
|
|
return false;
|
|
}
|
|
|
|
if (!scrollAncestor) {
|
|
MOZ_ASSERT(false); // If you hit this, please file a bug with STR.
|
|
|
|
// Attempt some sort of graceful handling based on a theory as to why we
|
|
// reach this point...
|
|
// If we get here, the document element is non-null, valid, but doesn't have
|
|
// a displayport. It's possible that the init code in ChromeProcessController
|
|
// failed for some reason, or the document element got swapped out at some
|
|
// later time. In this case let's try to set a displayport on the document
|
|
// element again and bail out on this operation.
|
|
APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n",
|
|
aWidget, dpElement.get());
|
|
APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresContext()->PresShell());
|
|
return false;
|
|
}
|
|
|
|
APZCCH_LOG("%p didn't have a displayport, so setting one...\n", dpElement.get());
|
|
bool activated = nsLayoutUtils::CalculateAndSetDisplayPortMargins(
|
|
scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
|
|
if (!activated) {
|
|
return false;
|
|
}
|
|
|
|
nsIFrame* frame = do_QueryFrame(scrollAncestor);
|
|
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame,
|
|
nsLayoutUtils::RepaintMode::Repaint);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
SendLayersDependentApzcTargetConfirmation(nsIPresShell* aShell, uint64_t aInputBlockId,
|
|
const nsTArray<ScrollableLayerGuid>& aTargets)
|
|
{
|
|
LayerManager* lm = aShell->GetLayerManager();
|
|
if (!lm) {
|
|
return;
|
|
}
|
|
|
|
LayerTransactionChild* shadow = lm->AsShadowForwarder()->GetShadowManager();
|
|
if (!shadow) {
|
|
return;
|
|
}
|
|
|
|
shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
|
|
}
|
|
|
|
class DisplayportSetListener : public nsAPostRefreshObserver {
|
|
public:
|
|
DisplayportSetListener(nsIPresShell* aPresShell,
|
|
const uint64_t& aInputBlockId,
|
|
const nsTArray<ScrollableLayerGuid>& aTargets)
|
|
: mPresShell(aPresShell)
|
|
, mInputBlockId(aInputBlockId)
|
|
, mTargets(aTargets)
|
|
{
|
|
}
|
|
|
|
virtual ~DisplayportSetListener()
|
|
{
|
|
}
|
|
|
|
void DidRefresh() override {
|
|
if (!mPresShell) {
|
|
MOZ_ASSERT_UNREACHABLE("Post-refresh observer fired again after failed attempt at unregistering it");
|
|
return;
|
|
}
|
|
|
|
APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n", mInputBlockId);
|
|
SendLayersDependentApzcTargetConfirmation(mPresShell, mInputBlockId, Move(mTargets));
|
|
|
|
if (!mPresShell->RemovePostRefreshObserver(this)) {
|
|
MOZ_ASSERT_UNREACHABLE("Unable to unregister post-refresh observer! Leaking it instead of leaving garbage registered");
|
|
// Graceful handling, just in case...
|
|
mPresShell = nullptr;
|
|
return;
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
RefPtr<nsIPresShell> mPresShell;
|
|
uint64_t mInputBlockId;
|
|
nsTArray<ScrollableLayerGuid> mTargets;
|
|
};
|
|
|
|
// Sends a SetTarget notification for APZC, given one or more previous
|
|
// calls to PrepareForAPZCSetTargetNotification().
|
|
static void
|
|
SendSetTargetAPZCNotificationHelper(nsIWidget* aWidget,
|
|
nsIPresShell* aShell,
|
|
const uint64_t& aInputBlockId,
|
|
const nsTArray<ScrollableLayerGuid>& aTargets,
|
|
bool aWaitForRefresh)
|
|
{
|
|
bool waitForRefresh = aWaitForRefresh;
|
|
if (waitForRefresh) {
|
|
APZCCH_LOG("At least one target got a new displayport, need to wait for refresh\n");
|
|
waitForRefresh = aShell->AddPostRefreshObserver(
|
|
new DisplayportSetListener(aShell, aInputBlockId, Move(aTargets)));
|
|
}
|
|
if (!waitForRefresh) {
|
|
APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", aInputBlockId);
|
|
aWidget->SetConfirmedTargetAPZC(aInputBlockId, aTargets);
|
|
} else {
|
|
APZCCH_LOG("Successfully registered post-refresh observer\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
|
|
nsIDocument* aDocument,
|
|
const WidgetGUIEvent& aEvent,
|
|
const ScrollableLayerGuid& aGuid,
|
|
uint64_t aInputBlockId)
|
|
{
|
|
if (!aWidget || !aDocument) {
|
|
return;
|
|
}
|
|
if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) {
|
|
// We have already confirmed the target APZC for a previous event of this
|
|
// input block. If we activated a scroll frame for this input block,
|
|
// sending another target APZC confirmation would be harmful, as it might
|
|
// race the original confirmation (which needs to go through a layers
|
|
// transaction).
|
|
APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64 "\n", aInputBlockId);
|
|
return;
|
|
}
|
|
sLastTargetAPZCNotificationInputBlock = aInputBlockId;
|
|
if (nsIPresShell* shell = aDocument->GetShell()) {
|
|
if (nsIFrame* rootFrame = shell->GetRootFrame()) {
|
|
rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
|
|
|
|
bool waitForRefresh = false;
|
|
nsTArray<ScrollableLayerGuid> targets;
|
|
|
|
if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) {
|
|
for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) {
|
|
waitForRefresh |= PrepareForSetTargetAPZCNotification(aWidget, aGuid,
|
|
rootFrame, touchEvent->mTouches[i]->mRefPoint, &targets);
|
|
}
|
|
} else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
|
|
waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
|
|
rootFrame, wheelEvent->mRefPoint, &targets);
|
|
} else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
|
|
waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
|
|
rootFrame, mouseEvent->mRefPoint, &targets);
|
|
}
|
|
// TODO: Do other types of events need to be handled?
|
|
|
|
if (!targets.IsEmpty()) {
|
|
SendSetTargetAPZCNotificationHelper(
|
|
aWidget,
|
|
shell,
|
|
aInputBlockId,
|
|
Move(targets),
|
|
waitForRefresh);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
|
|
nsIWidget* aWidget,
|
|
nsIDocument* aDocument,
|
|
const WidgetTouchEvent& aEvent,
|
|
uint64_t aInputBlockId,
|
|
const SetAllowedTouchBehaviorCallback& aCallback)
|
|
{
|
|
if (nsIPresShell* shell = aDocument->GetShell()) {
|
|
if (nsIFrame* rootFrame = shell->GetRootFrame()) {
|
|
rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
|
|
|
|
nsTArray<TouchBehaviorFlags> flags;
|
|
for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
|
|
flags.AppendElement(
|
|
TouchActionHelper::GetAllowedTouchBehavior(aWidget,
|
|
rootFrame, aEvent.mTouches[i]->mRefPoint));
|
|
}
|
|
aCallback(aInputBlockId, Move(flags));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
|
|
{
|
|
nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
|
|
if (!targetContent) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIDocument> ownerDoc = targetContent->OwnerDoc();
|
|
if (!ownerDoc) {
|
|
return;
|
|
}
|
|
|
|
nsContentUtils::DispatchTrustedEvent(
|
|
ownerDoc, targetContent,
|
|
aEvent,
|
|
true, true);
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::NotifyFlushComplete(nsIPresShell* aShell)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
// In some cases, flushing the APZ state to the main thread doesn't actually
|
|
// trigger a flush and repaint (this is an intentional optimization - the stuff
|
|
// visible to the user is still correct). However, reftests update their
|
|
// snapshot based on invalidation events that are emitted during paints,
|
|
// so we ensure that we kick off a paint when an APZ flush is done. Note that
|
|
// only chrome/testing code can trigger this behaviour.
|
|
if (aShell && aShell->GetRootFrame()) {
|
|
aShell->GetRootFrame()->SchedulePaint();
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
|
MOZ_ASSERT(observerService);
|
|
observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
|
|
}
|
|
|
|
static int32_t sActiveSuppressDisplayport = 0;
|
|
static bool sDisplayPortSuppressionRespected = true;
|
|
|
|
void
|
|
APZCCallbackHelper::SuppressDisplayport(const bool& aEnabled,
|
|
const nsCOMPtr<nsIPresShell>& aShell)
|
|
{
|
|
if (aEnabled) {
|
|
sActiveSuppressDisplayport++;
|
|
} else {
|
|
bool isSuppressed = IsDisplayportSuppressed();
|
|
sActiveSuppressDisplayport--;
|
|
if (isSuppressed && !IsDisplayportSuppressed() &&
|
|
aShell && aShell->GetRootFrame()) {
|
|
// We unsuppressed the displayport, trigger a paint
|
|
aShell->GetRootFrame()->SchedulePaint();
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(sActiveSuppressDisplayport >= 0);
|
|
}
|
|
|
|
void
|
|
APZCCallbackHelper::RespectDisplayPortSuppression(bool aEnabled,
|
|
const nsCOMPtr<nsIPresShell>& aShell)
|
|
{
|
|
bool isSuppressed = IsDisplayportSuppressed();
|
|
sDisplayPortSuppressionRespected = aEnabled;
|
|
if (isSuppressed && !IsDisplayportSuppressed() &&
|
|
aShell && aShell->GetRootFrame()) {
|
|
// We unsuppressed the displayport, trigger a paint
|
|
aShell->GetRootFrame()->SchedulePaint();
|
|
}
|
|
}
|
|
|
|
bool
|
|
APZCCallbackHelper::IsDisplayportSuppressed()
|
|
{
|
|
return sDisplayPortSuppressionRespected
|
|
&& sActiveSuppressDisplayport > 0;
|
|
}
|
|
|
|
/* static */ bool
|
|
APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
|
|
{
|
|
return aFrame->IsProcessingAsyncScroll()
|
|
|| nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
|
|
|| aFrame->LastSmoothScrollOrigin();
|
|
}
|
|
|
|
/* static */ void
|
|
APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
|
|
LayoutDeviceCoord aSpanChange,
|
|
Modifiers aModifiers,
|
|
nsIWidget* aWidget)
|
|
{
|
|
EventMessage msg;
|
|
switch (aType) {
|
|
case PinchGestureInput::PINCHGESTURE_START:
|
|
msg = eMagnifyGestureStart;
|
|
break;
|
|
case PinchGestureInput::PINCHGESTURE_SCALE:
|
|
msg = eMagnifyGestureUpdate;
|
|
break;
|
|
case PinchGestureInput::PINCHGESTURE_END:
|
|
msg = eMagnifyGesture;
|
|
break;
|
|
case PinchGestureInput::PINCHGESTURE_SENTINEL:
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Invalid gesture type");
|
|
return;
|
|
}
|
|
|
|
WidgetSimpleGestureEvent event(true, msg, aWidget);
|
|
event.mDelta = aSpanChange;
|
|
event.mModifiers = aModifiers;
|
|
DispatchWidgetEvent(event);
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|
|
|