/*
 * Copyright (C) 2007 Apple Inc.
 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
 * Copyright (C) 2008 Collabora Ltd.
 * Copyright (C) 2009 Kenneth Rohde Christiansen
 * Copyright (C) 2010 Igalia S.L.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "RenderThemeGtk.h"

#include "CSSValueKeywords.h"
#include "ExceptionCodePlaceholder.h"
#include "FileList.h"
#include "FileSystem.h"
#include "FontDescription.h"
#include "GRefPtrGtk.h"
#include "GUniquePtrGtk.h"
#include "Gradient.h"
#include "GraphicsContext.h"
#include "GtkVersioning.h"
#include "HTMLInputElement.h"
#include "HTMLMediaElement.h"
#include "LocalizedStrings.h"
#include "MediaControlElements.h"
#include "NamedNodeMap.h"
#include "Page.h"
#include "PaintInfo.h"
#include "PlatformContextCairo.h"
#include "RenderBox.h"
#include "RenderObject.h"
#include "RenderProgress.h"
#include "RenderThemeGadget.h"
#include "ScrollbarThemeGtk.h"
#include "StringTruncator.h"
#include "TimeRanges.h"
#include "UserAgentScripts.h"
#include "UserAgentStyleSheets.h"
#include <cmath>
#include <gdk/gdk.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>

namespace WebCore {

Ref<RenderTheme> RenderThemeGtk::create()
{
    return adoptRef(*new RenderThemeGtk());
}

PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*)
{
    static RenderTheme& rt = RenderThemeGtk::create().leakRef();
    return &rt;
}

static double getScreenDPI()
{
    // FIXME: Really this should be the widget's screen.
    GdkScreen* screen = gdk_screen_get_default();
    if (!screen)
        return 96; // Default to 96 DPI.

    float dpi = gdk_screen_get_resolution(screen);
    if (dpi <= 0)
        return 96;
    return dpi;
}

void RenderThemeGtk::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const
{
    GtkSettings* settings = gtk_settings_get_default();
    if (!settings)
        return;

    // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
    GUniqueOutPtr<gchar> fontName;
    g_object_get(settings, "gtk-font-name", &fontName.outPtr(), nullptr);
    if (!fontName || !fontName.get()[0])
        return;

    PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
    if (!pangoDescription)
        return;

    fontDescription.setOneFamily(pango_font_description_get_family(pangoDescription));

    int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
    // If the size of the font is in points, we need to convert it to pixels.
    if (!pango_font_description_get_size_is_absolute(pangoDescription))
        size = size * (getScreenDPI() / 72.0);

    fontDescription.setSpecifiedSize(size);
    fontDescription.setIsAbsoluteSize(true);
    fontDescription.setWeight(FontWeightNormal);
    fontDescription.setItalic(FontItalicOff);
    pango_font_description_free(pangoDescription);
}

#if ENABLE(DATALIST_ELEMENT)
IntSize RenderThemeGtk::sliderTickSize() const
{
    // FIXME: We need to set this to the size of one tick mark.
    return IntSize(0, 0);
}

int RenderThemeGtk::sliderTickOffsetFromTrackCenter() const
{
    // FIXME: We need to set this to the position of the tick marks.
    return 0;
}
#endif

#ifndef GTK_API_VERSION_2

static void themeChangedCallback()
{
    Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
}

RenderThemeGtk::RenderThemeGtk()
{
    static bool themeMonitorInitialized = false;
    if (!themeMonitorInitialized) {
        GtkSettings* settings = gtk_settings_get_default();
        g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
        g_signal_connect(settings, "notify::gtk-color-scheme", G_CALLBACK(themeChangedCallback), nullptr);
        themeMonitorInitialized = true;
    }
}

enum RenderThemePart {
    Entry,
    EntrySelection,
    EntryIconLeft,
    EntryIconRight,
    Button,
    CheckButton,
    RadioButton,
    ComboBox,
    ComboBoxButton,
    ComboBoxArrow,
    Scale,
    ScaleTrough,
    ScaleSlider,
    ProgressBar,
    ProgressBarTrough,
    ProgressBarProgress,
    ListBox,
    SpinButton,
    SpinButtonUpButton,
    SpinButtonDownButton,
#if ENABLE(VIDEO)
    MediaButton,
#endif
};

#if !GTK_CHECK_VERSION(3, 20, 0)
// This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c.
static const int minArrowSize = 15;
// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
static const int minSpinButtonArrowSize = 6;

static GRefPtr<GtkStyleContext> createStyleContext(RenderThemePart themePart, GtkStyleContext* parent = nullptr)
{
    GRefPtr<GtkWidgetPath> path = adoptGRef(parent ? gtk_widget_path_copy(gtk_style_context_get_path(parent)) : gtk_widget_path_new());

    switch (themePart) {
    case Entry:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_ENTRY);
        break;
    case EntryIconLeft:
    case EntryIconRight:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
        break;
    case Button:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
        break;
    case CheckButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_CHECK_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_CHECK);
        break;
    case RadioButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_RADIO_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_RADIO);
        break;
    case ComboBox:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_COMBO_BOX);
        break;
    case ComboBoxButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
        gtk_widget_path_iter_add_class(path.get(), -1, "combo");
        break;
    case ComboBoxArrow:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_ARROW);
        gtk_widget_path_iter_add_class(path.get(), -1, "arrow");
        break;
    case Scale:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
        break;
    case ScaleTrough:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
        break;
    case ScaleSlider:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SLIDER);
        break;
    case ProgressBar:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
        break;
    case ProgressBarTrough:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
        break;
    case ProgressBarProgress:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
        break;
    case ListBox:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_TREE_VIEW);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_VIEW);
        break;
    case SpinButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
        break;
    case SpinButtonUpButton:
    case SpinButtonDownButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
        break;
#if ENABLE(VIDEO)
    case MediaButton:
        gtk_widget_path_append_type(path.get(), GTK_TYPE_IMAGE);
        gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_IMAGE);
        break;
#endif // ENABLE(VIDEO)
    default:
        ASSERT_NOT_REACHED();
        break;
    }

    GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
    gtk_style_context_set_path(context.get(), path.get());
    gtk_style_context_set_parent(context.get(), parent);
    return context;
}

static GRefPtr<GdkPixbuf> loadThemedIcon(GtkStyleContext* context, const char* iconName, GtkIconSize iconSize)
{
    GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(iconName));
    unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
#if GTK_CHECK_VERSION(3, 14, 0)
    GtkTextDirection direction = gtk_style_context_get_direction(context);
    if (direction & GTK_TEXT_DIR_LTR)
        lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
    else if (direction & GTK_TEXT_DIR_RTL)
        lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
#endif
    int width, height;
    gtk_icon_size_lookup(iconSize, &width, &height);
    GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(), std::min(width, height), static_cast<GtkIconLookupFlags>(lookupFlags)));
    if (!iconInfo)
        return nullptr;

    return adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), context, nullptr, nullptr));
}
#endif // !GTK_CHECK_VERSION(3, 20, 0)

static bool nodeHasPseudo(Node* node, const char* pseudo)
{
    RefPtr<Node> attributeNode = node->attributes()->getNamedItem("pseudo");

    return attributeNode ? attributeNode->nodeValue() == pseudo : false;
}

static bool nodeHasClass(Node* node, const char* className)
{
    if (!is<Element>(*node))
        return false;

    Element& element = downcast<Element>(*node);

    if (!element.hasClass())
        return false;

    return element.classNames().contains(className);
}

RenderThemeGtk::~RenderThemeGtk()
{
}

static bool supportsFocus(ControlPart appearance)
{
    switch (appearance) {
    case PushButtonPart:
    case ButtonPart:
    case TextFieldPart:
    case TextAreaPart:
    case SearchFieldPart:
    case MenulistPart:
    case RadioPart:
    case CheckboxPart:
    case SliderHorizontalPart:
    case SliderVerticalPart:
        return true;
    default:
        return false;
    }
}

bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const
{
    return supportsFocus(style.appearance());
}

bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const
{
    return isEnabled(o);
}

int RenderThemeGtk::baselinePosition(const RenderBox& box) const
{
    // FIXME: This strategy is possibly incorrect for the GTK+ port.
    if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
        return box.marginTop() + box.height() - 2;
    return RenderTheme::baselinePosition(box);
}

#if GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&)
{
}
static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject)
{
    unsigned stateFlags = 0;
    switch (renderObject.style().direction()) {
    case RTL:
        stateFlags |= GTK_STATE_FLAG_DIR_RTL;
        break;
    case LTR:
        stateFlags |= GTK_STATE_FLAG_DIR_LTR;
        break;
    }

    if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject)))
        stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
    else {
        if (theme.isHovered(renderObject))
            stateFlags |= GTK_STATE_FLAG_PRELIGHT;
        if (theme.isFocused(renderObject))
            stateFlags |= GTK_STATE_FLAG_FOCUSED;
    }

    switch (themePart) {
    case CheckButton:
    case RadioButton:
        if (theme.isChecked(renderObject))
            stateFlags |= GTK_STATE_FLAG_CHECKED;
        if (theme.isIndeterminate(renderObject))
            stateFlags |= GTK_STATE_FLAG_INCONSISTENT;
        if (theme.isPressed(renderObject))
            stateFlags |= GTK_STATE_FLAG_SELECTED;
        break;
    case Button:
    case ComboBoxButton:
    case ScaleSlider:
    case EntryIconLeft:
    case EntryIconRight:
#if ENABLE(VIDEO)
    case MediaButton:
#endif
        if (theme.isPressed(renderObject))
            stateFlags |= GTK_STATE_FLAG_ACTIVE;
        break;
    case SpinButtonUpButton:
        if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject))
            stateFlags |= GTK_STATE_FLAG_ACTIVE;
        if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject))
            stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
        break;
    case SpinButtonDownButton:
        if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject))
            stateFlags |= GTK_STATE_FLAG_ACTIVE;
        if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject))
            stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
        break;
    default:
        break;
    }

    return static_cast<GtkStateFlags>(stateFlags);
}
#else
static GtkTextDirection gtkTextDirection(TextDirection direction)
{
    switch (direction) {
    case RTL:
        return GTK_TEXT_DIR_RTL;
    case LTR:
        return GTK_TEXT_DIR_LTR;
    default:
        return GTK_TEXT_DIR_NONE;
    }
}

static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject)
{
    if (!theme->isEnabled(renderObject))
        return GTK_STATE_FLAG_INSENSITIVE;
    if (theme->isPressed(renderObject))
        return GTK_STATE_FLAG_ACTIVE;
    if (theme->isHovered(renderObject))
        return GTK_STATE_FLAG_PRELIGHT;

    return GTK_STATE_FLAG_NORMAL;
}

static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
{
    gint focusWidth, focusPad;
    gtk_style_context_get_style(context, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
    rect.inflate(focusWidth + focusPad);
}

void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
{
    GRefPtr<GtkStyleContext> context;
    bool checkInteriorFocus = false;
    ControlPart part = renderObject.style().appearance();
    switch (part) {
    case CheckboxPart:
    case RadioPart:
        context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton);

        gint indicatorSpacing;
        gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
        rect.inflate(indicatorSpacing);

        return;
    case SliderVerticalPart:
    case SliderHorizontalPart:
        context = createStyleContext(ScaleSlider);
        break;
    case ButtonPart:
    case MenulistButtonPart:
    case MenulistPart:
        context = createStyleContext(Button);
        checkInteriorFocus = true;
        break;
    case TextFieldPart:
    case TextAreaPart:
        context = createStyleContext(Entry);
        checkInteriorFocus = true;
        break;
    default:
        return;
    }

    ASSERT(context);
    if (checkInteriorFocus) {
        gboolean interiorFocus;
        gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, nullptr);
        if (interiorFocus)
            return;
    }
    adjustRectForFocus(context.get(), rect);
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    // Some layout tests check explicitly that buttons ignore line-height.
    if (style.appearance() == PushButtonPart)
        style.setLineHeight(RenderStyle::initialLineHeight());
}

static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize)
{
    if (rect.width() > minSize.width()) {
        rect.inflateX(-(rect.width() - minSize.width()) / 2);
        rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1.
    }

    if (rect.height() > minSize.height()) {
        rect.inflateY(-(rect.height() - minSize.height()) / 2);
        rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1.
    }
}

#if GTK_CHECK_VERSION(3, 20, 0)
static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
{
    ASSERT(themePart == CheckButton || themePart == RadioButton);

    // The width and height are both specified, so we shouldn't change them.
    if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
        return;

    RenderThemeGadget::Info info = {
        .type = RenderThemeGadget::Type::Generic,
        .name = themePart == CheckButton ? "checkbutton" : "radiobutton",
        .state = GTK_STATE_FLAG_NORMAL,
        .classList = { }
    };
    auto parentGadget = RenderThemeGadget::create(info);
    if (themePart == CheckButton) {
        info.type = RenderThemeGadget::Type::Check;
        info.name = "check";
    } else {
        info.type = RenderThemeGadget::Type::Radio;
        info.name = "radio";
    }
    auto gadget = RenderThemeToggleGadget::create(info);
    IntSize preferredSize = parentGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(gadget->preferredSize());

    if (style.width().isIntrinsicOrAuto())
        style.setWidth(Length(preferredSize.width(), Fixed));

    if (style.height().isAuto())
        style.setHeight(Length(preferredSize.height(), Fixed));
}

static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
{
    ASSERT(themePart == CheckButton || themePart == RadioButton);

    RenderThemeGadget::Info parentInfo = {
        .type = RenderThemeGadget::Type::Generic,
        .name = themePart == CheckButton ? "checkbutton" : "radiobutton",
        .state = themePartStateFlags(*theme, themePart, renderObject),
        .classList = { "text-button" }
    };
    auto parentGadget = RenderThemeGadget::create(parentInfo);
    RenderThemeGadget::Info info;
    info.state = parentInfo.state;
    if (themePart == CheckButton) {
        info.type = RenderThemeGadget::Type::Check;
        info.name = "check";
    } else {
        info.type = RenderThemeGadget::Type::Radio;
        info.name = "radio";
    }
    auto gadget = RenderThemeGadget::create(info, parentGadget.get());

    FloatRect rect = fullRect;
    // Some themes do not render large toggle buttons properly, so we simply
    // shrink the rectangle back down to the default size and then center it
    // in the full toggle button region. The reason for not simply forcing toggle
    // buttons to be a smaller size is that we don't want to break site layouts.
    IntSize preferredSize = parentGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(gadget->preferredSize());
    shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize);
    parentGadget->render(paintInfo.context().platformContext()->cr(), rect);
    gadget->render(paintInfo.context().platformContext()->cr(), rect);

    if (theme->isFocused(renderObject))
        parentGadget->renderFocus(paintInfo.context().platformContext()->cr(), rect);
}
#else
static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
{
    // The width and height are both specified, so we shouldn't change them.
    if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
        return;

    GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
    // Other ports hard-code this to 13. GTK+ users tend to demand the native look.
    gint indicatorSize;
    gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);

    if (style.width().isIntrinsicOrAuto())
        style.setWidth(Length(indicatorSize, Fixed));

    if (style.height().isAuto())
        style.setHeight(Length(indicatorSize, Fixed));
}

static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
    gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));

    unsigned flags = 0;
    if (!theme->isEnabled(renderObject))
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    else if (theme->isHovered(renderObject))
        flags |= GTK_STATE_FLAG_PRELIGHT;
    if (theme->isIndeterminate(renderObject))
        flags |= GTK_STATE_FLAG_INCONSISTENT;
    else if (theme->isChecked(renderObject))
#if GTK_CHECK_VERSION(3, 13, 7)
        flags |= GTK_STATE_FLAG_CHECKED;
#else
        flags |= GTK_STATE_FLAG_ACTIVE;
#endif
    if (theme->isPressed(renderObject))
        flags |= GTK_STATE_FLAG_SELECTED;
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));

    // Some themes do not render large toggle buttons properly, so we simply
    // shrink the rectangle back down to the default size and then center it
    // in the full toggle button region. The reason for not simply forcing toggle
    // buttons to be a smaller size is that we don't want to break site layouts.
    FloatRect rect(fullRect);
    gint indicatorSize;
    gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
    IntSize minSize(indicatorSize, indicatorSize);
    shrinkToMinimumSizeAndCenterRectangle(rect, minSize);

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

    if (themePart == CheckButton)
        gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
    else
        gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());

    if (theme->isFocused(renderObject)) {
        IntRect indicatorRect(rect);
        gint indicatorSpacing;
        gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
        indicatorRect.inflate(indicatorSpacing);
        gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
            indicatorRect.width(), indicatorRect.height());
    }
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
{
    setToggleSize(CheckButton, style);
}

bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    paintToggle(this, CheckButton, renderObject, paintInfo, rect);
    return false;
}

void RenderThemeGtk::setRadioSize(RenderStyle& style) const
{
    setToggleSize(RadioButton, style);
}

bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    paintToggle(this, RadioButton, renderObject, paintInfo, rect);
    return false;
}

#if GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "button", themePartStateFlags(*this, Button, renderObject), { "text-button" } };
    if (isDefault(renderObject))
        info.classList.append("default");
    auto gadget = RenderThemeGadget::create(info);
    gadget->render(paintInfo.context().platformContext()->cr(), rect);
    if (isFocused(renderObject))
        gadget->renderFocus(paintInfo.context().platformContext()->cr(), rect);
    return false;
}
#else
static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    IntRect buttonRect(rect);

    guint flags = 0;
    if (!theme->isEnabled(renderObject))
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    else if (theme->isHovered(renderObject))
        flags |= GTK_STATE_FLAG_PRELIGHT;
    if (theme->isPressed(renderObject))
        flags |= GTK_STATE_FLAG_ACTIVE;
    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));

    if (theme->isDefault(renderObject)) {
        GtkBorder* borderPtr = 0;
        GtkBorder border = { 1, 1, 1, 1 };

        gtk_style_context_get_style(context, "default-border", &borderPtr, nullptr);
        if (borderPtr) {
            border = *borderPtr;
            gtk_border_free(borderPtr);
        }

        buttonRect.move(border.left, border.top);
        buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
        buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));

        gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
    }

    gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
    gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());

    if (theme->isFocused(renderObject)) {
        gint focusWidth, focusPad;
        gboolean displaceFocus, interiorFocus;
        gtk_style_context_get_style(
            context,
            "focus-line-width", &focusWidth,
            "focus-padding", &focusPad,
            "interior-focus", &interiorFocus,
            "displace-focus", &displaceFocus,
            nullptr);

        if (interiorFocus) {
            GtkBorder borderWidth;
            gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth);

            buttonRect = IntRect(
                buttonRect.x() + borderWidth.left + focusPad,
                buttonRect.y() + borderWidth.top + focusPad,
                buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
                buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
        } else
            buttonRect.inflate(focusWidth + focusPad);

        if (displaceFocus && theme->isPressed(renderObject)) {
            gint childDisplacementX;
            gint childDisplacementY;
            gtk_style_context_get_style(context, "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
            buttonRect.move(childDisplacementX, childDisplacementY);
        }

        gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
    }
}
bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(Button);
    gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
    renderButton(this, context.get(), renderObject, paintInfo, rect);
    return false;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

static Color menuListColor(const Element* element)
{
#if GTK_CHECK_VERSION(3, 20, 0)
    RenderThemeGadget::Info info { RenderThemeGadget::Type::Generic, "combobox", element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL, { } };
    auto comboGadget = RenderThemeGadget::create(info);
    Vector<RenderThemeGadget::Info> children {
        { RenderThemeGadget::Type::Generic, "button", info.state, { "combo" } }
    };
    info.name = "box";
    info.classList = { "horizontal", "linked" };
    return RenderThemeBoxGadget(info, GTK_ORIENTATION_HORIZONTAL, children, comboGadget.get()).child(0)->color();
#else
    GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
    GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
    gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL);

    GdkRGBA gdkRGBAColor;
    gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor);
    return gdkRGBAColor;
#endif // GTK_CHECK_VERSION(3, 20, 0)
}

void RenderThemeGtk::adjustMenuListStyle(StyleResolver&, RenderStyle& style, Element* element) const
{
    // The tests check explicitly that select menu buttons ignore line height.
    style.setLineHeight(RenderStyle::initialLineHeight());

    // We cannot give a proper rendering when border radius is active, unfortunately.
    style.resetBorderRadius();

    if (element)
        style.setColor(menuListColor(element));
}

void RenderThemeGtk::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* e) const
{
    adjustMenuListStyle(styleResolver, style, e);
}

#if GTK_CHECK_VERSION(3, 20, 0)
/*
 * GtkComboBox gadgets tree
 *
 * combobox
 * ├── box.linked
 * │   ╰── button.combo
 * │       ╰── box
 * │           ├── cellview
 * │           ╰── arrow
 * ╰── window.popup
 */
LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
{
    if (style.appearance() == NoControlPart)
        return LengthBox(0);

    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "combobox", GTK_STATE_FLAG_NORMAL, { } };
    auto comboGadget = RenderThemeGadget::create(info);
    Vector<RenderThemeGadget::Info> children = {
        { RenderThemeGadget::Type::Generic, "button", GTK_STATE_FLAG_NORMAL, { "combo" } }
    };
    info.name = "box";
    info.classList = { "horizontal", "linked" };
    auto boxGadget = std::make_unique<RenderThemeBoxGadget>(info, GTK_ORIENTATION_HORIZONTAL, children, comboGadget.get());
    RenderThemeGadget* buttonGadget = boxGadget->child(0);
    info.classList.removeLast();
    auto buttonBoxGadget = RenderThemeGadget::create(info, buttonGadget);
    info.name = "arrow";
    info.classList = { };
    auto arrowGadget = RenderThemeGadget::create(info, buttonBoxGadget.get());
    GtkBorder comboContentsBox = comboGadget->contentsBox();
    GtkBorder boxContentsBox = boxGadget->contentsBox();
    GtkBorder buttonContentsBox = buttonGadget->contentsBox();
    GtkBorder buttonBoxContentsBox = buttonBoxGadget->contentsBox();
    GtkBorder padding;
    padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left;
    padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right;
    padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top;
    padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom;

    auto arrowSize = arrowGadget->preferredSize();
    return LengthBox(padding.top, padding.right + (style.direction() == LTR ? arrowSize.width() : 0),
        padding.bottom, padding.left + (style.direction() == RTL ? arrowSize.width() : 0));
}

bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "combobox", themePartStateFlags(*this, ComboBoxButton, renderObject), { } };
    auto comboGadget = RenderThemeGadget::create(info);
    Vector<RenderThemeGadget::Info> children = {
        { RenderThemeGadget::Type::Generic, "button", info.state, { "combo" } }
    };
    info.name = "box";
    info.classList = { "horizontal", "linked" };
    auto boxGadget = std::make_unique<RenderThemeBoxGadget>(info, GTK_ORIENTATION_HORIZONTAL, children, comboGadget.get());
    RenderThemeGadget* buttonGadget = boxGadget->child(0);
    info.classList.removeLast();
    auto buttonBoxGadget = RenderThemeGadget::create(info, buttonGadget);
    info.type = RenderThemeGadget::Type::Arrow;
    info.name = "arrow";
    info.classList = { };
    auto arrowGadget = RenderThemeGadget::create(info, buttonBoxGadget.get());

    cairo_t* cr = paintInfo.context().platformContext()->cr();
    comboGadget->render(cr, rect);
    boxGadget->render(cr, rect);
    FloatRect contentsRect;
    buttonGadget->render(cr, rect, &contentsRect);
    buttonBoxGadget->render(cr, contentsRect);
    arrowGadget->render(cr, contentsRect);
    if (isFocused(renderObject))
        buttonGadget->renderFocus(cr, rect);

    return false;
}
#else
LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
{
    if (style.appearance() == NoControlPart)
        return { 0, 0, 0, 0 };

    GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox);
    GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get());
    gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
    GtkBorder borderWidth = { 0, 0, 0, 0 };
    gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth);

    gboolean interiorFocus;
    gint focusWidth, focusPad;
    gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
    focusWidth = interiorFocus ? focusWidth + focusPad : 0;

    return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == LTR ? minArrowSize : 0),
        borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == RTL ? minArrowSize : 0) };
}

bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
{
    // FIXME: adopt subpixel themes.
    IntRect rect = IntRect(r);

    cairo_t* cairoContext = paintInfo.context().platformContext()->cr();
    GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));

    GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);

    // Paint the button.
    GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
    gtk_style_context_set_direction(buttonStyleContext.get(), direction);
    renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect);

    // Get the inner rectangle.
    gint focusWidth, focusPad;
    GtkBorder* innerBorderPtr = 0;
    GtkBorder innerBorder = { 1, 1, 1, 1 };
    gtk_style_context_get_style(buttonStyleContext.get(), "inner-border", &innerBorderPtr, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
    if (innerBorderPtr) {
        innerBorder = *innerBorderPtr;
        gtk_border_free(innerBorderPtr);
    }

    GtkBorder borderWidth;
    GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get());
    gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth);

    focusWidth += focusPad;
    IntRect innerRect(
        rect.x() + innerBorder.left + borderWidth.left + focusWidth,
        rect.y() + innerBorder.top + borderWidth.top + focusWidth,
        rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
        rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));

    if (isPressed(renderObject)) {
        gint childDisplacementX;
        gint childDisplacementY;
        gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
        innerRect.move(childDisplacementX, childDisplacementY);
    }
    innerRect.setWidth(std::max(1, innerRect.width()));
    innerRect.setHeight(std::max(1, innerRect.height()));

    // Paint the arrow.
    GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get());
    gtk_style_context_set_direction(arrowStyleContext.get(), direction);

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

    IntSize arrowSize(minArrowSize, innerRect.height());
    FloatPoint arrowPosition(innerRect.location());
    if (direction == GTK_TEXT_DIR_LTR)
        arrowPosition.move(innerRect.width() - arrowSize.width(), 0);

    // GTK+ actually fetches the xalign and valign values from the widget, but since we
    // don't have a widget here, we are just using the default xalign and valign values of 0.5.
    gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
    arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);

    gtk_style_context_set_state(arrowStyleContext.get(), state);
    gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);

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

bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect)
{
    return paintMenuList(object, info, rect);
}

#if GTK_CHECK_VERSION(3, 20, 0)
void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, Element* element) const
{
    if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element)))
        return;

    // Spinbuttons need a minimum height to be rendered correctly.
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "spinbutton", GTK_STATE_FLAG_NORMAL, { "horizontal" } };
    auto spinbuttonGadget = RenderThemeGadget::create(info);
    info.type = RenderThemeGadget::Type::TextField;
    info.name = "entry";
    info.classList.clear();
    auto entryGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    info.type = RenderThemeGadget::Type::Icon;
    info.name = "button";
    info.classList.append("up");
    auto buttonUpGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    static_cast<RenderThemeIconGadget*>(buttonUpGadget.get())->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    info.classList[0] = "down";
    auto buttonDownGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    static_cast<RenderThemeIconGadget*>(buttonDownGadget.get())->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    IntSize preferredSize = spinbuttonGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(entryGadget->preferredSize());
    IntSize upPreferredSize = preferredSize.expandedTo(buttonUpGadget->preferredSize());
    IntSize downPreferredSize = preferredSize.expandedTo(buttonDownGadget->preferredSize());
    int height = std::max(upPreferredSize.height(), downPreferredSize.height());
    style.setMinHeight(Length(height, Fixed));
}

bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::TextField, "entry", themePartStateFlags(*this, Entry, renderObject), { } };
    std::unique_ptr<RenderThemeGadget> parentGadget;
    if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) {
        info.name = "spinbutton";
        info.classList.append("horizontal");
        parentGadget = RenderThemeTextFieldGadget::create(info);
        info.name = "entry";
        info.classList.clear();
    }

    auto entryGadget = RenderThemeTextFieldGadget::create(info, parentGadget.get());
    if (parentGadget)
        parentGadget->render(paintInfo.context().platformContext()->cr(), rect);
    entryGadget->render(paintInfo.context().platformContext()->cr(), rect);
    return false;
}
#else
void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, Element*) const
{
}

bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(Entry);
    gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));

    guint flags = 0;
    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    else if (isFocused(renderObject))
        flags |= GTK_STATE_FLAG_FOCUSED;
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));

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

    if (isFocused(renderObject) && isEnabled(renderObject)) {
        gboolean interiorFocus;
        gint focusWidth, focusPad;
        gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
        if (!interiorFocus) {
            IntRect focusRect(rect);
            focusRect.inflate(focusWidth + focusPad);
            gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
        }
    }

    return false;
}
#endif

