void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const { // Search up the parent chain until we find the first one that's scrollable. AccessibilityObject* scrollParent = parentObject(); ScrollableArea* scrollableArea; for (scrollableArea = 0; scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); scrollParent = scrollParent->parentObject()) { } if (!scrollableArea) return; LayoutRect objectRect = elementRect(); IntPoint scrollPosition = scrollableArea->scrollPosition(); IntRect scrollVisibleRect = scrollableArea->visibleContentRect(); int desiredX = computeBestScrollOffset( scrollPosition.x(), objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), objectRect.x(), objectRect.maxX(), 0, scrollVisibleRect.width()); int desiredY = computeBestScrollOffset( scrollPosition.y(), objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(), objectRect.y(), objectRect.maxY(), 0, scrollVisibleRect.height()); scrollParent->scrollTo(IntPoint(desiredX, desiredY)); // Recursively make sure the scroll parent itself is visible. if (scrollParent->parentObject()) scrollParent->scrollToMakeVisible(); }
void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const { // Search up the parent chain and create a vector of all scrollable parent objects // and ending with this object itself. Vector<const AccessibilityObject*> objects; AccessibilityObject* parentObject; for (parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) { if (parentObject->getScrollableAreaIfScrollable()) objects.prepend(parentObject); } objects.append(this); // Start with the outermost scrollable (the main window) and try to scroll the // next innermost object to the given point. int offsetX = 0, offsetY = 0; IntPoint point = globalPoint; size_t levels = objects.size() - 1; for (size_t i = 0; i < levels; i++) { const AccessibilityObject* outer = objects[i]; const AccessibilityObject* inner = objects[i + 1]; ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable(); LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->elementRect() : inner->elementRect(); LayoutRect objectRect = innerRect; IntPoint scrollPosition = scrollableArea->scrollPosition(); // Convert the object rect into local coordinates. objectRect.move(offsetX, offsetY); if (!outer->isAccessibilityScrollView()) objectRect.move(scrollPosition.x(), scrollPosition.y()); int desiredX = computeBestScrollOffset( 0, objectRect.x(), objectRect.maxX(), objectRect.x(), objectRect.maxX(), point.x(), point.x()); int desiredY = computeBestScrollOffset( 0, objectRect.y(), objectRect.maxY(), objectRect.y(), objectRect.maxY(), point.y(), point.y()); outer->scrollTo(IntPoint(desiredX, desiredY)); if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) { // If outer object we just scrolled is a scroll view (main window or iframe) but the // inner object is not, keep track of the coordinate transformation to apply to // future nested calculations. scrollPosition = scrollableArea->scrollPosition(); offsetX -= (scrollPosition.x() + point.x()); offsetY -= (scrollPosition.y() + point.y()); point.move(scrollPosition.x() - innerRect.x(), scrollPosition.y() - innerRect.y()); } else if (inner->isAccessibilityScrollView()) { // Otherwise, if the inner object is a scroll view, reset the coordinate transformation. offsetX = 0; offsetY = 0; } } }
void FocusController::setContainingWindowIsVisible(bool containingWindowIsVisible) { if (m_containingWindowIsVisible == containingWindowIsVisible) return; m_containingWindowIsVisible = containingWindowIsVisible; FrameView* view = m_page->mainFrame()->view(); if (!view) return; contentAreaDidShowOrHide(view, containingWindowIsVisible); for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree()->traverseNext()) { FrameView* frameView = frame->view(); if (!frameView) continue; const HashSet<ScrollableArea*>* scrollableAreas = frameView->scrollableAreas(); if (!scrollableAreas) continue; for (HashSet<ScrollableArea*>::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; ASSERT(scrollableArea->scrollbarsCanBeActive() || m_page->shouldSuppressScrollbarAnimations()); contentAreaDidShowOrHide(scrollableArea, containingWindowIsVisible); } } }
IntPoint AXObject::maximumScrollOffset() const { ScrollableArea* area = getScrollableAreaIfScrollable(); if (!area) return IntPoint(); return IntPoint(area->maximumScrollPosition().x(), area->maximumScrollPosition().y()); }
InRegionScrollableArea::InRegionScrollableArea(WebPagePrivate* webPage, RenderLayer* layer) : m_webPage(webPage) , m_layer(layer) { ASSERT(webPage); ASSERT(layer); m_isNull = false; // FIXME: Add an ASSERT here as the 'layer' must be scrollable. RenderObject* layerRenderer = layer->renderer(); ASSERT(layerRenderer); if (layerRenderer->isRenderView()) { // #document case FrameView* view = toRenderView(layerRenderer)->frameView(); ASSERT(view); Frame* frame = view->frame(); ASSERT_UNUSED(frame, frame); m_scrollPosition = m_webPage->mapToTransformed(view->scrollPosition()); m_contentsSize = m_webPage->mapToTransformed(view->contentsSize()); m_viewportSize = m_webPage->mapToTransformed(view->visibleContentRect(false /*includeScrollbars*/)).size(); m_visibleWindowRect = m_webPage->mapToTransformed(m_webPage->getRecursiveVisibleWindowRect(view)); IntRect transformedWindowRect = IntRect(IntPoint::zero(), m_webPage->transformedViewportSize()); m_visibleWindowRect.intersect(transformedWindowRect); m_scrollsHorizontally = view->contentsWidth() > view->visibleWidth(); m_scrollsVertically = view->contentsHeight() > view->visibleHeight(); m_overscrollLimitFactor = 0.0; // FIXME eventually support overscroll } else { // RenderBox-based elements case (scrollable boxes (div's, p's, textarea's, etc)). RenderBox* box = m_layer->renderBox(); ASSERT(box); ASSERT(box->canBeScrolledAndHasScrollableArea()); ScrollableArea* scrollableArea = static_cast<ScrollableArea*>(m_layer); m_scrollPosition = m_webPage->mapToTransformed(scrollableArea->scrollPosition()); m_contentsSize = m_webPage->mapToTransformed(scrollableArea->contentsSize()); m_viewportSize = m_webPage->mapToTransformed(scrollableArea->visibleContentRect(false /*includeScrollbars*/)).size(); m_visibleWindowRect = m_layer->renderer()->absoluteClippedOverflowRect(); m_visibleWindowRect = m_layer->renderer()->frame()->view()->contentsToWindow(m_visibleWindowRect); IntRect visibleFrameWindowRect = m_webPage->getRecursiveVisibleWindowRect(m_layer->renderer()->frame()->view()); m_visibleWindowRect.intersect(visibleFrameWindowRect); m_visibleWindowRect = m_webPage->mapToTransformed(m_visibleWindowRect); IntRect transformedWindowRect = IntRect(IntPoint::zero(), m_webPage->transformedViewportSize()); m_visibleWindowRect.intersect(transformedWindowRect); m_scrollsHorizontally = box->scrollWidth() != box->clientWidth() && box->scrollsOverflowX(); m_scrollsVertically = box->scrollHeight() != box->clientHeight() && box->scrollsOverflowY(); m_overscrollLimitFactor = 0.0; // FIXME eventually support overscroll } }
void AXObject::setScrollOffset(const IntPoint& offset) const { ScrollableArea* area = getScrollableAreaIfScrollable(); if (!area) return; // TODO(bokan): This should potentially be a UserScroll. area->setScrollPosition(DoublePoint(offset.x(), offset.y()), ProgrammaticScroll); }
Region ScrollingCoordinator::computeNonFastScrollableRegion(const Frame* frame, const IntPoint& frameLocation) const { #if ENABLE(IOS_TOUCH_EVENTS) // On iOS, we use nonFastScrollableRegion to represent the region covered by elements with touch event handlers. ASSERT(frame->isMainFrame()); UNUSED_PARAM(frameLocation); Document* document = frame->document(); if (!document) return Region(); Vector<IntRect> touchRects; document->getTouchRects(touchRects); Region touchRegion; for (const auto& rect : touchRects) touchRegion.unite(rect); return touchRegion; #else Region nonFastScrollableRegion; FrameView* frameView = frame->view(); if (!frameView) return nonFastScrollableRegion; IntPoint offset = frameLocation; offset.moveBy(frameView->frameRect().location()); offset.move(0, frameView->topContentInset()); if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) { for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; // Composited scrollable areas can be scrolled off the main thread. if (scrollableArea->usesCompositedScrolling()) continue; IntRect box = scrollableArea->scrollableAreaBoundingBox(); box.moveBy(offset); nonFastScrollableRegion.unite(box); } } for (const auto& child : frameView->children()) { if (!child->isPluginViewBase()) continue; PluginViewBase* pluginViewBase = toPluginViewBase(child.get()); if (pluginViewBase->wantsWheelEvents()) nonFastScrollableRegion.unite(pluginViewBase->frameRect()); } for (Frame* subframe = frame->tree().firstChild(); subframe; subframe = subframe->tree().nextSibling()) nonFastScrollableRegion.unite(computeNonFastScrollableRegion(subframe, offset)); return nonFastScrollableRegion; #endif }
ScrollAnimatorNone::ScrollAnimatorNone(ScrollableArea& scrollableArea) : ScrollAnimator(scrollableArea) , m_horizontalData(this, &m_currentPosX, scrollableArea.visibleWidth()) , m_verticalData(this, &m_currentPosY, scrollableArea.visibleHeight()) , m_startTime(0) #if USE(REQUEST_ANIMATION_FRAME_TIMER) , m_animationTimer(*this, &ScrollAnimatorNone::requestAnimationTimerFired) #else , m_animationActive(false) #endif { }
void AsyncScrollingCoordinator::scrollableAreaScrollbarLayerDidChange(ScrollableArea& scrollableArea, ScrollbarOrientation orientation) { ASSERT(isMainThread()); ASSERT(m_page); if (&scrollableArea != static_cast<ScrollableArea*>(m_page->mainFrame().view())) return; if (orientation == VerticalScrollbar) scrollableArea.verticalScrollbarLayerDidChange(); else scrollableArea.horizontalScrollbarLayerDidChange(); }
void AXObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const { // Search up the parent chain until we find the first one that's scrollable. AXObject* scrollParent = parentObject(); ScrollableArea* scrollableArea = 0; while (scrollParent) { scrollableArea = scrollParent->getScrollableAreaIfScrollable(); if (scrollableArea && !scrollParent->isAXScrollView()) break; scrollParent = scrollParent->parentObject(); } if (!scrollParent || !scrollableArea) return; IntRect objectRect = pixelSnappedIntRect(elementRect()); IntPoint scrollPosition = scrollableArea->scrollPosition(); IntRect scrollVisibleRect = scrollableArea->visibleContentRect(); // Convert the object rect into local coordinates. if (!scrollParent->isWebArea()) { objectRect.moveBy(scrollPosition); objectRect.moveBy(-pixelSnappedIntRect(scrollParent->elementRect()).location()); } int desiredX = computeBestScrollOffset( scrollPosition.x(), objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), objectRect.x(), objectRect.maxX(), 0, scrollVisibleRect.width()); int desiredY = computeBestScrollOffset( scrollPosition.y(), objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(), objectRect.y(), objectRect.maxY(), 0, scrollVisibleRect.height()); scrollParent->setScrollOffset(IntPoint(desiredX, desiredY)); // Convert the subfocus into the coordinates of the scroll parent. IntRect newSubfocus = subfocus; IntRect newElementRect = pixelSnappedIntRect(elementRect()); IntRect scrollParentRect = pixelSnappedIntRect(scrollParent->elementRect()); newSubfocus.move(newElementRect.x(), newElementRect.y()); newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y()); // Recursively make sure the scroll parent itself is visible. if (scrollParent->parentObject()) scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus); }
GraphicsLayer* TopDocumentRootScrollerController::rootScrollerLayer() const { if (!m_globalRootScroller) return nullptr; ScrollableArea* area = RootScrollerUtil::scrollableAreaFor(*m_globalRootScroller); if (!area) return nullptr; GraphicsLayer* graphicsLayer = area->layerForScrolling(); // TODO(bokan): We should assert graphicsLayer here and // RootScrollerController should do whatever needs to happen to ensure // the root scroller gets composited. return graphicsLayer; }
// Tests that scrolls on the root frame scroll the visual viewport before // trying to scroll the layout viewport. TEST_F(RootFrameViewportTest, ViewportScrollOrder) { IntSize viewportSize(100, 100); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(200, 300)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); visualViewport->setScale(2); rootFrameViewport->setScrollOffset(ScrollOffset(40, 40), UserScroll); EXPECT_SIZE_EQ(ScrollOffset(40, 40), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); rootFrameViewport->setScrollOffset(ScrollOffset(60, 60), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(50, 50), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(10, 10), layoutViewport->getScrollOffset()); }
Region ScrollingCoordinator::computeNonFastScrollableRegion(Frame* frame, const IntPoint& frameLocation) { Region nonFastScrollableRegion; FrameView* frameView = frame->view(); if (!frameView) return nonFastScrollableRegion; IntPoint offset = frameLocation; offset.moveBy(frameView->frameRect().location()); if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) { for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; #if USE(ACCELERATED_COMPOSITING) // Composited scrollable areas can be scrolled off the main thread. if (scrollableArea->usesCompositedScrolling()) continue; #endif IntRect box = scrollableArea->scrollableAreaBoundingBox(); box.moveBy(offset); nonFastScrollableRegion.unite(box); } } if (const HashSet<RefPtr<Widget> >* children = frameView->children()) { for (HashSet<RefPtr<Widget> >::const_iterator it = children->begin(), end = children->end(); it != end; ++it) { if (!(*it)->isPluginViewBase()) continue; PluginViewBase* pluginViewBase = static_cast<PluginViewBase*>((*it).get()); if (pluginViewBase->wantsWheelEvents()) nonFastScrollableRegion.unite(pluginViewBase->frameRect()); } } FrameTree* tree = frame->tree(); for (Frame* subFrame = tree->firstChild(); subFrame; subFrame = subFrame->tree()->nextSibling()) nonFastScrollableRegion.unite(computeNonFastScrollableRegion(subFrame, offset)); return nonFastScrollableRegion; }
static Region computeNonFastScrollableRegion(FrameView* frameView) { Region nonFastScrollableRegion; HashSet<FrameView*> childFrameViews; for (HashSet<RefPtr<Widget> >::const_iterator it = frameView->children()->begin(), end = frameView->children()->end(); it != end; ++it) { if ((*it)->isFrameView()) childFrameViews.add(static_cast<FrameView*>(it->get())); } if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) { for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; // Check if this area can be scrolled at all. // If this scrollable area is a frame view that itself has scrollable areas, then we need to add it to the region. if ((!scrollableArea->horizontalScrollbar() || !scrollableArea->horizontalScrollbar()->enabled()) && (!scrollableArea->verticalScrollbar() || !scrollableArea->verticalScrollbar()->enabled()) && (!childFrameViews.contains(static_cast<FrameView*>(scrollableArea)) || !static_cast<FrameView*>(scrollableArea)->scrollableAreas())) continue; nonFastScrollableRegion.unite(scrollableArea->scrollableAreaBoundingBox()); } } return nonFastScrollableRegion; }
void Page::setShouldSuppressScrollbarAnimations(bool suppressAnimations) { if (suppressAnimations == m_suppressScrollbarAnimations) return; if (!suppressAnimations) { // If animations are not going to be suppressed anymore, then there is nothing to do here but // change the cached value. m_suppressScrollbarAnimations = suppressAnimations; return; } // On the other hand, if we are going to start suppressing animations, then we need to make sure we // finish any current scroll animations first. FrameView* view = mainFrame()->view(); if (!view) return; view->finishCurrentScrollAnimations(); for (Frame* frame = mainFrame(); frame; frame = frame->tree()->traverseNext()) { FrameView* frameView = frame->view(); if (!frameView) continue; const HashSet<ScrollableArea*>* scrollableAreas = frameView->scrollableAreas(); if (!scrollableAreas) continue; for (HashSet<ScrollableArea*>::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; ASSERT(scrollableArea->scrollbarsCanBeActive()); scrollableArea->finishCurrentScrollAnimations(); } } m_suppressScrollbarAnimations = suppressAnimations; }
// Tests that the setScrollOffset method works correctly with both viewports. TEST_F(RootFrameViewportTest, SetScrollOffset) { IntSize viewportSize(500, 500); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(1000, 2000)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); visualViewport->setScale(2); // Ensure that the visual viewport scrolls first. rootFrameViewport->setScrollOffset(ScrollOffset(100, 100), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(100, 100), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); // Scroll to the visual viewport's extent, the layout viewport should scroll // the remainder. rootFrameViewport->setScrollOffset(ScrollOffset(300, 400), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(250, 250), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 150), layoutViewport->getScrollOffset()); // Only the layout viewport should scroll further. Make sure it doesn't scroll // out of bounds. rootFrameViewport->setScrollOffset(ScrollOffset(780, 1780), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(250, 250), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(500, 1500), layoutViewport->getScrollOffset()); // Scroll all the way back. rootFrameViewport->setScrollOffset(ScrollOffset(0, 0), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(0, 0), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); }
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle) { if (scrollingElementStyle.scrollSnapType() == ScrollSnapType::None) { scrollableArea.clearHorizontalSnapOffsets(); scrollableArea.clearVerticalSnapOffsets(); return; } LayoutUnit viewWidth = scrollingElementBox.width(); LayoutUnit viewHeight = scrollingElementBox.height(); LayoutUnit scrollWidth = scrollingElementBox.scrollWidth(); LayoutUnit scrollHeight = scrollingElementBox.scrollHeight(); bool canComputeHorizontalOffsets = scrollWidth > 0 && viewWidth > 0 && viewWidth < scrollWidth; bool canComputeVerticalOffsets = scrollHeight > 0 && viewHeight > 0 && viewHeight < scrollHeight; if (!canComputeHorizontalOffsets) scrollableArea.clearHorizontalSnapOffsets(); if (!canComputeVerticalOffsets) scrollableArea.clearVerticalSnapOffsets(); if (!canComputeHorizontalOffsets && !canComputeVerticalOffsets) return; Vector<LayoutUnit> horizontalSnapOffsetSubsequence; Vector<LayoutUnit> verticalSnapOffsetSubsequence; bool scrollSnapPointsXUsesElements = styleUsesElements(ScrollEventAxis::Horizontal, scrollingElementStyle); bool scrollSnapPointsYUsesElements = styleUsesElements(ScrollEventAxis::Vertical , scrollingElementStyle); if (scrollSnapPointsXUsesElements || scrollSnapPointsYUsesElements) { bool shouldAddHorizontalChildOffsets = scrollSnapPointsXUsesElements && canComputeHorizontalOffsets; bool shouldAddVerticalChildOffsets = scrollSnapPointsYUsesElements && canComputeVerticalOffsets; appendChildSnapOffsets(scrollingElement, shouldAddHorizontalChildOffsets, horizontalSnapOffsetSubsequence, shouldAddVerticalChildOffsets, verticalSnapOffsetSubsequence); } if (scrollingElementStyle.scrollSnapPointsX() && !scrollSnapPointsXUsesElements && canComputeHorizontalOffsets) { for (auto& snapLength : scrollingElementStyle.scrollSnapPointsX()->offsets) horizontalSnapOffsetSubsequence.append(valueForLength(snapLength, viewWidth)); } if (scrollingElementStyle.scrollSnapPointsY() && !scrollSnapPointsYUsesElements && canComputeVerticalOffsets) { for (auto& snapLength : scrollingElementStyle.scrollSnapPointsY()->offsets) verticalSnapOffsetSubsequence.append(valueForLength(snapLength, viewHeight)); } if (canComputeHorizontalOffsets) { auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>(); updateFromStyle(*horizontalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Horizontal, viewWidth, scrollWidth, horizontalSnapOffsetSubsequence); scrollableArea.setHorizontalSnapOffsets(WTF::move(horizontalSnapOffsets)); } if (canComputeVerticalOffsets) { auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>(); updateFromStyle(*verticalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Vertical, viewHeight, scrollHeight, verticalSnapOffsetSubsequence); scrollableArea.setVerticalSnapOffsets(WTF::move(verticalSnapOffsets)); } }
static Region computeNonFastScrollableRegion(FrameView* frameView) { Region nonFastScrollableRegion; if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) { for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { ScrollableArea* scrollableArea = *it; // Check if this area can be scrolled at all. if ((!scrollableArea->horizontalScrollbar() || !scrollableArea->horizontalScrollbar()->enabled()) && (!scrollableArea->verticalScrollbar() || !scrollableArea->verticalScrollbar()->enabled())) continue; nonFastScrollableRegion.unite(scrollableArea->scrollableAreaBoundingBox()); } } return nonFastScrollableRegion; }
// Tests that the visible rect (i.e. visual viewport rect) is correctly // calculated, taking into account both viewports and page scale. TEST_F(RootFrameViewportTest, VisibleContentRect) { IntSize viewportSize(500, 401); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(1000, 2000)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); rootFrameViewport->setScrollOffset(ScrollOffset(100, 75), ProgrammaticScroll); EXPECT_POINT_EQ(IntPoint(100, 75), rootFrameViewport->visibleContentRect().location()); EXPECT_SIZE_EQ(ScrollOffset(500, 401), rootFrameViewport->visibleContentRect().size()); visualViewport->setScale(2); EXPECT_POINT_EQ(IntPoint(100, 75), rootFrameViewport->visibleContentRect().location()); EXPECT_SIZE_EQ(ScrollOffset(250, 201), rootFrameViewport->visibleContentRect().size()); }
// Test that the scrollIntoView correctly scrolls the main frame // and visual viewport such that the given rect is centered in the viewport. TEST_F(RootFrameViewportTest, ScrollIntoView) { IntSize viewportSize(100, 150); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(200, 300)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); // Test that the visual viewport is scrolled if the viewport has been // resized (as is the case when the ChromeOS keyboard comes up) but not // scaled. visualViewport->setViewportSize(IntSize(100, 100)); rootFrameViewport->scrollIntoView(LayoutRect(100, 250, 50, 50), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); EXPECT_SIZE_EQ(ScrollOffset(50, 150), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 50), visualViewport->getScrollOffset()); rootFrameViewport->scrollIntoView(LayoutRect(25, 75, 50, 50), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); EXPECT_SIZE_EQ(ScrollOffset(25, 75), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 0), visualViewport->getScrollOffset()); // Reset the visual viewport's size, scale the page, and repeat the test visualViewport->setViewportSize(IntSize(100, 150)); visualViewport->setScale(2); rootFrameViewport->setScrollOffset(ScrollOffset(), ProgrammaticScroll); rootFrameViewport->scrollIntoView(LayoutRect(50, 75, 50, 75), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 75), visualViewport->getScrollOffset()); rootFrameViewport->scrollIntoView(LayoutRect(190, 290, 10, 10), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); EXPECT_SIZE_EQ(ScrollOffset(100, 150), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 75), visualViewport->getScrollOffset()); // Scrolling into view the viewport rect itself should be a no-op. visualViewport->setViewportSize(IntSize(100, 100)); visualViewport->setScale(1.5f); visualViewport->setScrollOffset(ScrollOffset(0, 10), ProgrammaticScroll); layoutViewport->setScrollOffset(ScrollOffset(50, 50), ProgrammaticScroll); rootFrameViewport->setScrollOffset(rootFrameViewport->getScrollOffset(), ProgrammaticScroll); rootFrameViewport->scrollIntoView( LayoutRect(rootFrameViewport->visibleContentRect(ExcludeScrollbars)), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); EXPECT_SIZE_EQ(ScrollOffset(50, 50), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 10), visualViewport->getScrollOffset()); rootFrameViewport->scrollIntoView( LayoutRect(rootFrameViewport->visibleContentRect(ExcludeScrollbars)), ScrollAlignment::alignCenterAlways, ScrollAlignment::alignCenterAlways); EXPECT_SIZE_EQ(ScrollOffset(50, 50), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 10), visualViewport->getScrollOffset()); rootFrameViewport->scrollIntoView( LayoutRect(rootFrameViewport->visibleContentRect(ExcludeScrollbars)), ScrollAlignment::alignTopAlways, ScrollAlignment::alignTopAlways); EXPECT_SIZE_EQ(ScrollOffset(50, 50), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 10), visualViewport->getScrollOffset()); }
BOOL OpDocumentEdit::OnInputAction(OpInputAction* action) { DEBUG_CHECKER(TRUE); if (!GetDoc()) return FALSE; CheckLogTreeChanged(TRUE); switch (action->GetAction()) { case OpInputAction::ACTION_LOWLEVEL_PREFILTER_ACTION: { OpInputAction* child_action = action->GetChildAction(); if (child_action->IsKeyboardInvoked()) { DOM_EventType key_event = DOM_EVENT_NONE; switch (child_action->GetAction()) { case OpInputAction::ACTION_LOWLEVEL_KEY_DOWN: key_event = ONKEYDOWN; break; case OpInputAction::ACTION_LOWLEVEL_KEY_UP: key_event = ONKEYUP; break; case OpInputAction::ACTION_LOWLEVEL_KEY_PRESSED: key_event = ONKEYPRESS; break; } if (key_event != DOM_EVENT_NONE) { OP_BOOLEAN result = FramesDocument::SendDocumentKeyEvent(GetDoc(), NULL, key_event, child_action->GetActionKeyCode(), child_action->GetKeyValue(), child_action->GetPlatformKeyEventData(), child_action->GetShiftKeys(), child_action->GetKeyRepeat(), child_action->GetKeyLocation(), static_cast<unsigned>(DOM_KEYEVENT_DOCUMENTEDIT)); if (OpStatus::IsMemoryError(result)) g_memory_manager->RaiseCondition(OpStatus::ERR_NO_MEMORY); return (result == OpBoolean::IS_TRUE); } } return FALSE; } case OpInputAction::ACTION_GET_ACTION_STATE: { OpInputAction* child_action = action->GetChildAction(); OpInputAction::Action action_type = child_action->GetAction(); #ifdef USE_OP_CLIPBOARD BOOL force_enabling = FALSE; if (action_type == OpInputAction::ACTION_CUT || action_type == OpInputAction::ACTION_COPY || action_type == OpInputAction::ACTION_PASTE) { HTML_Element* target = m_layout_modifier.IsActive() ? m_layout_modifier.m_helm : (m_selection.HasContent() ? m_selection.GetStartElement() : m_caret.GetElement()); force_enabling = g_clipboard_manager->ForceEnablingClipboardAction(action_type, m_doc, target); } #endif // USE_OP_CLIPBOARD switch (action_type) { case OpInputAction::ACTION_UNDO: child_action->SetEnabled(!m_readonly && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_UNDO)); return TRUE; case OpInputAction::ACTION_REDO: child_action->SetEnabled(!m_readonly && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_REDO)); return TRUE; case OpInputAction::ACTION_TOGGLE_STYLE_BOLD: child_action->SetEnabled(!m_readonly && !m_plain_text_mode && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_BOLD)); return TRUE; case OpInputAction::ACTION_TOGGLE_STYLE_ITALIC: child_action->SetEnabled(!m_readonly && !m_plain_text_mode && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_ITALIC)); return TRUE; case OpInputAction::ACTION_TOGGLE_STYLE_UNDERLINE: child_action->SetEnabled(!m_readonly && !m_plain_text_mode && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_UNDERLINE)); return TRUE; #ifdef USE_OP_CLIPBOARD case OpInputAction::ACTION_CUT: child_action->SetEnabled(force_enabling || (!m_readonly && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_CUT))); return TRUE; case OpInputAction::ACTION_COPY: child_action->SetEnabled(force_enabling || queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_COPY)); return TRUE; case OpInputAction::ACTION_PASTE: child_action->SetEnabled(force_enabling || (!m_readonly && queryCommandEnabled(OP_DOCUMENT_EDIT_COMMAND_PASTE))); return TRUE; #endif // USE_OP_CLIPBOARD } return FALSE; } case OpInputAction::ACTION_LOWLEVEL_KEY_PRESSED: { OpKey::Code key = action->GetActionKeyCode(); #if defined(_DOCEDIT_DEBUG) && defined(USE_OP_CLIPBOARD) if (key == OP_KEY_F10) { m_doc->GetLogicalDocument()->GetRoot()->DumpDebugTree(); return TRUE; } if (key == OP_KEY_F11) { OpString text; GetTextHTML(text); OP_NEW_DBG("OpDocumentEdit::OnInputAction()", "docedit"); OP_DBG_LEN(text.CStr(), text.Length()); return TRUE; } if (key == OP_KEY_F9) { const uni_char* tmp = UNI_L("<table border=1><tr><td> </td><td> </td></tr><tr><td> </td><td> </td></tr></table>"); InsertTextHTML(tmp, uni_strlen(tmp)); return TRUE; } #endif // _DOCEDIT_DEBUG && USE_OP_CLIPBOARD */ ShiftKeyState modifiers = action->GetShiftKeys(); const uni_char *key_value = action->GetKeyValue(); BOOL is_key_wanted; switch (key) { #ifdef OP_KEY_ENTER_ENABLED case OP_KEY_ENTER: is_key_wanted = (modifiers & (SHIFTKEY_CTRL | SHIFTKEY_ALT | SHIFTKEY_META)) == 0; break; #endif // OP_KEY_ENTER_ENABLED #ifdef OP_KEY_TAB_ENABLED case OP_KEY_TAB: is_key_wanted = m_wants_tab && modifiers == SHIFTKEY_NONE; break; #endif // OP_KEY_TAB_ENABLED default: /* A keypress involving CTRL is ignored, but not if it involves ALT (or META.) Some key mappings use those modifier combinations for dead/accented keys (e.g., Ctrl-Alt-Z on a Polish keyboard.) */ if ((modifiers & SHIFTKEY_CTRL) != 0 && (modifiers & (SHIFTKEY_ALT | SHIFTKEY_META)) == 0) is_key_wanted = FALSE; else is_key_wanted = key_value != NULL && key_value[0] >= 32 && key_value[0] != 0x7f; } if (m_caret.GetElement() && is_key_wanted) { EditAction(action); return TRUE; } return FALSE; } case OpInputAction::ACTION_GO_TO_START: case OpInputAction::ACTION_GO_TO_END: case OpInputAction::ACTION_GO_TO_LINE_START: case OpInputAction::ACTION_GO_TO_LINE_END: case OpInputAction::ACTION_PAGE_UP: case OpInputAction::ACTION_PAGE_DOWN: case OpInputAction::ACTION_NEXT_CHARACTER: case OpInputAction::ACTION_PREVIOUS_CHARACTER: case OpInputAction::ACTION_NEXT_CHARACTER_SPATIAL: case OpInputAction::ACTION_PREVIOUS_CHARACTER_SPATIAL: case OpInputAction::ACTION_NEXT_WORD: case OpInputAction::ACTION_PREVIOUS_WORD: case OpInputAction::ACTION_NEXT_LINE: case OpInputAction::ACTION_PREVIOUS_LINE: case OpInputAction::ACTION_NEXT_LINE_SPATIAL: case OpInputAction::ACTION_PREVIOUS_LINE_SPATIAL: case OpInputAction::ACTION_RANGE_GO_TO_START: case OpInputAction::ACTION_RANGE_GO_TO_END: case OpInputAction::ACTION_RANGE_GO_TO_LINE_START: case OpInputAction::ACTION_RANGE_GO_TO_LINE_END: case OpInputAction::ACTION_RANGE_PAGE_UP: case OpInputAction::ACTION_RANGE_PAGE_DOWN: case OpInputAction::ACTION_RANGE_NEXT_CHARACTER: case OpInputAction::ACTION_RANGE_PREVIOUS_CHARACTER: case OpInputAction::ACTION_RANGE_NEXT_WORD: case OpInputAction::ACTION_RANGE_PREVIOUS_WORD: case OpInputAction::ACTION_RANGE_NEXT_LINE: case OpInputAction::ACTION_RANGE_PREVIOUS_LINE: case OpInputAction::ACTION_DELETE: case OpInputAction::ACTION_DELETE_WORD: case OpInputAction::ACTION_DELETE_TO_END_OF_LINE: case OpInputAction::ACTION_BACKSPACE: case OpInputAction::ACTION_BACKSPACE_WORD: case OpInputAction::ACTION_TOGGLE_STYLE_BOLD: case OpInputAction::ACTION_TOGGLE_STYLE_ITALIC: case OpInputAction::ACTION_TOGGLE_STYLE_UNDERLINE: case OpInputAction::ACTION_CONVERT_HEX_TO_UNICODE: { if (!m_caret.GetElement()) { // Code is unfortunately dependant on m_caret.m_helm. Will be fixed in the next redesign/rewrite. return FALSE; } EditAction(action); return TRUE; } case OpInputAction::ACTION_TOGGLE_OVERSTRIKE: { if (!m_caret.GetElement()) { // Code is unfortunately dependant on m_caret.m_helm. Will be fixed in the next redesign/rewrite. return FALSE; } m_caret.SetOverstrike(!GetDoc()->GetCaretPainter()->GetOverstrike()); return TRUE; } #ifdef SUPPORT_TEXT_DIRECTION case OpInputAction::ACTION_CHANGE_DIRECTION_TO_LTR: case OpInputAction::ACTION_CHANGE_DIRECTION_TO_RTL: if (!m_caret.GetElement()) { // Code is unfortunately dependant on m_caret.m_helm. Will be fixed in the next redesign/rewrite. return FALSE; } SetRTL(action->GetAction() == OpInputAction::ACTION_CHANGE_DIRECTION_TO_RTL); return TRUE; #endif // SUPPORT_TEXT_DIRECTION case OpInputAction::ACTION_UNDO: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_UNDO)); case OpInputAction::ACTION_REDO: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_REDO)); #ifdef USE_OP_CLIPBOARD case OpInputAction::ACTION_CUT: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_CUT)); case OpInputAction::ACTION_COPY: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_COPY)); case OpInputAction::ACTION_PASTE: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_PASTE)); case OpInputAction::ACTION_PASTE_MOUSE_SELECTION: { if (!m_caret.GetElement()) { // Code is unfortunately dependant on m_caret.m_helm. Will be fixed in the next redesign/rewrite. return FALSE; } # ifdef _X11_SELECTION_POLICY_ g_clipboard_manager->SetMouseSelectionMode(TRUE); # endif // _X11_SELECTION_POLICY_ if( g_clipboard_manager->HasText() ) { if( action->GetActionData() == 1 ) { Clear(); } HTML_Element* target = m_selection.HasContent() ? m_selection.GetStartElement() : m_caret.GetElement(); g_clipboard_manager->Paste(this, m_doc, target); } # ifdef _X11_SELECTION_POLICY_ g_clipboard_manager->SetMouseSelectionMode(FALSE); # endif // _X11_SELECTION_POLICY_ return TRUE; } #endif // USE_OP_CLIPBOARD case OpInputAction::ACTION_SELECT_ALL: return OpStatus::IsSuccess(execCommand(OP_DOCUMENT_EDIT_COMMAND_SELECTALL)); #ifdef DEBUG_DOCUMENT_EDIT case OpInputAction::ACTION_RELOAD: case OpInputAction::ACTION_FORCE_RELOAD: #endif case OpInputAction::ACTION_CLEAR: Clear(); return TRUE; case OpInputAction::ACTION_INSERT: if (!m_caret.GetElement()) { // Code is unfortunately dependant on m_caret.m_helm. Will be fixed in the next redesign/rewrite. return FALSE; } InsertText(action->GetActionDataString(), uni_strlen(action->GetActionDataString())); return TRUE; case OpInputAction::ACTION_FOCUS_NEXT_WIDGET: case OpInputAction::ACTION_FOCUS_PREVIOUS_WIDGET: if (m_caret.GetElement()) { HTML_Element *ec = GetEditableContainer(m_caret.GetElement()); if (ec && ec->Type() == HE_BODY) { FramesDocElm *fd_elm = m_doc->GetDocManager()->GetFrame(); if (fd_elm && fd_elm->IsInlineFrame()) { // This is a IFRAME so we should focus the parentdocument and call this action on it, so // focus will be moved within the parentdocument. fd_elm->GetCurrentDoc()->GetParentDoc()->GetVisualDevice()->SetFocus(FOCUS_REASON_KEYBOARD); OP_BOOLEAN res = fd_elm->GetCurrentDoc()->GetParentDoc()->OnInputAction(action); return res == OpBoolean::IS_TRUE; } } } return FALSE; case OpInputAction::ACTION_SCROLL_UP: case OpInputAction::ACTION_SCROLL_DOWN: case OpInputAction::ACTION_SCROLL_LEFT: case OpInputAction::ACTION_SCROLL_RIGHT: if (m_caret.GetElement()) { // Redirect theese actions to a scrollablecontainer so selectionscroll works. // The ScrollableContainer is not a parent or child of the DocumentEdits inputcontext. HTML_Element *ec = GetEditableContainer(m_caret.GetElement()); if (ec && ec->GetLayoutBox()) { ScrollableArea* sc = ec->GetLayoutBox()->GetScrollable(); if (sc) return sc->TriggerScrollInputAction(action); } } break; } return FALSE; }
InRegionScrollableArea::InRegionScrollableArea(WebPagePrivate* webPage, RenderLayer* layer) : m_webPage(webPage) , m_layer(layer) , m_document(0) , m_hasWindowVisibleRectCalculated(false) { ASSERT(webPage); ASSERT(layer); m_isNull = false; // Add a pointer to the enclosing document as the pointer to layer or node along the way may become invalid. if (m_layer->enclosingElement()) m_document = m_layer->enclosingElement()->document(); // FIXME: Add an ASSERT here as the 'layer' must be scrollable. RenderObject* layerRenderer = layer->renderer(); ASSERT(layerRenderer); if (layerRenderer->isRenderView()) { // #document case RenderView* renderView = toRenderView(layerRenderer); ASSERT(renderView); FrameView* view = toRenderView(layerRenderer)->frameView(); ASSERT(view); Frame* frame = view->frame(); ASSERT_UNUSED(frame, frame); m_scrollPosition = m_webPage->mapToTransformed(view->scrollPosition()); m_contentsSize = m_webPage->mapToTransformed(view->contentsSize()); m_viewportSize = m_webPage->mapToTransformed(view->visibleContentRect(ScrollableArea::ExcludeScrollbars)).size(); m_documentViewportRect = view->frameRect(); m_scrollsHorizontally = view->contentsWidth() > view->visibleWidth(); m_scrollsVertically = view->contentsHeight() > view->visibleHeight(); m_supportsCompositedScrolling = true; m_scrollTarget = InnerFrame; ASSERT(!m_cachedNonCompositedScrollableNode); m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(renderView->compositor()->scrollLayer()->platformLayer()); m_cachedCompositedScrollableLayer = renderView->compositor()->scrollLayer()->platformLayer(); } else { // RenderBox-based elements case (scrollable boxes (div's, p's, textarea's, etc)). RenderBox* box = m_layer->renderBox(); ASSERT(box); ASSERT(InRegionScrollerPrivate::canScrollRenderBox(box)); ScrollableArea* scrollableArea = static_cast<ScrollableArea*>(m_layer); m_scrollPosition = m_webPage->mapToTransformed(scrollableArea->scrollPosition()); m_contentsSize = m_webPage->mapToTransformed(scrollableArea->contentsSize()); m_viewportSize = m_webPage->mapToTransformed(scrollableArea->visibleContentRect(ScrollableArea::ExcludeScrollbars)).size(); m_documentViewportRect = enclosingIntRect(box->absoluteClippedOverflowRect()); m_scrollsHorizontally = box->scrollWidth() != box->clientWidth(); m_scrollsVertically = box->scrollHeight() != box->clientHeight(); // Check the overflow if its not an input field because overflow can be set to hidden etc. by the content. if (!box->node() || !box->node()->rendererIsEditable()) { m_scrollsHorizontally = m_scrollsHorizontally && box->scrollsOverflowX(); m_scrollsVertically = m_scrollsVertically && box->scrollsOverflowY(); } m_scrollTarget = BlockElement; // Both caches below are self-exclusive. if (m_layer->usesCompositedScrolling()) { m_forceContentToBeVerticallyScrollable = true; m_supportsCompositedScrolling = true; ASSERT(m_layer->backing()->hasScrollingLayer()); m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(m_layer->backing()->scrollingContentsLayer()->platformLayer()); m_cachedCompositedScrollableLayer = m_layer->backing()->scrollingContentsLayer()->platformLayer(); ASSERT(!m_cachedNonCompositedScrollableNode); } else { m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(m_layer->enclosingElement()); m_cachedNonCompositedScrollableNode = m_layer->enclosingElement(); ASSERT(!m_cachedCompositedScrollableLayer); } } }
// Tests that scrolling the viewport when the layout viewport is // !userInputScrollable (as happens when overflow:hidden is set) works // correctly, that is, the visual viewport can scroll, but not the layout. TEST_F(RootFrameViewportTest, UserInputScrollable) { IntSize viewportSize(100, 150); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(200, 300)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); visualViewport->setScale(2); // Disable just the layout viewport's horizontal scrolling, the // RootFrameViewport should remain scrollable overall. layoutViewport->setUserInputScrollable(false, true); visualViewport->setUserInputScrollable(true, true); EXPECT_TRUE(rootFrameViewport->userInputScrollable(HorizontalScrollbar)); EXPECT_TRUE(rootFrameViewport->userInputScrollable(VerticalScrollbar)); // Layout viewport shouldn't scroll since it's not horizontally scrollable, // but visual viewport should. rootFrameViewport->userScroll(ScrollByPixel, FloatSize(300, 0)); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 0), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 0), rootFrameViewport->getScrollOffset()); // Vertical scrolling should be unaffected. rootFrameViewport->userScroll(ScrollByPixel, FloatSize(0, 300)); EXPECT_SIZE_EQ(ScrollOffset(0, 150), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 75), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 225), rootFrameViewport->getScrollOffset()); // Try the same checks as above but for the vertical direction. // =============================================== rootFrameViewport->setScrollOffset(ScrollOffset(), ProgrammaticScroll); // Disable just the layout viewport's vertical scrolling, the // RootFrameViewport should remain scrollable overall. layoutViewport->setUserInputScrollable(true, false); visualViewport->setUserInputScrollable(true, true); EXPECT_TRUE(rootFrameViewport->userInputScrollable(HorizontalScrollbar)); EXPECT_TRUE(rootFrameViewport->userInputScrollable(VerticalScrollbar)); // Layout viewport shouldn't scroll since it's not vertically scrollable, // but visual viewport should. rootFrameViewport->userScroll(ScrollByPixel, FloatSize(0, 300)); EXPECT_SIZE_EQ(ScrollOffset(0, 0), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 75), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 75), rootFrameViewport->getScrollOffset()); // Horizontal scrolling should be unaffected. rootFrameViewport->userScroll(ScrollByPixel, FloatSize(300, 0)); EXPECT_SIZE_EQ(ScrollOffset(100, 0), layoutViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(50, 75), visualViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(150, 75), rootFrameViewport->getScrollOffset()); }
// Make sure scrolls using the scroll animator (scroll(), setScrollOffset()) // work correctly when one of the subviewports is explicitly scrolled without // using the // RootFrameViewport interface. TEST_F(RootFrameViewportTest, TestScrollAnimatorUpdatedBeforeScroll) { IntSize viewportSize(100, 150); RootFrameViewStub* layoutViewport = RootFrameViewStub::create(viewportSize, IntSize(200, 300)); VisualViewportStub* visualViewport = VisualViewportStub::create(viewportSize, viewportSize); ScrollableArea* rootFrameViewport = RootFrameViewport::create(*visualViewport, *layoutViewport); visualViewport->setScale(2); visualViewport->setScrollOffset(ScrollOffset(50, 75), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(50, 75), rootFrameViewport->getScrollOffset()); // If the scroll animator doesn't update, it will still think it's at (0, 0) // and so it may early exit. rootFrameViewport->setScrollOffset(ScrollOffset(0, 0), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(0, 0), rootFrameViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 0), visualViewport->getScrollOffset()); // Try again for userScroll() visualViewport->setScrollOffset(ScrollOffset(50, 75), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(50, 75), rootFrameViewport->getScrollOffset()); rootFrameViewport->userScroll(ScrollByPixel, FloatSize(-50, 0)); EXPECT_SIZE_EQ(ScrollOffset(0, 75), rootFrameViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 75), visualViewport->getScrollOffset()); // Make sure the layout viewport is also accounted for. rootFrameViewport->setScrollOffset(ScrollOffset(0, 0), ProgrammaticScroll); layoutViewport->setScrollOffset(ScrollOffset(100, 150), ProgrammaticScroll); EXPECT_SIZE_EQ(ScrollOffset(100, 150), rootFrameViewport->getScrollOffset()); rootFrameViewport->userScroll(ScrollByPixel, FloatSize(-100, 0)); EXPECT_SIZE_EQ(ScrollOffset(0, 150), rootFrameViewport->getScrollOffset()); EXPECT_SIZE_EQ(ScrollOffset(0, 150), layoutViewport->getScrollOffset()); }
InRegionScrollableArea::InRegionScrollableArea(WebPagePrivate* webPage, RenderLayer* layer) : m_webPage(webPage) , m_layer(layer) , m_document(0) , m_hasWindowVisibleRectCalculated(false) { ASSERT(webPage); ASSERT(layer); m_isNull = false; // Add a pointer to the enclosing document as the pointer to layer or node along the way may become invalid. if (m_layer->enclosingElement()) m_document = m_layer->enclosingElement()->document(); // FIXME: Add an ASSERT here as the 'layer' must be scrollable. RenderObject* layerRenderer = layer->renderer(); ASSERT(layerRenderer); if (layerRenderer->isRenderView()) { // #document case RenderView* renderView = toRenderView(layerRenderer); ASSERT(renderView); FrameView* view = toRenderView(layerRenderer)->frameView(); ASSERT(view); Frame* frame = view->frame(); ASSERT_UNUSED(frame, frame); const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor; m_scrollPosition = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatPoint(view->scrollPosition())); m_contentsSize = viewportAccessor->roundToPixelFromDocumentContents(Platform::FloatRect(Platform::FloatPoint::zero(), WebCore::FloatSize(view->contentsSize()))).size(); m_viewportSize = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatRect(view->visibleContentRect(ScrollableArea::ExcludeScrollbars))).size(); m_documentViewportRect = view->frameRect(); m_scrollsHorizontally = view->contentsWidth() > view->visibleWidth(); m_scrollsVertically = view->contentsHeight() > view->visibleHeight(); m_supportsCompositedScrolling = true; m_scrollTarget = InnerFrame; ASSERT(!m_cachedNonCompositedScrollableNode); m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(renderView->compositor()->scrollLayer()->platformLayer()); m_cachedCompositedScrollableLayer = renderView->compositor()->scrollLayer()->platformLayer(); } else { // RenderBox-based elements case (scrollable boxes (div's, p's, textarea's, etc)). RenderBox* box = m_layer->renderBox(); ASSERT(box); ASSERT(InRegionScrollerPrivate::canScrollRenderBox(box)); const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor; ScrollableArea* scrollableArea = static_cast<ScrollableArea*>(m_layer); m_scrollPosition = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatPoint(scrollableArea->scrollPosition())); m_contentsSize = viewportAccessor->roundToPixelFromDocumentContents(Platform::FloatRect(Platform::FloatPoint::zero(), WebCore::FloatSize(scrollableArea->contentsSize()))).size(); m_viewportSize = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatRect(scrollableArea->visibleContentRect(ScrollableArea::ExcludeScrollbars))).size(); m_documentViewportRect = enclosingIntRect(box->absoluteClippedOverflowRect()); m_scrollsHorizontally = box->scrollWidth() != box->clientWidth(); m_scrollsVertically = box->scrollHeight() != box->clientHeight(); // Check the overflow if its not an input field because overflow can be set to hidden etc. by the content. if (!DOMSupport::isShadowHostTextInputElement(box->node())) { m_scrollsHorizontally = m_scrollsHorizontally && box->scrollsOverflowX(); m_scrollsVertically = m_scrollsVertically && box->scrollsOverflowY(); } m_scrollTarget = BlockElement; // Both caches below are self-exclusive. if (m_layer->usesCompositedScrolling()) { m_forceContentToBeHorizontallyScrollable = m_scrollsHorizontally; m_forceContentToBeVerticallyScrollable = m_scrollsVertically; // Force content to be scrollable even if it doesn't need to scroll in either direction. if (!m_scrollsHorizontally && !m_scrollsVertically) { if (box->scrollsOverflowY()) m_forceContentToBeVerticallyScrollable = true; else if (box->scrollsOverflowX()) // If it's already forced scrollable vertically, don't force it to scroll horizontally m_forceContentToBeHorizontallyScrollable = true; } m_supportsCompositedScrolling = true; ASSERT(m_layer->backing()->hasScrollingLayer()); m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(m_layer->backing()->scrollingContentsLayer()->platformLayer()); m_cachedCompositedScrollableLayer = m_layer->backing()->scrollingContentsLayer()->platformLayer(); ASSERT(!m_cachedNonCompositedScrollableNode); } else { m_camouflagedCompositedScrollableLayer = reinterpret_cast<unsigned>(m_layer->enclosingElement()); m_cachedNonCompositedScrollableNode = m_layer->enclosingElement(); ASSERT(!m_cachedCompositedScrollableLayer); } } }
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle) { auto* scrollContainer = scrollingElement.renderer(); auto scrollSnapType = scrollingElementStyle.scrollSnapType(); if (!scrollContainer || scrollSnapType.strictness == ScrollSnapStrictness::None || scrollContainer->view().boxesWithScrollSnapPositions().isEmpty()) { scrollableArea.clearHorizontalSnapOffsets(); scrollableArea.clearVerticalSnapOffsets(); return; } Vector<LayoutUnit> verticalSnapOffsets; Vector<LayoutUnit> horizontalSnapOffsets; Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges; Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges; HashSet<float> seenVerticalSnapOffsets; HashSet<float> seenHorizontalSnapOffsets; bool hasHorizontalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::XAxis || scrollSnapType.axis == ScrollSnapAxis::Inline; bool hasVerticalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::YAxis || scrollSnapType.axis == ScrollSnapAxis::Block; auto maxScrollLeft = scrollingElementBox.scrollWidth() - scrollingElementBox.contentWidth(); auto maxScrollTop = scrollingElementBox.scrollHeight() - scrollingElementBox.contentHeight(); LayoutPoint containerScrollOffset(scrollingElementBox.scrollLeft(), scrollingElementBox.scrollTop()); // The bounds of the scrolling container's snap port, where the top left of the scrolling container's border box is the origin. auto scrollSnapPort = computeScrollSnapPortOrAreaRect(scrollingElementBox.paddingBoxRect(), scrollingElementStyle.scrollPadding(), InsetOrOutset::Inset); #if !LOG_DISABLED LOG(Scrolling, "Computing scroll snap offsets in snap port: %s", snapPortOrAreaToString(scrollSnapPort).utf8().data()); #endif for (auto* child : scrollContainer->view().boxesWithScrollSnapPositions()) { if (child->findEnclosingScrollableContainer() != scrollContainer) continue; // The bounds of the child element's snap area, where the top left of the scrolling container's border box is the origin. // The snap area is the bounding box of the child element's border box, after applying transformations. auto scrollSnapArea = LayoutRect(child->localToContainerQuad(FloatQuad(child->borderBoundingBox()), scrollingElement.renderBox()).boundingBox()); scrollSnapArea.moveBy(containerScrollOffset); scrollSnapArea = computeScrollSnapPortOrAreaRect(scrollSnapArea, child->style().scrollSnapMargin(), InsetOrOutset::Outset); #if !LOG_DISABLED LOG(Scrolling, " Considering scroll snap area: %s", snapPortOrAreaToString(scrollSnapArea).utf8().data()); #endif auto alignment = child->style().scrollSnapAlign(); if (hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None) { auto absoluteScrollOffset = clampTo<LayoutUnit>(computeScrollSnapAlignOffset(scrollSnapArea.x(), scrollSnapArea.width(), alignment.x) - computeScrollSnapAlignOffset(scrollSnapPort.x(), scrollSnapPort.width(), alignment.x), 0, maxScrollLeft); if (!seenHorizontalSnapOffsets.contains(absoluteScrollOffset)) { seenHorizontalSnapOffsets.add(absoluteScrollOffset); horizontalSnapOffsets.append(absoluteScrollOffset); } } if (hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None) { auto absoluteScrollOffset = clampTo<LayoutUnit>(computeScrollSnapAlignOffset(scrollSnapArea.y(), scrollSnapArea.height(), alignment.y) - computeScrollSnapAlignOffset(scrollSnapPort.y(), scrollSnapPort.height(), alignment.y), 0, maxScrollTop); if (!seenVerticalSnapOffsets.contains(absoluteScrollOffset)) { seenVerticalSnapOffsets.add(absoluteScrollOffset); verticalSnapOffsets.append(absoluteScrollOffset); } } } if (!horizontalSnapOffsets.isEmpty()) { adjustAxisSnapOffsetsForScrollExtent(horizontalSnapOffsets, maxScrollLeft); #if !LOG_DISABLED LOG(Scrolling, " => Computed horizontal scroll snap offsets: %s", snapOffsetsToString(horizontalSnapOffsets).utf8().data()); LOG(Scrolling, " => Computed horizontal scroll snap offset ranges: %s", snapOffsetRangesToString(horizontalSnapOffsetRanges).utf8().data()); #endif if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) computeAxisProximitySnapOffsetRanges(horizontalSnapOffsets, horizontalSnapOffsetRanges, scrollSnapPort.width()); scrollableArea.setHorizontalSnapOffsets(horizontalSnapOffsets); scrollableArea.setHorizontalSnapOffsetRanges(horizontalSnapOffsetRanges); } else scrollableArea.clearHorizontalSnapOffsets(); if (!verticalSnapOffsets.isEmpty()) { adjustAxisSnapOffsetsForScrollExtent(verticalSnapOffsets, maxScrollTop); #if !LOG_DISABLED LOG(Scrolling, " => Computed vertical scroll snap offsets: %s", snapOffsetsToString(verticalSnapOffsets).utf8().data()); LOG(Scrolling, " => Computed vertical scroll snap offset ranges: %s", snapOffsetRangesToString(verticalSnapOffsetRanges).utf8().data()); #endif if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) computeAxisProximitySnapOffsetRanges(verticalSnapOffsets, verticalSnapOffsetRanges, scrollSnapPort.height()); scrollableArea.setVerticalSnapOffsets(verticalSnapOffsets); scrollableArea.setVerticalSnapOffsetRanges(verticalSnapOffsetRanges); } else scrollableArea.clearVerticalSnapOffsets(); }