void RootFrameViewport::restoreToAnchor(const ScrollOffset& targetOffset) { // Clamp the scroll offset of each viewport now so that we force any invalid // offsets to become valid so we can compute the correct deltas. visualViewport().setScrollOffset(visualViewport().getScrollOffset(), ProgrammaticScroll); layoutViewport().setScrollOffset(layoutViewport().getScrollOffset(), ProgrammaticScroll); ScrollOffset delta = targetOffset - getScrollOffset(); visualViewport().setScrollOffset(visualViewport().getScrollOffset() + delta, ProgrammaticScroll); delta = targetOffset - getScrollOffset(); // Since the main thread FrameView has integer scroll offsets, scroll it to // the next pixel and then we'll scroll the visual viewport again to // compensate for the sub-pixel offset. We need this "overscroll" to ensure // the pixel of which we want to be partially in appears fully inside the // FrameView since the VisualViewport is bounded by the FrameView. IntSize layoutDelta = IntSize( delta.width() < 0 ? floor(delta.width()) : ceil(delta.width()), delta.height() < 0 ? floor(delta.height()) : ceil(delta.height())); layoutViewport().setScrollOffset( ScrollOffset(layoutViewport().scrollOffsetInt() + layoutDelta), ProgrammaticScroll); delta = targetOffset - getScrollOffset(); visualViewport().setScrollOffset(visualViewport().getScrollOffset() + delta, ProgrammaticScroll); }
ScrollOffset clampedScrollOffset(const ScrollOffset& offset) { ScrollOffset minOffset = minimumScrollOffset(); ScrollOffset maxOffset = maximumScrollOffset(); float width = std::min(std::max(offset.width(), minOffset.width()), maxOffset.width()); float height = std::min(std::max(offset.height(), minOffset.height()), maxOffset.height()); return ScrollOffset(width, height); }
ScrollResult ScrollAnimatorBase::userScroll(ScrollGranularity, const ScrollOffset& delta) { ScrollOffset consumedDelta = computeDeltaToConsume(delta); ScrollOffset newPos = m_currentOffset + consumedDelta; if (m_currentOffset == newPos) return ScrollResult(false, false, delta.width(), delta.height()); m_currentOffset = newPos; notifyOffsetChanged(); return ScrollResult(consumedDelta.width(), consumedDelta.height(), delta.width() - consumedDelta.width(), delta.height() - consumedDelta.height()); }
TEST(ScrollAnimatorTest, MainThreadAnimationTargetAdjustment) { MockScrollableArea* scrollableArea = MockScrollableArea::create( true, ScrollOffset(-100, -100), ScrollOffset(1000, 1000)); ScrollAnimator* animator = new ScrollAnimator(scrollableArea, getMockedTime); scrollableArea->setScrollAnimator(animator); // Twice from tickAnimation, once from reset, and twice from // adjustAnimationAndSetScrollOffset. EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(5); // One from call to userScroll and one from updateCompositorAnimations. EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(2); EXPECT_CALL(*scrollableArea, scheduleAnimation()) .Times(AtLeast(1)) .WillRepeatedly(Return(true)); // Idle EXPECT_FALSE(animator->hasAnimationThatRequiresService()); EXPECT_EQ(ScrollOffset(), animator->currentOffset()); // WaitingToSendToCompositor animator->userScroll(ScrollByLine, ScrollOffset(100, 100)); // RunningOnMainThread gMockedTime += 0.05; animator->updateCompositorAnimations(); animator->tickAnimation(getMockedTime()); ScrollOffset offset = animator->currentOffset(); EXPECT_EQ(ScrollOffset(100, 100), animator->desiredTargetOffset()); EXPECT_GT(offset.width(), 0); EXPECT_GT(offset.height(), 0); // Adjustment ScrollOffset newOffset = offset + ScrollOffset(10, -10); animator->adjustAnimationAndSetScrollOffset(newOffset, AnchoringScroll); EXPECT_EQ(ScrollOffset(110, 90), animator->desiredTargetOffset()); // Adjusting after finished animation should do nothing. gMockedTime += 1.0; animator->updateCompositorAnimations(); animator->tickAnimation(getMockedTime()); EXPECT_EQ( animator->runStateForTesting(), ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup); newOffset = animator->currentOffset() + ScrollOffset(10, -10); animator->adjustAnimationAndSetScrollOffset(newOffset, AnchoringScroll); EXPECT_EQ( animator->runStateForTesting(), ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup); EXPECT_EQ(ScrollOffset(110, 90), animator->desiredTargetOffset()); reset(*animator); // Forced GC in order to finalize objects depending on the mock object. ThreadState::current()->collectAllGarbage(); }
void Scrollbar::moveThumb(int pos, bool draggingDocument) { if (!m_scrollableArea) return; int delta = pos - m_pressedPos; if (draggingDocument) { if (m_draggingDocument) delta = pos - m_documentDragPos; m_draggingDocument = true; ScrollOffset currentPosition = m_scrollableArea->scrollAnimator().currentOffset(); float destinationPosition = (m_orientation == HorizontalScrollbar ? currentPosition.width() : currentPosition.height()) + delta; destinationPosition = m_scrollableArea->clampScrollOffset(m_orientation, destinationPosition); m_scrollableArea->setScrollOffsetSingleAxis( m_orientation, destinationPosition, UserScroll); m_documentDragPos = pos; return; } if (m_draggingDocument) { delta += m_pressedPos - m_documentDragPos; m_draggingDocument = false; } // Drag the thumb. int thumbPos = theme().thumbPosition(*this); int thumbLen = theme().thumbLength(*this); int trackLen = theme().trackLength(*this); ASSERT(thumbLen <= trackLen); if (thumbLen == trackLen) return; if (delta > 0) delta = std::min(trackLen - thumbLen - thumbPos, delta); else if (delta < 0) delta = std::max(-thumbPos, delta); float minOffset = m_scrollableArea->minimumScrollOffset(m_orientation); float maxOffset = m_scrollableArea->maximumScrollOffset(m_orientation); if (delta) { float newOffset = static_cast<float>(thumbPos + delta) * (maxOffset - minOffset) / (trackLen - thumbLen) + minOffset; m_scrollableArea->setScrollOffsetSingleAxis(m_orientation, newOffset, UserScroll); } }
void LocalFrame::setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor) { if (m_pageZoomFactor == pageZoomFactor && m_textZoomFactor == textZoomFactor) return; Page* page = this->page(); if (!page) return; Document* document = this->document(); if (!document) return; // Respect SVGs zoomAndPan="disabled" property in standalone SVG documents. // FIXME: How to handle compound documents + zoomAndPan="disabled"? Needs SVG // WG clarification. if (document->isSVGDocument()) { if (!document->accessSVGExtensions().zoomAndPanEnabled()) return; } if (m_pageZoomFactor != pageZoomFactor) { if (FrameView* view = this->view()) { // Update the scroll position when doing a full page zoom, so the content // stays in relatively the same position. ScrollOffset scrollOffset = view->scrollOffset(); float percentDifference = (pageZoomFactor / m_pageZoomFactor); view->setScrollOffset( ScrollOffset(scrollOffset.width() * percentDifference, scrollOffset.height() * percentDifference), ProgrammaticScroll); } } m_pageZoomFactor = pageZoomFactor; m_textZoomFactor = textZoomFactor; for (Frame* child = tree().firstChild(); child; child = child->tree().nextSibling()) { if (child->isLocalFrame()) toLocalFrame(child)->setPageAndTextZoomFactors(m_pageZoomFactor, m_textZoomFactor); } document->mediaQueryAffectingValueChanged(); document->setNeedsStyleRecalc( SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Zoom)); document->updateStyleAndLayoutIgnorePendingStylesheets(); }
ScrollResult ScrollAnimator::userScroll(ScrollGranularity granularity, const ScrollOffset& delta) { if (!m_scrollableArea->scrollAnimatorEnabled()) return ScrollAnimatorBase::userScroll(granularity, delta); TRACE_EVENT0("blink", "ScrollAnimator::scroll"); if (granularity == ScrollByPrecisePixel) { // Cancel scroll animation because asked to instant scroll. if (hasRunningAnimation()) cancelAnimation(); return ScrollAnimatorBase::userScroll(granularity, delta); } bool needsPostAnimationCleanup = m_runState == RunState::PostAnimationCleanup; if (m_runState == RunState::PostAnimationCleanup) resetAnimationState(); ScrollOffset consumedDelta = computeDeltaToConsume(delta); ScrollOffset targetOffset = desiredTargetOffset(); targetOffset += consumedDelta; if (willAnimateToOffset(targetOffset)) { m_lastGranularity = granularity; // Report unused delta only if there is no animation running. See // comment below regarding scroll latching. // TODO(bokan): Need to standardize how ScrollAnimators report // unusedDelta. This differs from ScrollAnimatorMac currently. return ScrollResult(true, true, 0, 0); } // If the run state when this method was called was PostAnimationCleanup and // we're not starting an animation, stay in PostAnimationCleanup state so // that the main thread scrolling reason can be removed. if (needsPostAnimationCleanup) m_runState = RunState::PostAnimationCleanup; // Report unused delta only if there is no animation and we are not // starting one. This ensures we latch for the duration of the // animation rather than animating multiple scrollers at the same time. return ScrollResult(false, false, delta.width(), delta.height()); }
void ScrollAnchor::notifyBeforeLayout() { if (m_queued) { m_scrollAnchorDisablingStyleChanged |= computeScrollAnchorDisablingStyleChanged(); return; } DCHECK(m_scroller); ScrollOffset scrollOffset = m_scroller->getScrollOffset(); float blockDirectionScrollOffset = scrollerLayoutBox(m_scroller)->isHorizontalWritingMode() ? scrollOffset.height() : scrollOffset.width(); if (blockDirectionScrollOffset == 0) { clearSelf(); return; } if (!m_anchorObject) { findAnchor(); if (!m_anchorObject) return; m_anchorObject->setIsScrollAnchorObject(); m_savedRelativeOffset = computeRelativeOffset(m_anchorObject, m_scroller, m_corner); } m_scrollAnchorDisablingStyleChanged = computeScrollAnchorDisablingStyleChanged(); FrameView* frameView = scrollerLayoutBox(m_scroller)->frameView(); ScrollableArea* owningScroller = m_scroller->isRootFrameViewport() ? &toRootFrameViewport(m_scroller)->layoutViewport() : m_scroller.get(); frameView->enqueueScrollAnchoringAdjustment(owningScroller); m_queued = true; }
void PaintPropertyTreeBuilder::buildTreeNodes( FrameView& frameView, PaintPropertyTreeBuilderContext& context) { if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { LayoutView* layoutView = frameView.layoutView(); if (!layoutView) return; TransformationMatrix frameTranslate; frameTranslate.translate(frameView.x() + layoutView->location().x() + context.current.paintOffset.x(), frameView.y() + layoutView->location().y() + context.current.paintOffset.y()); context.current.transform = layoutView->getMutableForPainting() .ensurePaintProperties() .updatePaintOffsetTranslation(context.current.transform, frameTranslate, FloatPoint3D()); context.current.paintOffset = LayoutPoint(); context.current.renderingContextID = 0; context.current.shouldFlattenInheritedTransform = true; context.absolutePosition = context.current; context.containerForAbsolutePosition = nullptr; // This will get set in updateOutOfFlowContext(). context.fixedPosition = context.current; return; } TransformationMatrix frameTranslate; frameTranslate.translate(frameView.x() + context.current.paintOffset.x(), frameView.y() + context.current.paintOffset.y()); context.current.transform = updateFrameViewPreTranslation( frameView, context.current.transform, frameTranslate, FloatPoint3D()); FloatRoundedRect contentClip( IntRect(IntPoint(), frameView.visibleContentSize())); context.current.clip = updateFrameViewContentClip( frameView, context.current.clip, frameView.preTranslation(), contentClip); // Record the fixed properties before any scrolling occurs. const auto* fixedTransformNode = context.current.transform; auto* fixedScrollNode = context.current.scroll; ScrollOffset scrollOffset = frameView.scrollOffset(); if (frameView.isScrollable() || !scrollOffset.isZero()) { TransformationMatrix frameScroll; frameScroll.translate(-scrollOffset.width(), -scrollOffset.height()); context.current.transform = updateFrameViewScrollTranslation( frameView, frameView.preTranslation(), frameScroll, FloatPoint3D()); IntSize scrollClip = frameView.visibleContentSize(); IntSize scrollBounds = frameView.contentsSize(); bool userScrollableHorizontal = frameView.userInputScrollable(HorizontalScrollbar); bool userScrollableVertical = frameView.userInputScrollable(VerticalScrollbar); context.current.scroll = updateFrameViewScroll( frameView, context.current.scroll, frameView.scrollTranslation(), scrollClip, scrollBounds, userScrollableHorizontal, userScrollableVertical); } else { // Ensure pre-existing properties are cleared when there is no scrolling. frameView.setScrollTranslation(nullptr); frameView.setScroll(nullptr); } // Initialize the context for current, absolute and fixed position cases. // They are the same, except that scroll translation does not apply to // fixed position descendants. context.current.paintOffset = LayoutPoint(); context.current.renderingContextID = 0; context.current.shouldFlattenInheritedTransform = true; context.absolutePosition = context.current; context.containerForAbsolutePosition = nullptr; context.fixedPosition = context.current; context.fixedPosition.transform = fixedTransformNode; context.fixedPosition.scroll = fixedScrollNode; std::unique_ptr<PropertyTreeState> contentsState( new PropertyTreeState(context.current.transform, context.current.clip, context.currentEffect, context.current.scroll)); frameView.setTotalPropertyTreeStateForContents(std::move(contentsState)); }
void PaintPropertyTreeBuilder::updateFramePropertiesAndContext( FrameView& frameView, PaintPropertyTreeBuilderContext& context) { if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { // With root layer scrolling, the LayoutView (a LayoutObject) properties are // updated like other objects (see updatePropertiesAndContextForSelf and // updatePropertiesAndContextForChildren) instead of needing LayoutView- // specific property updates here. context.current.paintOffset.moveBy(frameView.location()); context.current.renderingContextID = 0; context.current.shouldFlattenInheritedTransform = true; context.absolutePosition = context.current; context.containerForAbsolutePosition = nullptr; context.fixedPosition = context.current; return; } TransformationMatrix frameTranslate; frameTranslate.translate(frameView.x() + context.current.paintOffset.x(), frameView.y() + context.current.paintOffset.y()); updateFrameViewPreTranslation(frameView, context.current.transform, frameTranslate, FloatPoint3D()); FloatRoundedRect contentClip( IntRect(IntPoint(), frameView.visibleContentSize())); updateFrameViewContentClip(frameView, context.current.clip, frameView.preTranslation(), contentClip); ScrollOffset scrollOffset = frameView.scrollOffset(); if (frameView.isScrollable() || !scrollOffset.isZero()) { TransformationMatrix frameScroll; frameScroll.translate(-scrollOffset.width(), -scrollOffset.height()); updateFrameViewScrollTranslation(frameView, frameView.preTranslation(), frameScroll, FloatPoint3D()); IntSize scrollClip = frameView.visibleContentSize(); IntSize scrollBounds = frameView.contentsSize(); bool userScrollableHorizontal = frameView.userInputScrollable(HorizontalScrollbar); bool userScrollableVertical = frameView.userInputScrollable(VerticalScrollbar); updateFrameViewScroll(frameView, context.current.scroll, frameView.scrollTranslation(), scrollClip, scrollBounds, userScrollableHorizontal, userScrollableVertical); } else { // Ensure pre-existing properties are cleared when there is no scrolling. frameView.setScrollTranslation(nullptr); frameView.setScroll(nullptr); } // Initialize the context for current, absolute and fixed position cases. // They are the same, except that scroll translation does not apply to // fixed position descendants. const auto* fixedTransformNode = frameView.preTranslation() ? frameView.preTranslation() : context.current.transform; auto* fixedScrollNode = context.current.scroll; DCHECK(frameView.preTranslation()); context.current.transform = frameView.preTranslation(); DCHECK(frameView.contentClip()); context.current.clip = frameView.contentClip(); if (const auto* scrollTranslation = frameView.scrollTranslation()) context.current.transform = scrollTranslation; if (auto* scroll = frameView.scroll()) context.current.scroll = scroll; context.current.paintOffset = LayoutPoint(); context.current.renderingContextID = 0; context.current.shouldFlattenInheritedTransform = true; context.absolutePosition = context.current; context.containerForAbsolutePosition = nullptr; context.fixedPosition = context.current; context.fixedPosition.transform = fixedTransformNode; context.fixedPosition.scroll = fixedScrollNode; std::unique_ptr<PropertyTreeState> contentsState( new PropertyTreeState(context.current.transform, context.current.clip, context.currentEffect, context.current.scroll)); frameView.setTotalPropertyTreeStateForContents(std::move(contentsState)); }
WebPoint WebHistoryItem::scrollOffset() const { ScrollOffset offset = m_private->scrollOffset(); return WebPoint(offset.width(), offset.height()); }
WebFloatPoint WebHistoryItem::visualViewportScrollOffset() const { ScrollOffset offset = m_private->visualViewportScrollOffset(); return WebFloatPoint(offset.width(), offset.height()); }