#if GTK_CHECK_VERSION(3, 20, 0)
static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
{
    ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::TextField, "entry", GTK_STATE_FLAG_NORMAL, { } };
    auto parentGadget = RenderThemeGadget::create(info);
    info.type = RenderThemeGadget::Type::Icon;
    info.name = "image";
    if (themePart == EntryIconLeft)
        info.classList.append("left");
    else
        info.classList.append("right");
    auto gadget = RenderThemeIconGadget::create(info, parentGadget.get());

    // Get the icon size based on the font size.
    static_cast<RenderThemeIconGadget*>(gadget.get())->setIconSize(style.fontSize());
    IntSize preferredSize = gadget->preferredSize();
    GtkBorder contentsBox = parentGadget->contentsBox();
    if (themePart == EntryIconLeft)
        preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom);
    else
        preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom);
    style.setWidth(Length(preferredSize.width(), Fixed));
    style.setHeight(Length(preferredSize.height(), Fixed));
}
#else
// Defined in GTK+ (gtk/gtkiconfactory.c)
static const gint gtkIconSizeMenu = 16;
static const gint gtkIconSizeSmallToolbar = 18;
static const gint gtkIconSizeButton = 20;
static const gint gtkIconSizeLargeToolbar = 24;
static const gint gtkIconSizeDnd = 32;
static const gint gtkIconSizeDialog = 48;

static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
{
    if (pixelSize < gtkIconSizeSmallToolbar)
        return GTK_ICON_SIZE_MENU;
    if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
        return GTK_ICON_SIZE_SMALL_TOOLBAR;
    if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
        return GTK_ICON_SIZE_BUTTON;
    if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
        return GTK_ICON_SIZE_LARGE_TOOLBAR;
    if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
        return GTK_ICON_SIZE_DND;

    return GTK_ICON_SIZE_DIALOG;
}

static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
{
    style.resetBorder();
    style.resetPadding();

    GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
    GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());

    GtkBorder padding;
    gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);

    // Get the icon size based on the font size.
    int fontSize = style.fontSize();
    if (fontSize < gtkIconSizeMenu) {
        style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed));
        style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed));
        return;
    }
    gint width = 0, height = 0;
    gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
    style.setWidth(Length(width + (padding.left + padding.right), Fixed));
    style.setHeight(Length(height + (padding.top + padding.bottom), Fixed));
}
#endif

bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r)
{
    return paintTextField(o, i, r);
}

void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* e) const
{
    adjustSearchFieldCancelButtonStyle(styleResolver, style, e);
}

bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect)
{
    return paintSearchFieldResultsDecorationPart(o, i, rect);
}

void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    adjustSearchFieldIconStyle(EntryIconLeft, style);
}

void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    adjustSearchFieldIconStyle(EntryIconRight, style);
}

#if GTK_CHECK_VERSION(3, 20, 0)
static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::TextField, "entry", themePartStateFlags(*theme, Entry, renderObject), { } };
    auto parentGadget = RenderThemeGadget::create(info);
    info.type = RenderThemeGadget::Type::Icon;
    info.state = themePartStateFlags(*theme, themePart, renderObject);
    info.name = "image";
    if (themePart == EntryIconLeft)
        info.classList.append("left");
    else
        info.classList.append("right");
    auto gadget = RenderThemeGadget::create(info, parentGadget.get());
    auto* gadgetIcon = static_cast<RenderThemeIconGadget*>(gadget.get());
    gadgetIcon->setIconSize(renderObject.style().fontSize());
    if (themePart == EntryIconLeft)
        gadgetIcon->setIconName("edit-find-symbolic");
    else
        gadgetIcon->setIconName("edit-clear-symbolic");
    GtkBorder contentsBox = parentGadget->contentsBox();
    IntRect iconRect = rect;
    if (themePart == EntryIconLeft) {
        iconRect.move(contentsBox.left, contentsBox.top);
        iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom);
    } else
        iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom);
    return !gadget->render(paintInfo.context().platformContext()->cr(), iconRect);
}
bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect);
}

bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect);
}
#else
static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
    GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height()));
    if (!icon)
        return false;

    if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height())
        icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR));

    gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y());
    return true;
}

static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state)
{
    GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
    GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
    gtk_style_context_set_direction(context.get(), direction);
    gtk_style_context_set_state(context.get(), state);
    return paintIcon(context.get(), graphicsContext, rect, iconName);
}

static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect)
{
    if (!renderObject.node())
        return IntRect();

    // Get the renderer of <input> element.
    Node* input = renderObject.node()->shadowHost();
    if (!input)
        input = renderObject.node();
    if (!is<RenderBox>(*input->renderer()))
        return IntRect();

    // If possible center the y-coordinate of the rect vertically in the parent input element.
    // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
    // that are even, which looks in relation to the box text.
    IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox();

    // Make sure the scaled decoration stays square and will fit in its parent's box.
    int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
    IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
    return scaledRect;
}

bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
    if (iconRect.isEmpty())
        return true;

    return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
        gtkIconStateFlags(this, renderObject));
}

bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
    if (iconRect.isEmpty())
        return true;

    return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
        gtkIconStateFlags(this, renderObject));
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    // We cannot give a proper rendering when border radius is active, unfortunately.
    style.resetBorderRadius();
    style.setLineHeight(RenderStyle::initialLineHeight());
}

bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect)
{
    return paintTextField(o, i, rect);
}

bool RenderThemeGtk::shouldHaveCapsLockIndicator(HTMLInputElement& element) const
{
    return element.isPasswordField();
}

void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    style.setBoxShadow(nullptr);
}

void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const
{
    RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
    style.setBoxShadow(nullptr);
}

#if GTK_CHECK_VERSION(3, 20, 0)
/*
 * GtkScale
 *
 * scale
 * ╰── contents
 *     ╰── trough
 *         ├── slider
 *         ╰── [highlight]
 */
bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    ControlPart part = renderObject.style().appearance();
    ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);

    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "scale", themePartStateFlags(*this, Scale, renderObject), { } };
    if (part == SliderHorizontalPart)
        info.classList.append("horizontal");
    else
        info.classList.append("vertical");
    auto scaleGadget = RenderThemeGadget::create(info);
    info.name = "contents";
    info.classList.clear();
    auto contentsGadget = RenderThemeGadget::create(info, scaleGadget.get());
    info.name = "trough";
    auto troughGadget = RenderThemeGadget::create(info, contentsGadget.get());
    info.name = "slider";
    auto sliderGadget = RenderThemeGadget::create(info, troughGadget.get());
    info.name = "highlight";
    auto highlightGadget = RenderThemeGadget::create(info, troughGadget.get());

    // The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it.
    IntSize preferredSize = scaleGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(contentsGadget->preferredSize());
    preferredSize = preferredSize.expandedTo(troughGadget->preferredSize());
    FloatRect trackRect = rect;
    if (part == SliderHorizontalPart) {
        trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2));
        trackRect.setHeight(preferredSize.height());
    } else {
        trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0);
        trackRect.setWidth(preferredSize.width());
    }

    FloatRect contentsRect;
    scaleGadget->render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect);
    contentsGadget->render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect);
    // Scale trough defines its size querying slider and highlight.
    if (part == SliderHorizontalPart)
        contentsRect.setHeight(troughGadget->preferredSize().height() + std::max(sliderGadget->preferredSize().height(), highlightGadget->preferredSize().height()));
    else
        contentsRect.setWidth(troughGadget->preferredSize().width() + std::max(sliderGadget->preferredSize().width(), highlightGadget->preferredSize().width()));
    FloatRect troughRect = contentsRect;
    troughGadget->render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect);
    if (isFocused(renderObject))
        troughGadget->renderFocus(paintInfo.context().platformContext()->cr(), troughRect);

    LayoutPoint thumbLocation;
    if (is<HTMLInputElement>(renderObject.node())) {
        auto& input = downcast<HTMLInputElement>(*renderObject.node());
        if (auto* element = input.sliderThumbElement())
            thumbLocation = element->renderBox()->location();
    }

    if (part == SliderHorizontalPart) {
        if (renderObject.style().direction() == RTL) {
            contentsRect.move(thumbLocation.x(), 0);
            contentsRect.setWidth(contentsRect.width() - thumbLocation.x());
        } else
            contentsRect.setWidth(thumbLocation.x());
    } else
        contentsRect.setHeight(thumbLocation.y());
    highlightGadget->render(paintInfo.context().platformContext()->cr(), contentsRect);

    return false;
}

