bool ClientTiledPaintedLayer::IsScrollingOnCompositor(const FrameMetrics& aParentMetrics) { CompositorChild* compositor = nullptr; if (Manager() && Manager()->AsClientLayerManager()) { compositor = Manager()->AsClientLayerManager()->GetCompositorChild(); } if (!compositor) { return false; } FrameMetrics compositorMetrics; if (!compositor->LookupCompositorFrameMetrics(aParentMetrics.GetScrollId(), compositorMetrics)) { return false; } // 1 is a tad high for a fuzzy equals epsilon however if our scroll delta // is so small then we have nothing to gain from using paint heuristics. float COORDINATE_EPSILON = 1.f; return !FuzzyEqualsAdditive(compositorMetrics.GetScrollOffset().x, aParentMetrics.GetScrollOffset().x, COORDINATE_EPSILON) || !FuzzyEqualsAdditive(compositorMetrics.GetScrollOffset().y, aParentMetrics.GetScrollOffset().y, COORDINATE_EPSILON); }
void APZCCallbackHelper::UpdateCallbackTransform(const FrameMetrics& aApzcMetrics, const FrameMetrics& aActualMetrics) { nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aApzcMetrics.GetScrollId()); if (!content) { return; } CSSPoint scrollDelta = aApzcMetrics.GetScrollOffset() - aActualMetrics.GetScrollOffset(); content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta), nsINode::DeleteProperty<CSSPoint>); }
bool WheelScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { TimeStamp now = AsyncPanZoomController::GetFrameTime(); CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); // If the animation is finished, make sure the final position is correct by // using one last displacement. Otherwise, compute the delta via the timing // function as normal. bool finished = IsFinished(now); nsPoint sampledDest = finished ? mDestination : PositionAt(now); ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; // Note: we ignore overscroll for wheel animations. ParentLayerPoint adjustedOffset, overscroll; mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x); mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y, !aFrameMetrics.AllowVerticalScrollWithWheel()); // If we expected to scroll, but there's no more scroll range on either axis, // then end the animation early. Note that the initial displacement could be 0 // if the compositor ran very quickly (<1ms) after the animation was created. // When that happens we want to make sure the animation continues. if (!IsZero(displacement) && IsZero(adjustedOffset)) { // Nothing more to do - end the animation. return false; } aFrameMetrics.ScrollBy(adjustedOffset / zoom); return !finished; }
bool WheelScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { TimeStamp now = AsyncPanZoomController::GetFrameTime(); CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); // If the animation is finished, make sure the final position is correct by // using one last displacement. Otherwise, compute the delta via the timing // function as normal. bool finished = IsFinished(now); nsPoint sampledDest = finished ? mDestination : PositionAt(now); ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; // Note: we ignore overscroll for wheel animations. ParentLayerPoint adjustedOffset, overscroll; mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x); mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y, !aFrameMetrics.AllowVerticalScrollWithWheel()); if (IsZero(adjustedOffset)) { // Nothing more to do - end the animation. return false; } aFrameMetrics.ScrollBy(adjustedOffset / zoom); return !finished; }
bool ClientLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, FrameMetrics& aMetrics, bool aDrawingCritical) { #ifdef MOZ_WIDGET_ANDROID MOZ_ASSERT(aMetrics.IsScrollable()); // This is derived from the code in // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree. CSSToLayerScale paintScale = aMetrics.LayersPixelsPerCSSPixel().ToScaleFactor(); const CSSRect& metricsDisplayPort = (aDrawingCritical && !aMetrics.GetCriticalDisplayPort().IsEmpty()) ? aMetrics.GetCriticalDisplayPort() : aMetrics.GetDisplayPort(); LayerRect displayPort = (metricsDisplayPort + aMetrics.GetScrollOffset()) * paintScale; ParentLayerPoint scrollOffset; CSSToParentLayerScale zoom; bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback( aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical, scrollOffset, zoom); aMetrics.SetScrollOffset(scrollOffset / zoom); aMetrics.SetZoom(CSSToParentLayerScale2D(zoom)); return ret; #else return false; #endif }
TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { FrameMetrics originalMetrics = GetPinchableFrameMetrics(); apzc->SetFrameMetrics(originalMetrics); MakeApzcWaitForMainThread(); MakeApzcZoomable(); int touchInputId = 0; uint64_t blockId = 0; PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId); // Send the prevent-default notification for the touch block apzc->ContentReceivedInputBlock(blockId, true); // verify the metrics didn't change (i.e. the pinch was ignored) FrameMetrics fm = apzc->GetFrameMetrics(); EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x); EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y); apzc->AssertStateIsReset(); }
/** * 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->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer()); } bool scrollUpdated = false; CSSPoint apzScrollOffset = aMetrics.GetScrollOffset(); CSSPoint actualScrollOffset = ScrollFrameTo(sf, apzScrollOffset, 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. if (aContent) { CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset; aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta), nsINode::DeleteProperty<CSSPoint>); } }
void APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent, FrameMetrics& aMetrics) { // Precondition checks MOZ_ASSERT(aContent); MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins()); if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { return; } nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent); if (!utils) { return; } // We currently do not support zooming arbitrary subframes. They can only // be scrolled, so here we only have to set the scroll position and displayport. nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); bool scrollUpdated = false; CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent); if (element) { if (scrollUpdated) { AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset); } else { RecenterDisplayPort(aMetrics); } gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : gfx::IntSize(0, 0); LayerMargin margins = aMetrics.GetDisplayPortMargins(); utils->SetDisplayPortMarginsForElement(margins.left, margins.top, margins.right, margins.bottom, alignment.width, alignment.height, element, 0); CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels(); nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base); } aMetrics.SetScrollOffset(actualScrollOffset); }
bool GenericScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { TimeStamp now = mApzc.GetFrameTime(); CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); // If the animation is finished, make sure the final position is correct by // using one last displacement. Otherwise, compute the delta via the timing // function as normal. bool finished = mAnimationPhysics->IsFinished(now); nsPoint sampledDest = mAnimationPhysics->PositionAt(now); ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; if (finished) { mApzc.mX.SetVelocity(0); mApzc.mY.SetVelocity(0); } else if (!IsZero(displacement)) { // Convert velocity from AppUnits/Seconds to ParentLayerCoords/Milliseconds nsSize velocity = mAnimationPhysics->VelocityAt(now); ParentLayerPoint velocityPL = CSSPoint::FromAppUnits(nsPoint(velocity.width, velocity.height)) * zoom; mApzc.mX.SetVelocity(velocityPL.x / 1000.0); mApzc.mY.SetVelocity(velocityPL.y / 1000.0); } // Note: we ignore overscroll for generic animations. ParentLayerPoint adjustedOffset, overscroll; mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x, mDirectionForcedToOverscroll == Some(ScrollDirection::eHorizontal)); mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y, mDirectionForcedToOverscroll == Some(ScrollDirection::eVertical)); // If we expected to scroll, but there's no more scroll range on either axis, // then end the animation early. Note that the initial displacement could be 0 // if the compositor ran very quickly (<1ms) after the animation was created. // When that happens we want to make sure the animation continues. if (!IsZero(displacement) && IsZero(adjustedOffset)) { // Nothing more to do - end the animation. return false; } aFrameMetrics.ScrollBy(adjustedOffset / zoom); return !finished; }
TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { // set up APZ apzc->SetFrameMetrics(GetPinchableFrameMetrics()); MakeApzcUnzoomable(); nsEventStatus statuses[3]; // scalebegin, scale, scaleend PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses); FrameMetrics fm = apzc->GetFrameMetrics(); // It starts from (300, 300), then moves the focus point from (250, 350) to // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which // causes the scroll offset to change by half of that (25, 25) pixels. EXPECT_EQ(325, fm.GetScrollOffset().x); EXPECT_EQ(325, fm.GetScrollOffset().y); EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale); }
void AppendToString(std::stringstream& aStream, const FrameMetrics& m, const char* pfx, const char* sfx, bool detailed) { aStream << pfx; AppendToString(aStream, m.GetCompositionBounds(), "{ [cb="); AppendToString(aStream, m.GetScrollableRect(), "] [sr="); AppendToString(aStream, m.GetScrollOffset(), "] [s="); if (m.GetDoSmoothScroll()) { AppendToString(aStream, m.GetSmoothScrollOffset(), "] [ss="); } AppendToString(aStream, m.GetDisplayPort(), "] [dp="); AppendToString(aStream, m.GetCriticalDisplayPort(), "] [cdp="); AppendToString(aStream, m.GetBackgroundColor(), "] [color="); if (!detailed) { AppendToString(aStream, m.GetScrollId(), "] [scrollId="); if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) { AppendToString(aStream, m.GetScrollParentId(), "] [scrollParent="); } if (m.IsRootContent()) { aStream << "] [rcd"; } if (m.HasClipRect()) { AppendToString(aStream, m.ClipRect(), "] [clip="); } AppendToString(aStream, m.GetZoom(), "] [z=", "] }"); } else { AppendToString(aStream, m.GetDisplayPortMargins(), " [dpm="); aStream << nsPrintfCString("] um=%d", m.GetUseDisplayPortMargins()).get(); AppendToString(aStream, m.GetRootCompositionSize(), "] [rcs="); AppendToString(aStream, m.GetViewport(), "] [v="); aStream << nsPrintfCString("] [z=(ld=%.3f r=%.3f", m.GetDevPixelsPerCSSPixel().scale, m.GetPresShellResolution()).get(); AppendToString(aStream, m.GetCumulativeResolution(), " cr="); AppendToString(aStream, m.GetZoom(), " z="); AppendToString(aStream, m.GetExtraResolution(), " er="); aStream << nsPrintfCString(")] [u=(%d %d %lu)", m.GetScrollOffsetUpdated(), m.GetDoSmoothScroll(), m.GetScrollGeneration()).get(); AppendToString(aStream, m.GetScrollParentId(), "] [p="); aStream << nsPrintfCString("] [i=(%ld %lld %d)] }", m.GetPresShellId(), m.GetScrollId(), m.IsRootContent()).get(); } aStream << sfx; }
bool GenericScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { TimeStamp now = mApzc.GetFrameTime(); CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); // If the animation is finished, make sure the final position is correct by // using one last displacement. Otherwise, compute the delta via the timing // function as normal. bool finished = IsFinished(now); nsPoint sampledDest = finished ? mDestination : PositionAt(now); ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; if (finished) { mApzc.mX.SetVelocity(0); mApzc.mY.SetVelocity(0); } else if (!IsZero(displacement)) { // Velocity is measured in ParentLayerCoords / Milliseconds float xVelocity = displacement.x / aDelta.ToMilliseconds(); float yVelocity = displacement.y / aDelta.ToMilliseconds(); mApzc.mX.SetVelocity(xVelocity); mApzc.mY.SetVelocity(yVelocity); } // Note: we ignore overscroll for generic animations. ParentLayerPoint adjustedOffset, overscroll; mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x); mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y, mForceVerticalOverscroll); // If we expected to scroll, but there's no more scroll range on either axis, // then end the animation early. Note that the initial displacement could be 0 // if the compositor ran very quickly (<1ms) after the animation was created. // When that happens we want to make sure the animation continues. if (!IsZero(displacement) && IsZero(adjustedOffset)) { // Nothing more to do - end the animation. return false; } aFrameMetrics.ScrollBy(adjustedOffset / zoom); return !finished; }
void AppendToString(std::stringstream& aStream, const FrameMetrics& m, const char* pfx, const char* sfx, bool detailed) { aStream << pfx; AppendToString(aStream, m.mCompositionBounds, "{ cb="); AppendToString(aStream, m.mScrollableRect, " sr="); AppendToString(aStream, m.GetScrollOffset(), " s="); if (m.GetDoSmoothScroll()) { AppendToString(aStream, m.GetSmoothScrollOffset(), " ss="); } AppendToString(aStream, m.mDisplayPort, " dp="); AppendToString(aStream, m.mCriticalDisplayPort, " cdp="); AppendToString(aStream, m.GetBackgroundColor(), " color="); if (!detailed) { AppendToString(aStream, m.GetScrollId(), " scrollId="); if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) { AppendToString(aStream, m.GetScrollParentId(), " scrollParent="); } aStream << nsPrintfCString(" z=%.3f }", m.GetZoom().scale).get(); } else { AppendToString(aStream, m.GetDisplayPortMargins(), " dpm="); aStream << nsPrintfCString(" um=%d", m.GetUseDisplayPortMargins()).get(); AppendToString(aStream, m.GetRootCompositionSize(), " rcs="); AppendToString(aStream, m.GetViewport(), " v="); aStream << nsPrintfCString(" z=(ld=%.3f r=%.3f cr=%.3f z=%.3f er=%.3f)", m.mDevPixelsPerCSSPixel.scale, m.mPresShellResolution, m.mCumulativeResolution.scale, m.GetZoom().scale, m.GetExtraResolution().scale).get(); aStream << nsPrintfCString(" u=(%d %d %lu)", m.GetScrollOffsetUpdated(), m.GetDoSmoothScroll(), m.GetScrollGeneration()).get(); AppendToString(aStream, m.GetScrollParentId(), " p="); aStream << nsPrintfCString(" i=(%ld %lld) }", m.GetPresShellId(), m.GetScrollId()).get(); } aStream << sfx; }
TEST_F(APZCBasicTester, Overzoom) { // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 FrameMetrics fm; fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); fm.SetScrollOffset(CSSPoint(10, 0)); fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0)); fm.SetIsRootContent(true); apzc->SetFrameMetrics(fm); MakeApzcZoomable(); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); fm = apzc->GetFrameMetrics(); EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); // bug 936721 - PGO builds introduce rounding error so // use a fuzzy match instead EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5); EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5); }
/** * 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()); bool scrollUpdated = false; CSSPoint apzScrollOffset = aMetrics.GetScrollOffset(); CSSPoint actualScrollOffset = ScrollFrameTo(sf, apzScrollOffset, scrollUpdated); if (scrollUpdated) { // Correct the display port due to the difference between mScrollOffset and the // actual scroll offset. 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. if (aContent) { CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset; aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta), nsINode::DeleteProperty<CSSPoint>); } }
bool SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics, const FrameMetrics& aCompositorMetrics) { return !aContentMetrics.mDisplayPort.Contains(CSSRect(aCompositorMetrics.CalculateCompositedRectInCssPixels()) - aCompositorMetrics.GetScrollOffset()); }
void APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils, FrameMetrics& aMetrics) { // Precondition checks MOZ_ASSERT(aUtils); MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins()); if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { return; } // Set the scroll port size, which determines the scroll range. For example if // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent // overscroll). Note that if the content here was zoomed to 2x, the document would // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum // scroll range would be 900. Therefore this calculation depends on the zoom applied // to the content relative to the container. CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels(); aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height); // Scroll the window to the desired spot nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); bool scrollUpdated = false; CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); if (scrollUpdated) { // Correct the display port due to the difference between mScrollOffset and the // actual scroll offset. 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); // The pres shell resolution is updated by the the async zoom since the // last paint. float presShellResolution = aMetrics.GetPresShellResolution() * aMetrics.GetAsyncZoom().scale; aUtils->SetResolutionAndScaleTo(presShellResolution, presShellResolution); // Finally, we set the displayport. nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); if (!content) { return; } nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content); if (!element) { return; } ScreenMargin margins = aMetrics.GetDisplayPortMargins(); aUtils->SetDisplayPortMarginsForElement(margins.left, margins.top, margins.right, margins.bottom, element, 0); CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels(); nsRect base(0, 0, baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base); }
// Recursively create a new array of scrollables, preserving any scrollables // that are still in the layer tree. // // aXScale and aYScale are used to calculate any values that need to be in // chrome-document CSS pixels and aren't part of the rendering loop, such as // the initial scroll offset for a new view. static void BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews, nsFrameLoader* aFrameLoader, Layer* aLayer, float aXScale = 1, float aYScale = 1, float aAccConfigXScale = 1, float aAccConfigYScale = 1) { ContainerLayer* container = aLayer->AsContainerLayer(); if (!container) return; const FrameMetrics metrics = container->GetFrameMetrics(); const ViewID scrollId = metrics.GetScrollId(); gfx3DMatrix transform; To3DMatrix(aLayer->GetTransform(), transform); aXScale *= GetXScale(transform); aYScale *= GetYScale(transform); if (metrics.IsScrollable()) { nscoord auPerDevPixel = aFrameLoader->GetPrimaryFrameOfOwningContent() ->PresContext()->AppUnitsPerDevPixel(); nscoord auPerCSSPixel = auPerDevPixel * metrics.mDevPixelsPerCSSPixel.scale; nsContentView* view = FindViewForId(oldContentViews, scrollId); if (view) { // View already exists. Be sure to propagate scales for any values // that need to be calculated something in chrome-doc CSS pixels. ViewConfig config = view->GetViewConfig(); aXScale *= config.mXScale; aYScale *= config.mYScale; view->mFrameLoader = aFrameLoader; // If scale has changed, then we should update // current scroll offset to new scaled value if (aAccConfigXScale != view->mParentScaleX || aAccConfigYScale != view->mParentScaleY) { float xscroll = 0, yscroll = 0; view->GetScrollX(&xscroll); view->GetScrollY(&yscroll); xscroll = xscroll * (aAccConfigXScale / view->mParentScaleX); yscroll = yscroll * (aAccConfigYScale / view->mParentScaleY); view->ScrollTo(xscroll, yscroll); view->mParentScaleX = aAccConfigXScale; view->mParentScaleY = aAccConfigYScale; } // Collect only config scale values for scroll compensation aAccConfigXScale *= config.mXScale; aAccConfigYScale *= config.mYScale; } else { // View doesn't exist, so generate one. We start the view scroll offset at // the same position as the framemetric's scroll offset from the layer. // The default scale is 1, so no need to propagate scale down. ViewConfig config; config.mScrollOffset = nsPoint( NSIntPixelsToAppUnits(metrics.GetScrollOffset().x, auPerCSSPixel) * aXScale, NSIntPixelsToAppUnits(metrics.GetScrollOffset().y, auPerCSSPixel) * aYScale); view = new nsContentView(aFrameLoader, scrollId, metrics.mIsRoot, config); view->mParentScaleX = aAccConfigXScale; view->mParentScaleY = aAccConfigYScale; } // I don't know what units mViewportSize is in, hence use ToUnknownRect // here to mark the current frontier in type info propagation gfx::Rect viewport = metrics.mViewport.ToUnknownRect(); view->mViewportSize = nsSize( NSIntPixelsToAppUnits(viewport.width, auPerDevPixel) * aXScale, NSIntPixelsToAppUnits(viewport.height, auPerDevPixel) * aYScale); view->mContentSize = nsSize( NSFloatPixelsToAppUnits(metrics.mScrollableRect.width, auPerCSSPixel) * aXScale, NSFloatPixelsToAppUnits(metrics.mScrollableRect.height, auPerCSSPixel) * aYScale); newContentViews[scrollId] = view; } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { BuildViewMap(oldContentViews, newContentViews, aFrameLoader, child, aXScale, aYScale, aAccConfigXScale, aAccConfigYScale); } }
CSSRect CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument, const CSSPoint& aPoint) { // Ensure the layout information we get is up-to-date. aRootContentDocument->FlushPendingNotifications(FlushType::Layout); // An empty rect as return value is interpreted as "zoom out". const CSSRect zoomOut; nsCOMPtr<nsIPresShell> shell = aRootContentDocument->GetShell(); if (!shell) { return zoomOut; } nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable(); if (!rootScrollFrame) { return zoomOut; } nsCOMPtr<dom::Element> element = ElementFromPoint(shell, aPoint); if (!element) { return zoomOut; } while (element && !ShouldZoomToElement(element)) { element = element->GetParentElement(); } if (!element) { return zoomOut; } FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame); CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels()); const CSSCoord margin = 15; CSSRect rect = nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame); // If the element is taller than the visible area of the page scale // the height of the |rect| so that it has the same aspect ratio as // the root frame. The clipped |rect| is centered on the y value of // the touch point. This allows tall narrow elements to be zoomed. if (!rect.IsEmpty() && compositedArea.width > 0.0f) { const float widthRatio = rect.width / compositedArea.width; float targetHeight = compositedArea.height * widthRatio; if (widthRatio < 0.9 && targetHeight < rect.height) { const CSSPoint scrollPoint = CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition()); float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f); if ((newY + targetHeight) > (rect.y + rect.height)) { rect.y += rect.height - targetHeight; } else if (newY > rect.y) { rect.y = newY; } rect.height = targetHeight; } } rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin), rect.y, rect.width + 2 * margin, rect.height); // Constrict the rect to the screen's right edge rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x); // If the rect is already taking up most of the visible area and is // stretching the width of the page, then we want to zoom out instead. if (IsRectZoomedIn(rect, compositedArea)) { return zoomOut; } CSSRect rounded(rect); rounded.Round(); // If the block we're zooming to is really tall, and the user double-tapped // more than a screenful of height from the top of it, then adjust the // y-coordinate so that we center the actual point the user double-tapped // upon. This prevents flying to the top of the page when double-tapping // to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to // compensate for 'rect' including horizontal margins but not vertical ones. CSSCoord cssTapY = metrics.GetScrollOffset().y + aPoint.y; if ((rect.height > rounded.height) && (cssTapY > rounded.y + (rounded.height * 1.2))) { rounded.y = cssTapY - (rounded.height / 2); } return rounded; }
bool SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics( ContainerLayer* aLayer, bool aHasPendingNewThebesContent, bool aLowPrecision, ParentLayerRect& aCompositionBounds, CSSToParentLayerScale& aZoom) { MOZ_ASSERT(aLayer); CompositorChild* compositor = CompositorChild::Get(); if (!compositor) { FindFallbackContentFrameMetrics(aLayer, aCompositionBounds, aZoom); return false; } const FrameMetrics& contentMetrics = aLayer->GetFrameMetrics(); FrameMetrics compositorMetrics; if (!compositor->LookupCompositorFrameMetrics(contentMetrics.mScrollId, compositorMetrics)) { FindFallbackContentFrameMetrics(aLayer, aCompositionBounds, aZoom); return false; } aCompositionBounds = ParentLayerRect(compositorMetrics.mCompositionBounds); aZoom = compositorMetrics.GetZoomToParent(); // Reset the checkerboard risk flag when switching to low precision // rendering. if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) { // Skip low precision rendering until we're at risk of checkerboarding. if (!mProgressiveUpdateWasInDanger) { return true; } mProgressiveUpdateWasInDanger = false; } mLastProgressiveUpdateWasLowPrecision = aLowPrecision; // Always abort updates if the resolution has changed. There's no use // in drawing at the incorrect resolution. if (!FuzzyEquals(compositorMetrics.GetZoom().scale, contentMetrics.GetZoom().scale)) { return true; } // Never abort drawing if we can't be sure we've sent a more recent // display-port. If we abort updating when we shouldn't, we can end up // with blank regions on the screen and we open up the risk of entering // an endless updating cycle. if (fabsf(contentMetrics.GetScrollOffset().x - compositorMetrics.GetScrollOffset().x) <= 2 && fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 && fabsf(contentMetrics.mDisplayPort.x - compositorMetrics.mDisplayPort.x) <= 2 && fabsf(contentMetrics.mDisplayPort.y - compositorMetrics.mDisplayPort.y) <= 2 && fabsf(contentMetrics.mDisplayPort.width - compositorMetrics.mDisplayPort.width) <= 2 && fabsf(contentMetrics.mDisplayPort.height - compositorMetrics.mDisplayPort.height)) { return false; } // When not a low precision pass and the page is in danger of checker boarding // abort update. if (!aLowPrecision && !mProgressiveUpdateWasInDanger) { if (AboutToCheckerboard(contentMetrics, compositorMetrics)) { mProgressiveUpdateWasInDanger = true; return true; } } // Abort drawing stale low-precision content if there's a more recent // display-port in the pipeline. if (aLowPrecision && !aHasPendingNewThebesContent) { return true; } return false; }
TEST_F(APZCGestureDetectorTester, Pan_With_Tap) { SCOPED_GFX_PREF(TouchActionEnabled, bool, false); FrameMetrics originalMetrics = GetPinchableFrameMetrics(); apzc->SetFrameMetrics(originalMetrics); // Making the APZC zoomable isn't really needed for the correct operation of // this test, but it could help catch regressions where we accidentally enter // a pinch state. MakeApzcZoomable(); // Test parameters int touchX = 250; int touchY = 300; int panDistance = 20; int firstFingerId = 0; int secondFingerId = firstFingerId + 1; // Put finger down MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Start a pan, break through the threshold touchY += 40; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Do an actual pan for a bit touchY += panDistance; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Put a second finger down mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Lift the second finger mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Bust through the threshold again touchY += 40; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Do some more actual panning touchY += panDistance; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Lift the first finger mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, nullptr); // Verify that we scrolled FrameMetrics finalMetrics = apzc->GetFrameMetrics(); float zoom = finalMetrics.GetZoom().ToScaleFactor().scale; EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y); // Clear out any remaining fling animation and pending tasks apzc->AdvanceAnimationsUntilEnd(); while (mcc->RunThroughDelayedTasks()); apzc->AssertStateIsReset(); }
void APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils, FrameMetrics& aMetrics) { // Precondition checks MOZ_ASSERT(aUtils); MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins()); if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { return; } // Set the scroll port size, which determines the scroll range. For example if // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent // overscroll). Note that if the content here was zoomed to 2x, the document would // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum // scroll range would be 900. Therefore this calculation depends on the zoom applied // to the content relative to the container. CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels(); aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height); // Scroll the window to the desired spot nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); bool scrollUpdated = false; CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); if (scrollUpdated) { // Correct the display port due to the difference between mScrollOffset and the // actual scroll offset. 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); // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this // frame. This scale includes all of the (cumulative) resolutions set on the presShells // from the root down to this frame. However, when setting the resolution, we only // want the piece of the resolution that corresponds to this presShell, rather than // all of the cumulative stuff, so we need to divide out the parent resolutions. // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to // take the async zoom calculated by the APZC and tell gecko about it (turning it into // a "sync" zoom) which will update the resolution at which the layer is painted. ParentLayerToLayerScale presShellResolution = aMetrics.GetZoom() / aMetrics.mDevPixelsPerCSSPixel / aMetrics.GetParentResolution() * ScreenToLayerScale(1.0f); aUtils->SetResolution(presShellResolution.scale, presShellResolution.scale); // Finally, we set the displayport. nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); if (!content) { return; } nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content); if (!element) { return; } gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : gfx::IntSize(0, 0); LayerMargin margins = aMetrics.GetDisplayPortMargins(); aUtils->SetDisplayPortMarginsForElement(margins.left, margins.top, margins.right, margins.bottom, alignment.width, alignment.height, element, 0); CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels(); nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base); }
void DoPinchTest(bool aShouldTriggerPinch, nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr) { apzc->SetFrameMetrics(GetPinchableFrameMetrics()); MakeApzcZoomable(); if (aShouldTriggerPinch) { // One repaint request for each gesture. EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); } else { EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); } int touchInputId = 0; if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch); } FrameMetrics fm = apzc->GetFrameMetrics(); if (aShouldTriggerPinch) { // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80 EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(305, fm.GetScrollOffset().x); EXPECT_EQ(310, fm.GetScrollOffset().y); } else { // The frame metrics should stay the same since touch-action:none makes // apzc ignore pinch gestures. EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(300, fm.GetScrollOffset().x); EXPECT_EQ(300, fm.GetScrollOffset().y); } // part 2 of the test, move to the top-right corner of the page and pinch and // make sure we stay in the correct spot fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); fm.SetScrollOffset(CSSPoint(930, 5)); apzc->SetFrameMetrics(fm); // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch); } fm = apzc->GetFrameMetrics(); if (aShouldTriggerPinch) { // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200 EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(880, fm.GetScrollOffset().x); EXPECT_EQ(0, fm.GetScrollOffset().y); } else { EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(930, fm.GetScrollOffset().x); EXPECT_EQ(5, fm.GetScrollOffset().y); } }
CSSRect CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument, const CSSPoint& aPoint) { // Ensure the layout information we get is up-to-date. aRootContentDocument->FlushPendingNotifications(Flush_Layout); // An empty rect as return value is interpreted as "zoom out". const CSSRect zoomOut; nsCOMPtr<nsIPresShell> shell = aRootContentDocument->GetShell(); if (!shell) { return zoomOut; } nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable(); if (!rootScrollFrame) { return zoomOut; } nsCOMPtr<dom::Element> element = ElementFromPoint(shell, aPoint); if (!element) { return zoomOut; } while (element && !ShouldZoomToElement(element)) { element = element->GetParentElement(); } if (!element) { return zoomOut; } FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame); CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels()); const CSSCoord margin = 15; CSSRect rect = GetBoundingContentRect(shell, element, rootScrollFrame); rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin), rect.y, rect.width + 2 * margin, rect.height); // Constrict the rect to the screen's right edge rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x); // If the rect is already taking up most of the visible area and is // stretching the width of the page, then we want to zoom out instead. if (IsRectZoomedIn(rect, compositedArea)) { return zoomOut; } CSSRect rounded(rect); rounded.Round(); // If the block we're zooming to is really tall, and the user double-tapped // more than a screenful of height from the top of it, then adjust the // y-coordinate so that we center the actual point the user double-tapped // upon. This prevents flying to the top of the page when double-tapping // to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to // compensate for 'rect' including horizontal margins but not vertical ones. CSSCoord cssTapY = metrics.GetScrollOffset().y + aPoint.y; if ((rect.height > rounded.height) && (cssTapY > rounded.y + (rounded.height * 1.2))) { rounded.y = cssTapY - (rounded.height / 2); } return rounded; }
TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { SCOPED_GFX_PREF(TouchActionEnabled, bool, false); FrameMetrics originalMetrics = GetPinchableFrameMetrics(); apzc->SetFrameMetrics(originalMetrics); MakeApzcZoomable(); // Test parameters float zoomAmount = 1.25; float pinchLength = 100.0; float pinchLengthScaled = pinchLength * zoomAmount; int focusX = 250; int focusY = 300; int panDistance = 20; int firstFingerId = 0; int secondFingerId = firstFingerId + 1; // Put fingers down MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY)); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // Spread fingers out to enter the pinch state mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY)); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // Do the actual pinch of 1.25x mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // Verify that the zoom changed, just to make sure our code above did what it // was supposed to. FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale; EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom); // Now we lift one finger... mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // ... and pan with the remaining finger. This pan just breaks through the // distance threshold. focusY += 40; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // This one does an actual pan of 20 pixels focusY += panDistance; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // Lift the remaining finger mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); apzc->ReceiveInputEvent(mti, nullptr); // Verify that we scrolled FrameMetrics finalMetrics = apzc->GetFrameMetrics(); EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y); // Clear out any remaining fling animation and pending tasks apzc->AdvanceAnimationsUntilEnd(); while (mcc->RunThroughDelayedTasks()); apzc->AssertStateIsReset(); }