Cherry-pick 306838@main (8e1ad9587c9f). https://bugs.webkit.org/show_bug.cgi?id=305561

    [GTK][WPE][Coordinated Graphics] Paint scrollbars in the scrolling thread for async scrolling
    https://bugs.webkit.org/show_bug.cgi?id=305561

    Reviewed by Carlos Garcia Campos.

    Scrollbars weren't updated for wheel event async scrolling while the main
    thread was blocked because scrollbar layers were painted in the main thread.

    Paint scrollbars in the scrolling thread for composited layers. Extracted the
    scrollbar painting code from ScrollbarThemeAdwaita.cpp to AdwaitaScrollbarPainter.cpp
    to make it thread-safe.

    Added a new ScrollbarsController implementation ScrollbarsControllerCoordinated
    based on Cocoa's RemoteScrollbarsController. Since
    WebChromeClient::ensureScrollbarsController dynamically switches between
    ScrollbarsControllerCoordinated and ScrollbarsControllerGeneric, they should
    work properly even if it is created after Scrollbar is created. Fixed
    ScrollbarsControllerGeneric to check scrollbars are already created.

    PlayStation port is using Coordinated Graphics, but not using
    ScrollbarThemeAdwaita. Added a new macro
    USE_COORDINATED_GRAPHICS_ASYNC_SCROLLBAR to preserve the original behavior.

    * Source/WTF/wtf/PlatformUse.h:
    * Source/WebCore/Headers.cmake:
    * Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp:
    (WebCore::AsyncScrollingCoordinator::setHoveredAndPressedScrollbarParts):
    (WebCore::AsyncScrollingCoordinator::setScrollbarOpacity):
    (WebCore::AsyncScrollingCoordinator::setScrollingNodeScrollableAreaGeometry):
    * Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h:
    * Source/WebCore/page/scrolling/ScrollingCoordinator.h:
    (WebCore::ScrollingCoordinator::setHoveredAndPressedScrollbarParts):
    (WebCore::ScrollingCoordinator::setScrollbarOpacity):
    * Source/WebCore/page/scrolling/ScrollingStateNode.h:
    * Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp:
    (WebCore::ScrollingStateScrollingNode::ScrollingStateScrollingNode):
    (WebCore::ScrollingStateScrollingNode::setScrollbarOpacity):
    * Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h:
    (WebCore::ScrollingStateScrollingNode::verticalScrollerImp const):
    (WebCore::ScrollingStateScrollingNode::horizontalScrollerImp const):
    (WebCore::ScrollingStateScrollingNode::scrollbarOpacity const):
    * Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.cpp: Added.
    (WebCore::ScrollerCoordinated::ScrollerCoordinated):
    (WebCore::ScrollerCoordinated::setScrollerImp):
    (WebCore::ScrollerCoordinated::setHostLayer):
    (WebCore::ScrollerCoordinated::updateValues):
    (WebCore::ScrollerCoordinated::setHoveredAndPressedParts):
    (WebCore::ScrollerCoordinated::setEnabled):
    (WebCore::ScrollerCoordinated::setOverlayScrollbarEnabled):
    (WebCore::ScrollerCoordinated::setUseDarkAppearance):
    (WebCore::ScrollerCoordinated::setOpacity):
    (WebCore::ScrollerCoordinated::setScrollbarLayoutDirection):
    * Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.h: Added.
    * Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.cpp: Added.
    (WebCore::ScrollerPairCoordinated::ScrollerPairCoordinated):
    (WebCore::ScrollerPairCoordinated::updateValues):
    (WebCore::ScrollerPairCoordinated::valuesForOrientation):
    * Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.h: Added.
    (WebCore::ScrollerPairCoordinated::create):
    (WebCore::ScrollerPairCoordinated::verticalScroller):
    (WebCore::ScrollerPairCoordinated::horizontalScroller):
    * Source/WebCore/page/scrolling/coordinated/ScrollingStateScrollingNodeCoordinated.cpp: Copied from Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h.
    (WebCore::ScrollingStateScrollingNode::setScrollerImpsFromScrollbars):
    * Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.cpp:
    (WebCore::ScrollingTreeScrollingNodeDelegateCoordinated::ScrollingTreeScrollingNodeDelegateCoordinated):
    (WebCore::ScrollingTreeScrollingNodeDelegateCoordinated::updateVisibleLengths):
    (WebCore::ScrollingTreeScrollingNodeDelegateCoordinated::updateFromStateNode):
    * Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h:
    * Source/WebCore/platform/Adwaita.cmake:
    * Source/WebCore/platform/ScrollableArea.cpp:
    (WebCore::ScrollableArea::scrollbarOpacity const):
    * Source/WebCore/platform/ScrollableArea.h:
    * Source/WebCore/platform/Scrollbar.cpp:
    (WebCore::Scrollbar::setHoveredPart):
    (WebCore::Scrollbar::setPressedPart):
    (WebCore::Scrollbar::supportsUpdateOnSecondaryThread const):
    * Source/WebCore/platform/ScrollbarTheme.h:
    (WebCore::ScrollbarTheme::isScrollbarThemeAdwaita const):
    * Source/WebCore/platform/ScrollbarsController.h:
    (WebCore::ScrollbarsController::isScrollbarsControllerCoordinated const):
    (WebCore::ScrollbarsController::hoveredPartChanged):
    (WebCore::ScrollbarsController::pressedPartChanged):
    (WebCore::ScrollbarsController::scrollbarOpacityChanged):
    * Source/WebCore/platform/SourcesAdwaita.txt:
    * Source/WebCore/platform/TextureMapper.cmake:
    * Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.cpp: Copied from Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp.
    (WebCore::AdwaitaScrollbarPainter::paint):
    * Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.h: Added.
    * Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp:
    (WebCore::ScrollbarThemeAdwaita::paint):
    (WebCore::ScrollbarThemeAdwaita::scrollerImpForScrollbar):
    (): Deleted.
    * Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.h:
    (isType):
    * Source/WebCore/platform/adwaita/ScrollerImpAdwaita.h: Copied from Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h.
    (WebCore::ScrollerImpAdwaita::paint):
    * Source/WebCore/platform/generic/ScrollbarsControllerGeneric.cpp:
    (WebCore::ScrollbarsControllerGeneric::ScrollbarsControllerGeneric):
    (WebCore::ScrollbarsControllerGeneric::updateOverlayScrollbarsOpacity):
    * Source/WebCore/platform/generic/ScrollbarsControllerGeneric.h:
    * Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp:
    (WebCore::CoordinatedPlatformLayer::setContentsScrollbarImageForScrolling):
    (WebCore::CoordinatedPlatformLayer::flushCompositingState):
    * Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h:
    * Source/WebKit/SourcesGTK.txt:
    * Source/WebKit/SourcesWPE.txt:
    * Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp:
    (WebKit::WebChromeClient::ensureScrollbarsController const):
    * Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h:
    * Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp: Added.
    (WebKit::ScrollbarsControllerCoordinated::ScrollbarsControllerCoordinated):
    (WebKit::ScrollbarsControllerCoordinated::scrollbarLayoutDirectionChanged):
    (WebKit::ScrollbarsControllerCoordinated::shouldDrawIntoScrollbarLayer const):
    (WebKit::ScrollbarsControllerCoordinated::updateScrollbarEnabledState):
    (WebKit::ScrollbarsControllerCoordinated::updateScrollbarStyle):
    (WebKit::ScrollbarsControllerCoordinated::scrollbarOpacityChanged):
    (WebKit::ScrollbarsControllerCoordinated::hoveredPartChanged):
    (WebKit::ScrollbarsControllerCoordinated::pressedPartChanged):
    * Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.h: Added.
    (isType):

    Canonical link: https://commits.webkit.org/306838@main
diff --git a/Source/WTF/wtf/PlatformUse.h b/Source/WTF/wtf/PlatformUse.h
index e4b143c..a3ba67c 100644
--- a/Source/WTF/wtf/PlatformUse.h
+++ b/Source/WTF/wtf/PlatformUse.h
@@ -83,6 +83,10 @@
 #define USE_SOUP 1
 #endif
 
+#if USE(COORDINATED_GRAPHICS) && USE(THEME_ADWAITA)
+#define USE_COORDINATED_GRAPHICS_ASYNC_SCROLLBAR 1
+#endif
+
 #if PLATFORM(COCOA)
 #define USE_CF 1
 #endif
diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake
index 730b562..8ce17c1 100644
--- a/Source/WebCore/Headers.cmake
+++ b/Source/WebCore/Headers.cmake
@@ -2522,6 +2522,8 @@
     platform/gamepad/SharedGamepadValue.h
     platform/gamepad/ShouldRequireExplicitConsentForGamepadAccess.h
 
+    platform/generic/ScrollbarsControllerGeneric.h
+
     platform/graphics/AV1Utilities.h
     platform/graphics/AlphaPremultiplication.h
     platform/graphics/AnimationFrameRate.h
diff --git a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp
index 212182c..70ad593 100644
--- a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp
+++ b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp
@@ -476,6 +476,26 @@
     stateNode->setScrollbarColor(scrollbarColor);
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+void AsyncScrollingCoordinator::setHoveredAndPressedScrollbarParts(ScrollableArea& scrollableArea)
+{
+    ASSERT(isMainThread());
+    ASSERT(page());
+    auto stateNode = dynamicDowncast<ScrollingStateScrollingNode>(stateNodeForScrollableArea(scrollableArea));
+    if (!stateNode)
+        return;
+    ScrollbarHoverState state;
+    if (RefPtr scrollbar = scrollableArea.verticalScrollbar()) {
+        state.hoveredPartInVerticalScrollbar = scrollbar->hoveredPart();
+        state.pressedPartInVerticalScrollbar = scrollbar->pressedPart();
+    }
+    if (RefPtr scrollbar = scrollableArea.horizontalScrollbar()) {
+        state.hoveredPartInHorizontalScrollbar = scrollbar->hoveredPart();
+        state.pressedPartInHorizontalScrollbar = scrollbar->pressedPart();
+    }
+    stateNode->setScrollbarHoverState(WTF::move(state));
+}
+#else
 void AsyncScrollingCoordinator::setMouseIsOverScrollbar(Scrollbar* scrollbar, bool isOverScrollbar)
 {
     ASSERT(isMainThread());
@@ -485,6 +505,7 @@
         return;
     stateNode->setScrollbarHoverState({ scrollbar->orientation() == ScrollbarOrientation::Vertical ? false : isOverScrollbar, scrollbar->orientation() == ScrollbarOrientation::Vertical ? isOverScrollbar : false });
 }
