/* -*- 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); } } 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 GetPresShell(const nsIContent* aContent) { nsCOMPtr 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); } void APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics) { if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { return; } nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); if (!content) { return; } nsCOMPtr 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 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 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 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& aPresShell, const nsString& aType, const CSSPoint& aPoint, int32_t aButton, int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, unsigned short aInputSourceArg) { NS_ENSURE_TRUE(aPresShell, true); bool defaultPrevented = false; nsContentUtils::SendMouseEvent(aPresShell, aType, aPoint.x, aPoint.y, aButton, nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount, aModifiers, aIgnoreRootScrollFrame, 0, aInputSourceArg, false, &defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false); return defaultPrevented; } void APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint, Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget) { if (aWidget->Destroyed()) { return; } APZCCH_LOG("Dispatching single-tap component events to %s\n", Stringify(aPoint).c_str()); int time = 0; DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, aClickCount, aWidget); DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aClickCount, aWidget); DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, aWidget); } static dom::Element* GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame) { if (!aScrollableFrame) { return nullptr; } nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame(); if (!scrolledFrame) { return nullptr; } // |scrolledFrame| should at this point be the root content frame of the // nearest ancestor scrollable frame. The element corresponding to this // frame should be the one with the displayport set on it, so find that // element and return it. nsIContent* content = scrolledFrame->GetContent(); MOZ_ASSERT(content->IsElement()); // roc says this must be true return content->AsElement(); } static dom::Element* GetRootDocumentElementFor(nsIWidget* aWidget) { // This returns the root element that ChromeProcessController sets the // displayport on during initialization. if (nsView* view = nsView::GetViewFor(aWidget)) { if (nsIPresShell* shell = view->GetPresShell()) { MOZ_ASSERT(shell->GetDocument()); return shell->GetDocument()->GetDocumentElement(); } } return nullptr; } static nsIFrame* UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame) { #if defined(MOZ_WIDGET_ANDROID) // Re-target so that the hit test is performed relative to the frame for the // Root Content Document instead of the Root Document which are different in // Android. See bug 1229752 comment 16 for an explanation of why this is necessary. if (nsIDocument* doc = aRootFrame->PresContext()->PresShell()->GetTouchEventTargetDocument()) { if (nsIPresShell* shell = doc->GetShell()) { if (nsIFrame* frame = shell->GetRootFrame()) { return frame; } } } #endif return aRootFrame; } // Determine the scrollable target frame for the given point and add it to // the target list. If the frame doesn't have a displayport, set one. // Return whether or not a displayport was set. static bool PrepareForSetTargetAPZCNotification(nsIWidget* aWidget, const ScrollableLayerGuid& aGuid, nsIFrame* aRootFrame, const LayoutDeviceIntPoint& aRefPoint, nsTArray* 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 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& 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& 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 mPresShell; uint64_t mInputBlockId; nsTArray 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& 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 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 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 targetContent = nsLayoutUtils::FindContentFor(aScrollId); if (!targetContent) { return; } nsCOMPtr 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 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& 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& 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