void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, Element*) const
{
    ControlPart part = style.appearance();
    if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
        return;

    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "scale", GTK_STATE_FLAG_NORMAL, { } };
    if (part == SliderHorizontalPart)
        info.classList.append("horizontal");
    else
        info.classList.append("vertical");
    auto scaleGadget = RenderThemeGadget::create(info);
    info.name = "contents";
    info.classList.clear();
    auto contentsGadget = RenderThemeGadget::create(info, scaleGadget.get());
    info.name = "trough";
    auto troughGadget = RenderThemeGadget::create(info, contentsGadget.get());
    info.name = "slider";
    auto sliderGadget = RenderThemeGadget::create(info, troughGadget.get());
    info.name = "highlight";
    auto highlightGadget = RenderThemeGadget::create(info, troughGadget.get());

    IntSize preferredSize = scaleGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(contentsGadget->preferredSize());
    preferredSize = preferredSize.expandedTo(troughGadget->preferredSize());
    preferredSize = preferredSize.expandedTo(sliderGadget->preferredSize());
    if (part == SliderThumbHorizontalPart) {
        style.setWidth(Length(preferredSize.width(), Fixed));
        style.setHeight(Length(preferredSize.height(), Fixed));
        return;
    }
    ASSERT(part == SliderThumbVerticalPart);
    style.setWidth(Length(preferredSize.height(), Fixed));
    style.setHeight(Length(preferredSize.width(), Fixed));
}

bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    ControlPart part = renderObject.style().appearance();
    ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);

    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "scale", themePartStateFlags(*this, Scale, renderObject), { } };
    if (part == SliderHorizontalPart)
        info.classList.append("horizontal");
    else
        info.classList.append("vertical");
    auto scaleGadget = RenderThemeGadget::create(info);
    info.name = "contents";
    info.classList.clear();
    auto contentsGadget = RenderThemeGadget::create(info, scaleGadget.get());
    info.name = "trough";
    auto troughGadget = RenderThemeGadget::create(info, contentsGadget.get());
    info.name = "slider";
    info.state = themePartStateFlags(*this, ScaleSlider, renderObject);
    auto sliderGadget = RenderThemeGadget::create(info, troughGadget.get());
    info.name = "highlight";
    auto highlightGadget = RenderThemeGadget::create(info, troughGadget.get());

    GtkBorder scaleContentsBox = scaleGadget->contentsBox();
    GtkBorder contentsContentsBox = contentsGadget->contentsBox();
    GtkBorder troughContentsBox = troughGadget->contentsBox();
    GtkBorder padding;
    padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left;
    padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right;
    padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top;
    padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom;

    // Scale trough defines its size querying slider and highlight.
    int troughHeight = troughGadget->preferredSize().height() + std::max(sliderGadget->preferredSize().height(), highlightGadget->preferredSize().height());
    IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight));
    sliderRect.move(padding.left, padding.top);
    sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom);
    sliderGadget->render(paintInfo.context().platformContext()->cr(), sliderRect);
    return false;
}
#else
bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    ControlPart part = renderObject.style().appearance();
    ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);

    GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
    gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
    GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get());
    gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));

    if (!isEnabled(renderObject))
        gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE);

    IntRect sliderRect = rect;
    // GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and
    // the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes
    // and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look
    // good in other themes as well.
    static const int sliderSize = 4;

    if (part == SliderHorizontalPart) {
        sliderRect.setHeight(std::min(rect.height(), sliderSize));
        sliderRect.move(0, (rect.height() - sliderRect.height()) / 2);
    } else {
        sliderRect.setWidth(std::min(rect.width(), sliderSize));
        sliderRect.move((rect.width() - sliderRect.width()) / 2, 0);
    }

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

    if (isFocused(renderObject)) {
        gint focusWidth, focusPad;
        gtk_style_context_get_style(context.get(), "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
        IntRect focusRect(sliderRect);
        focusRect.inflate(focusWidth + focusPad);
        gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
    }

    return false;
}

void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, Element*) const
{
    ControlPart part = style.appearance();
    if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
        return;

    GRefPtr<GtkStyleContext> context = createStyleContext(Scale);
    gint sliderWidth, sliderLength;
    gtk_style_context_get_style(context.get(), "slider-width", &sliderWidth, "slider-length", &sliderLength, nullptr);

    if (part == SliderThumbHorizontalPart) {
        style.setWidth(Length(sliderLength, Fixed));
        style.setHeight(Length(sliderWidth, Fixed));
        return;
    }
    ASSERT(part == SliderThumbVerticalPart);
    style.setWidth(Length(sliderWidth, Fixed));
    style.setHeight(Length(sliderLength, Fixed));
}

bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    ControlPart part = renderObject.style().appearance();
    ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);

    // FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle.
    GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
    gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
    GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get());
    GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get());
    gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));

    guint flags = 0;
    if (!isEnabled(renderObject))
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    else if (isHovered(renderObject))
        flags |= GTK_STATE_FLAG_PRELIGHT;
    if (isPressed(renderObject))
        flags |= GTK_STATE_FLAG_ACTIVE;
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));

    gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
        part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);

    return false;
}
#endif

#if GTK_CHECK_VERSION(3, 20, 0)
IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "progressbar", GTK_STATE_FLAG_NORMAL, { "horizontal" } };
    auto progressBarGadget = RenderThemeGadget::create(info);
    info.name = "trough";
    info.classList.clear();
    auto troughGadget = RenderThemeGadget::create(info, progressBarGadget.get());
    info.name = "progress";
    if (renderObject.style().direction() == RTL)
        info.classList.append("right");
    else
        info.classList.append("left");
    const auto& renderProgress = downcast<RenderProgress>(renderObject);
    if (renderProgress.isDeterminate())
        info.classList.append("pulse");
    auto progressGadget = RenderThemeGadget::create(info, troughGadget.get());
    IntSize preferredSize = progressBarGadget->preferredSize();
    preferredSize = preferredSize.expandedTo(troughGadget->preferredSize());
    preferredSize = preferredSize.expandedTo(progressGadget->preferredSize());
    return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height());
}

bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    if (!renderObject.isProgress())
        return true;

    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "progressbar", GTK_STATE_FLAG_NORMAL, { "horizontal" } };
    auto progressBarGadget = RenderThemeGadget::create(info);
    info.name = "trough";
    info.classList.clear();
    auto troughGadget = RenderThemeGadget::create(info, progressBarGadget.get());
    info.name = "progress";
    if (renderObject.style().direction() == RTL)
        info.classList.append("right");
    else
        info.classList.append("left");
    const auto& renderProgress = downcast<RenderProgress>(renderObject);
    if (renderProgress.isDeterminate())
        info.classList.append("pulse");
    auto progressGadget = RenderThemeGadget::create(info, troughGadget.get());

    progressBarGadget->render(paintInfo.context().platformContext()->cr(), rect);
    troughGadget->render(paintInfo.context().platformContext()->cr(), rect);
    progressGadget->render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect));
    return false;
}
#else
IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const
{
    return bounds;
}

bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    if (!renderObject.isProgress())
        return true;

    GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar);
    GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get());
    GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get());

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

    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));

    GtkBorder padding;
    gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
    IntRect progressRect(
        rect.x() + padding.left,
        rect.y() + padding.top,
        rect.width() - (padding.left + padding.right),
        rect.height() - (padding.top + padding.bottom));
    progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);

    if (!progressRect.isEmpty()) {
#if GTK_CHECK_VERSION(3, 13, 7)
        gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
        gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
#else
        gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
#endif
    }

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

#if GTK_CHECK_VERSION(3, 20, 0)
RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const
{
    return renderObject.style().direction() == RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight;
}

void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "spinbutton", GTK_STATE_FLAG_NORMAL, { "horizontal" } };
    auto spinbuttonGadget = RenderThemeGadget::create(info);
    info.type = RenderThemeGadget::Type::TextField;
    info.name = "entry";
    info.classList.clear();
    auto entryGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    info.type = RenderThemeGadget::Type::Icon;
    info.name = "button";
    info.classList.append("up");
    auto buttonUpGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    static_cast<RenderThemeIconGadget*>(buttonUpGadget.get())->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    info.classList[0] = "down";
    auto buttonDownGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    static_cast<RenderThemeIconGadget*>(buttonDownGadget.get())->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    IntSize upPreferredSize = buttonUpGadget->preferredSize();
    IntSize downPreferredSize = buttonDownGadget->preferredSize();
    int buttonSize = std::max(std::max(upPreferredSize.width(), downPreferredSize.width()), std::max(upPreferredSize.height(), downPreferredSize.height()));
    style.setWidth(Length(buttonSize * 2, Fixed));
    style.setHeight(Length(buttonSize, Fixed));
}

bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, "spinbutton", themePartStateFlags(*this, SpinButton, renderObject), { } };
    auto spinbuttonGadget = RenderThemeGadget::create(info);
    info.type = RenderThemeGadget::Type::TextField;
    info.name = "entry";
    info.classList.clear();
    auto entryGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    info.type = RenderThemeGadget::Type::Icon;
    info.name = "button";
    info.classList.append("up");
    info.state = themePartStateFlags(*this, SpinButtonUpButton, renderObject);
    auto buttonUpGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    auto* gadgetIcon = static_cast<RenderThemeIconGadget*>(buttonUpGadget.get());
    gadgetIcon->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    gadgetIcon->setIconName("list-add-symbolic");
    info.classList[0] = "down";
    info.state = themePartStateFlags(*this, SpinButtonDownButton, renderObject);
    auto buttonDownGadget = RenderThemeGadget::create(info, spinbuttonGadget.get());
    gadgetIcon = static_cast<RenderThemeIconGadget*>(buttonDownGadget.get());
    gadgetIcon->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    gadgetIcon->setIconName("list-remove-symbolic");

    IntRect iconRect = rect;
    iconRect.setWidth(iconRect.width() / 2);
    if (renderObject.style().direction() == RTL)
        buttonUpGadget->render(paintInfo.context().platformContext()->cr(), iconRect);
    else
        buttonDownGadget->render(paintInfo.context().platformContext()->cr(), iconRect);
    iconRect.move(iconRect.width(), 0);
    if (renderObject.style().direction() == RTL)
        buttonDownGadget->render(paintInfo.context().platformContext()->cr(), iconRect);
    else
        buttonUpGadget->render(paintInfo.context().platformContext()->cr(), iconRect);

    return false;
}
#else
RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const
{
    return InnerSpinButtonLayout::Vertical;
}
static gint spinButtonArrowSize(GtkStyleContext* context)
{
    PangoFontDescription* fontDescription;
    gtk_style_context_get(context, gtk_style_context_get_state(context), "font", &fontDescription, nullptr);
    gint fontSize = pango_font_description_get_size(fontDescription);
    gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
    pango_font_description_free(fontDescription);

    return arrowSize - arrowSize % 2; // Force even.
}

void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);

    GtkBorder padding;
    gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);

    int width = spinButtonArrowSize(context.get()) + padding.left + padding.right;
    style.setWidth(Length(width, Fixed));
    style.setMinWidth(Length(width, Fixed));
}

static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
{
    ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);

    GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext);
    GtkTextDirection direction = gtk_style_context_get_direction(context.get());
    guint state = static_cast<guint>(gtk_style_context_get_state(context.get()));
    if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
        if (theme->isPressed(renderObject)) {
            if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
                || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
                state |= GTK_STATE_FLAG_ACTIVE;
        } else if (theme->isHovered(renderObject)) {
            if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
                || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
                state |= GTK_STATE_FLAG_PRELIGHT;
        }
    }
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state));

    // Paint button.
    IntRect buttonRect(rect);
    guint junction = gtk_style_context_get_junction_sides(context.get());
    if (arrowType == GTK_ARROW_UP)
        junction |= GTK_JUNCTION_BOTTOM;
    else {
        junction |= GTK_JUNCTION_TOP;
        buttonRect.move(0, rect.height() / 2);
    }
    buttonRect.setHeight(rect.height() / 2);
    gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction));

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

    // Paint arrow centered inside button.
    // This code is based on gtkspinbutton.c code.
    IntRect arrowRect;
    gdouble angle;
    if (arrowType == GTK_ARROW_UP) {
        angle = 0;
        arrowRect.setY(rect.y());
        arrowRect.setHeight(rect.height() / 2 - 2);
    } else {
        angle = G_PI;
        arrowRect.setY(rect.y() + buttonRect.y());
        arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
    }
    arrowRect.setWidth(rect.width() - 3);
    if (direction == GTK_TEXT_DIR_LTR)
        arrowRect.setX(rect.x() + 1);
    else
        arrowRect.setX(rect.x() + 2);

    gint width = arrowRect.width() / 2;
    width -= width % 2 - 1; // Force odd.
    gint height = (width + 1) / 2;

    arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
    gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
}

bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
    gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));

    guint flags = 0;
    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
        flags |= GTK_STATE_FLAG_INSENSITIVE;
    else if (isFocused(renderObject))
        flags |= GTK_STATE_FLAG_FOCUSED;
    gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));

    paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP);
    paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN);

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

double RenderThemeGtk::caretBlinkInterval() const
{
    GtkSettings* settings = gtk_settings_get_default();

    gboolean shouldBlink;
    gint time;

    g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, nullptr);

    if (!shouldBlink)
        return 0;

    return time / 2000.;
}

enum StyleColorType { StyleColorBackground, StyleColorForeground };