+#endif
 
 void AsyncScrollingCoordinator::setMouseIsOverContentArea(ScrollableArea& scrollableArea, bool isOverContentArea)
 {
@@ -521,6 +542,18 @@
     stateNode->setLayerHostingContextIdentifier(identifier);
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+void AsyncScrollingCoordinator::setScrollbarOpacity(ScrollableArea& scrollableArea)
+{
+    ASSERT(isMainThread());
+    ASSERT(page());
+    auto stateNode = dynamicDowncast<ScrollingStateScrollingNode>(stateNodeForScrollableArea(scrollableArea));
+    if (!stateNode)
+        return;
+    stateNode->setScrollbarOpacity(scrollableArea.scrollbarOpacity());
+}
+#endif
+
 void AsyncScrollingCoordinator::setScrollbarEnabled(Scrollbar& scrollbar)
 {
     ASSERT(isMainThread());
@@ -1104,6 +1137,9 @@
     scrollingNode->setScrollableAreaSize(scrollableArea.visibleSize());
 
     scrollingNode->setUseDarkAppearanceForScrollbars(scrollableArea.useDarkAppearanceForScrollbars());
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    scrollingNode->setScrollbarOpacity(scrollableArea.scrollbarOpacity());
+#endif
 
     ScrollableAreaParameters scrollParameters;
     scrollParameters.horizontalScrollElasticity = scrollableArea.horizontalOverscrollBehavior() == OverscrollBehavior::None ? ScrollElasticity::None : scrollableArea.horizontalScrollElasticity();
diff --git a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h
index fd2d2e2..0ccefea 100644
--- a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h
+++ b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h
@@ -83,6 +83,9 @@
     WEBCORE_EXPORT void setMouseIsOverContentArea(ScrollableArea&, bool) override;
     WEBCORE_EXPORT void setMouseMovedInContentArea(ScrollableArea&) override;
     WEBCORE_EXPORT void setLayerHostingContextIdentifierForFrameHostingNode(ScrollingNodeID, std::optional<LayerHostingContextIdentifier>) override;
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    void setScrollbarOpacity(ScrollableArea&) override;
+#endif
     LocalFrameView* frameViewForScrollingNode(LocalFrame& localMainFrame, std::optional<ScrollingNodeID>) const;
 
     WEBCORE_EXPORT ScrollingStateTree& ensureScrollingStateTreeForRootFrameID(FrameIdentifier);
@@ -189,7 +192,11 @@
     void wheelEventScrollDidEndForNode(ScrollingNodeID);
     void notifyScrollableAreasForScrollEnd(ScrollingNodeID);
     
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    void setHoveredAndPressedScrollbarParts(ScrollableArea&) override;
+#else
     WEBCORE_EXPORT void setMouseIsOverScrollbar(Scrollbar*, bool isOverScrollbar) override;
+#endif
     WEBCORE_EXPORT void setScrollbarEnabled(Scrollbar&) override;
     WEBCORE_EXPORT void setScrollbarWidth(ScrollableArea&, ScrollbarWidth) override;
 
diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.h b/Source/WebCore/page/scrolling/ScrollingCoordinator.h
index 5e91683..4ebf091 100644
--- a/Source/WebCore/page/scrolling/ScrollingCoordinator.h
+++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.h
@@ -215,12 +215,19 @@
 
     WEBCORE_EXPORT virtual void setMouseIsOverContentArea(ScrollableArea&, bool) { }
     WEBCORE_EXPORT virtual void setMouseMovedInContentArea(ScrollableArea&) { }
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    virtual void setHoveredAndPressedScrollbarParts(ScrollableArea&) { }
+#else
     WEBCORE_EXPORT virtual void setMouseIsOverScrollbar(Scrollbar*, bool) { }
+#endif
     WEBCORE_EXPORT virtual void setScrollbarEnabled(Scrollbar&) { }
     WEBCORE_EXPORT virtual void setLayerHostingContextIdentifierForFrameHostingNode(ScrollingNodeID, std::optional<LayerHostingContextIdentifier>) { }
     WEBCORE_EXPORT virtual void setScrollbarLayoutDirection(ScrollableArea&, UserInterfaceLayoutDirection) { }
     WEBCORE_EXPORT virtual void setScrollbarWidth(ScrollableArea&, ScrollbarWidth) { }
     WEBCORE_EXPORT virtual void setScrollbarColor(ScrollableArea&, std::optional<ScrollbarColor>);
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    virtual void setScrollbarOpacity(ScrollableArea&) { }
+#endif
 
     FrameIdentifier mainFrameIdentifier() const;
 
diff --git a/Source/WebCore/page/scrolling/ScrollingStateNode.h b/Source/WebCore/page/scrolling/ScrollingStateNode.h
index b279412..e6c3eae 100644
--- a/Source/WebCore/page/scrolling/ScrollingStateNode.h
+++ b/Source/WebCore/page/scrolling/ScrollingStateNode.h
@@ -229,6 +229,9 @@
     ScrollbarLayoutDirection                    = ScrollbarColor << 1,
     ScrollbarWidth                              = ScrollbarLayoutDirection << 1,
     UseDarkAppearanceForScrollbars              = ScrollbarWidth << 1,
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    ScrollbarOpacity                            = 1LLU << 51, // Not serialized
+#endif
     // ScrollingStateFrameScrollingNode
     KeyboardScrollData                          = UseDarkAppearanceForScrollbars << 1,
     FrameScaleFactor                            = KeyboardScrollData << 1,
diff --git a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp
index c9f5981..1c40d81 100644
--- a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp
+++ b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp
@@ -32,6 +32,10 @@
 #include <wtf/TZoneMallocInlines.h>
 #include <wtf/text/TextStream.h>
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+#include "ScrollerImpAdwaita.h"
+#endif
+
 namespace WebCore {
 
 WTF_MAKE_TZONE_ALLOCATED_IMPL(ScrollingStateScrollingNode);
@@ -115,9 +119,13 @@
     , m_scrollPosition(stateNode.scrollPosition())
     , m_scrollOrigin(stateNode.scrollOrigin())
     , m_snapOffsetsInfo(stateNode.m_snapOffsetsInfo)
-#if PLATFORM(MAC)
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
     , m_scrollbarHoverState(stateNode.scrollbarHoverState())
+#endif
+#if PLATFORM(MAC)
     , m_mouseLocationState(stateNode.mouseLocationState())
+#endif
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
     , m_scrollbarEnabledState(stateNode.scrollbarEnabledState())
     , m_verticalScrollerImp(stateNode.verticalScrollerImp())
     , m_horizontalScrollerImp(stateNode.horizontalScrollerImp())
@@ -136,6 +144,9 @@
     , m_useDarkAppearanceForScrollbars(stateNode.useDarkAppearanceForScrollbars())
     , m_isMonitoringWheelEvents(stateNode.isMonitoringWheelEvents())
     , m_mouseIsOverContentArea(stateNode.mouseIsOverContentArea())
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    , m_scrollbarOpacity(stateNode.scrollbarOpacity())
+#endif
 {
     scrollingStateTree().scrollingNodeAdded();
 
@@ -347,7 +358,7 @@
     setPropertyChanged(Property::VerticalScrollbarLayer);
 }
 
-#if !PLATFORM(MAC)
+#if !PLATFORM(MAC) && !USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
 void ScrollingStateScrollingNode::setScrollerImpsFromScrollbars(Scrollbar*, Scrollbar*)
 {
 }
@@ -424,6 +435,16 @@
     setPropertyChanged(Property::UseDarkAppearanceForScrollbars);
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+void ScrollingStateScrollingNode::setScrollbarOpacity(float scrollbarOpacity)
+{
+    if (scrollbarOpacity == m_scrollbarOpacity)
+        return;
+    m_scrollbarOpacity = scrollbarOpacity;
+    setPropertyChanged(Property::ScrollbarOpacity);
+}
+#endif
+
 void ScrollingStateScrollingNode::dumpProperties(TextStream& ts, OptionSet<ScrollingStateTreeAsTextBehavior> behavior) const
 {
     ScrollingStateNode::dumpProperties(ts, behavior);
diff --git a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h
index f3a70e6..fe28ab5 100644
--- a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h
+++ b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h
@@ -39,9 +39,20 @@
 
 namespace WebCore {
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+class ScrollerImpAdwaita;
+#endif
+
 struct ScrollbarHoverState {
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    ScrollbarPart hoveredPartInHorizontalScrollbar { NoPart };
+    ScrollbarPart hoveredPartInVerticalScrollbar { NoPart };
+    ScrollbarPart pressedPartInHorizontalScrollbar { NoPart };
+    ScrollbarPart pressedPartInVerticalScrollbar { NoPart };
+#else
     bool mouseIsOverHorizontalScrollbar { false };
     bool mouseIsOverVerticalScrollbar { false };
+#endif
 
     friend bool operator==(const ScrollbarHoverState&, const ScrollbarHoverState&) = default;
 };
@@ -123,6 +134,9 @@
 #if PLATFORM(MAC)
     NSScrollerImp *verticalScrollerImp() const { return m_verticalScrollerImp.get(); }
     NSScrollerImp *horizontalScrollerImp() const { return m_horizontalScrollerImp.get(); }
+#elif USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    ScrollerImpAdwaita* verticalScrollerImp() const { return m_verticalScrollerImp; }
+    ScrollerImpAdwaita* horizontalScrollerImp() const { return m_horizontalScrollerImp; }
 #endif
     ScrollbarHoverState scrollbarHoverState() const { return m_scrollbarHoverState; }
     WEBCORE_EXPORT void setScrollbarHoverState(ScrollbarHoverState);
@@ -150,6 +164,11 @@
     WEBCORE_EXPORT void setUseDarkAppearanceForScrollbars(bool);
     bool useDarkAppearanceForScrollbars() const { return m_useDarkAppearanceForScrollbars; }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    void setScrollbarOpacity(float);
+    float scrollbarOpacity() const { return m_scrollbarOpacity; };
+#endif
+
 protected:
     ScrollingStateScrollingNode(
         ScrollingNodeType,
@@ -214,6 +233,9 @@
 #if PLATFORM(MAC)
     RetainPtr<NSScrollerImp> m_verticalScrollerImp;
     RetainPtr<NSScrollerImp> m_horizontalScrollerImp;
+#elif USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    RefPtr<ScrollerImpAdwaita> m_verticalScrollerImp;
+    RefPtr<ScrollerImpAdwaita> m_horizontalScrollerImp;
 #endif
 
     std::optional<ScrollbarColor> m_scrollbarColor;
@@ -229,7 +251,9 @@
     bool m_useDarkAppearanceForScrollbars { false };
     bool m_isMonitoringWheelEvents { false };
     bool m_mouseIsOverContentArea { false };
-
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    float m_scrollbarOpacity { 1 };
+#endif
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.cpp b/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.cpp
new file mode 100644
index 0000000..0d97068
--- /dev/null
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScrollerCoordinated.h"
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+#include "CoordinatedPlatformLayer.h"
+#include "ImageBuffer.h"
+#include "NativeImage.h"
+#include "ScrollerImpAdwaita.h"
+#include <wtf/TZoneMallocInlines.h>
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(ScrollerPairCoordinated);
+
+ScrollerCoordinated::ScrollerCoordinated(ScrollerPairCoordinated& pair, ScrollbarOrientation orientation)
+    : m_pair(pair)
+    , m_orientation(orientation)
+{
+    m_state.orientation = m_orientation;
+    m_state.pressedPart = NoPart;
+}
+
+ScrollerCoordinated::~ScrollerCoordinated() = default;
+
+void ScrollerCoordinated::setScrollerImp(ScrollerImpAdwaita* scrollerImp)
+{
+    Locker locker { m_lock };
+    m_scrollerImp = scrollerImp;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setHostLayer(CoordinatedPlatformLayer* layer)
+{
+    Locker locker { m_lock };
+    m_hostLayer = layer;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::updateValues()
+{
+    RefPtr<CoordinatedPlatformLayer> hostLayer;
+    RefPtr<ScrollerImpAdwaita> scrollerImp;
+    {
+        Locker locker { m_lock };
+        scrollerImp = m_scrollerImp;
+        hostLayer = m_hostLayer;
+    }
+
+    if (!hostLayer)
+        return;
+
+    if (!scrollerImp) {
+        // Custom scrollbars are painted by RenderScrollbar
+        hostLayer->setContentsScrollbarImageForScrolling(nullptr);
+        return;
+    }
+
+    RefPtr pair = m_pair.get();
+    auto values = pair->valuesForOrientation(m_orientation);
+    AdwaitaScrollbarPainter::State state;
+
+    {
+        Locker locker { m_lock };
+        if (m_currentValue != values)
+            m_needsUpdate = true;
+        m_currentValue = values;
+        if (!m_needsUpdate)
+            return;
+        m_needsUpdate = false;
+        state = m_state;
+    }
+
+    int width = AdwaitaScrollbarPainter::scrollbarSize;
+    IntRect rect { 0, 0, width, int(values.visibleSize) };
+
+    if (m_orientation == ScrollbarOrientation::Horizontal)
+        rect = rect.transposedRect();
+
+    const int minimumThumbLength = AdwaitaScrollbarPainter::minimumThumbSize;
+    if (values.visibleSize <= minimumThumbLength)
+        state.thumbLength = 0;
+    else
+        state.thumbLength = std::max<int>(values.visibleSize * values.proportion, minimumThumbLength);
+    state.thumbPosition = (values.visibleSize - state.thumbLength) * values.value;
+    state.frameRect = rect;
+
+    RefPtr imageBuffer = ImageBuffer::create(state.frameRect.size(), RenderingMode::Accelerated, RenderingPurpose::DOM, 1, DestinationColorSpace::SRGB(), PixelFormat::RGBA8);
+    if (!imageBuffer)
+        return;
+    scrollerImp->paint(imageBuffer->context(), state.frameRect, state);
+    RefPtr nativeImage = ImageBuffer::sinkIntoNativeImage(WTF::move(imageBuffer));
+    if (!nativeImage)
+        return;
+    hostLayer->setContentsScrollbarImageForScrolling(WTF::move(nativeImage));
+}
+
+void ScrollerCoordinated::setHoveredAndPressedParts(ScrollbarPart hoveredPart, ScrollbarPart pressedPart)
+{
+    Locker locker { m_lock };
+    m_state.hoveredPart = hoveredPart;
+    m_state.pressedPart = pressedPart;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setEnabled(bool enabled)
+{
+    Locker locker { m_lock };
+    m_state.enabled = enabled;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setOverlayScrollbarEnabled(bool enabled)
+{
+    Locker locker { m_lock };
+    m_state.usesOverlayScrollbars = enabled;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setUseDarkAppearance(bool isDark)
+{
+    Locker locker { m_lock };
+    m_state.useDarkAppearanceForScrollbars = isDark;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setOpacity(float opacity)
+{
+    Locker locker { m_lock };
+    m_state.opacity = opacity;
+    m_needsUpdate = true;
+}
+
+void ScrollerCoordinated::setScrollbarLayoutDirection(UserInterfaceLayoutDirection direction)
+{
+    Locker locker { m_lock };
+    m_state.shouldPlaceVerticalScrollbarOnLeft = direction == UserInterfaceLayoutDirection::RTL;
+    m_needsUpdate = true;
+}
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.h b/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.h
new file mode 100644
index 0000000..50d54a7
--- /dev/null
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollerCoordinated.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include "ScrollTypes.h"
+#include "ScrollerImpAdwaita.h"
+#include "ScrollerPairCoordinated.h"
+#include "UserInterfaceLayoutDirection.h"
+#include <wtf/Lock.h>
+#include <wtf/TZoneMalloc.h>
+
+namespace WebCore {
+
+class CoordinatedPlatformLayer;
+
+class ScrollerCoordinated final : public CanMakeThreadSafeCheckedPtr<ScrollerCoordinated> {
+    WTF_MAKE_TZONE_ALLOCATED(ScrollerCoordinated);
+    WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScrollerCoordinated);
+public:
+    ScrollerCoordinated(ScrollerPairCoordinated&, ScrollbarOrientation);
+    ~ScrollerCoordinated();
+
+    void setScrollerImp(ScrollerImpAdwaita*);
+    void setHostLayer(CoordinatedPlatformLayer*);
+    void updateValues();
+    void setEnabled(bool);
+    void setOverlayScrollbarEnabled(bool);
+    void setUseDarkAppearance(bool);
+    void setHoveredAndPressedParts(ScrollbarPart hoveredPart, ScrollbarPart pressedPart);
+    void setOpacity(float);
+    void setScrollbarLayoutDirection(UserInterfaceLayoutDirection);
+
+private:
+    ThreadSafeWeakPtr<ScrollerPairCoordinated> m_pair;
+    const ScrollbarOrientation m_orientation;
+    Lock m_lock;
+    RefPtr<ScrollerImpAdwaita> m_scrollerImp WTF_GUARDED_BY_LOCK(m_lock);
+    bool m_needsUpdate WTF_GUARDED_BY_LOCK(m_lock) { true };
+    RefPtr<CoordinatedPlatformLayer> m_hostLayer WTF_GUARDED_BY_LOCK(m_lock);
+    AdwaitaScrollbarPainter::State m_state WTF_GUARDED_BY_LOCK(m_lock);
+    ScrollerPairCoordinated::Values m_currentValue WTF_GUARDED_BY_LOCK(m_lock);
+};
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.cpp b/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.cpp
new file mode 100644
index 0000000..16e7d35
--- /dev/null
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScrollerPairCoordinated.h"
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include "ScrollTypes.h"
+#include "ScrollerCoordinated.h"
+#include "ScrollingTreeScrollingNode.h"
+#include <wtf/TZoneMallocInlines.h>
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(ScrollerPairCoordinated);
+
+ScrollerPairCoordinated::ScrollerPairCoordinated(ScrollingTreeScrollingNode& node)
+    : m_scrollingNode(node)
+    , m_verticalScroller(makeUniqueRef<ScrollerCoordinated>(*this, ScrollbarOrientation::Vertical))
+    , m_horizontalScroller(makeUniqueRef<ScrollerCoordinated>(*this, ScrollbarOrientation::Horizontal))
+{
+}
+
+ScrollerPairCoordinated::~ScrollerPairCoordinated() = default;
+
+void ScrollerPairCoordinated::updateValues()
+{
+    RefPtr node = m_scrollingNode.get();
+    if (!node)
+        return;
+
+    m_horizontalScroller->updateValues();
+    m_verticalScroller->updateValues();
+}
+
+ScrollerPairCoordinated::Values ScrollerPairCoordinated::valuesForOrientation(ScrollbarOrientation orientation)
+{
+    RefPtr node = m_scrollingNode.get();
+    if (!node)
+        return { };
+
+    float position;
+    float totalSize;
+    float visibleSize;
+    if (orientation == ScrollbarOrientation::Vertical) {
+        position = node->currentScrollOffset().y();
+        totalSize = node->totalContentsSize().height();
+        visibleSize = node->scrollableAreaSize().height();
+    } else {
+        position = node->currentScrollOffset().x();
+        totalSize = node->totalContentsSize().width();
+        visibleSize = node->scrollableAreaSize().width();
+    }
+
+    float value;
+    float overhang;
+    ScrollableArea::computeScrollbarValueAndOverhang(position, totalSize, visibleSize, value, overhang);
+
+    float proportion = totalSize ? (visibleSize - overhang) / totalSize : 1;
+
+    return { value, proportion, visibleSize };
+}
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.h b/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.h
new file mode 100644
index 0000000..24d56ac
--- /dev/null
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollerPairCoordinated.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include "ScrollingStateScrollingNode.h"
+#include <wtf/Forward.h>
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+#include <wtf/ThreadSafeWeakPtr.h>
+#include <wtf/UniqueRef.h>
+
+namespace WebCore {
+
+class ScrollerCoordinated;
+class ScrollingTreeScrollingNode;
+
+class ScrollerPairCoordinated : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<ScrollerPairCoordinated> {
+    WTF_MAKE_TZONE_ALLOCATED(ScrollerPairCoordinated);
+    friend class ScrollerCoordinated;
+public:
+    static Ref<ScrollerPairCoordinated> create(ScrollingTreeScrollingNode& node)
+    {
+        return adoptRef(*new ScrollerPairCoordinated(node));
+    }
+
+    ~ScrollerPairCoordinated();
+
+protected:
+    friend class ScrollingTreeScrollingNodeDelegateCoordinated;
+    ScrollerCoordinated& verticalScroller() { return m_verticalScroller.get(); }
+    ScrollerCoordinated& horizontalScroller() { return m_horizontalScroller.get(); }
+
+private:
+    ScrollerPairCoordinated(ScrollingTreeScrollingNode&);
+
+    void updateValues();
+
+    struct Values {
+        float value { 0 };
+        float proportion { 0 };
+        float visibleSize { 0 };
+
+        friend bool operator==(const Values&, const Values&) = default;
+    };
+    Values valuesForOrientation(ScrollbarOrientation);
+
+    ThreadSafeWeakPtr<ScrollingTreeScrollingNode> m_scrollingNode;
+    const UniqueRef<ScrollerCoordinated> m_verticalScroller;
+    const UniqueRef<ScrollerCoordinated> m_horizontalScroller;
+};
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollingStateScrollingNodeCoordinated.cpp b/Source/WebCore/page/scrolling/coordinated/ScrollingStateScrollingNodeCoordinated.cpp
new file mode 100644
index 0000000..67f6a36
--- /dev/null
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollingStateScrollingNodeCoordinated.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScrollingStateScrollingNode.h"
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+#include "ScrollbarThemeAdwaita.h"
+#include "ScrollerImpAdwaita.h"
+
+namespace WebCore {
+
+void ScrollingStateScrollingNode::setScrollerImpsFromScrollbars(Scrollbar* verticalScrollbar, Scrollbar* horizontalScrollbar)
+{
+    ScrollbarTheme& scrollbarTheme = ScrollbarTheme::theme();
+    if (scrollbarTheme.isMockTheme())
+        return;
+
+    auto& adwaitaTheme = downcast<ScrollbarThemeAdwaita>(scrollbarTheme);
+
+    ScrollerImpAdwaita* verticalPainter = verticalScrollbar && verticalScrollbar->supportsUpdateOnSecondaryThread()
+        ? adwaitaTheme.scrollerImpForScrollbar(*verticalScrollbar) : nullptr;
+    ScrollerImpAdwaita* horizontalPainter = horizontalScrollbar && horizontalScrollbar->supportsUpdateOnSecondaryThread()
+        ? adwaitaTheme.scrollerImpForScrollbar(*horizontalScrollbar) : nullptr;
+
+    if (m_verticalScrollerImp == verticalPainter && m_horizontalScrollerImp == horizontalPainter)
+        return;
+
+    m_verticalScrollerImp = verticalPainter;
+    m_horizontalScrollerImp = horizontalPainter;
+
+    setPropertyChanged(Property::PainterForScrollbar);
+}
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.cpp b/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.cpp
index 15a3211..6504540 100644
--- a/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.cpp
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.cpp
@@ -28,14 +28,26 @@
 #include "ScrollingTreeScrollingNodeDelegateCoordinated.h"
 
 #if ENABLE(ASYNC_SCROLLING) && USE(COORDINATED_GRAPHICS)
-#include "ScrollingTreeFrameScrollingNode.h"
+#include "ScrollerCoordinated.h"
+#include "ScrollingStateFrameScrollingNode.h"
+#include "ScrollingTreeOverflowScrollingNode.h"
 
 namespace WebCore {
 
 ScrollingTreeScrollingNodeDelegateCoordinated::ScrollingTreeScrollingNodeDelegateCoordinated(ScrollingTreeScrollingNode& scrollingNode, bool scrollAnimatorEnabled)
     : ThreadedScrollingTreeScrollingNodeDelegate(scrollingNode)
     , m_scrollAnimatorEnabled(scrollAnimatorEnabled)
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    , m_scrollerPair(ScrollerPairCoordinated::create(scrollingNode))
+#endif
 {
+    ASSERT(isMainThread());
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    if (is<ScrollingTreeOverflowScrollingNode>(scrollingNode) && ScrollbarTheme::theme().usesOverlayScrollbars()) {
+        m_scrollerPair->horizontalScroller().setOverlayScrollbarEnabled(true);
+        m_scrollerPair->verticalScroller().setOverlayScrollbarEnabled(true);
+    }
+#endif
 }
 
 ScrollingTreeScrollingNodeDelegateCoordinated::~ScrollingTreeScrollingNodeDelegateCoordinated() = default;
@@ -43,6 +55,9 @@
 void ScrollingTreeScrollingNodeDelegateCoordinated::updateVisibleLengths()
 {
     m_scrollController.contentsSizeChanged();
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    m_scrollerPair->updateValues();
+#endif
 }
 
 bool ScrollingTreeScrollingNodeDelegateCoordinated::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
@@ -54,6 +69,66 @@
     return m_scrollController.handleWheelEvent(wheelEvent);
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+void ScrollingTreeScrollingNodeDelegateCoordinated::updateFromStateNode(const ScrollingStateScrollingNode& scrollingStateNode)
+{
+    ASSERT(isMainThread());
+    CheckedRef horizontalScroller = m_scrollerPair->horizontalScroller();
+    CheckedRef verticalScroller = m_scrollerPair->verticalScroller();
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::PainterForScrollbar)) {
+        horizontalScroller->setScrollerImp(scrollingStateNode.horizontalScrollerImp());
+        verticalScroller->setScrollerImp(scrollingStateNode.verticalScrollerImp());
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::ScrollbarHoverState)) {
+        auto hoverState = scrollingStateNode.scrollbarHoverState();
+        verticalScroller->setHoveredAndPressedParts(hoverState.hoveredPartInVerticalScrollbar, hoverState.pressedPartInVerticalScrollbar);
+        horizontalScroller->setHoveredAndPressedParts(hoverState.hoveredPartInHorizontalScrollbar, hoverState.pressedPartInHorizontalScrollbar);
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::HorizontalScrollbarLayer))
+        horizontalScroller->setHostLayer(static_cast<CoordinatedPlatformLayer*>(scrollingStateNode.horizontalScrollbarLayer()));
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::VerticalScrollbarLayer))
+        verticalScroller->setHostLayer(static_cast<CoordinatedPlatformLayer*>(scrollingStateNode.verticalScrollbarLayer()));
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::ScrollbarEnabledState)) {
+        auto scrollbarEnabledState = scrollingStateNode.scrollbarEnabledState();
+        horizontalScroller->setEnabled(scrollbarEnabledState.horizontalScrollbarIsEnabled);
+        verticalScroller->setEnabled(scrollbarEnabledState.verticalScrollbarIsEnabled);
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::ScrollbarLayoutDirection)) {
+        auto scrollbarLayoutDirection = scrollingStateNode.scrollbarLayoutDirection();
+        horizontalScroller->setScrollbarLayoutDirection(scrollbarLayoutDirection);
+        verticalScroller->setScrollbarLayoutDirection(scrollbarLayoutDirection);
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::UseDarkAppearanceForScrollbars)) {
+        bool useDarkAppearanceForScrollbars = scrollingStateNode.useDarkAppearanceForScrollbars();
+        horizontalScroller->setUseDarkAppearance(useDarkAppearanceForScrollbars);
+        verticalScroller->setUseDarkAppearance(useDarkAppearanceForScrollbars);
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::OverlayScrollbarsEnabled)) {
+        if (auto* scrollingStateFrameScrollingNode = dynamicDowncast<ScrollingStateFrameScrollingNode>(&scrollingStateNode)) {
+            auto overlayScrollbarsEnabled = scrollingStateFrameScrollingNode->overlayScrollbarsEnabled();
+            horizontalScroller->setOverlayScrollbarEnabled(overlayScrollbarsEnabled);
+            verticalScroller->setOverlayScrollbarEnabled(overlayScrollbarsEnabled);
+        }
+    }
+
+    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::ScrollbarOpacity)) {
+        float scrollbarOpacity = scrollingStateNode.scrollbarOpacity();
+        horizontalScroller->setOpacity(scrollbarOpacity);
+        verticalScroller->setOpacity(scrollbarOpacity);
+    }
+
+    ThreadedScrollingTreeScrollingNodeDelegate::updateFromStateNode(scrollingStateNode);
+}
+#endif
+
 } // namespace WebCore
 
 #endif // ENABLE(ASYNC_SCROLLING) && USE(COORDINATED_GRAPHICS)
diff --git a/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h b/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h
index 89774e5..1683a4b 100644
--- a/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h
+++ b/Source/WebCore/page/scrolling/coordinated/ScrollingTreeScrollingNodeDelegateCoordinated.h
@@ -33,6 +33,8 @@
 
 namespace WebCore {
 
+class ScrollerPairCoordinated;
+
 class ScrollingTreeScrollingNodeDelegateCoordinated final : public ThreadedScrollingTreeScrollingNodeDelegate {
 public:
     explicit ScrollingTreeScrollingNodeDelegateCoordinated(ScrollingTreeScrollingNode&, bool scrollAnimatorEnabled);
@@ -42,10 +44,18 @@
     bool handleWheelEvent(const PlatformWheelEvent&);
 
 private:
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    // ThreadedScrollingTreeScrollingNodeDelegate
+    void updateFromStateNode(const ScrollingStateScrollingNode&) final;
+#endif
+
     // ScrollingEffectsControllerClient.
     bool scrollAnimationEnabled() const final { return m_scrollAnimatorEnabled; }
 
     bool m_scrollAnimatorEnabled { false };
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    const Ref<ScrollerPairCoordinated> m_scrollerPair;
+#endif
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/Adwaita.cmake b/Source/WebCore/platform/Adwaita.cmake
index d52b445..41a674e 100644
--- a/Source/WebCore/platform/Adwaita.cmake
+++ b/Source/WebCore/platform/Adwaita.cmake
@@ -9,6 +9,7 @@
 )
 
 list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS
+    platform/adwaita/AdwaitaScrollbarPainter.h
     platform/adwaita/ScrollbarThemeAdwaita.h
     platform/adwaita/ThemeAdwaita.h
 
diff --git a/Source/WebCore/platform/ScrollableArea.cpp b/Source/WebCore/platform/ScrollableArea.cpp
index 5c776ea..0c71177 100644
--- a/Source/WebCore/platform/ScrollableArea.cpp
+++ b/Source/WebCore/platform/ScrollableArea.cpp
@@ -288,6 +288,17 @@
 }
 #endif
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+float ScrollableArea::scrollbarOpacity() const
+{
+    if (auto scrollbar = verticalScrollbar())
+        return scrollbar->opacity();
+    if (auto scrollbar = horizontalScrollbar())
+        return scrollbar->opacity();
+    return 1;
+}
+#endif
+
 // NOTE: Only called from Internals for testing.
 void ScrollableArea::setScrollOffsetFromInternals(const ScrollOffset& offset)
 {
diff --git a/Source/WebCore/platform/ScrollableArea.h b/Source/WebCore/platform/ScrollableArea.h
index 875649c..ca55cbe 100644
--- a/Source/WebCore/platform/ScrollableArea.h
+++ b/Source/WebCore/platform/ScrollableArea.h
@@ -370,6 +370,10 @@
 
     virtual bool isInStableState() const { return true; }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    float scrollbarOpacity() const;
+#endif
+
     // NOTE: Only called from Internals for testing.
     WEBCORE_EXPORT void setScrollOffsetFromInternals(const ScrollOffset&);
 
diff --git a/Source/WebCore/platform/Scrollbar.cpp b/Source/WebCore/platform/Scrollbar.cpp
index 67bdd53..8800b6c 100644
--- a/Source/WebCore/platform/Scrollbar.cpp
+++ b/Source/WebCore/platform/Scrollbar.cpp
@@ -326,6 +326,9 @@
         theme().invalidatePart(*this, m_hoveredPart);
     }
     m_hoveredPart = part;
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    checkedScrollableArea()->scrollbarsController().hoveredPartChanged(*this);
+#endif
 }
 
 void Scrollbar::setPressedPart(ScrollbarPart part)
