ScrollbarThemeGtk.cpp   [plain text]


/*
 * Copyright (C) 2016 Igalia S.L.
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * 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. ``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
 * 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 "ScrollbarThemeGtk.h"

#include "GRefPtrGtk.h"
#include "PlatformContextCairo.h"
#include "PlatformMouseEvent.h"
#include "RenderThemeWidget.h"
#include "ScrollView.h"
#include "Scrollbar.h"
#include <cstdlib>
#include <gtk/gtk.h>

namespace WebCore {

ScrollbarTheme& ScrollbarTheme::nativeTheme()
{
    static ScrollbarThemeGtk theme;
    return theme;
}

ScrollbarThemeGtk::~ScrollbarThemeGtk() = default;

#ifndef GTK_API_VERSION_2
static void themeChangedCallback()
{
    ScrollbarTheme::theme().themeChanged();
}

ScrollbarThemeGtk::ScrollbarThemeGtk()
{
#if GTK_CHECK_VERSION(3, 20, 0)
    m_usesOverlayScrollbars = g_strcmp0(g_getenv("GTK_OVERLAY_SCROLLING"), "0");
#endif
    static bool themeMonitorInitialized = false;
    if (!themeMonitorInitialized) {
        g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
        themeMonitorInitialized = true;
        updateThemeProperties();
    }
}

#if !GTK_CHECK_VERSION(3, 20, 0)
static GRefPtr<GtkStyleContext> createStyleContext(Scrollbar* scrollbar = nullptr)
{
    GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
    GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_new());
    gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
    gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
    gtk_widget_path_iter_add_class(path.get(), -1, scrollbar && scrollbar->orientation() == HorizontalScrollbar ? "horizontal" : "vertical");
    gtk_style_context_set_path(styleContext.get(), path.get());
    return styleContext;
}

static GRefPtr<GtkStyleContext> createChildStyleContext(GtkStyleContext* parent, const char* className)
{
    ASSERT(parent);
    GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent)));
    gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
    gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
    gtk_widget_path_iter_add_class(path.get(), -1, className);

    GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
    gtk_style_context_set_path(styleContext.get(), path.get());
    gtk_style_context_set_parent(styleContext.get(), parent);
    return styleContext;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

void ScrollbarThemeGtk::themeChanged()
{
#if GTK_CHECK_VERSION(3, 20, 0)
    RenderThemeWidget::clearCache();
#endif
    updateThemeProperties();
}

#if GTK_CHECK_VERSION(3, 20, 0)
void ScrollbarThemeGtk::updateThemeProperties()
{
    auto& scrollbar = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
    m_hasBackButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
    m_hasForwardButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
    m_hasBackButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
    m_hasForwardButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
}
#else
void ScrollbarThemeGtk::updateThemeProperties()
{
    gboolean hasBackButtonStartPart, hasForwardButtonEndPart, hasBackButtonEndPart, hasForwardButtonStartPart;
    gtk_style_context_get_style(createStyleContext().get(),
        "has-backward-stepper", &hasBackButtonStartPart,
        "has-forward-stepper", &hasForwardButtonEndPart,
        "has-secondary-backward-stepper", &hasBackButtonEndPart,
        "has-secondary-forward-stepper", &hasForwardButtonStartPart,
        nullptr);
    m_hasBackButtonStartPart = hasBackButtonStartPart;
    m_hasForwardButtonEndPart = hasForwardButtonEndPart;
    m_hasBackButtonEndPart = hasBackButtonEndPart;
    m_hasForwardButtonStartPart = hasForwardButtonStartPart;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

bool ScrollbarThemeGtk::hasButtons(Scrollbar& scrollbar)
{
    return scrollbar.enabled() && (m_hasBackButtonStartPart || m_hasForwardButtonEndPart || m_hasBackButtonEndPart || m_hasForwardButtonStartPart);
}

#if GTK_CHECK_VERSION(3, 20, 0)
static GtkStateFlags scrollbarPartStateFlags(Scrollbar& scrollbar, ScrollbarPart part, bool painting = false)
{
    unsigned stateFlags = 0;
    switch (part) {
    case AllParts:
        if (!painting || scrollbar.hoveredPart() != NoPart)
            stateFlags |= GTK_STATE_FLAG_PRELIGHT;
        break;
    case BackTrackPart:
    case ForwardTrackPart:
        if (scrollbar.hoveredPart() == BackTrackPart || scrollbar.hoveredPart() == ForwardTrackPart)
            stateFlags |= GTK_STATE_FLAG_PRELIGHT;
        if (scrollbar.pressedPart() == BackTrackPart || scrollbar.pressedPart() == ForwardTrackPart)
            stateFlags |= GTK_STATE_FLAG_ACTIVE;
        break;
    case BackButtonStartPart:
    case ForwardButtonStartPart:
    case BackButtonEndPart:
    case ForwardButtonEndPart:
        if (((part == BackButtonStartPart || part == BackButtonEndPart) && !scrollbar.currentPos())
            || ((part == ForwardButtonEndPart || part == ForwardButtonStartPart) && scrollbar.currentPos() == scrollbar.maximum())) {
            stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
            break;
        }
        FALLTHROUGH;
    default:
        if (scrollbar.hoveredPart() == part)
            stateFlags |= GTK_STATE_FLAG_PRELIGHT;

        if (scrollbar.pressedPart() == part)
            stateFlags |= GTK_STATE_FLAG_ACTIVE;
        break;
    }

    return static_cast<GtkStateFlags>(stateFlags);
}

static RenderThemeWidget::Type widgetTypeForScrollbar(Scrollbar& scrollbar, GtkStateFlags scrollbarState)
{
    if (scrollbar.orientation() == VerticalScrollbar) {
        if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
            return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarLeft : RenderThemeWidget::Type::VerticalScrollIndicatorLeft;
        return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarRight : RenderThemeWidget::Type::VerticalScrollIndicatorRight;
    }
    return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::HorizontalScrollbar : RenderThemeWidget::Type::HorizontalScrollIndicator;
}

static IntRect contentsRectangle(Scrollbar& scrollbar, RenderThemeScrollbar& scrollbarWidget)
{
    GtkBorder scrollbarContentsBox = scrollbarWidget.scrollbar().contentsBox();
    GtkBorder contentsContentsBox = scrollbarWidget.contents().contentsBox();
    GtkBorder padding;
    padding.left = scrollbarContentsBox.left + contentsContentsBox.left;
    padding.right = scrollbarContentsBox.right + contentsContentsBox.right;
    padding.top = scrollbarContentsBox.top + contentsContentsBox.top;
    padding.bottom = scrollbarContentsBox.bottom + contentsContentsBox.bottom;
    IntRect contentsRect = scrollbar.frameRect();
    contentsRect.move(padding.left, padding.top);
    contentsRect.contract(padding.left + padding.right, padding.top + padding.bottom);
    return contentsRect;
}

IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
{
    auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
    scrollbarWidget.scrollbar().setState(scrollbarState);

    IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
    if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
        backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
        IntSize stepperSize = backwardStepper->preferredSize();
        if (scrollbar.orientation() == VerticalScrollbar) {
            rect.move(0, stepperSize.height());
            rect.contract(0, stepperSize.height());
        } else {
            rect.move(stepperSize.width(), 0);
            rect.contract(stepperSize.width(), 0);
        }
    }
    if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
        secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
        IntSize stepperSize = secondaryForwardStepper->preferredSize();
        if (scrollbar.orientation() == VerticalScrollbar) {
            rect.move(0, stepperSize.height());
            rect.contract(0, stepperSize.height());
        } else {
            rect.move(stepperSize.width(), 0);
            rect.contract(stepperSize.width(), 0);
        }
    }
    if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
        secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
        if (scrollbar.orientation() == VerticalScrollbar)
            rect.contract(0, secondaryBackwardStepper->preferredSize().height());
        else
            rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
    }
    if (auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward)) {
        forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
        if (scrollbar.orientation() == VerticalScrollbar)
            rect.contract(0, forwardStepper->preferredSize().height());
        else
            rect.contract(forwardStepper->preferredSize().width(), 0);
    }

    if (scrollbar.orientation() == VerticalScrollbar)
        return scrollbar.height() < rect.height() ? IntRect() : rect;

    return scrollbar.width() < rect.width() ? IntRect() : rect;
}
#else
IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
{
    GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
    // The padding along the thumb movement axis includes the trough border
    // plus the size of stepper spacing (the space between the stepper and
    // the place where the thumb stops). There is often no stepper spacing.
    int stepperSpacing, stepperSize, troughBorderWidth, thumbFat;
    gtk_style_context_get_style(styleContext.get(), "stepper-spacing", &stepperSpacing, "stepper-size", &stepperSize, "trough-border",
        &troughBorderWidth, "slider-width", &thumbFat, nullptr);

    // The fatness of the scrollbar on the non-movement axis.
    int thickness = thumbFat + 2 * troughBorderWidth;

    int startButtonsOffset = 0;
    int buttonsWidth = 0;
    if (m_hasForwardButtonStartPart) {
        startButtonsOffset += stepperSize;
        buttonsWidth += stepperSize;
    }
    if (m_hasBackButtonStartPart) {
        startButtonsOffset += stepperSize;
        buttonsWidth += stepperSize;
    }
    if (m_hasBackButtonEndPart)
        buttonsWidth += stepperSize;
    if (m_hasForwardButtonEndPart)
        buttonsWidth += stepperSize;

    if (scrollbar.orientation() == HorizontalScrollbar) {
        // Once the scrollbar becomes smaller than the natural size of the two buttons and the thumb, the track disappears.
        if (scrollbar.width() < buttonsWidth + minimumThumbLength(scrollbar))
            return IntRect();
        return IntRect(scrollbar.x() + troughBorderWidth + stepperSpacing + startButtonsOffset, scrollbar.y(),
            scrollbar.width() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth, thickness);
    }

    if (scrollbar.height() < buttonsWidth + minimumThumbLength(scrollbar))
        return IntRect();
    return IntRect(scrollbar.x(), scrollbar.y() + troughBorderWidth + stepperSpacing + startButtonsOffset,
        thickness, scrollbar.height() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth);
}
#endif

bool ScrollbarThemeGtk::hasThumb(Scrollbar& scrollbar)
{
    // This method is just called as a paint-time optimization to see if
    // painting the thumb can be skipped. We don't have to be exact here.
    return thumbLength(scrollbar) > 0;
}

#if GTK_CHECK_VERSION(3, 20, 0)
IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
{
    ASSERT(part == BackButtonStartPart || part == BackButtonEndPart);
    if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
        return IntRect();

    auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
    scrollbarWidget.scrollbar().setState(scrollbarState);

    IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
    if (part == BackButtonStartPart) {
        auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
        ASSERT(backwardStepper);
        backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
        return IntRect(rect.location(), backwardStepper->preferredSize());
    }

    if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
        secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
        IntSize preferredSize = secondaryForwardStepper->preferredSize();
        if (scrollbar.orientation() == VerticalScrollbar) {
            rect.move(0, preferredSize.height());
            rect.contract(0, preferredSize.height());
        } else {
            rect.move(preferredSize.width(), 0);
            rect.contract(0, preferredSize.width());
        }
    }

    if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
        secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
        if (scrollbar.orientation() == VerticalScrollbar)
            rect.contract(0, secondaryBackwardStepper->preferredSize().height());
        else
            rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
    }

    auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
    ASSERT(forwardStepper);
    forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
    IntSize preferredSize = forwardStepper->preferredSize();
    if (scrollbar.orientation() == VerticalScrollbar)
        rect.move(0, rect.height() - preferredSize.height());
    else
        rect.move(rect.width() - preferredSize.width(), 0);

    return IntRect(rect.location(), preferredSize);
}

IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
{
    ASSERT(part == ForwardButtonStartPart || part == ForwardButtonEndPart);
    if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
        return IntRect();

    auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
    scrollbarWidget.scrollbar().setState(scrollbarState);

    IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
    if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
        backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
        IntSize preferredSize = backwardStepper->preferredSize();
        if (scrollbar.orientation() == VerticalScrollbar) {
            rect.move(0, preferredSize.height());
            rect.contract(0, preferredSize.height());
        } else {
            rect.move(preferredSize.width(), 0);
            rect.contract(preferredSize.width(), 0);
        }
    }

    if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
        secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
        IntSize preferredSize = secondaryForwardStepper->preferredSize();
        if (part == ForwardButtonStartPart)
            return IntRect(rect.location(), preferredSize);

        if (scrollbar.orientation() == VerticalScrollbar) {
            rect.move(0, preferredSize.height());
            rect.contract(0, preferredSize.height());
        } else {
            rect.move(preferredSize.width(), 0);
            rect.contract(preferredSize.width(), 0);
        }
    }

    auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
    ASSERT(forwardStepper);
    forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
    IntSize preferredSize = forwardStepper->preferredSize();
    if (scrollbar.orientation() == VerticalScrollbar)
        rect.move(0, rect.height() - preferredSize.height());
    else
        rect.move(rect.width() - preferredSize.width(), 0);

    return IntRect(rect.location(), preferredSize);
}
#else
IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
{
    if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
        return IntRect();

    GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
    int troughBorderWidth, stepperSize, thumbFat;
    gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
    int x = scrollbar.x() + troughBorderWidth;
    int y = scrollbar.y() + troughBorderWidth;
    if (part == BackButtonStartPart) {
        if (scrollbar.orientation() == HorizontalScrollbar)
            return IntRect(x, y, stepperSize, thumbFat);
        return IntRect(x, y, thumbFat, stepperSize);
    }

    // BackButtonEndPart (alternate button)
    if (scrollbar.orientation() == HorizontalScrollbar)
        return IntRect(scrollbar.x() + scrollbar.width() - troughBorderWidth - (2 * stepperSize), y, stepperSize, thumbFat);

    // VerticalScrollbar alternate button
    return IntRect(x, scrollbar.y() + scrollbar.height() - troughBorderWidth - (2 * stepperSize), thumbFat, stepperSize);
}

IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
{
    if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
        return IntRect();

    GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
    int troughBorderWidth, stepperSize, thumbFat;
    gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
    if (scrollbar.orientation() == HorizontalScrollbar) {
        int y = scrollbar.y() + troughBorderWidth;
        if (part == ForwardButtonEndPart)
            return IntRect(scrollbar.x() + scrollbar.width() - stepperSize - troughBorderWidth, y, stepperSize, thumbFat);

        // ForwardButtonStartPart (alternate button)
        return IntRect(scrollbar.x() + troughBorderWidth + stepperSize, y, stepperSize, thumbFat);
    }

    // VerticalScrollbar
    int x = scrollbar.x() + troughBorderWidth;
    if (part == ForwardButtonEndPart)
        return IntRect(x, scrollbar.y() + scrollbar.height() - stepperSize - troughBorderWidth, thumbFat, stepperSize);

    // ForwardButtonStartPart (alternate button)
    return IntRect(x, scrollbar.y() + troughBorderWidth + stepperSize, thumbFat, stepperSize);
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

#if GTK_CHECK_VERSION(3, 20, 0)
bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
{
    if (graphicsContext.paintingDisabled())
        return false;

    if (!scrollbar.enabled())
        return true;

    double opacity = scrollbar.hoveredPart() == NoPart ? scrollbar.opacity() : 1;
    if (!opacity)
        return true;

    IntRect rect = scrollbar.frameRect();
    if (!rect.intersects(damageRect))
        return true;

    auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts, true);
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
    auto& scrollbarGadget = scrollbarWidget.scrollbar();
    scrollbarGadget.setState(scrollbarState);
    if (m_usesOverlayScrollbars)
        opacity *= scrollbarGadget.opacity();
    if (!opacity)
        return true;

    auto& trough = scrollbarWidget.trough();
    trough.setState(scrollbarPartStateFlags(scrollbar, BackTrackPart));

    auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
    if (backwardStepper)
        backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
    auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
    if (secondaryForwardStepper)
        secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
    auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
    if (secondaryBackwardStepper)
        secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
    auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
    if (forwardStepper)
        forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));

    IntSize preferredSize = scrollbarWidget.contents().preferredSize();
    int thumbSize = thumbLength(scrollbar);
    if (thumbSize) {
        scrollbarWidget.slider().setState(scrollbarPartStateFlags(scrollbar, ThumbPart));
        preferredSize = preferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
    }
    preferredSize += scrollbarGadget.preferredSize() - scrollbarGadget.minimumSize();

    FloatRect contentsRect(rect);
    // When using overlay scrollbars we always claim the size of the scrollbar when hovered, so when
    // drawing the indicator we need to adjust the rectangle to its actual size in indicator mode.
    if (scrollbar.orientation() == VerticalScrollbar) {
        if (rect.width() != preferredSize.width()) {
            if (!scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
                contentsRect.move(std::abs(rect.width() - preferredSize.width()), 0);
            contentsRect.setWidth(preferredSize.width());
        }
    } else {
        if (rect.height() != preferredSize.height()) {
            contentsRect.move(0, std::abs(rect.height() - preferredSize.height()));
            contentsRect.setHeight(preferredSize.height());
        }
    }

    if (opacity != 1) {
        graphicsContext.save();
        graphicsContext.clip(damageRect);
        graphicsContext.beginTransparencyLayer(opacity);
    }

    scrollbarGadget.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
    scrollbarWidget.contents().render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);

    if (backwardStepper) {
        FloatRect buttonRect = contentsRect;
        if (scrollbar.orientation() == VerticalScrollbar)
            buttonRect.setHeight(backwardStepper->preferredSize().height());
        else
            buttonRect.setWidth(backwardStepper->preferredSize().width());
        static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, backwardStepper,
            scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Backward);
        if (scrollbar.orientation() == VerticalScrollbar) {
            contentsRect.move(0, buttonRect.height());
            contentsRect.contract(0, buttonRect.height());
        } else {
            contentsRect.move(buttonRect.width(), 0);
            contentsRect.contract(buttonRect.width(), 0);
        }
    }
    if (secondaryForwardStepper) {
        FloatRect buttonRect = contentsRect;
        if (scrollbar.orientation() == VerticalScrollbar)
            buttonRect.setHeight(secondaryForwardStepper->preferredSize().height());
        else
            buttonRect.setWidth(secondaryForwardStepper->preferredSize().width());
        static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryForwardStepper,
            scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryForward);
        if (scrollbar.orientation() == VerticalScrollbar) {
            contentsRect.move(0, buttonRect.height());
            contentsRect.contract(0, buttonRect.height());
        } else {
            contentsRect.move(buttonRect.width(), 0);
            contentsRect.contract(buttonRect.width(), 0);
        }
    }
    if (secondaryBackwardStepper) {
        FloatRect buttonRect = contentsRect;
        if (scrollbar.orientation() == VerticalScrollbar) {
            buttonRect.setHeight(secondaryBackwardStepper->preferredSize().height());
            buttonRect.move(0, contentsRect.height() - buttonRect.height());
        } else {
            buttonRect.setWidth(secondaryBackwardStepper->preferredSize().width());
            buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
        }
        static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryBackwardStepper,
            scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
        if (scrollbar.orientation() == VerticalScrollbar)
            contentsRect.contract(0, buttonRect.height());
        else
            contentsRect.contract(buttonRect.width(), 0);
    }
    if (forwardStepper) {
        FloatRect buttonRect = contentsRect;
        if (scrollbar.orientation() == VerticalScrollbar) {
            buttonRect.setHeight(forwardStepper->preferredSize().height());
            buttonRect.move(0, contentsRect.height() - buttonRect.height());
        } else {
            buttonRect.setWidth(forwardStepper->preferredSize().width());
            buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
        }
        static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, forwardStepper,
            scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Forward);
        if (scrollbar.orientation() == VerticalScrollbar)
            contentsRect.contract(0, buttonRect.height());
        else
            contentsRect.contract(buttonRect.width(), 0);
    }

    trough.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);

    if (thumbSize) {
        if (scrollbar.orientation() == VerticalScrollbar) {
            contentsRect.move(0, thumbPosition(scrollbar));
            contentsRect.setWidth(scrollbarWidget.slider().preferredSize().width());
            contentsRect.setHeight(thumbSize);
        } else {
            contentsRect.move(thumbPosition(scrollbar), 0);
            contentsRect.setWidth(thumbSize);
            contentsRect.setHeight(scrollbarWidget.slider().preferredSize().height());
        }
        if (contentsRect.intersects(damageRect))
            scrollbarWidget.slider().render(graphicsContext.platformContext()->cr(), contentsRect);
    }

    if (opacity != 1) {
        graphicsContext.endTransparencyLayer();
        graphicsContext.restore();
    }

    return true;
}
#else
static void paintStepper(GtkStyleContext* parentContext, GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part)
{
    ScrollbarOrientation orientation = scrollbar.orientation();
    GRefPtr<GtkStyleContext> styleContext = createChildStyleContext(parentContext, "button");

    unsigned flags = 0;
    if ((BackButtonStartPart == part && scrollbar.currentPos())
        || (BackButtonEndPart == part && scrollbar.currentPos())
        || (ForwardButtonEndPart == part && scrollbar.currentPos() != scrollbar.maximum())
        || (ForwardButtonStartPart == part && scrollbar.currentPos() != scrollbar.maximum())) {
        if (part == scrollbar.pressedPart())
            flags |= GTK_STATE_FLAG_ACTIVE;
        if (part == scrollbar.hoveredPart())
            flags |= GTK_STATE_FLAG_PRELIGHT;
    } else
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    gtk_style_context_set_state(styleContext.get(), static_cast<GtkStateFlags>(flags));

    gtk_render_background(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
    gtk_render_frame(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());

    gfloat arrowScaling;
    gtk_style_context_get_style(styleContext.get(), "arrow-scaling", &arrowScaling, nullptr);

    double arrowSize = std::min(rect.width(), rect.height()) * arrowScaling;
    FloatPoint arrowPoint(rect.x() + (rect.width() - arrowSize) / 2, rect.y() + (rect.height() - arrowSize) / 2);

    if (flags & GTK_STATE_FLAG_ACTIVE) {
        gint arrowDisplacementX, arrowDisplacementY;
        gtk_style_context_get_style(styleContext.get(), "arrow-displacement-x", &arrowDisplacementX, "arrow-displacement-y", &arrowDisplacementY, nullptr);
        arrowPoint.move(arrowDisplacementX, arrowDisplacementY);
    }

    gdouble angle;
    if (orientation == VerticalScrollbar)
        angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI : 0;
    else
        angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI / 2 : 3 * (G_PI / 2);

    gtk_render_arrow(styleContext.get(), context.platformContext()->cr(), angle, arrowPoint.x(), arrowPoint.y(), arrowSize);
}

static void adjustRectAccordingToMargin(GtkStyleContext* context, IntRect& rect)
{
    GtkBorder margin;
    gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
    rect.move(margin.left, margin.top);
    rect.contract(margin.left + margin.right, margin.top + margin.bottom);
}

bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
{
    if (graphicsContext.paintingDisabled())
        return false;

    GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);

    // Create the ScrollbarControlPartMask based on the damageRect
    ScrollbarControlPartMask scrollMask = NoPart;

    IntRect backButtonStartPaintRect;
    IntRect backButtonEndPaintRect;
    IntRect forwardButtonStartPaintRect;
    IntRect forwardButtonEndPaintRect;
    if (hasButtons(scrollbar)) {
        backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
        if (damageRect.intersects(backButtonStartPaintRect))
            scrollMask |= BackButtonStartPart;
        backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
        if (damageRect.intersects(backButtonEndPaintRect))
            scrollMask |= BackButtonEndPart;
        forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
        if (damageRect.intersects(forwardButtonStartPaintRect))
            scrollMask |= ForwardButtonStartPart;
        forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
        if (damageRect.intersects(forwardButtonEndPaintRect))
            scrollMask |= ForwardButtonEndPart;
    }

    IntRect trackPaintRect = trackRect(scrollbar, true);
    if (damageRect.intersects(trackPaintRect))
        scrollMask |= TrackBGPart;

    gboolean troughUnderSteppers;
    gtk_style_context_get_style(styleContext.get(), "trough-under-steppers", &troughUnderSteppers, nullptr);
    if (troughUnderSteppers && (scrollMask & BackButtonStartPart
            || scrollMask & BackButtonEndPart
            || scrollMask & ForwardButtonStartPart
            || scrollMask & ForwardButtonEndPart))
        scrollMask |= TrackBGPart;

    IntRect currentThumbRect;
    if (hasThumb(scrollbar)) {
        IntRect track = trackRect(scrollbar, false);
        IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, track);
        int thumbFat;
        gtk_style_context_get_style(styleContext.get(), "slider-width", &thumbFat, nullptr);
        if (scrollbar.orientation() == HorizontalScrollbar)
            currentThumbRect = IntRect(trackRect.x() + thumbPosition(scrollbar), trackRect.y() + (trackRect.height() - thumbFat) / 2, thumbLength(scrollbar), thumbFat);
        else
            currentThumbRect = IntRect(trackRect.x() + (trackRect.width() - thumbFat) / 2, trackRect.y() + thumbPosition(scrollbar), thumbFat, thumbLength(scrollbar));
        if (damageRect.intersects(currentThumbRect))
            scrollMask |= ThumbPart;
    }

    if (scrollMask == NoPart)
        return true;

    ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart | ForwardButtonStartPart | ForwardButtonEndPart;

    // Paint the track background. If the trough-under-steppers property is true, this
    // should be the full size of the scrollbar, but if is false, it should only be the
    // track rect.
    GRefPtr<GtkStyleContext> troughStyleContext = createChildStyleContext(styleContext.get(), "trough");
    if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons) {
        IntRect fullScrollbarRect = trackPaintRect;
        if (troughUnderSteppers)
            fullScrollbarRect = scrollbar.frameRect();

        IntRect adjustedRect = fullScrollbarRect;
        adjustRectAccordingToMargin(styleContext.get(), adjustedRect);
        gtk_render_background(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
        gtk_render_frame(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());

        adjustedRect = fullScrollbarRect;
        adjustRectAccordingToMargin(troughStyleContext.get(), adjustedRect);
        gtk_render_background(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
        gtk_render_frame(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
    }

    // Paint the back and forward buttons.
    if (scrollMask & BackButtonStartPart)
        paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
    if (scrollMask & BackButtonEndPart)
        paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
    if (scrollMask & ForwardButtonStartPart)
        paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
    if (scrollMask & ForwardButtonEndPart)
        paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);

    // Paint the thumb.
    if (scrollMask & ThumbPart) {
        GRefPtr<GtkStyleContext> thumbStyleContext = createChildStyleContext(troughStyleContext.get(), "slider");
        unsigned flags = 0;
        if (scrollbar.pressedPart() == ThumbPart)
            flags |= GTK_STATE_FLAG_ACTIVE;
        if (scrollbar.hoveredPart() == ThumbPart)
            flags |= GTK_STATE_FLAG_PRELIGHT;
        gtk_style_context_set_state(thumbStyleContext.get(), static_cast<GtkStateFlags>(flags));

        IntRect thumbRect(currentThumbRect);
        adjustRectAccordingToMargin(thumbStyleContext.get(), thumbRect);
        gtk_render_slider(thumbStyleContext.get(), graphicsContext.platformContext()->cr(), thumbRect.x(), thumbRect.y(), thumbRect.width(), thumbRect.height(),
            scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
    }

    return true;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

ScrollbarButtonPressAction ScrollbarThemeGtk::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
{
    switch (pressedPart) {
    case BackTrackPart:
    case ForwardTrackPart:
        if (event.button() == LeftButton)
            return ScrollbarButtonPressAction::CenterOnThumb;
        if (event.button() == RightButton)
            return ScrollbarButtonPressAction::Scroll;
        break;
    case ThumbPart:
        if (event.button() != RightButton)
            return ScrollbarButtonPressAction::StartDrag;
        break;
    case BackButtonStartPart:
    case ForwardButtonStartPart:
    case BackButtonEndPart:
    case ForwardButtonEndPart:
        return ScrollbarButtonPressAction::Scroll;
    default:
        break;
    }

    return ScrollbarButtonPressAction::None;
}

#if GTK_CHECK_VERSION(3, 20, 0)
int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
{
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
    scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
    IntSize contentsPreferredSize = scrollbarWidget.contents().preferredSize();
    contentsPreferredSize = contentsPreferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
    IntSize preferredSize = contentsPreferredSize + scrollbarWidget.scrollbar().preferredSize() - scrollbarWidget.scrollbar().minimumSize();
    return preferredSize.width();
}
#else
int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
{
    int thumbFat, troughBorderWidth;
    gtk_style_context_get_style(createStyleContext().get(), "slider-width", &thumbFat, "trough-border", &troughBorderWidth, nullptr);
    return thumbFat + 2 * troughBorderWidth;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

#if GTK_CHECK_VERSION(3, 20, 0)
int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
{
    auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
    scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
    IntSize minSize = scrollbarWidget.slider().minimumSize();
    return scrollbar.orientation() == VerticalScrollbar ? minSize.height() : minSize.width();
}
#else
int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
{
    int minThumbLength = 0;
    gtk_style_context_get_style(createStyleContext(&scrollbar).get(), "min-slider-length", &minThumbLength, nullptr);
    return minThumbLength;
}
#endif

#else // GTK_API_VERSION_2
bool ScrollbarThemeGtk::hasThumb(Scrollbar&)
{
    return false;
}

IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar&, ScrollbarPart, bool)
{
    return IntRect();
}

IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar&, ScrollbarPart, bool)
{
    return IntRect();
}

IntRect ScrollbarThemeGtk::trackRect(Scrollbar&, bool)
{
    return IntRect();
}

bool ScrollbarThemeGtk::hasButtons(Scrollbar&)
{
    return false;
}
#endif // GTK_API_VERSION_2

}