TEST_F(ScrollableAreaTest, InvalidatesNonCompositedScrollbarsWhenThumbMoves) { ScrollbarThemeWithMockInvalidation theme; OwnPtrWillBeRawPtr<MockScrollableArea> scrollableArea = MockScrollableArea::create(IntPoint(100, 100)); RefPtrWillBeRawPtr<Scrollbar> horizontalScrollbar = Scrollbar::createForTesting(scrollableArea.get(), HorizontalScrollbar, RegularScrollbar, &theme); RefPtrWillBeRawPtr<Scrollbar> verticalScrollbar = Scrollbar::createForTesting(scrollableArea.get(), VerticalScrollbar, RegularScrollbar, &theme); EXPECT_CALL(*scrollableArea, horizontalScrollbar()).WillRepeatedly(Return(horizontalScrollbar.get())); EXPECT_CALL(*scrollableArea, verticalScrollbar()).WillRepeatedly(Return(verticalScrollbar.get())); // Regardless of whether the theme invalidates any parts, non-composited // scrollbars have to be repainted if the thumb moves. EXPECT_CALL(*scrollableArea, layerForHorizontalScrollbar()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(*scrollableArea, layerForVerticalScrollbar()).WillRepeatedly(Return(nullptr)); ASSERT_FALSE(scrollableArea->hasLayerForVerticalScrollbar()); ASSERT_FALSE(scrollableArea->hasLayerForHorizontalScrollbar()); EXPECT_CALL(theme, shouldRepaintAllPartsOnInvalidation()).WillRepeatedly(Return(false)); EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).WillRepeatedly(Return(NoPart)); // A scroll in each direction should only invalidate one scrollbar. scrollableArea->setScrollPosition(DoublePoint(0, 50), ProgrammaticScroll); EXPECT_FALSE(scrollableArea->horizontalScrollbarNeedsPaintInvalidation()); EXPECT_TRUE(scrollableArea->verticalScrollbarNeedsPaintInvalidation()); scrollableArea->clearNeedsPaintInvalidationForScrollControls(); scrollableArea->setScrollPosition(DoublePoint(50, 50), ProgrammaticScroll); EXPECT_TRUE(scrollableArea->horizontalScrollbarNeedsPaintInvalidation()); EXPECT_FALSE(scrollableArea->verticalScrollbarNeedsPaintInvalidation()); scrollableArea->clearNeedsPaintInvalidationForScrollControls(); // Forced GC in order to finalize objects depending on the mock object. Heap::collectAllGarbage(); }
void ScrollableArea::setScrollPositionSingleAxis(ScrollbarOrientation orientation, double position, ScrollType scrollType, ScrollBehavior behavior) { DoublePoint newPosition; if (orientation == HorizontalScrollbar) newPosition = DoublePoint(position, scrollAnimator()->currentPosition().y()); else newPosition = DoublePoint(scrollAnimator()->currentPosition().x(), position); // TODO(bokan): Note, this doesn't use the derived class versions since this method is currently used // exclusively by code that adjusts the position by the scroll origin and the derived class versions // differ on whether they take that into account or not. ScrollableArea::setScrollPosition(newPosition, scrollType, behavior); }
TEST_F(VisualRectMappingTest, LayoutViewSubpixelRounding) { document().setBaseURLOverride(KURL(ParsedURLString, "http://test.com")); setBodyInnerHTML( "<style>body { margin: 0; }</style>" "<div id=frameContainer style='position: relative; left: 0.5px'>" " <iframe style='position: relative; left: 0.5px' " "src='http://test.com' width='200' height='200' frameBorder='0'></iframe>" "</div>"); setChildFrameHTML( "<style>body { margin: 0; }</style><div id='target' style='position: " "relative; width: 100px; height: 100px; left: 0.5px'>"); document().view()->updateAllLifecyclePhases(); LayoutBlock* frameContainer = toLayoutBlock(getLayoutObjectByElementId("frameContainer")); LayoutObject* target = childDocument().getElementById("target")->layoutObject(); LayoutRect rect(0, 0, 100, 100); EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(frameContainer, rect)); // When passing from the iframe to the parent frame, the rect of (0.5, 0, 100, // 100) is expanded to (0, 0, 100, 100), and then offset by the 0.5 offset of // frameContainer. EXPECT_EQ(LayoutRect(LayoutPoint(DoublePoint(0.5, 0)), LayoutSize(101, 100)), rect); }
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 position. Instead, we use // ScrollAnimatorBase::currentPosition and construct a LayoutRect from that. LayoutRect frameRectInContent = LayoutRect( layoutViewport().scrollAnimator().currentPosition(), layoutViewport().visibleContentRect().size()); LayoutRect visualRectInContent = LayoutRect( scrollOffsetFromScrollAnimators(), 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) setScrollPosition(DoublePoint(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; }
TEST_F(EventHandlerTest, dragSelectionAfterScroll) { setHtmlInnerHTML("<style> body { margin: 0px; } .upper { width: 300px; height: 400px; }" ".lower { margin: 0px; width: 300px; height: 400px; } .line { display: block; width: 300px; height: 30px; } </style>" "<div class='upper'></div>" "<div class='lower'>" "<span class='line'>Line 1</span><span class='line'>Line 2</span><span class='line'>Line 3</span><span class='line'>Line 4</span><span class='line'>Line 5</span>" "<span class='line'>Line 6</span><span class='line'>Line 7</span><span class='line'>Line 8</span><span class='line'>Line 9</span><span class='line'>Line 10</span>" "</div>"); FrameView* frameView = document().view(); frameView->scrollTo(DoublePoint(0, 400)); PlatformMouseEvent mouseDownEvent( IntPoint(0, 0), IntPoint(100, 200), LeftButton, PlatformEvent::MousePressed, 1, static_cast<PlatformEvent::Modifiers>(0), WTF::currentTime()); document().frame()->eventHandler().handleMousePressEvent(mouseDownEvent); PlatformMouseEvent mouseMoveEvent( IntPoint(100, 50), IntPoint(200, 250), LeftButton, PlatformEvent::MouseMoved, 1, static_cast<PlatformEvent::Modifiers>(0), WTF::currentTime()); document().frame()->eventHandler().handleMouseMoveEvent(mouseMoveEvent); page().autoscrollController().animate(WTF::currentTime()); page().animator().serviceScriptedAnimations(WTF::currentTime()); PlatformMouseEvent mouseUpEvent( IntPoint(100, 50), IntPoint(200, 250), LeftButton, PlatformEvent::MouseReleased, 1, static_cast<PlatformEvent::Modifiers>(0), WTF::currentTime()); document().frame()->eventHandler().handleMouseReleaseEvent(mouseUpEvent); FrameSelection& selection = document().frame()->selection(); ASSERT_TRUE(selection.isRange()); RefPtrWillBeRawPtr<Range> range = createRange(selection.selection().toNormalizedEphemeralRange()); ASSERT_TRUE(range.get()); EXPECT_EQ("Line 1\nLine 2", range->text()); }
TEST_F(ScrollingCoordinatorTest, fractionalScrollingNonLayerFixedPosition) { registerMockedHttpURLLoad("fractional-scroll-fixed-position.html"); navigateTo(m_baseURL + "fractional-scroll-fixed-position.html"); // Prevent fixed-position element from getting its own layer. webViewImpl()->settings()->setPreferCompositingToLCDTextEnabled(false); forceFullCompositingUpdate(); FrameView* frameView = frame()->view(); frameView->scrollTo(DoublePoint(1.5, 1.5)); WebLayer* rootScrollLayer = getRootScrollLayer(); // Scroll on main if there is non-composited fixed position element. // And the containing scroll layer should not get fractional scroll offset. ASSERT_TRUE(rootScrollLayer->shouldScrollOnMainThread()); ASSERT_EQ(1.0, rootScrollLayer->scrollPositionDouble().x); ASSERT_EQ(1.0, rootScrollLayer->scrollPositionDouble().y); ASSERT_EQ(0.0, rootScrollLayer->position().x); ASSERT_EQ(0.0, rootScrollLayer->position().y); }
void ScrollableArea::scrollIntoRect(const LayoutRect& rectInContent, const FloatRect& targetRectInFrame) { // Use |pixelSnappedIntRect| for rounding to pixel as opposed to |enclosingIntRect|. It gives a better // combined (location and size) rounding error resulting in a more accurate scroll offset. // FIXME: It would probably be best to do the whole calculation in LayoutUnits but contentsToRootFrame // and friends don't have LayoutRect/Point versions yet. IntRect boundsInContent = pixelSnappedIntRect(rectInContent); IntRect boundsInFrame(boundsInContent.location() - toIntSize(scrollPosition()), boundsInContent.size()); int centeringOffsetX = (targetRectInFrame.width() - boundsInFrame.width()) / 2; int centeringOffsetY = (targetRectInFrame.height() - boundsInFrame.height()) / 2; IntSize scrollDelta( boundsInFrame.x() - centeringOffsetX - targetRectInFrame.x(), boundsInFrame.y() - centeringOffsetY - targetRectInFrame.y()); DoublePoint targetOffset = DoublePoint(scrollPosition() + scrollDelta); setScrollPosition(targetOffset, ProgrammaticScroll); }
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()); }
DoublePoint VisualViewport::maximumScrollPositionDouble() const { if (!mainFrame()) return IntPoint(); // FIXME: We probably shouldn't be storing the bounds in a float. crbug.com/422331. FloatSize frameViewSize(contentsSize()); if (m_topControlsAdjustment) { float minScale = frameHost().pageScaleConstraintsSet().finalConstraints().minimumScale; frameViewSize.expand(0, m_topControlsAdjustment / minScale); } frameViewSize.scale(m_scale); frameViewSize = flooredIntSize(frameViewSize); FloatSize viewportSize(m_size); viewportSize.expand(0, m_topControlsAdjustment); FloatSize maxPosition = frameViewSize - viewportSize; maxPosition.scale(1 / m_scale); return DoublePoint(maxPosition); }
// NOTE: Only called from Internals for testing. void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) { setScrollOffsetFromAnimation(DoublePoint(offset)); }
void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position) { scrollPositionChanged(DoublePoint(position)); scrollAnimator()->setCurrentPosition(position); }
void VisualViewport::setScrollOffset(const IntPoint& offset, ScrollType scrollType) { setScrollOffset(DoublePoint(offset), scrollType); }
TEST_F(ScrollableAreaTest, InvalidatesCompositedScrollbarsIfPartsNeedRepaint) { ScrollbarThemeWithMockInvalidation theme; OwnPtrWillBeRawPtr<MockScrollableArea> scrollableArea = MockScrollableArea::create(IntPoint(100, 100)); RefPtrWillBeRawPtr<Scrollbar> horizontalScrollbar = Scrollbar::createForTesting(scrollableArea.get(), HorizontalScrollbar, RegularScrollbar, &theme); horizontalScrollbar->clearTrackNeedsRepaint(); horizontalScrollbar->clearThumbNeedsRepaint(); RefPtrWillBeRawPtr<Scrollbar> verticalScrollbar = Scrollbar::createForTesting(scrollableArea.get(), VerticalScrollbar, RegularScrollbar, &theme); verticalScrollbar->clearTrackNeedsRepaint(); verticalScrollbar->clearThumbNeedsRepaint(); EXPECT_CALL(*scrollableArea, horizontalScrollbar()).WillRepeatedly(Return(horizontalScrollbar.get())); EXPECT_CALL(*scrollableArea, verticalScrollbar()).WillRepeatedly(Return(verticalScrollbar.get())); // Composited scrollbars only need repainting when parts become invalid // (e.g. if the track changes appearance when the thumb reaches the end). MockGraphicsLayerClient graphicsLayerClient; MockGraphicsLayer layerForHorizontalScrollbar(&graphicsLayerClient); layerForHorizontalScrollbar.setDrawsContent(true); layerForHorizontalScrollbar.setSize(FloatSize(10, 10)); MockGraphicsLayer layerForVerticalScrollbar(&graphicsLayerClient); layerForVerticalScrollbar.setDrawsContent(true); layerForVerticalScrollbar.setSize(FloatSize(10, 10)); EXPECT_CALL(*scrollableArea, layerForHorizontalScrollbar()).WillRepeatedly(Return(&layerForHorizontalScrollbar)); EXPECT_CALL(*scrollableArea, layerForVerticalScrollbar()).WillRepeatedly(Return(&layerForVerticalScrollbar)); ASSERT_TRUE(scrollableArea->hasLayerForHorizontalScrollbar()); ASSERT_TRUE(scrollableArea->hasLayerForVerticalScrollbar()); EXPECT_CALL(theme, shouldRepaintAllPartsOnInvalidation()).WillRepeatedly(Return(false)); // First, we'll scroll horizontally, and the theme will require repainting // the back button (i.e. the track). EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).WillOnce(Return(BackButtonStartPart)); scrollableArea->setScrollPosition(DoublePoint(50, 0), ProgrammaticScroll); EXPECT_TRUE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations()); EXPECT_FALSE(layerForVerticalScrollbar.hasTrackedPaintInvalidations()); EXPECT_TRUE(horizontalScrollbar->trackNeedsRepaint()); EXPECT_FALSE(horizontalScrollbar->thumbNeedsRepaint()); layerForHorizontalScrollbar.resetTrackedPaintInvalidations(); horizontalScrollbar->clearTrackNeedsRepaint(); // Next, we'll scroll vertically, but invalidate the thumb. EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).WillOnce(Return(ThumbPart)); scrollableArea->setScrollPosition(DoublePoint(50, 50), ProgrammaticScroll); EXPECT_FALSE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations()); EXPECT_TRUE(layerForVerticalScrollbar.hasTrackedPaintInvalidations()); EXPECT_FALSE(verticalScrollbar->trackNeedsRepaint()); EXPECT_TRUE(verticalScrollbar->thumbNeedsRepaint()); layerForVerticalScrollbar.resetTrackedPaintInvalidations(); verticalScrollbar->clearThumbNeedsRepaint(); // Next we'll scroll in both, but the thumb position moving requires no // invalidations. Nonetheless the GraphicsLayer should be invalidated, // because we still need to update the underlying layer (though no // rasterization will be required). EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).Times(2).WillRepeatedly(Return(NoPart)); scrollableArea->setScrollPosition(DoublePoint(70, 70), ProgrammaticScroll); EXPECT_TRUE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations()); EXPECT_TRUE(layerForVerticalScrollbar.hasTrackedPaintInvalidations()); EXPECT_FALSE(horizontalScrollbar->trackNeedsRepaint()); EXPECT_FALSE(horizontalScrollbar->thumbNeedsRepaint()); EXPECT_FALSE(verticalScrollbar->trackNeedsRepaint()); EXPECT_FALSE(verticalScrollbar->thumbNeedsRepaint()); // Forced GC in order to finalize objects depending on the mock object. Heap::collectAllGarbage(); }
// NOTE: Only called from Internals for testing. void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) { scrollPositionChanged(DoublePoint(offset), ProgrammaticScroll); }
void RootFrameViewport::setScrollOffset(const IntPoint& offset, ScrollType scrollType) { setScrollOffset(DoublePoint(offset), scrollType); }
void RootFrameViewport::setScrollOffset(const DoublePoint& offset, ScrollType scrollType) { distributeScrollBetweenViewports(DoublePoint(offset), scrollType, ScrollBehaviorInstant); }