@@ -337,6 +340,9 @@
         theme().invalidatePart(*this, m_pressedPart);
     else if (m_hoveredPart != NoPart)  // When we no longer have a pressed part, we can start drawing a hovered state on the hovered part.
         theme().invalidatePart(*this, m_hoveredPart);
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    checkedScrollableArea()->scrollbarsController().pressedPartChanged(*this);
+#endif
 }
 
 #if !PLATFORM(IOS_FAMILY)
@@ -510,7 +516,7 @@
 {
     // It's unfortunate that this needs to be done with an ifdef. Ideally there would be a way to feature-detect
     // the necessary support within AppKit.
-#if PLATFORM(MAC)
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
     CheckedRef scrollableArea = m_scrollableArea.get();
     return !scrollableArea->forceUpdateScrollbarsOnMainThreadForPerformanceTesting()
         && (scrollableArea->hasLayerForVerticalScrollbar() || scrollableArea->hasLayerForHorizontalScrollbar())
diff --git a/Source/WebCore/platform/ScrollbarTheme.h b/Source/WebCore/platform/ScrollbarTheme.h
index 79279db..eff9c6d 100644
--- a/Source/WebCore/platform/ScrollbarTheme.h
+++ b/Source/WebCore/platform/ScrollbarTheme.h
@@ -111,6 +111,9 @@
 
     virtual bool isMockTheme() const { return false; }
     virtual bool isScrollbarThemeMac() const { return false; }
+#if USE(THEME_ADWAITA)
+    virtual bool isScrollbarThemeAdwaita() const { return false; }
+#endif
 
     WEBCORE_EXPORT static ScrollbarTheme& theme();
 
diff --git a/Source/WebCore/platform/ScrollbarsController.h b/Source/WebCore/platform/ScrollbarsController.h
index a48b286..af89d73 100644
--- a/Source/WebCore/platform/ScrollbarsController.h
+++ b/Source/WebCore/platform/ScrollbarsController.h
@@ -55,8 +55,12 @@
 
     bool scrollbarAnimationsUnsuspendedByUserInteraction() const { return m_scrollbarAnimationsUnsuspendedByUserInteraction; }
     void setScrollbarAnimationsUnsuspendedByUserInteraction(bool unsuspended) { m_scrollbarAnimationsUnsuspendedByUserInteraction = unsuspended; }
-    
+
+#if PLATFORM(MAC)
     WEBCORE_EXPORT virtual bool isRemoteScrollbarsController() const { return false; }
+#elif USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    virtual bool isScrollbarsControllerCoordinated() const { return false; }
+#endif
     WEBCORE_EXPORT virtual bool isScrollbarsControllerMac() const { return false; }
     WEBCORE_EXPORT virtual bool isScrollbarsControllerMock() const { return false; }
 
@@ -77,6 +81,10 @@
     WEBCORE_EXPORT virtual void mouseEnteredScrollbar(Scrollbar*) const { }
     WEBCORE_EXPORT virtual void mouseExitedScrollbar(Scrollbar*) const { }
     WEBCORE_EXPORT virtual void mouseIsDownInScrollbar(Scrollbar*, bool) const { }
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    void virtual hoveredPartChanged(Scrollbar&) { }
+    void virtual pressedPartChanged(Scrollbar&) { }
+#endif
     WEBCORE_EXPORT virtual void willStartLiveResize() { }
     WEBCORE_EXPORT virtual void contentsSizeChanged() const { }
     WEBCORE_EXPORT virtual void willEndLiveResize() { }
@@ -111,6 +119,9 @@
     WEBCORE_EXPORT virtual int minimumThumbLength(WebCore::ScrollbarOrientation) { return 0; }
     WEBCORE_EXPORT virtual void scrollbarLayoutDirectionChanged(UserInterfaceLayoutDirection) { }
     WEBCORE_EXPORT virtual void scrollbarColorChanged(std::optional<ScrollbarColor>);
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    virtual void scrollbarOpacityChanged() { }
+#endif
 
     WEBCORE_EXPORT virtual void updateScrollerStyle() { }
 
diff --git a/Source/WebCore/platform/SourcesAdwaita.txt b/Source/WebCore/platform/SourcesAdwaita.txt
index 1dd2efe..5599541 100644
--- a/Source/WebCore/platform/SourcesAdwaita.txt
+++ b/Source/WebCore/platform/SourcesAdwaita.txt
@@ -21,6 +21,7 @@
 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 // THE POSSIBILITY OF SUCH DAMAGE.
 
+platform/adwaita/AdwaitaScrollbarPainter.cpp
 platform/adwaita/ScrollbarThemeAdwaita.cpp
 platform/adwaita/ThemeAdwaita.cpp
 
diff --git a/Source/WebCore/platform/TextureMapper.cmake b/Source/WebCore/platform/TextureMapper.cmake
index 0b3286a..0cf26b8 100644
--- a/Source/WebCore/platform/TextureMapper.cmake
+++ b/Source/WebCore/platform/TextureMapper.cmake
@@ -54,7 +54,10 @@
         "${WEBCORE_DIR}/platform/graphics/texmap/coordinated"
     )
     list(APPEND WebCore_SOURCES
+        page/scrolling/coordinated/ScrollerCoordinated.cpp
+        page/scrolling/coordinated/ScrollerPairCoordinated.cpp
         page/scrolling/coordinated/ScrollingStateNodeCoordinated.cpp
+        page/scrolling/coordinated/ScrollingStateScrollingNodeCoordinated.cpp
         page/scrolling/coordinated/ScrollingTreeCoordinated.cpp
         page/scrolling/coordinated/ScrollingTreeFixedNodeCoordinated.cpp
         page/scrolling/coordinated/ScrollingTreeFrameScrollingNodeCoordinated.cpp
diff --git a/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.cpp b/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.cpp
new file mode 100644
index 0000000..1db9987
--- /dev/null
+++ b/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AdwaitaScrollbarPainter.h"
+
+#if USE(THEME_ADWAITA)
+
+#include "GraphicsContext.h"
+
+namespace WebCore::AdwaitaScrollbarPainter {
+
+void paint(GraphicsContext& graphicsContext, const IntRect& damageRect, const State& scrollbar)
+{
+    if (graphicsContext.paintingDisabled())
+        return;
+
+    if (!scrollbar.enabled && scrollbar.usesOverlayScrollbars)
+        return;
+
+    IntRect rect = scrollbar.frameRect;
+    if (!rect.intersects(damageRect))
+        return;
+
+    double opacity;
+    if (scrollbar.usesOverlayScrollbars)
+        opacity = scrollbar.opacity;
+    else
+        opacity = 1;
+    if (!opacity)
+        return;
+
+    SRGBA<uint8_t> scrollbarBackgroundColor;
+    SRGBA<uint8_t> scrollbarBorderColor;
+    SRGBA<uint8_t> overlayThumbBorderColor;
+    SRGBA<uint8_t> overlayTroughBorderColor;
+    SRGBA<uint8_t> overlayTroughColor;
+    SRGBA<uint8_t> thumbHoveredColor;
+    SRGBA<uint8_t> thumbPressedColor;
+    SRGBA<uint8_t> thumbColor;
+
+    if (scrollbar.useDarkAppearanceForScrollbars) {
+        scrollbarBackgroundColor = scrollbarBackgroundColorDark;
+        scrollbarBorderColor = scrollbarBorderColorDark;
+        overlayThumbBorderColor = overlayThumbBorderColorDark;
+        overlayTroughBorderColor = overlayThumbBorderColorDark;
+        overlayTroughColor = overlayTroughColorDark;
+        thumbHoveredColor = thumbHoveredColorDark;
+        thumbPressedColor = thumbPressedColorDark;
+        thumbColor = thumbColorDark;
+    } else {
+        scrollbarBackgroundColor = scrollbarBackgroundColorLight;
+        scrollbarBorderColor = scrollbarBorderColorLight;
+        overlayThumbBorderColor = overlayThumbBorderColorLight;
+        overlayTroughBorderColor = overlayThumbBorderColorLight;
+        overlayTroughColor = overlayTroughColorLight;
+        thumbHoveredColor = thumbHoveredColorLight;
+        thumbPressedColor = thumbPressedColorLight;
+        thumbColor = thumbColorLight;
+    }
+
+    GraphicsContextStateSaver stateSaver(graphicsContext);
+    if (opacity != 1) {
+        graphicsContext.clip(damageRect);
+        graphicsContext.beginTransparencyLayer(opacity);
+    }
+
+    int thumbSize = scrollbarSize - scrollbarBorderSize - horizThumbMargin * 2;
+
+    if (!scrollbar.usesOverlayScrollbars) {
+        graphicsContext.fillRect(rect, scrollbarBackgroundColor);
+
+        IntRect frame = rect;
+        if (scrollbar.orientation == ScrollbarOrientation::Vertical) {
+            if (scrollbar.shouldPlaceVerticalScrollbarOnLeft)
+                frame.move(frame.width() - scrollbarBorderSize, 0);
+            frame.setWidth(scrollbarBorderSize);
+        } else
+            frame.setHeight(scrollbarBorderSize);
+        graphicsContext.fillRect(frame, scrollbarBorderColor);
+    } else if (scrollbar.hoveredPart != NoPart) {
+        int thumbCornerSize = thumbSize / 2;
+        FloatSize corner(thumbCornerSize, thumbCornerSize);
+        FloatSize borderCorner(thumbCornerSize + thumbBorderSize, thumbCornerSize + thumbBorderSize);
+
+        IntRect trough = rect;
+        if (scrollbar.orientation == ScrollbarOrientation::Vertical) {
+            if (scrollbar.shouldPlaceVerticalScrollbarOnLeft)
+                trough.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2) - scrollbarBorderSize, vertThumbMargin);
+            else
+                trough.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2), vertThumbMargin);
+            trough.setWidth(thumbSize);
+            trough.setHeight(rect.height() - vertThumbMargin * 2);
+        } else {
+            trough.move(vertThumbMargin, scrollbarSize - (scrollbarSize / 2 + thumbSize / 2));
+            trough.setWidth(rect.width() - vertThumbMargin * 2);
+            trough.setHeight(thumbSize);
+        }
+
+        IntRect troughBorder(trough);
+        troughBorder.inflate(thumbBorderSize);
+
+        Path path;
+        path.addRoundedRect(trough, corner);
+        graphicsContext.setFillRule(WindRule::NonZero);
+        graphicsContext.setFillColor(overlayTroughColor);
+        graphicsContext.fillPath(path);
+        path.clear();
+
+        path.addRoundedRect(trough, corner);
+        path.addRoundedRect(troughBorder, borderCorner);
+        graphicsContext.setFillRule(WindRule::EvenOdd);
+        graphicsContext.setFillColor(overlayTroughBorderColor);
+        graphicsContext.fillPath(path);
+    }
+
+    int thumbCornerSize;
+    int thumbPos = scrollbar.thumbPosition;
+    int thumbLen = scrollbar.thumbLength;
+    IntRect thumb = rect;
+    if (scrollbar.hoveredPart == NoPart && scrollbar.usesOverlayScrollbars) {
+        thumbCornerSize = overlayThumbSize / 2;
+
+        if (scrollbar.orientation == ScrollbarOrientation::Vertical) {
+            if (scrollbar.shouldPlaceVerticalScrollbarOnLeft)
+                thumb.move(horizOverlayThumbMargin, thumbPos + vertThumbMargin);
+            else
+                thumb.move(scrollbarSize - overlayThumbSize - horizOverlayThumbMargin, thumbPos + vertThumbMargin);
+            thumb.setWidth(overlayThumbSize);
+            thumb.setHeight(thumbLen - vertThumbMargin * 2);
+        } else {
+            thumb.move(thumbPos + vertThumbMargin, scrollbarSize - overlayThumbSize - horizOverlayThumbMargin);
+            thumb.setWidth(thumbLen - vertThumbMargin * 2);
+            thumb.setHeight(overlayThumbSize);
+        }
+    } else {
+        thumbCornerSize = thumbSize / 2;
+
+        if (scrollbar.orientation == ScrollbarOrientation::Vertical) {
+            if (scrollbar.shouldPlaceVerticalScrollbarOnLeft)
+                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2) - scrollbarBorderSize, thumbPos + vertThumbMargin);
+            else
+                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2), thumbPos + vertThumbMargin);
+            thumb.setWidth(thumbSize);
+            thumb.setHeight(thumbLen - vertThumbMargin * 2);
+        } else {
+            thumb.move(thumbPos + vertThumbMargin, scrollbarSize - (scrollbarSize / 2 + thumbSize / 2));
+            thumb.setWidth(thumbLen - vertThumbMargin * 2);
+            thumb.setHeight(thumbSize);
+        }
+    }
+
+    FloatSize corner(thumbCornerSize, thumbCornerSize);
+    FloatSize borderCorner(thumbCornerSize + thumbBorderSize, thumbCornerSize + thumbBorderSize);
+
+    Path path;
+
+    path.addRoundedRect(thumb, corner);
+    graphicsContext.setFillRule(WindRule::NonZero);
+    if (scrollbar.pressedPart == ThumbPart)
+        graphicsContext.setFillColor(thumbPressedColor);
+    else if (scrollbar.hoveredPart == ThumbPart)
+        graphicsContext.setFillColor(thumbHoveredColor);
+    else
+        graphicsContext.setFillColor(thumbColor);
+    graphicsContext.fillPath(path);
+    path.clear();
+
+    if (scrollbar.usesOverlayScrollbars) {
+        IntRect thumbBorder(thumb);
+        thumbBorder.inflate(thumbBorderSize);
+
+        path.addRoundedRect(thumb, corner);
+        path.addRoundedRect(thumbBorder, borderCorner);
+        graphicsContext.setFillRule(WindRule::EvenOdd);
+        graphicsContext.setFillColor(overlayThumbBorderColor);
+        graphicsContext.fillPath(path);
+    }
+
+    if (opacity != 1)
+        graphicsContext.endTransparencyLayer();
+}
+
+} // namespace WebCore::AdwaitaScrollbarPainter
+
+#endif // USE(THEME_ADWAITA)
diff --git a/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.h b/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.h
new file mode 100644
index 0000000..2a8741e
--- /dev/null
+++ b/Source/WebCore/platform/adwaita/AdwaitaScrollbarPainter.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(THEME_ADWAITA)
+
+#include "IntRect.h"
+#include "ScrollTypes.h"
+
+namespace WebCore {
+
+class GraphicsContext;
+
+namespace AdwaitaScrollbarPainter {
+
+static const unsigned scrollbarSize = 21;
+static const unsigned scrollbarBorderSize = 1;
+static const unsigned thumbBorderSize = 1;
+static const unsigned overlayThumbSize = 3;
+static const unsigned minimumThumbSize = 40;
+static const unsigned horizThumbMargin = 6;
+static const unsigned horizOverlayThumbMargin = 3;
+static const unsigned vertThumbMargin = 7;
+
+static constexpr auto scrollbarBackgroundColorLight = Color::white;
+static constexpr auto scrollbarBorderColorLight = Color::black.colorWithAlphaByte(38);
+static constexpr auto overlayThumbBorderColorLight = Color::white.colorWithAlphaByte(102);
+static constexpr auto overlayTroughColorLight = Color::black.colorWithAlphaByte(25);
+static constexpr auto thumbHoveredColorLight = Color::black.colorWithAlphaByte(102);
+static constexpr auto thumbPressedColorLight = Color::black.colorWithAlphaByte(153);
+static constexpr auto thumbColorLight = Color::black.colorWithAlphaByte(51);
+
+static constexpr auto scrollbarBackgroundColorDark = SRGBA<uint8_t> { 30, 30, 30 };
+static constexpr auto scrollbarBorderColorDark = Color::white.colorWithAlphaByte(38);
+static constexpr auto overlayThumbBorderColorDark = Color::black.colorWithAlphaByte(51);
+static constexpr auto overlayTroughColorDark = Color::white.colorWithAlphaByte(25);
+static constexpr auto thumbHoveredColorDark = Color::white.colorWithAlphaByte(102);
+static constexpr auto thumbPressedColorDark = Color::white.colorWithAlphaByte(153);
+static constexpr auto thumbColorDark = Color::white.colorWithAlphaByte(51);
+
+struct State {
+    bool enabled { false };
+    bool useDarkAppearanceForScrollbars { false };
+    bool shouldPlaceVerticalScrollbarOnLeft { false };
+    bool usesOverlayScrollbars { false };
+    ScrollbarOrientation orientation { ScrollbarOrientation::Horizontal };
+    ScrollbarPart hoveredPart { NoPart };
+    ScrollbarPart pressedPart { NoPart };
+    int thumbPosition { 0 };
+    int thumbLength { 0 };
+    IntRect frameRect;
+    double opacity { 1 };
+};
+
+void paint(GraphicsContext&, const IntRect&, const State&);
+
+} // namespace AdwaitaScrollbarPainter
+} // namespace WebCore
+
+#endif // USE(THEME_ADWAITA)
diff --git a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp
index 744f448..461e2faf 100644
--- a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp
+++ b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp
@@ -28,6 +28,7 @@
 
 #if USE(THEME_ADWAITA)
 