#if GTK_CHECK_VERSION(3, 20, 0)
static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Generic, nullptr, state, { } };
    std::unique_ptr<RenderThemeGadget> parentGadget;
    RenderThemePart part = themePart;
    if (themePart == Entry && (state & GTK_STATE_FLAG_SELECTED)) {
        info.name = "entry";
        parentGadget = RenderThemeGadget::create(info);
        part = EntrySelection;
    }

    switch (part) {
    case Entry:
        info.name = "entry";
        break;
    case EntrySelection:
        info.name = "selection";
        break;
    case ListBox:
        info.name = "treeview";
        info.classList.append("view");
        break;
    case Button:
        info.name = "button";
        break;
    default:
        ASSERT_NOT_REACHED();
        info.name = "entry";
    }

    auto gadget = RenderThemeGadget::create(info, parentGadget.get());
    return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color();
}
#else
static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
    gtk_style_context_set_state(context.get(), state);

    GdkRGBA gdkRGBAColor;
    if (colorType == StyleColorBackground)
        gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor);
    else
        gtk_style_context_get_color(context.get(), state, &gdkRGBAColor);
    return gdkRGBAColor;
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
{
    return styleColor(Entry, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
}

Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
{
    return styleColor(Entry, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
}

Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
{
    return styleColor(Entry, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
}

Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
{
    return styleColor(Entry, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
}

Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor() const
{
    return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
}

Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor() const
{
    return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
}

Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor() const
{
    return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
}

Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor() const
{
    return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
}

Color RenderThemeGtk::systemColor(CSSValueID cssValueId) const
{
    switch (cssValueId) {
    case CSSValueButtontext:
        return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
    case CSSValueCaptiontext:
        return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
    default:
        return RenderTheme::systemColor(cssValueId);
    }
}

void RenderThemeGtk::platformColorsDidChange()
{
    RenderTheme::platformColorsDidChange();
}

#if ENABLE(VIDEO)
String RenderThemeGtk::extraMediaControlsStyleSheet()
{
    return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
}

#if ENABLE(FULLSCREEN_API)
String RenderThemeGtk::extraFullScreenStyleSheet()
{
    return String();
}
#endif

#if GTK_CHECK_VERSION(3, 20, 0)
bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
    RenderThemeGadget::Info info = { RenderThemeGadget::Type::Icon, "image", themePartStateFlags(*this, MediaButton, renderObject), { } };
    auto gadget = RenderThemeGadget::create(info);
    auto* gadgetIcon = static_cast<RenderThemeIconGadget*>(gadget.get());
    gadgetIcon->setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
    gadgetIcon->setIconName(iconName);
    return !gadget->render(graphicsContext.platformContext()->cr(), rect);
}
#else
bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
{
    GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton);
    gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
    gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject));
    static const unsigned mediaIconSize = 16;
    IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize);
    return !paintIcon(context.get(), graphicsContext, iconRect, iconName);
}
#endif // GTK_CHECK_VERSION(3, 20, 0)

bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const
{
    return (part != MediaMuteButtonPart);
}

bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic");
}

bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    Node* node = renderObject.node();
    if (!node)
        return true;
    Node* mediaNode = node->shadowHost();
    if (!is<HTMLMediaElement>(mediaNode))
        return true;

    HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode);
    return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
}

bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    Node* node = renderObject.node();
    if (!node)
        return true;
    if (!nodeHasPseudo(node, "-webkit-media-controls-play-button"))
        return true;

    return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused") ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
}

bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic");
}

bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic");
}

#if ENABLE(VIDEO_TRACK)
bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic");
}
#endif

static FloatRoundedRect::Radii borderRadiiFromStyle(RenderStyle& style)
{
    return FloatRoundedRect::Radii(
        IntSize(style.borderTopLeftRadius().width().intValue(), style.borderTopLeftRadius().height().intValue()),
        IntSize(style.borderTopRightRadius().width().intValue(), style.borderTopRightRadius().height().intValue()),
        IntSize(style.borderBottomLeftRadius().width().intValue(), style.borderBottomLeftRadius().height().intValue()),
        IntSize(style.borderBottomRightRadius().width().intValue(), style.borderBottomRightRadius().height().intValue()));
}

bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
{
    HTMLMediaElement* mediaElement = parentMediaElement(o);
    if (!mediaElement)
        return true;

    GraphicsContext& context = paintInfo.context();
    context.save();
    context.setStrokeStyle(NoStroke);

    float mediaDuration = mediaElement->duration();
    float totalTrackWidth = r.width();
    RenderStyle& style = o.style();
    RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
    for (unsigned index = 0; index < timeRanges->length(); ++index) {
        float start = timeRanges->start(index, IGNORE_EXCEPTION);
        float end = timeRanges->end(index, IGNORE_EXCEPTION);
        float startRatio = start / mediaDuration;
        float lengthRatio = (end - start) / mediaDuration;
        if (!lengthRatio)
            continue;

        IntRect rangeRect(r);
        rangeRect.setWidth(lengthRatio * totalTrackWidth);
        if (index)
            rangeRect.move(startRatio * totalTrackWidth, 0);
        context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
    }

    context.restore();
    return false;
}

bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
{
    RenderStyle& style = o.style();
    paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
    return false;
}

bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    HTMLMediaElement* mediaElement = parentMediaElement(renderObject);
    if (!mediaElement)
        return true;

    float volume = mediaElement->muted() ? 0.0f : mediaElement->volume();
    if (!volume)
        return true;

    GraphicsContext& context = paintInfo.context();
    context.save();
    context.setStrokeStyle(NoStroke);

    int rectHeight = rect.height();
    float trackHeight = rectHeight * volume;
    RenderStyle& style = renderObject.style();
    IntRect volumeRect(rect);
    volumeRect.move(0, rectHeight - trackHeight);
    volumeRect.setHeight(ceil(trackHeight));

    context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
    context.restore();

    return false;
}

bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMediaSliderThumb(renderObject, paintInfo, rect);
}

String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
{
    return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
}

bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&)
{
    return false;
}
#endif

void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, Element*) const
{
    style.setBoxShadow(nullptr);
}

// These values have been copied from RenderThemeChromiumSkia.cpp
static const int progressActivityBlocks = 5;
static const int progressAnimationFrames = 10;
static const double progressAnimationInterval = 0.125;
double RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const
{
    return progressAnimationInterval;
}

double RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const
{
    return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
}

IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect)
{
    IntRect progressRect(fullBarRect);
    const auto& renderProgress = downcast<RenderProgress>(renderObject);
    if (renderProgress.isDeterminate()) {
        int progressWidth = progressRect.width() * renderProgress.position();
        if (renderObject.style().direction() == RTL)
            progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
        progressRect.setWidth(progressWidth);
        return progressRect;
    }

    double animationProgress = renderProgress.animationProgress();

    // Never let the progress rect shrink smaller than 2 pixels.
    int newWidth = std::max(2, progressRect.width() / progressActivityBlocks);
    int movableWidth = progressRect.width() - newWidth;
    progressRect.setWidth(newWidth);

    // We want the first 0.5 units of the animation progress to represent the
    // forward motion and the second 0.5 units to represent the backward motion,
    // thus we multiply by two here to get the full sweep of the progress bar with
    // each direction.
    if (animationProgress < 0.5)
        progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
    else
        progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));
    return progressRect;
}

String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
{
    if (width <= 0)
        return String();

    if (fileList->length() > 1)
        return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);

    String string;
    if (fileList->length())
        string = pathGetFileName(fileList->item(0)->path());
    else if (multipleFilesAllowed)
        string = fileButtonNoFilesSelectedLabel();
    else
        string = fileButtonNoFileSelectedLabel();

    return StringTruncator::centerTruncate(string, width, font);
}

#if ENABLE(VIDEO)
String RenderThemeGtk::mediaControlsScript()
{
    StringBuilder scriptBuilder;
    scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
    scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript));
    scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript));
    return scriptBuilder.toString();
}
#endif // ENABLE(VIDEO)

#endif // GTK_API_VERSION_2
}
