void ScrollAnimator::removeMainThreadScrollingReason() { if (WebLayer* scrollLayer = toWebLayer(getScrollableArea()->layerForScrolling())) { scrollLayer->clearMainThreadScrollingReasons( MainThreadScrollingReason::kHandlingScrollFromMainThread); } }
void ProgrammaticScrollAnimator::updateCompositorAnimations() { if (m_runState == RunState::PostAnimationCleanup) { // No special cleanup, simply reset animation state. We have this state // here because the state machine is shared with ScrollAnimator which // has to do some cleanup that requires the compositing state to be clean. return resetAnimationState(); } if (m_compositorAnimationId && m_runState != RunState::RunningOnCompositor) { // If the current run state is WaitingToSendToCompositor but we have a // non-zero compositor animation id, there's a currently running // compositor animation that needs to be removed here before the new // animation is added below. ASSERT(m_runState == RunState::WaitingToCancelOnCompositor || m_runState == RunState::WaitingToSendToCompositor); removeAnimation(); m_compositorAnimationId = 0; m_compositorAnimationGroupId = 0; if (m_runState == RunState::WaitingToCancelOnCompositor) { resetAnimationState(); return; } } if (m_runState == RunState::WaitingToSendToCompositor) { if (!m_compositorAnimationAttachedToElementId) reattachCompositorPlayerIfNeeded( getScrollableArea()->compositorAnimationTimeline()); bool sentToCompositor = false; if (!m_scrollableArea->shouldScrollOnMainThread()) { std::unique_ptr<CompositorAnimation> animation = CompositorAnimation::create( *m_animationCurve, CompositorTargetProperty::SCROLL_OFFSET, 0, 0); int animationId = animation->id(); int animationGroupId = animation->group(); if (addAnimation(std::move(animation))) { sentToCompositor = true; m_runState = RunState::RunningOnCompositor; m_compositorAnimationId = animationId; m_compositorAnimationGroupId = animationGroupId; } } if (!sentToCompositor) { m_runState = RunState::RunningOnMainThread; m_animationCurve->setInitialValue( compositorOffsetFromBlinkOffset(m_scrollableArea->getScrollOffset())); if (!m_scrollableArea->scheduleAnimation()) { notifyOffsetChanged(m_targetOffset); resetAnimationState(); } } } }
bool ScrollAnimator::registerAndScheduleAnimation() { getScrollableArea()->registerForAnimation(); if (!m_scrollableArea->scheduleAnimation()) { scrollToOffsetWithoutAnimation(m_targetOffset); resetAnimationState(); return false; } return true; }
void ScrollableAreaPainter::paintScrollCorner( GraphicsContext& context, const IntPoint& paintOffset, const CullRect& adjustedCullRect) { IntRect absRect = getScrollableArea().scrollCornerRect(); if (absRect.isEmpty()) return; absRect.moveBy(paintOffset); if (getScrollableArea().scrollCorner()) { if (!adjustedCullRect.intersectsCullRect(absRect)) return; ScrollbarPainter::paintIntoRect(*getScrollableArea().scrollCorner(), context, paintOffset, LayoutRect(absRect)); return; } // We don't want to paint white if we have overlay scrollbars, since we need // to see what is behind it. if (getScrollableArea().hasOverlayScrollbars()) return; if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( context, getScrollableArea().box(), DisplayItem::kScrollbarCorner)) return; LayoutObjectDrawingRecorder recorder(context, getScrollableArea().box(), DisplayItem::kScrollbarCorner, absRect); context.fillRect(absRect, Color::white); }
void ScrollableAreaPainter::paintResizer(GraphicsContext& context, const IntPoint& paintOffset, const CullRect& cullRect) { if (getScrollableArea().box().style()->resize() == RESIZE_NONE) return; IntRect absRect = getScrollableArea().resizerCornerRect( getScrollableArea().box().pixelSnappedBorderBoxRect(), ResizerForPointer); if (absRect.isEmpty()) return; absRect.moveBy(paintOffset); if (getScrollableArea().resizer()) { if (!cullRect.intersectsCullRect(absRect)) return; ScrollbarPainter::paintIntoRect(*getScrollableArea().resizer(), context, paintOffset, LayoutRect(absRect)); return; } if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( context, getScrollableArea().box(), DisplayItem::kResizer)) return; LayoutObjectDrawingRecorder recorder(context, getScrollableArea().box(), DisplayItem::kResizer, absRect); drawPlatformResizerImage(context, absRect); // Draw a frame around the resizer (1px grey line) if there are any scrollbars // present. Clipping will exclude the right and bottom edges of this frame. if (!getScrollableArea().hasOverlayScrollbars() && getScrollableArea().hasScrollbar()) { GraphicsContextStateSaver stateSaver(context); context.clip(absRect); IntRect largerCorner = absRect; largerCorner.setSize( IntSize(largerCorner.width() + 1, largerCorner.height() + 1)); context.setStrokeColor(Color(217, 217, 217)); context.setStrokeThickness(1.0f); context.setFillColor(Color::transparent); context.drawRect(largerCorner); } }
bool ScrollableAreaPainter::overflowControlsIntersectRect( const CullRect& cullRect) const { const IntRect borderBox = getScrollableArea().box().pixelSnappedBorderBoxRect(); if (cullRect.intersectsCullRect( getScrollableArea().rectForHorizontalScrollbar(borderBox))) return true; if (cullRect.intersectsCullRect( getScrollableArea().rectForVerticalScrollbar(borderBox))) return true; if (cullRect.intersectsCullRect(getScrollableArea().scrollCornerRect())) return true; if (cullRect.intersectsCullRect( getScrollableArea().resizerCornerRect(borderBox, ResizerForPointer))) return true; return false; }
void ScrollableAreaPainter::drawPlatformResizerImage( GraphicsContext& context, IntRect resizerCornerRect) { float deviceScaleFactor = blink::deviceScaleFactor(getScrollableArea().box().frame()); RefPtr<Image> resizeCornerImage; IntSize cornerResizerSize; if (deviceScaleFactor >= 2) { DEFINE_STATIC_REF(Image, resizeCornerImageHiRes, (Image::loadPlatformResource("textAreaResizeCorner@2x"))); resizeCornerImage = resizeCornerImageHiRes; cornerResizerSize = resizeCornerImage->size(); cornerResizerSize.scale(0.5f); } else { DEFINE_STATIC_REF(Image, resizeCornerImageLoRes, (Image::loadPlatformResource("textAreaResizeCorner"))); resizeCornerImage = resizeCornerImageLoRes; cornerResizerSize = resizeCornerImage->size(); } if (getScrollableArea() .box() .shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { context.save(); context.translate(resizerCornerRect.x() + cornerResizerSize.width(), resizerCornerRect.y() + resizerCornerRect.height() - cornerResizerSize.height()); context.scale(-1.0, 1.0); context.drawImage(resizeCornerImage.get(), IntRect(IntPoint(), cornerResizerSize)); context.restore(); return; } IntRect imageRect(resizerCornerRect.maxXMaxYCorner() - cornerResizerSize, cornerResizerSize); context.drawImage(resizeCornerImage.get(), imageRect); }
void ScrollAnimator::tickAnimation(double monotonicTime) { if (m_runState != RunState::RunningOnMainThread) return; TRACE_EVENT0("blink", "ScrollAnimator::tickAnimation"); double elapsedTime = monotonicTime - m_startTime; bool isFinished = (elapsedTime > m_animationCurve->duration()); ScrollOffset offset = blinkOffsetFromCompositorOffset( isFinished ? m_animationCurve->targetValue() : m_animationCurve->getValue(elapsedTime)); offset = m_scrollableArea->clampScrollOffset(offset); m_currentOffset = offset; if (isFinished) m_runState = RunState::PostAnimationCleanup; else getScrollableArea()->scheduleAnimation(); TRACE_EVENT0("blink", "ScrollAnimator::notifyOffsetChanged"); notifyOffsetChanged(); }
void ScrollableAreaPainter::paintOverflowControls( GraphicsContext& context, const IntPoint& paintOffset, const CullRect& cullRect, bool paintingOverlayControls) { // Don't do anything if we have no overflow. if (!getScrollableArea().box().hasOverflowClip()) return; IntPoint adjustedPaintOffset = paintOffset; if (paintingOverlayControls) adjustedPaintOffset = getScrollableArea().cachedOverlayScrollbarOffset(); CullRect adjustedCullRect(cullRect, -adjustedPaintOffset); // Overlay scrollbars paint in a second pass through the layer tree so that // they will paint on top of everything else. If this is the normal painting // pass, paintingOverlayControls will be false, and we should just tell the // root layer that there are overlay scrollbars that need to be painted. That // will cause the second pass through the layer tree to run, and we'll paint // the scrollbars then. In the meantime, cache tx and ty so that the second // pass doesn't need to re-enter the LayoutTree to get it right. if (getScrollableArea().hasOverlayScrollbars() && !paintingOverlayControls) { getScrollableArea().setCachedOverlayScrollbarOffset(paintOffset); // It's not necessary to do the second pass if the scrollbars paint into // layers. if ((getScrollableArea().horizontalScrollbar() && getScrollableArea().layerForHorizontalScrollbar()) || (getScrollableArea().verticalScrollbar() && getScrollableArea().layerForVerticalScrollbar())) return; if (!overflowControlsIntersectRect(adjustedCullRect)) return; LayoutView* layoutView = getScrollableArea().box().view(); PaintLayer* paintingRoot = getScrollableArea().layer()->enclosingLayerWithCompositedLayerMapping( IncludeSelf); if (!paintingRoot) paintingRoot = layoutView->layer(); paintingRoot->setContainsDirtyOverlayScrollbars(true); return; } // This check is required to avoid painting custom CSS scrollbars twice. if (paintingOverlayControls && !getScrollableArea().hasOverlayScrollbars()) return; { Optional<ScopedPaintChunkProperties> scopedTransformProperty; if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { const auto* objectProperties = getScrollableArea().box().paintProperties(); if (objectProperties && objectProperties->scrollbarPaintOffset()) { PaintChunkProperties properties( context.getPaintController().currentPaintChunkProperties()); properties.transform = objectProperties->scrollbarPaintOffset(); scopedTransformProperty.emplace( context.getPaintController(), getScrollableArea().box(), DisplayItem::kScrollOverflowControls, properties); } } if (getScrollableArea().horizontalScrollbar() && !getScrollableArea().layerForHorizontalScrollbar()) { TransformRecorder translateRecorder( context, *getScrollableArea().horizontalScrollbar(), AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y())); getScrollableArea().horizontalScrollbar()->paint(context, adjustedCullRect); } if (getScrollableArea().verticalScrollbar() && !getScrollableArea().layerForVerticalScrollbar()) { TransformRecorder translateRecorder( context, *getScrollableArea().verticalScrollbar(), AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y())); getScrollableArea().verticalScrollbar()->paint(context, adjustedCullRect); } } if (getScrollableArea().layerForScrollCorner()) return; // We fill our scroll corner with white if we have a scrollbar that doesn't // run all the way up to the edge of the box. paintScrollCorner(context, adjustedPaintOffset, cullRect); // Paint our resizer last, since it sits on top of the scroll corner. paintResizer(context, adjustedPaintOffset, cullRect); }
void ScrollAnimator::updateCompositorAnimations() { ScrollAnimatorCompositorCoordinator::updateCompositorAnimations(); if (m_runState == RunState::PostAnimationCleanup) { postAnimationCleanupAndReset(); return; } if (m_runState == RunState::WaitingToCancelOnCompositor) { DCHECK(m_compositorAnimationId); abortAnimation(); postAnimationCleanupAndReset(); return; } if (m_runState == RunState::RunningOnCompositorButNeedsTakeover) { // The call to ::takeOverCompositorAnimation aborted the animation and // put us in this state. The assumption is that takeOver is called // because a main thread scrolling reason is added, and simply trying // to ::sendAnimationToCompositor will fail and we will run on the main // thread. resetAnimationIds(); m_runState = RunState::WaitingToSendToCompositor; } if (m_runState == RunState::RunningOnCompositorButNeedsUpdate || m_runState == RunState::WaitingToCancelOnCompositorButNewScroll || m_runState == RunState::RunningOnCompositorButNeedsAdjustment) { // Abort the running animation before a new one with an updated // target is added. abortAnimation(); resetAnimationIds(); if (m_runState != RunState::RunningOnCompositorButNeedsAdjustment) { // When in RunningOnCompositorButNeedsAdjustment, the call to // ::adjustScrollOffsetAnimation should have made the necessary // adjustment to the curve. m_animationCurve->updateTarget( m_timeFunction() - m_startTime, compositorOffsetFromBlinkOffset(m_targetOffset)); } if (m_runState == RunState::WaitingToCancelOnCompositorButNewScroll) { m_animationCurve->setInitialValue( compositorOffsetFromBlinkOffset(currentOffset())); } m_runState = RunState::WaitingToSendToCompositor; } if (m_runState == RunState::WaitingToSendToCompositor) { if (!m_compositorAnimationAttachedToElementId) reattachCompositorPlayerIfNeeded( getScrollableArea()->compositorAnimationTimeline()); if (!m_animationCurve) createAnimationCurve(); bool runningOnMainThread = false; bool sentToCompositor = sendAnimationToCompositor(); if (!sentToCompositor) { runningOnMainThread = registerAndScheduleAnimation(); if (runningOnMainThread) m_runState = RunState::RunningOnMainThread; } // Main thread should deal with the scroll animations it started. if (sentToCompositor || runningOnMainThread) addMainThreadScrollingReason(); else removeMainThreadScrollingReason(); } }