+#include "AdwaitaScrollbarPainter.h"
 #include "Color.h"
 #include "FloatRoundedRect.h"
 #include "GraphicsContext.h"
@@ -41,32 +42,14 @@
 #include "SystemSettings.h"
 #endif
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+#include "ScrollbarsController.h"
+#include "ScrollerImpAdwaita.h"
+#include <wtf/NeverDestroyed.h>
+#endif
+
 namespace WebCore {
-
-static const unsigned scrollbarSize = 21;
-static const unsigned scrollbarBorderSize = 1;
-static const unsigned thumbBorderSize = 1;
-static const unsigned overlayThumbSize = 3;
-static const unsigned minimumThumbSize = 40;
-static const unsigned horizThumbMargin = 6;
-static const unsigned horizOverlayThumbMargin = 3;
-static const unsigned vertThumbMargin = 7;
-
-static constexpr auto scrollbarBackgroundColorLight = Color::white;
-static constexpr auto scrollbarBorderColorLight = Color::black.colorWithAlphaByte(38);
-static constexpr auto overlayThumbBorderColorLight = Color::white.colorWithAlphaByte(102);
-static constexpr auto overlayTroughColorLight = Color::black.colorWithAlphaByte(25);
-static constexpr auto thumbHoveredColorLight = Color::black.colorWithAlphaByte(102);
-static constexpr auto thumbPressedColorLight = Color::black.colorWithAlphaByte(153);
-static constexpr auto thumbColorLight = Color::black.colorWithAlphaByte(51);
-
-static constexpr auto scrollbarBackgroundColorDark = SRGBA<uint8_t> { 30, 30, 30 };
-static constexpr auto scrollbarBorderColorDark = Color::white.colorWithAlphaByte(38);
-static constexpr auto overlayThumbBorderColorDark = Color::black.colorWithAlphaByte(51);
-static constexpr auto overlayTroughColorDark = Color::white.colorWithAlphaByte(25);
-static constexpr auto thumbHoveredColorDark = Color::white.colorWithAlphaByte(102);
-static constexpr auto thumbPressedColorDark = Color::white.colorWithAlphaByte(153);
-static constexpr auto thumbColorDark = Color::white.colorWithAlphaByte(51);
+using namespace WebCore::AdwaitaScrollbarPainter;
 
 void ScrollbarThemeAdwaita::updateScrollbarOverlayStyle(Scrollbar& scrollbar)
 {
@@ -125,174 +108,26 @@
 
 bool ScrollbarThemeAdwaita::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
 {
-    if (graphicsContext.paintingDisabled())
-        return false;
-
-    if (!scrollbar.enabled() && usesOverlayScrollbars())
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    if (scrollbar.checkedScrollableArea()->usesCompositedScrolling()) {
+        // Painting is done by ScrollerCoordinated in the scrolling thread.
         return true;
-
-    IntRect rect = scrollbar.frameRect();
-    if (!rect.intersects(damageRect))
-        return true;
-
-    double opacity;
-    if (usesOverlayScrollbars())
-        opacity = scrollbar.opacity();
-    else
-        opacity = 1;
-    if (!opacity)
-        return true;
-
-    SRGBA<uint8_t> scrollbarBackgroundColor;
-    SRGBA<uint8_t> scrollbarBorderColor;
-    SRGBA<uint8_t> overlayThumbBorderColor;
-    SRGBA<uint8_t> overlayTroughBorderColor;
-    SRGBA<uint8_t> overlayTroughColor;
-    SRGBA<uint8_t> thumbHoveredColor;
-    SRGBA<uint8_t> thumbPressedColor;
-    SRGBA<uint8_t> thumbColor;
-
-    if (scrollbar.scrollableArea().useDarkAppearanceForScrollbars()) {
-        scrollbarBackgroundColor = scrollbarBackgroundColorDark;
-        scrollbarBorderColor = scrollbarBorderColorDark;
-        overlayThumbBorderColor = overlayThumbBorderColorDark;
-        overlayTroughBorderColor = overlayThumbBorderColorDark;
-        overlayTroughColor = overlayTroughColorDark;
-        thumbHoveredColor = thumbHoveredColorDark;
-        thumbPressedColor = thumbPressedColorDark;
-        thumbColor = thumbColorDark;
-    } else {
-        scrollbarBackgroundColor = scrollbarBackgroundColorLight;
-        scrollbarBorderColor = scrollbarBorderColorLight;
-        overlayThumbBorderColor = overlayThumbBorderColorLight;
-        overlayTroughBorderColor = overlayThumbBorderColorLight;
-        overlayTroughColor = overlayTroughColorLight;
-        thumbHoveredColor = thumbHoveredColorLight;
-        thumbPressedColor = thumbPressedColorLight;
-        thumbColor = thumbColorLight;
     }
-
-    GraphicsContextStateSaver stateSaver(graphicsContext);
-    if (opacity != 1) {
-        graphicsContext.clip(damageRect);
-        graphicsContext.beginTransparencyLayer(opacity);
-    }
-
-    int thumbSize = scrollbarSize - scrollbarBorderSize - horizThumbMargin * 2;
-
-    if (!usesOverlayScrollbars()) {
-        graphicsContext.fillRect(rect, scrollbarBackgroundColor);
-
-        IntRect frame = rect;
-        if (scrollbar.orientation() == ScrollbarOrientation::Vertical) {
-            if (scrollbar.scrollableArea().shouldPlaceVerticalScrollbarOnLeft())
-                frame.move(frame.width() - scrollbarBorderSize, 0);
-            frame.setWidth(scrollbarBorderSize);
-        } else
-            frame.setHeight(scrollbarBorderSize);
-        graphicsContext.fillRect(frame, scrollbarBorderColor);
-    } else if (scrollbar.hoveredPart() != NoPart) {
-        int thumbCornerSize = thumbSize / 2;
-        FloatSize corner(thumbCornerSize, thumbCornerSize);
-        FloatSize borderCorner(thumbCornerSize + thumbBorderSize, thumbCornerSize + thumbBorderSize);
-
-        IntRect trough = rect;
-        if (scrollbar.orientation() == ScrollbarOrientation::Vertical) {
-            if (scrollbar.scrollableArea().shouldPlaceVerticalScrollbarOnLeft())
-                trough.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2) - scrollbarBorderSize, vertThumbMargin);
-            else
-                trough.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2), vertThumbMargin);
-            trough.setWidth(thumbSize);
-            trough.setHeight(rect.height() - vertThumbMargin * 2);
-        } else {
-            trough.move(vertThumbMargin, scrollbarSize - (scrollbarSize / 2 + thumbSize / 2));
-            trough.setWidth(rect.width() - vertThumbMargin * 2);
-            trough.setHeight(thumbSize);
-        }
-
-        IntRect troughBorder(trough);
-        troughBorder.inflate(thumbBorderSize);
-
-        Path path;
-        path.addRoundedRect(trough, corner);
-        graphicsContext.setFillRule(WindRule::NonZero);
-        graphicsContext.setFillColor(overlayTroughColor);
-        graphicsContext.fillPath(path);
-        path.clear();
-
-        path.addRoundedRect(trough, corner);
-        path.addRoundedRect(troughBorder, borderCorner);
-        graphicsContext.setFillRule(WindRule::EvenOdd);
-        graphicsContext.setFillColor(overlayTroughBorderColor);
-        graphicsContext.fillPath(path);
-    }
-
-    int thumbCornerSize;
-    int thumbPos = thumbPosition(scrollbar);
-    int thumbLen = thumbLength(scrollbar);
-    IntRect thumb = rect;
-    if (scrollbar.hoveredPart() == NoPart && usesOverlayScrollbars()) {
-        thumbCornerSize = overlayThumbSize / 2;
-
-        if (scrollbar.orientation() == ScrollbarOrientation::Vertical) {
-            if (scrollbar.scrollableArea().shouldPlaceVerticalScrollbarOnLeft())
-                thumb.move(horizOverlayThumbMargin, thumbPos + vertThumbMargin);
-            else
-                thumb.move(scrollbarSize - overlayThumbSize - horizOverlayThumbMargin, thumbPos + vertThumbMargin);
-            thumb.setWidth(overlayThumbSize);
-            thumb.setHeight(thumbLen - vertThumbMargin * 2);
-        } else {
-            thumb.move(thumbPos + vertThumbMargin, scrollbarSize - overlayThumbSize - horizOverlayThumbMargin);
-            thumb.setWidth(thumbLen - vertThumbMargin * 2);
-            thumb.setHeight(overlayThumbSize);
-        }
-    } else {
-        thumbCornerSize = thumbSize / 2;
-
-        if (scrollbar.orientation() == ScrollbarOrientation::Vertical) {
-            if (scrollbar.scrollableArea().shouldPlaceVerticalScrollbarOnLeft())
-                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2) - scrollbarBorderSize, thumbPos + vertThumbMargin);
-            else
-                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2), thumbPos + vertThumbMargin);
-            thumb.setWidth(thumbSize);
-            thumb.setHeight(thumbLen - vertThumbMargin * 2);
-        } else {
-            thumb.move(thumbPos + vertThumbMargin, scrollbarSize - (scrollbarSize / 2 + thumbSize / 2));
-            thumb.setWidth(thumbLen - vertThumbMargin * 2);
-            thumb.setHeight(thumbSize);
-        }
-    }
-
-    FloatSize corner(thumbCornerSize, thumbCornerSize);
-    FloatSize borderCorner(thumbCornerSize + thumbBorderSize, thumbCornerSize + thumbBorderSize);
-
-    Path path;
-
-    path.addRoundedRect(thumb, corner);
-    graphicsContext.setFillRule(WindRule::NonZero);
-    if (scrollbar.pressedPart() == ThumbPart)
-        graphicsContext.setFillColor(thumbPressedColor);
-    else if (scrollbar.hoveredPart() == ThumbPart)
-        graphicsContext.setFillColor(thumbHoveredColor);
-    else
-        graphicsContext.setFillColor(thumbColor);
-    graphicsContext.fillPath(path);
-    path.clear();
-
-    if (usesOverlayScrollbars()) {
-        IntRect thumbBorder(thumb);
-        thumbBorder.inflate(thumbBorderSize);
-
-        path.addRoundedRect(thumb, corner);
-        path.addRoundedRect(thumbBorder, borderCorner);
-        graphicsContext.setFillRule(WindRule::EvenOdd);
-        graphicsContext.setFillColor(overlayThumbBorderColor);
-        graphicsContext.fillPath(path);
-    }
-
-    if (opacity != 1)
-        graphicsContext.endTransparencyLayer();
-
+#endif
+    State state {
+        .enabled = scrollbar.enabled(),
+        .useDarkAppearanceForScrollbars = scrollbar.scrollableArea().useDarkAppearanceForScrollbars(),
+        .shouldPlaceVerticalScrollbarOnLeft = scrollbar.scrollableArea().shouldPlaceVerticalScrollbarOnLeft(),
+        .usesOverlayScrollbars = usesOverlayScrollbars(),
+        .orientation = scrollbar.orientation(),
+        .hoveredPart = scrollbar.hoveredPart(),
+        .pressedPart = scrollbar.pressedPart(),
+        .thumbPosition = thumbPosition(scrollbar),
+        .thumbLength = thumbLength(scrollbar),
+        .frameRect = scrollbar.frameRect(),
+        .opacity = scrollbar.opacity(),
+    };
+    AdwaitaScrollbarPainter::paint(graphicsContext, damageRect, state);
     return true;
 }
 
