void RotationViewportAnchor::computeOrigins( const FloatSize& innerSize, IntPoint& mainFrameOffset, FloatPoint& visualViewportOffset) const { IntSize outerSize = layoutViewport().visibleContentRect().size(); // Compute the viewport origins in CSS pixels relative to the document. FloatSize absVisualViewportOffset = m_normalizedVisualViewportOffset; absVisualViewportOffset.scale(outerSize.width(), outerSize.height()); FloatPoint innerOrigin = getInnerOrigin(innerSize); FloatPoint outerOrigin = innerOrigin - absVisualViewportOffset; IntRect outerRect = IntRect(flooredIntPoint(outerOrigin), outerSize); FloatRect innerRect = FloatRect(innerOrigin, innerSize); moveToEncloseRect(outerRect, innerRect); outerRect.setLocation(IntPoint( layoutViewport().clampScrollOffset(toIntSize(outerRect.location())))); moveIntoRect(innerRect, outerRect); mainFrameOffset = outerRect.location(); visualViewportOffset = FloatPoint(innerRect.location() - outerRect.location()); }
void RootFrameViewport::distributeScrollBetweenViewports(const DoublePoint& offset, ScrollType scrollType, ScrollBehavior behavior) { // Make sure we use the scroll positions as reported by each viewport's ScrollAnimatorBase, since its // ScrollableArea's position may have the fractional part truncated off. DoublePoint oldPosition = scrollOffsetFromScrollAnimators(); DoubleSize delta = offset - oldPosition; if (delta.isZero()) return; DoublePoint targetPosition = visualViewport().clampScrollPosition( visualViewport().scrollAnimator().currentPosition() + delta); visualViewport().setScrollPosition(targetPosition, scrollType, behavior); // Scroll the secondary viewport if all of the scroll was not applied to the // primary viewport. DoublePoint updatedPosition = layoutViewport().scrollAnimator().currentPosition() + FloatPoint(targetPosition); DoubleSize applied = updatedPosition - oldPosition; delta -= applied; if (delta.isZero()) return; targetPosition = layoutViewport().clampScrollPosition(layoutViewport().scrollAnimator().currentPosition() + delta); layoutViewport().setScrollPosition(targetPosition, scrollType, behavior); }
void RootFrameViewport::setScrollOffset(const DoublePoint& offset, ScrollType scrollType) { // Make sure we use the scroll positions as reported by each viewport's ScrollAnimator, since its // ScrollableArea's position may have the fractional part truncated off. DoublePoint oldPosition = scrollOffsetFromScrollAnimators(); DoubleSize delta = offset - oldPosition; if (delta.isZero()) return; ScrollableArea& primary = !m_invertScrollOrder ? layoutViewport() : visualViewport(); ScrollableArea& secondary = !m_invertScrollOrder ? visualViewport() : layoutViewport(); DoublePoint targetPosition = primary.clampScrollPosition(primary.scrollAnimator()->currentPosition() + delta); primary.setScrollPosition(targetPosition, scrollType); DoubleSize applied = scrollOffsetFromScrollAnimators() - oldPosition; delta -= applied; if (delta.isZero()) return; targetPosition = secondary.clampScrollPosition(secondary.scrollAnimator()->currentPosition() + delta); secondary.setScrollPosition(targetPosition, scrollType); }
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); }
LayoutRect RootFrameViewport::scrollIntoView(const LayoutRect& rectInContent, const ScrollAlignment& alignX, const ScrollAlignment& alignY, ScrollType scrollType) { // We want to move the rect into the viewport that excludes the scrollbars so // we intersect the visual viewport with the scrollbar-excluded frameView // content rect. However, we don't use visibleContentRect directly since it // floors the scroll offset. Instead, we use ScrollAnimatorBase::currentOffset // and construct a LayoutRect from that. LayoutRect frameRectInContent = LayoutRect(FloatPoint(layoutViewport().scrollAnimator().currentOffset()), FloatSize(layoutViewport().visibleContentRect().size())); LayoutRect visualRectInContent = LayoutRect(FloatPoint(scrollOffsetFromScrollAnimators()), FloatSize(visualViewport().visibleContentRect().size())); // Intersect layout and visual rects to exclude the scrollbar from the view // rect. LayoutRect viewRectInContent = intersection(visualRectInContent, frameRectInContent); LayoutRect targetViewport = ScrollAlignment::getRectToExpose( viewRectInContent, rectInContent, alignX, alignY); if (targetViewport != viewRectInContent) { setScrollOffset(ScrollOffset(targetViewport.x(), targetViewport.y()), scrollType); } // RootFrameViewport only changes the viewport relative to the document so we // can't change the input rect's location relative to the document origin. return rectInContent; }
void RootFrameViewport::setScrollOffset(const ScrollOffset& offset, ScrollType scrollType, ScrollBehavior scrollBehavior) { updateScrollAnimator(); if (scrollBehavior == ScrollBehaviorAuto) scrollBehavior = scrollBehaviorStyle(); if (scrollType == ProgrammaticScroll && !layoutViewport().isProgrammaticallyScrollable()) return; if (scrollType == AnchoringScroll) { distributeScrollBetweenViewports(offset, scrollType, scrollBehavior, LayoutViewport); return; } if (scrollBehavior == ScrollBehaviorSmooth) { distributeScrollBetweenViewports(offset, scrollType, scrollBehavior, VisualViewport); return; } ScrollOffset clampedOffset = clampScrollOffset(offset); ScrollableArea::setScrollOffset(clampedOffset, scrollType, scrollBehavior); }
void RotationViewportAnchor::setAnchor() { RootFrameViewport* rootFrameViewport = m_rootFrameView->getRootFrameViewport(); DCHECK(rootFrameViewport); m_oldPageScaleFactor = m_visualViewport->scale(); m_oldMinimumPageScaleFactor = m_pageScaleConstraintsSet.finalConstraints().minimumScale; // Save the absolute location in case we won't find the anchor node, we'll // fall back to that. m_visualViewportInDocument = FloatPoint(rootFrameViewport->visibleContentRect().location()); m_anchorNode.clear(); m_anchorNodeBounds = LayoutRect(); m_anchorInNodeCoords = FloatSize(); m_normalizedVisualViewportOffset = FloatSize(); IntRect innerViewRect = rootFrameViewport->visibleContentRect(); // Preserve origins at the absolute screen origin. if (innerViewRect.location() == IntPoint::zero() || innerViewRect.isEmpty()) return; IntRect outerViewRect = layoutViewport().visibleContentRect(IncludeScrollbars); // Normalize by the size of the outer rect DCHECK(!outerViewRect.isEmpty()); m_normalizedVisualViewportOffset = m_visualViewport->getScrollOffset(); m_normalizedVisualViewportOffset.scale(1.0 / outerViewRect.width(), 1.0 / outerViewRect.height()); // Note, we specifically use the unscaled visual viewport size here as the // conversion into content-space below will apply the scale. FloatPoint anchorOffset(m_visualViewport->size()); anchorOffset.scale(m_anchorInInnerViewCoords.width(), m_anchorInInnerViewCoords.height()); // Note, we specifically convert to the rootFrameView contents here, rather // than the layout viewport. That's because hit testing works from the // FrameView's content coordinates even if it's not the layout viewport. const FloatPoint anchorPointInContents = m_rootFrameView->frameToContents( m_visualViewport->viewportToRootFrame(anchorOffset)); Node* node = findNonEmptyAnchorNode(flooredIntPoint(anchorPointInContents), innerViewRect, m_rootFrameView->frame().eventHandler()); if (!node) return; m_anchorNode = node; m_anchorNodeBounds = node->boundingBox(); m_anchorInNodeCoords = anchorPointInContents - FloatPoint(m_anchorNodeBounds.location()); m_anchorInNodeCoords.scale(1.f / m_anchorNodeBounds.width(), 1.f / m_anchorNodeBounds.height()); }
void RootFrameViewport::setScrollPosition(const DoublePoint& position, ScrollType scrollType, ScrollBehavior scrollBehavior) { updateScrollAnimator(); // TODO(bokan): Support smooth scrolling the visual viewport. if (scrollBehavior == ScrollBehaviorAuto) scrollBehavior = scrollBehaviorStyle(); if (scrollBehavior == ScrollBehaviorSmooth) { layoutViewport().setScrollPosition(position, scrollType, scrollBehavior); return; } if (scrollType == ProgrammaticScroll && !layoutViewport().isProgrammaticallyScrollable()) return; DoublePoint clampedPosition = clampScrollPosition(position); ScrollableArea::setScrollPosition(clampedPosition, scrollType, scrollBehavior); }
LayoutRect RootFrameViewport::scrollIntoView(const LayoutRect& rectInContent, const ScrollAlignment& alignX, const ScrollAlignment& alignY) { // We want to move the rect into the viewport that excludes the scrollbars so we intersect // the visual viewport with the scrollbar-excluded frameView content rect. However, we don't // use visibleContentRect directly since it floors the scroll position. Instead, we use // FrameView::scrollPositionDouble and construct a LayoutRect from that (the FrameView size // is always integer sized. LayoutRect frameRectInContent = LayoutRect( layoutViewport().scrollPositionDouble(), layoutViewport().visibleContentRect().size()); LayoutRect visualRectInContent = LayoutRect( layoutViewport().scrollPositionDouble() + toDoubleSize(visualViewport().scrollPositionDouble()), visualViewport().visibleContentRect().size()); LayoutRect viewRectInContent = intersection(visualRectInContent, frameRectInContent); LayoutRect targetViewport = ScrollAlignment::getRectToExpose(viewRectInContent, rectInContent, alignX, alignY); // visualViewport.scrollIntoView will attempt to center the given rect within the viewport // so to prevent it from adjusting r's coordinates the rect must match the viewport's size // i.e. add the subtracted scrollbars from above back in. // FIXME: This is hacky and required because getRectToExpose doesn't naturally account // for the two viewports. crbug.com/449340. targetViewport.setSize(LayoutSize(visualViewport().visibleContentRect().size())); // Snap the visible rect to layout units to match the calculated target viewport rect. FloatRect visible = LayoutRect(visualViewport().scrollPositionDouble(), visualViewport().visibleContentRect().size()); float centeringOffsetX = (visible.width() - targetViewport.width()) / 2; float centeringOffsetY = (visible.height() - targetViewport.height()) / 2; DoublePoint targetOffset( targetViewport.x() - centeringOffsetX, targetViewport.y() - centeringOffsetY); setScrollPosition(targetOffset, ProgrammaticScroll); // RootFrameViewport only changes the viewport relative to the document so we can't change the input // rect's location relative to the document origin. return rectInContent; }
LayoutRect RootFrameViewport::rootContentsToLayoutViewportContents( FrameView& rootFrameView, const LayoutRect& rect) const { LayoutRect ret(rect); // If the root FrameView is the layout viewport then coordinates in the // root FrameView's content space are already in the layout viewport's // content space. if (rootFrameView.layoutViewportScrollableArea() == &layoutViewport()) return ret; // Make the given rect relative to the top of the layout viewport's content // by adding the scroll position. // TODO(bokan): This will have to be revisited if we ever remove the // restriction that a root scroller must be exactly screen filling. ret.move(LayoutSize(layoutViewport().getScrollOffset())); return ret; }
void RootFrameViewport::distributeScrollBetweenViewports( const ScrollOffset& offset, ScrollType scrollType, ScrollBehavior behavior, ViewportToScrollFirst scrollFirst) { // Make sure we use the scroll offsets as reported by each viewport's // ScrollAnimatorBase, since its ScrollableArea's offset may have the // fractional part truncated off. // TODO(szager): Now that scroll offsets are stored as floats, can we take the // scroll offset directly from the ScrollableArea's rather than the animators? ScrollOffset oldOffset = scrollOffsetFromScrollAnimators(); ScrollOffset delta = offset - oldOffset; if (delta.isZero()) return; ScrollableArea& primary = scrollFirst == VisualViewport ? visualViewport() : layoutViewport(); ScrollableArea& secondary = scrollFirst == VisualViewport ? layoutViewport() : visualViewport(); ScrollOffset targetOffset = primary.clampScrollOffset( primary.scrollAnimator().currentOffset() + delta); primary.setScrollOffset(targetOffset, scrollType, behavior); // Scroll the secondary viewport if all of the scroll was not applied to the // primary viewport. ScrollOffset updatedOffset = secondary.scrollAnimator().currentOffset() + FloatSize(targetOffset); ScrollOffset applied = updatedOffset - oldOffset; delta -= applied; if (delta.isZero()) return; targetOffset = secondary.clampScrollOffset( secondary.scrollAnimator().currentOffset() + delta); secondary.setScrollOffset(targetOffset, scrollType, behavior); }
ScrollResult RootFrameViewport::handleWheel(const PlatformWheelEvent& event) { updateScrollAnimator(); ScrollableArea& primary = !m_invertScrollOrder ? layoutViewport() : visualViewport(); ScrollableArea& secondary = !m_invertScrollOrder ? visualViewport() : layoutViewport(); ScrollResult viewScrollResult = primary.handleWheel(event); // The visual viewport will only accept pixel scrolls. if (!event.canScroll() || event.granularity() == ScrollByPageWheelEvent) return viewScrollResult; // TODO(sataya.m) : The delta in PlatformWheelEvent is negative when scrolling the // wheel towards the user, so negate it to get the scroll delta that should be applied // to the page. unusedScrollDelta computed in the ScrollResult is also negative. Say // there is WheelEvent({0, -10} and page scroll by 2px and unusedScrollDelta computed // is {0, -8}. Due to which we have to negate the unusedScrollDelta to obtain the expected // animation.Please address http://crbug.com/504389. DoublePoint oldOffset = secondary.scrollPositionDouble(); DoublePoint locationDelta; if (viewScrollResult.didScroll()) { locationDelta = -DoublePoint(viewScrollResult.unusedScrollDeltaX, viewScrollResult.unusedScrollDeltaY); } else { if (event.railsMode() != PlatformEvent::RailsModeVertical) locationDelta.setX(-event.deltaX()); if (event.railsMode() != PlatformEvent::RailsModeHorizontal) locationDelta.setY(-event.deltaY()); } DoublePoint targetPosition = secondary.clampScrollPosition( secondary.scrollPositionDouble() + toDoubleSize(locationDelta)); secondary.setScrollPosition(targetPosition, UserScroll); DoublePoint usedLocationDelta(secondary.scrollPositionDouble() - oldOffset); bool didScrollX = viewScrollResult.didScrollX || usedLocationDelta.x(); bool didScrollY = viewScrollResult.didScrollY || usedLocationDelta.y(); return ScrollResult(didScrollX, didScrollY, -viewScrollResult.unusedScrollDeltaX - usedLocationDelta.x(), -viewScrollResult.unusedScrollDeltaY - usedLocationDelta.y()); }
ScrollResultOneDimensional RootFrameViewport::userScroll(ScrollDirectionPhysical direction, ScrollGranularity granularity, float delta) { updateScrollAnimator(); ScrollbarOrientation orientation; if (direction == ScrollUp || direction == ScrollDown) orientation = VerticalScrollbar; else orientation = HorizontalScrollbar; if (layoutViewport().userInputScrollable(orientation) && visualViewport().userInputScrollable(orientation)) return ScrollableArea::userScroll(direction, granularity, delta); if (visualViewport().userInputScrollable(orientation)) return visualViewport().userScroll(direction, granularity, delta); if (layoutViewport().userInputScrollable(orientation)) return layoutViewport().userScroll(direction, granularity, delta); return ScrollResultOneDimensional(false, delta); }
void RootFrameViewport::setScrollPosition(const DoublePoint& position, ScrollType scrollType, ScrollBehavior scrollBehavior) { updateScrollAnimator(); if (scrollBehavior == ScrollBehaviorAuto) scrollBehavior = scrollBehaviorStyle(); if (scrollType == ProgrammaticScroll && !layoutViewport().isProgrammaticallyScrollable()) return; if (scrollBehavior == ScrollBehaviorSmooth) { distributeScrollBetweenViewports(position, scrollType, scrollBehavior); return; } DoublePoint clampedPosition = clampScrollPosition(position); ScrollableArea::setScrollPosition(clampedPosition, scrollType, scrollBehavior); }
void RotationViewportAnchor::restoreToAnchor() { float newPageScaleFactor = m_oldPageScaleFactor / m_oldMinimumPageScaleFactor * m_pageScaleConstraintsSet.finalConstraints().minimumScale; newPageScaleFactor = m_pageScaleConstraintsSet.finalConstraints().clampToConstraints( newPageScaleFactor); FloatSize visualViewportSize(m_visualViewport->size()); visualViewportSize.scale(1 / newPageScaleFactor); IntPoint mainFrameOrigin; FloatPoint visualViewportOrigin; computeOrigins(visualViewportSize, mainFrameOrigin, visualViewportOrigin); layoutViewport().setScrollOffset(toScrollOffset(mainFrameOrigin), ProgrammaticScroll); // Set scale before location, since location can be clamped on setting scale. m_visualViewport->setScale(newPageScaleFactor); m_visualViewport->setLocation(visualViewportOrigin); }
bool RootFrameViewport::scrollAnimatorEnabled() const { return layoutViewport().scrollAnimatorEnabled(); }
LayoutBox* RootFrameViewport::layoutBox() const { return layoutViewport().layoutBox(); }
void RootFrameViewport::didUpdateVisualViewport() { if (RuntimeEnabledFeatures::scrollAnchoringEnabled()) { if (ScrollAnchor* anchor = layoutViewport().scrollAnchor()) anchor->clear(); } }
bool RootFrameViewport::shouldPlaceVerticalScrollbarOnLeft() const { return layoutViewport().shouldPlaceVerticalScrollbarOnLeft(); }
void RootFrameViewport::clearScrollableArea() { ScrollableArea::clearScrollableArea(); layoutViewport().clearScrollableArea(); visualViewport().clearScrollableArea(); }
void RootFrameViewport::cancelProgrammaticScrollAnimation() { ScrollableArea::cancelProgrammaticScrollAnimation(); layoutViewport().cancelProgrammaticScrollAnimation(); visualViewport().cancelProgrammaticScrollAnimation(); }
void RootFrameViewport::updateCompositorScrollAnimations() { ScrollableArea::updateCompositorScrollAnimations(); layoutViewport().updateCompositorScrollAnimations(); visualViewport().updateCompositorScrollAnimations(); }
void RootFrameViewport::serviceScrollAnimations(double monotonicTime) { ScrollableArea::serviceScrollAnimations(monotonicTime); layoutViewport().serviceScrollAnimations(monotonicTime); visualViewport().serviceScrollAnimations(monotonicTime); }
HostWindow* RootFrameViewport::getHostWindow() const { return layoutViewport().getHostWindow(); }
bool RootFrameViewport::userInputScrollable( ScrollbarOrientation orientation) const { return visualViewport().userInputScrollable(orientation) || layoutViewport().userInputScrollable(orientation); }
void RootFrameViewport::scrollControlWasSetNeedsPaintInvalidation() { layoutViewport().scrollControlWasSetNeedsPaintInvalidation(); }
GraphicsLayer* RootFrameViewport::layerForContainer() const { return layoutViewport().layerForContainer(); }
GraphicsLayer* RootFrameViewport::layerForVerticalScrollbar() const { return layoutViewport().layerForVerticalScrollbar(); }
GraphicsLayer* RootFrameViewport::layerForScrollCorner() const { return layoutViewport().layerForScrollCorner(); }
ScrollResult RootFrameViewport::userScroll(ScrollGranularity granularity, const FloatSize& delta) { // TODO(bokan/ymalik): Once smooth scrolling is permanently enabled we // should be able to remove this method override and use the base class // version: ScrollableArea::userScroll. updateScrollAnimator(); // Distribute the scroll between the visual and layout viewport. float stepX = scrollStep(granularity, HorizontalScrollbar); float stepY = scrollStep(granularity, VerticalScrollbar); FloatSize pixelDelta(delta); pixelDelta.scale(stepX, stepY); // Precompute the amount of possible scrolling since, when animated, // ScrollAnimator::userScroll will report having consumed the total given // scroll delta, regardless of how much will actually scroll, but we need to // know how much to leave for the layout viewport. FloatSize visualConsumedDelta = visualViewport().scrollAnimator().computeDeltaToConsume(pixelDelta); // Split the remaining delta between scrollable and unscrollable axes of the // layout viewport. We only pass a delta to the scrollable axes and remember // how much was held back so we can add it to the unused delta in the // result. FloatSize layoutDelta = pixelDelta - visualConsumedDelta; FloatSize scrollableAxisDelta( layoutViewport().userInputScrollable(HorizontalScrollbar) ? layoutDelta.width() : 0, layoutViewport().userInputScrollable(VerticalScrollbar) ? layoutDelta.height() : 0); // If there won't be any scrolling, bail early so we don't produce any side // effects like cancelling existing animations. if (visualConsumedDelta.isZero() && scrollableAxisDelta.isZero()) { return ScrollResult(false, false, pixelDelta.width(), pixelDelta.height()); } cancelProgrammaticScrollAnimation(); // TODO(bokan): Why do we call userScroll on the animators directly and // not through the ScrollableAreas? ScrollResult visualResult = visualViewport().scrollAnimator().userScroll( granularity, visualConsumedDelta); if (visualConsumedDelta == pixelDelta) return visualResult; ScrollResult layoutResult = layoutViewport().scrollAnimator().userScroll( granularity, scrollableAxisDelta); // Remember to add any delta not used because of !userInputScrollable to the // unusedScrollDelta in the result. FloatSize unscrollableAxisDelta = layoutDelta - scrollableAxisDelta; return ScrollResult( visualResult.didScrollX || layoutResult.didScrollX, visualResult.didScrollY || layoutResult.didScrollY, layoutResult.unusedScrollDeltaX + unscrollableAxisDelta.width(), layoutResult.unusedScrollDeltaY + unscrollableAxisDelta.height()); }