@@ -352,6 +187,16 @@
     return ScrollbarButtonPressAction::None;
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+ScrollerImpAdwaita* ScrollbarThemeAdwaita::scrollerImpForScrollbar(Scrollbar& scrollbar)
+{
+    if (scrollbar.isCustomScrollbar())
+        return nullptr;
+    static NeverDestroyed<ScrollerImpAdwaita> scrollerImp;
+    return &scrollerImp.get();
+}
+#endif
+
 #if !PLATFORM(GTK) || USE(GTK4) || USE(SKIA)
 ScrollbarTheme& ScrollbarTheme::nativeTheme()
 {
diff --git a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.h b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.h
index a37ced0..fea966a 100644
--- a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.h
+++ b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.h
@@ -26,16 +26,20 @@
 #pragma once
 
 #if USE(THEME_ADWAITA)
-
 #include "ScrollbarThemeComposite.h"
 
 namespace WebCore {
+class ScrollerImpAdwaita;
 
 class ScrollbarThemeAdwaita : public ScrollbarThemeComposite {
 public:
     ScrollbarThemeAdwaita() = default;
     virtual ~ScrollbarThemeAdwaita() = default;
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    ScrollerImpAdwaita* scrollerImpForScrollbar(Scrollbar&);
+#endif
+
 protected:
     bool usesOverlayScrollbars() const override;
     bool invalidateOnMouseEnterExit() override { return usesOverlayScrollbars(); }
@@ -55,8 +59,14 @@
     IntRect backButtonRect(Scrollbar&, ScrollbarPart, bool painting = false) override;
     IntRect forwardButtonRect(Scrollbar&, ScrollbarPart, bool painting = false) override;
     IntRect trackRect(Scrollbar&, bool painting = false) override;
+
+    bool isScrollbarThemeAdwaita() const override { return true; };
 };
 
 } // namespace WebCore
 
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ScrollbarThemeAdwaita)
+static bool isType(const WebCore::ScrollbarTheme& scrollTheme) { return scrollTheme.isScrollbarThemeAdwaita(); }
+SPECIALIZE_TYPE_TRAITS_END()
+
 #endif // USE(THEME_ADWAITA)
diff --git a/Source/WebCore/platform/adwaita/ScrollerImpAdwaita.h b/Source/WebCore/platform/adwaita/ScrollerImpAdwaita.h
new file mode 100644
index 0000000..adf3120
--- /dev/null
+++ b/Source/WebCore/platform/adwaita/ScrollerImpAdwaita.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include "AdwaitaScrollbarPainter.h"
+
+namespace WebCore {
+
+class ScrollerImpAdwaita : public ThreadSafeRefCounted<ScrollerImpAdwaita> {
+public:
+    void paint(GraphicsContext& graphicsContext, const IntRect& damageRect, const AdwaitaScrollbarPainter::State& state)
+    {
+        AdwaitaScrollbarPainter::paint(graphicsContext, damageRect, state);
+    }
+};
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.cpp b/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.cpp
index 81a7527..26c09ec 100644
--- a/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.cpp
+++ b/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.cpp
@@ -35,6 +35,7 @@
 #include "ScrollAnimationSmooth.h"
 #include "ScrollableArea.h"
 #include "ScrollbarTheme.h"
+#include "ScrollbarsControllerInlines.h"
 
 namespace WebCore {
 
@@ -50,6 +51,10 @@
     : ScrollbarsController(scrollableArea)
     , m_overlayScrollbarAnimationTimer(*this, &ScrollbarsControllerGeneric::overlayScrollbarAnimationTimerFired)
 {
+    if (RefPtr verticalScrollbar = scrollableArea.verticalScrollbar())
+        didAddVerticalScrollbar(verticalScrollbar);
+    if (RefPtr horizontalScrollbar = scrollableArea.horizontalScrollbar())
+        didAddHorizontalScrollbar(horizontalScrollbar);
 }
 
 ScrollbarsControllerGeneric::~ScrollbarsControllerGeneric() = default;
@@ -111,6 +116,9 @@
         if (m_horizontalOverlayScrollbar->hoveredPart() == NoPart)
             m_horizontalOverlayScrollbar->invalidate();
     }
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    scrollbarOpacityChanged();
+#endif
 }
 
 static inline double easeOutCubic(double t)
diff --git a/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.h b/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.h
index 94bc1ed..d2317fb 100644
--- a/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.h
+++ b/Source/WebCore/platform/generic/ScrollbarsControllerGeneric.h
@@ -36,14 +36,13 @@
 
 namespace WebCore {
 
-class ScrollbarsControllerGeneric final : public ScrollbarsController, public CanMakeCheckedPtr<ScrollbarsControllerGeneric> {
+class ScrollbarsControllerGeneric : public ScrollbarsController, public CanMakeCheckedPtr<ScrollbarsControllerGeneric> {
     WTF_DEPRECATED_MAKE_FAST_ALLOCATED(ScrollbarsControllerGeneric);
     WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScrollbarsControllerGeneric);
 public:
     explicit ScrollbarsControllerGeneric(ScrollableArea&);
     virtual ~ScrollbarsControllerGeneric();
 
-private:
     void didAddVerticalScrollbar(Scrollbar*) override;
     void didAddHorizontalScrollbar(Scrollbar*) override;
     void willRemoveVerticalScrollbar(Scrollbar*) override;
@@ -57,6 +56,7 @@
     void notifyContentAreaScrolled(const FloatSize& delta) override;
     void lockOverlayScrollbarStateToHidden(bool) override;
 
+private:
     void overlayScrollbarAnimationTimerFired();
     void showOverlayScrollbars();
     void hideOverlayScrollbars();
diff --git a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
index 1f3fce7..b952a1a 100644
--- a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
+++ b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
@@ -579,6 +579,20 @@
 #endif
 }
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+void CoordinatedPlatformLayer::setContentsScrollbarImageForScrolling(NativeImage* image)
+{
+    Locker locker { m_lock };
+    if (image) {
+        setContentsImage(image);
+        IntRect rect { { }, image->size() };
+        setContentsRect(rect);
+        setContentsClippingRect(FloatRoundedRect(rect));
+    } else
+        setContentsImage(nullptr);
+}
+#endif
+
 #if ENABLE(DAMAGE_TRACKING)
 void CoordinatedPlatformLayer::addDamage(Damage&& damage)
 {
@@ -907,6 +921,21 @@
             layer.setBoundsOrigin(m_boundsOrigin);
             m_pendingChanges.remove(Change::BoundsOrigin);
         }
+
+        if (m_pendingChanges.contains(Change::ContentsRect)) {
+            layer.setContentsRect(m_contentsRect);
+            m_pendingChanges.remove(Change::ContentsRect);
+        }
+
+        if (m_pendingChanges.contains(Change::ContentsClippingRect)) {
+            layer.setContentsClippingRect(m_contentsClippingRect);
+            m_pendingChanges.remove(Change::ContentsClippingRect);
+        }
+
+        if (m_pendingChanges.contains(Change::ContentsImage)) {
+            m_imageBackingStore.committed = m_imageBackingStore.current;
+            m_pendingChanges.remove(Change::ContentsImage);
+        }
     }
 
     if (reasons.contains(CompositionReason::RenderingUpdate)) {
@@ -976,11 +1005,6 @@
             m_pendingChanges.remove(Change::ContentsOpaque);
         }
 
-        if (m_pendingChanges.contains(Change::ContentsRect)) {
-            layer.setContentsRect(m_contentsRect);
-            m_pendingChanges.remove(Change::ContentsRect);
-        }
-
         if (m_pendingChanges.contains(Change::ContentsRectClipsDescendants)) {
             layer.setContentsRectClipsDescendants(m_contentsRectClipsDescendants);
             m_pendingChanges.remove(Change::ContentsRectClipsDescendants);
@@ -992,16 +1016,6 @@
             m_pendingChanges.remove(Change::ContentsTiling);
         }
 
-        if (m_pendingChanges.contains(Change::ContentsClippingRect)) {
-            layer.setContentsClippingRect(m_contentsClippingRect);
-            m_pendingChanges.remove(Change::ContentsClippingRect);
-        }
-
-        if (m_pendingChanges.contains(Change::ContentsImage)) {
-            m_imageBackingStore.committed = m_imageBackingStore.current;
-            m_pendingChanges.remove(Change::ContentsImage);
-        }
-
         if (m_pendingChanges.contains(Change::ContentsColor)) {
             layer.setSolidColor(m_contentsColor);
             m_pendingChanges.remove(Change::ContentsColor);
@@ -1077,7 +1091,7 @@
         }
     }
 
-    if (reasons.containsAny({ CompositionReason::RenderingUpdate, CompositionReason::VideoFrame })) {
+    if (reasons.containsAny({ CompositionReason::RenderingUpdate, CompositionReason::VideoFrame, CompositionReason::AsyncScrolling })) {
         if (m_pendingChanges.contains(Change::ContentsBuffer)) {
             m_contentsBuffer.committed = WTF::move(m_contentsBuffer.pending);
             m_pendingChanges.remove(Change::ContentsBuffer);
@@ -1086,7 +1100,7 @@
         if (m_contentsBuffer.committed)
             layer.setContentsLayer(m_contentsBuffer.committed.get());
         else if (m_imageBackingStore.committed) {
-            if (reasons.contains(CompositionReason::RenderingUpdate))
+            if (reasons.containsAny({ CompositionReason::RenderingUpdate, CompositionReason::AsyncScrolling }))
                 layer.setContentsLayer(m_imageBackingStore.committed->buffer());
         } else
             layer.setContentsLayer(nullptr);
diff --git a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
index 3728699..caa5041 100644
--- a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
+++ b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
@@ -160,6 +160,10 @@
     void setContentsTilePhase(const FloatSize&);
     void setDirtyRegion(Damage&&);
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    void setContentsScrollbarImageForScrolling(NativeImage*);
+#endif
+
     void setFilters(const FilterOperations&);
     void setMask(CoordinatedPlatformLayer*);
     void setReplica(CoordinatedPlatformLayer*);
diff --git a/Source/WebKit/SourcesGTK.txt b/Source/WebKit/SourcesGTK.txt
index 497bce8..717ab45 100644
--- a/Source/WebKit/SourcesGTK.txt
+++ b/Source/WebKit/SourcesGTK.txt
@@ -360,6 +360,7 @@
 WebProcess/WebPage/CoordinatedGraphics/CoordinatedSceneState.cpp
 WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp
 WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp
+WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp
 WebProcess/WebPage/CoordinatedGraphics/ScrollingCoordinatorCoordinated.cpp
 WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.cpp
 
diff --git a/Source/WebKit/SourcesWPE.txt b/Source/WebKit/SourcesWPE.txt
index a3e7352..91e5a16 100644
--- a/Source/WebKit/SourcesWPE.txt
+++ b/Source/WebKit/SourcesWPE.txt
@@ -330,6 +330,7 @@
 WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp
 WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp
 WebProcess/WebPage/CoordinatedGraphics/NonCompositedFrameRenderer.cpp
+WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp
 WebProcess/WebPage/CoordinatedGraphics/ScrollingCoordinatorCoordinated.cpp
 WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.cpp
 
diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
index 52a94ad..57c6e71 100644
--- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
+++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
@@ -182,9 +182,16 @@
 
 #if PLATFORM(MAC)
 #include "RemoteScrollbarsController.h"
+#endif
+
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
 #include <WebCore/ScrollbarsControllerMock.h>
 #endif
 
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+#include "ScrollbarsControllerCoordinated.h"
+#endif
+
 #if ENABLE(DAMAGE_TRACKING)
 #include <WebCore/Damage.h>
 #endif
@@ -1370,7 +1377,7 @@
 
 #endif
 
-#if PLATFORM(MAC)
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
 void WebChromeClient::ensureScrollbarsController(Page& corePage, ScrollableArea& area, bool update) const
 {
     RefPtr page = m_page.get();
@@ -1385,6 +1392,7 @@
         return;
     }
 
+#if PLATFORM(MAC)
 #if ENABLE(TILED_CA_DRAWING_AREA)
     switch (page->protectedDrawingArea()->type()) {
     case DrawingAreaType::RemoteLayerTree: {
@@ -1405,6 +1413,15 @@
     else if (area.usesCompositedScrolling() && (!currentScrollbarsController || !is<RemoteScrollbarsController>(currentScrollbarsController)))
         area.setScrollbarsController(makeUnique<RemoteScrollbarsController>(area, corePage.scrollingCoordinator()));
 #endif
+#elif USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+    if (area.usesCompositedScrolling()) {
+        if (!currentScrollbarsController || !is<ScrollbarsControllerCoordinated>(currentScrollbarsController))
+            area.setScrollbarsController(makeUnique<ScrollbarsControllerCoordinated>(area, corePage.scrollingCoordinator()));
+    } else {
+        if (!currentScrollbarsController || is<ScrollbarsControllerCoordinated>(currentScrollbarsController))
+            area.setScrollbarsController(ScrollbarsController::create(area));
+    }
+#endif
 }
 
 #endif
diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
index eaeaf29..08e2012 100644
--- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
+++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
@@ -309,7 +309,7 @@
     RefPtr<WebCore::ScrollingCoordinator> createScrollingCoordinator(WebCore::Page&) const final;
 #endif
 
-#if PLATFORM(MAC)
+#if PLATFORM(MAC) || USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
     void ensureScrollbarsController(WebCore::Page&, WebCore::ScrollableArea&, bool update = false) const final;
 #endif
 
diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp
new file mode 100644
index 0000000..35f6456
--- /dev/null
+++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 Apple Inc. All rights reserved.
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScrollbarsControllerCoordinated.h"
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include <WebCore/ScrollableArea.h>
+#include <WebCore/ScrollbarTheme.h>
+#include <WebCore/ScrollbarsControllerInlines.h>
+#include <WebCore/ScrollingCoordinator.h>
+#include <wtf/TZoneMallocInlines.h>
+
+namespace WebKit {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(ScrollbarsControllerCoordinated);
+
+ScrollbarsControllerCoordinated::ScrollbarsControllerCoordinated(WebCore::ScrollableArea& scrollableArea, WebCore::ScrollingCoordinator* coordinator)
+    : WebCore::ScrollbarsControllerGeneric(scrollableArea)
+    , m_coordinator(ThreadSafeWeakPtr<WebCore::ScrollingCoordinator>(coordinator))
+{
+    if (auto scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setScrollbarWidth(scrollableArea, scrollableArea.scrollbarWidthStyle());
+}
+
+void ScrollbarsControllerCoordinated::scrollbarLayoutDirectionChanged(WebCore::UserInterfaceLayoutDirection scrollbarLayoutDirection)
+{
+    WebCore::ScrollbarsControllerGeneric::scrollbarLayoutDirectionChanged(scrollbarLayoutDirection);
+
+    if (RefPtr scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setScrollbarLayoutDirection(checkedScrollableArea(), scrollbarLayoutDirection);
+}
+
+bool ScrollbarsControllerCoordinated::shouldDrawIntoScrollbarLayer(WebCore::Scrollbar& scrollbar) const
+{
+    return scrollbar.isCustomScrollbar() || scrollbar.isMockScrollbar();
+}
+
+void ScrollbarsControllerCoordinated::updateScrollbarEnabledState(WebCore::Scrollbar& scrollbar)
+{
+    if (auto scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setScrollbarEnabled(scrollbar);
+}
+
+void ScrollbarsControllerCoordinated::updateScrollbarStyle()
+{
+    auto& theme = WebCore::ScrollbarTheme::theme();
+    if (theme.isMockTheme())
+        return;
+
+    // The different scrollbar styles have different thicknesses, so we must re-set the
+    // frameRect to the new thickness, and the re-layout below will ensure the position
+    // and length are properly updated.
+    updateScrollbarsThickness();
+
+    checkedScrollableArea()->scrollbarStyleChanged(theme.usesOverlayScrollbars() ? WebCore::ScrollbarStyle::Overlay : WebCore::ScrollbarStyle::AlwaysVisible, true);
+}
+
+void ScrollbarsControllerCoordinated::scrollbarOpacityChanged()
+{
+    if (auto scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setScrollbarOpacity(checkedScrollableArea());
+}
+
+void ScrollbarsControllerCoordinated::hoveredPartChanged(WebCore::Scrollbar& scrollbar)
+{
+    if (auto scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setHoveredAndPressedScrollbarParts(checkedScrollableArea());
+}
+
+void ScrollbarsControllerCoordinated::pressedPartChanged(WebCore::Scrollbar& scrollbar)
+{
+    if (auto scrollingCoordinator = m_coordinator.get())
+        scrollingCoordinator->setHoveredAndPressedScrollbarParts(checkedScrollableArea());
+}
+
+}
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.h b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.h
new file mode 100644
index 0000000..85605db
--- /dev/null
+++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ScrollbarsControllerCoordinated.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 Apple Inc. All rights reserved.
+ * Copyright (C) 2026 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
+
+#include <WebCore/ScrollbarsControllerGeneric.h>
+#include <WebCore/UserInterfaceLayoutDirection.h>
+#include <wtf/RetainPtr.h>
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeWeakPtr.h>
+
+namespace WebCore {
+class ScrollingCoordinator;
+class Color;
+}
+
+namespace WebKit {
+
+class ScrollbarsControllerCoordinated final : public WebCore::ScrollbarsControllerGeneric {
+    WTF_MAKE_TZONE_ALLOCATED(ScrollbarsControllerCoordinated);
+    WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScrollbarsControllerCoordinated);
+public:
+    ScrollbarsControllerCoordinated(WebCore::ScrollableArea&, WebCore::ScrollingCoordinator*);
+    bool shouldDrawIntoScrollbarLayer(WebCore::Scrollbar&) const final;
+    void updateScrollbarEnabledState(WebCore::Scrollbar&) final;
+    void scrollbarLayoutDirectionChanged(WebCore::UserInterfaceLayoutDirection) final;
+    void scrollbarOpacityChanged() final;
+    void updateScrollbarStyle() final;
+    bool isScrollbarsControllerCoordinated() const final { return true; }
+    void hoveredPartChanged(WebCore::Scrollbar&) final;
+    void pressedPartChanged(WebCore::Scrollbar&) final;
+
+private:
+    ThreadSafeWeakPtr<WebCore::ScrollingCoordinator> m_coordinator;
+};
+
+} // namespace WebKit
+
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebKit::ScrollbarsControllerCoordinated)
+    static bool isType(const WebCore::ScrollbarsController& controller) { return controller.isScrollbarsControllerCoordinated(); }
+SPECIALIZE_TYPE_TRAITS_END()
+
+#endif // USE(COORDINATED_GRAPHICS_ASYNC_SCROLLBAR)
diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.h b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.h
index 051e6d1..14e1255 100644
--- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.h
+++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ThreadedCompositor.h
@@ -105,7 +105,7 @@
     void scheduleUpdateLocked();
     void flushCompositingState(const OptionSet<WebCore::CompositionReason>&);
     void renderLayerTree();
-    void paintToCurrentGLContext(const WebCore::TransformationMatrix&, const WebCore::IntSize&, const OptionSet<CompositionReason>&);
+    void paintToCurrentGLContext(const WebCore::TransformationMatrix&, const WebCore::IntSize&, const OptionSet<WebCore::CompositionReason>&);
     void frameComplete();
 
     void didCompositeRunLoopObserverFired();