Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / platform / gtk / ScrollbarThemeGtk.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "ScrollbarThemeGtk.h"
29
30 #include "GRefPtrGtk.h"
31 #include "PlatformContextCairo.h"
32 #include "PlatformMouseEvent.h"
33 #include "RenderThemeWidget.h"
34 #include "ScrollView.h"
35 #include "Scrollbar.h"
36 #include <cstdlib>
37 #include <gtk/gtk.h>
38
39 namespace WebCore {
40
41 ScrollbarTheme& ScrollbarTheme::nativeTheme()
42 {
43     static ScrollbarThemeGtk theme;
44     return theme;
45 }
46
47 ScrollbarThemeGtk::~ScrollbarThemeGtk() = default;
48
49 #ifndef GTK_API_VERSION_2
50 static void themeChangedCallback()
51 {
52     ScrollbarTheme::theme().themeChanged();
53 }
54
55 ScrollbarThemeGtk::ScrollbarThemeGtk()
56 {
57 #if GTK_CHECK_VERSION(3, 20, 0)
58     m_usesOverlayScrollbars = g_strcmp0(g_getenv("GTK_OVERLAY_SCROLLING"), "0");
59 #endif
60     static bool themeMonitorInitialized = false;
61     if (!themeMonitorInitialized) {
62         g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
63         themeMonitorInitialized = true;
64         updateThemeProperties();
65     }
66 }
67
68 #if !GTK_CHECK_VERSION(3, 20, 0)
69 static GRefPtr<GtkStyleContext> createStyleContext(Scrollbar* scrollbar = nullptr)
70 {
71     GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
72     GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_new());
73     gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
74     gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
75     gtk_widget_path_iter_add_class(path.get(), -1, scrollbar && scrollbar->orientation() == HorizontalScrollbar ? "horizontal" : "vertical");
76     gtk_style_context_set_path(styleContext.get(), path.get());
77     return styleContext;
78 }
79
80 static GRefPtr<GtkStyleContext> createChildStyleContext(GtkStyleContext* parent, const char* className)
81 {
82     ASSERT(parent);
83     GRefPtr<GtkWidgetPath> path = adoptGRef(gtk_widget_path_copy(gtk_style_context_get_path(parent)));
84     gtk_widget_path_append_type(path.get(), GTK_TYPE_SCROLLBAR);
85     gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCROLLBAR);
86     gtk_widget_path_iter_add_class(path.get(), -1, className);
87
88     GRefPtr<GtkStyleContext> styleContext = adoptGRef(gtk_style_context_new());
89     gtk_style_context_set_path(styleContext.get(), path.get());
90     gtk_style_context_set_parent(styleContext.get(), parent);
91     return styleContext;
92 }
93 #endif // GTK_CHECK_VERSION(3, 20, 0)
94
95 void ScrollbarThemeGtk::themeChanged()
96 {
97 #if GTK_CHECK_VERSION(3, 20, 0)
98     RenderThemeWidget::clearCache();
99 #endif
100     updateThemeProperties();
101 }
102
103 #if GTK_CHECK_VERSION(3, 20, 0)
104 void ScrollbarThemeGtk::updateThemeProperties()
105 {
106     auto& scrollbar = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
107     m_hasBackButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
108     m_hasForwardButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
109     m_hasBackButtonEndPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
110     m_hasForwardButtonStartPart = scrollbar.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
111 }
112 #else
113 void ScrollbarThemeGtk::updateThemeProperties()
114 {
115     gboolean hasBackButtonStartPart, hasForwardButtonEndPart, hasBackButtonEndPart, hasForwardButtonStartPart;
116     gtk_style_context_get_style(createStyleContext().get(),
117         "has-backward-stepper", &hasBackButtonStartPart,
118         "has-forward-stepper", &hasForwardButtonEndPart,
119         "has-secondary-backward-stepper", &hasBackButtonEndPart,
120         "has-secondary-forward-stepper", &hasForwardButtonStartPart,
121         nullptr);
122     m_hasBackButtonStartPart = hasBackButtonStartPart;
123     m_hasForwardButtonEndPart = hasForwardButtonEndPart;
124     m_hasBackButtonEndPart = hasBackButtonEndPart;
125     m_hasForwardButtonStartPart = hasForwardButtonStartPart;
126 }
127 #endif // GTK_CHECK_VERSION(3, 20, 0)
128
129 bool ScrollbarThemeGtk::hasButtons(Scrollbar& scrollbar)
130 {
131     return scrollbar.enabled() && (m_hasBackButtonStartPart || m_hasForwardButtonEndPart || m_hasBackButtonEndPart || m_hasForwardButtonStartPart);
132 }
133
134 #if GTK_CHECK_VERSION(3, 20, 0)
135 static GtkStateFlags scrollbarPartStateFlags(Scrollbar& scrollbar, ScrollbarPart part, bool painting = false)
136 {
137     unsigned stateFlags = 0;
138     switch (part) {
139     case AllParts:
140         if (!painting || scrollbar.hoveredPart() != NoPart)
141             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
142         break;
143     case BackTrackPart:
144     case ForwardTrackPart:
145         if (scrollbar.hoveredPart() == BackTrackPart || scrollbar.hoveredPart() == ForwardTrackPart)
146             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
147         if (scrollbar.pressedPart() == BackTrackPart || scrollbar.pressedPart() == ForwardTrackPart)
148             stateFlags |= GTK_STATE_FLAG_ACTIVE;
149         break;
150     case BackButtonStartPart:
151     case ForwardButtonStartPart:
152     case BackButtonEndPart:
153     case ForwardButtonEndPart:
154         if (((part == BackButtonStartPart || part == BackButtonEndPart) && !scrollbar.currentPos())
155             || ((part == ForwardButtonEndPart || part == ForwardButtonStartPart) && scrollbar.currentPos() == scrollbar.maximum())) {
156             stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
157             break;
158         }
159         FALLTHROUGH;
160     default:
161         if (scrollbar.hoveredPart() == part)
162             stateFlags |= GTK_STATE_FLAG_PRELIGHT;
163
164         if (scrollbar.pressedPart() == part)
165             stateFlags |= GTK_STATE_FLAG_ACTIVE;
166         break;
167     }
168
169     return static_cast<GtkStateFlags>(stateFlags);
170 }
171
172 static RenderThemeWidget::Type widgetTypeForScrollbar(Scrollbar& scrollbar, GtkStateFlags scrollbarState)
173 {
174     if (scrollbar.orientation() == VerticalScrollbar) {
175         if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
176             return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarLeft : RenderThemeWidget::Type::VerticalScrollIndicatorLeft;
177         return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::VerticalScrollbarRight : RenderThemeWidget::Type::VerticalScrollIndicatorRight;
178     }
179     return scrollbarState & GTK_STATE_FLAG_PRELIGHT ? RenderThemeWidget::Type::HorizontalScrollbar : RenderThemeWidget::Type::HorizontalScrollIndicator;
180 }
181
182 static IntRect contentsRectangle(Scrollbar& scrollbar, RenderThemeScrollbar& scrollbarWidget)
183 {
184     GtkBorder scrollbarContentsBox = scrollbarWidget.scrollbar().contentsBox();
185     GtkBorder contentsContentsBox = scrollbarWidget.contents().contentsBox();
186     GtkBorder padding;
187     padding.left = scrollbarContentsBox.left + contentsContentsBox.left;
188     padding.right = scrollbarContentsBox.right + contentsContentsBox.right;
189     padding.top = scrollbarContentsBox.top + contentsContentsBox.top;
190     padding.bottom = scrollbarContentsBox.bottom + contentsContentsBox.bottom;
191     IntRect contentsRect = scrollbar.frameRect();
192     contentsRect.move(padding.left, padding.top);
193     contentsRect.contract(padding.left + padding.right, padding.top + padding.bottom);
194     return contentsRect;
195 }
196
197 IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
198 {
199     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
200     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
201     scrollbarWidget.scrollbar().setState(scrollbarState);
202
203     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
204     if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
205         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
206         IntSize stepperSize = backwardStepper->preferredSize();
207         if (scrollbar.orientation() == VerticalScrollbar) {
208             rect.move(0, stepperSize.height());
209             rect.contract(0, stepperSize.height());
210         } else {
211             rect.move(stepperSize.width(), 0);
212             rect.contract(stepperSize.width(), 0);
213         }
214     }
215     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
216         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
217         IntSize stepperSize = secondaryForwardStepper->preferredSize();
218         if (scrollbar.orientation() == VerticalScrollbar) {
219             rect.move(0, stepperSize.height());
220             rect.contract(0, stepperSize.height());
221         } else {
222             rect.move(stepperSize.width(), 0);
223             rect.contract(stepperSize.width(), 0);
224         }
225     }
226     if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
227         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
228         if (scrollbar.orientation() == VerticalScrollbar)
229             rect.contract(0, secondaryBackwardStepper->preferredSize().height());
230         else
231             rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
232     }
233     if (auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward)) {
234         forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
235         if (scrollbar.orientation() == VerticalScrollbar)
236             rect.contract(0, forwardStepper->preferredSize().height());
237         else
238             rect.contract(forwardStepper->preferredSize().width(), 0);
239     }
240
241     if (scrollbar.orientation() == VerticalScrollbar)
242         return scrollbar.height() < rect.height() ? IntRect() : rect;
243
244     return scrollbar.width() < rect.width() ? IntRect() : rect;
245 }
246 #else
247 IntRect ScrollbarThemeGtk::trackRect(Scrollbar& scrollbar, bool /*painting*/)
248 {
249     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
250     // The padding along the thumb movement axis includes the trough border
251     // plus the size of stepper spacing (the space between the stepper and
252     // the place where the thumb stops). There is often no stepper spacing.
253     int stepperSpacing, stepperSize, troughBorderWidth, thumbFat;
254     gtk_style_context_get_style(styleContext.get(), "stepper-spacing", &stepperSpacing, "stepper-size", &stepperSize, "trough-border",
255         &troughBorderWidth, "slider-width", &thumbFat, nullptr);
256
257     // The fatness of the scrollbar on the non-movement axis.
258     int thickness = thumbFat + 2 * troughBorderWidth;
259
260     int startButtonsOffset = 0;
261     int buttonsWidth = 0;
262     if (m_hasForwardButtonStartPart) {
263         startButtonsOffset += stepperSize;
264         buttonsWidth += stepperSize;
265     }
266     if (m_hasBackButtonStartPart) {
267         startButtonsOffset += stepperSize;
268         buttonsWidth += stepperSize;
269     }
270     if (m_hasBackButtonEndPart)
271         buttonsWidth += stepperSize;
272     if (m_hasForwardButtonEndPart)
273         buttonsWidth += stepperSize;
274
275     if (scrollbar.orientation() == HorizontalScrollbar) {
276         // Once the scrollbar becomes smaller than the natural size of the two buttons and the thumb, the track disappears.
277         if (scrollbar.width() < buttonsWidth + minimumThumbLength(scrollbar))
278             return IntRect();
279         return IntRect(scrollbar.x() + troughBorderWidth + stepperSpacing + startButtonsOffset, scrollbar.y(),
280             scrollbar.width() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth, thickness);
281     }
282
283     if (scrollbar.height() < buttonsWidth + minimumThumbLength(scrollbar))
284         return IntRect();
285     return IntRect(scrollbar.x(), scrollbar.y() + troughBorderWidth + stepperSpacing + startButtonsOffset,
286         thickness, scrollbar.height() - (2 * troughBorderWidth) - (2 * stepperSpacing) - buttonsWidth);
287 }
288 #endif
289
290 bool ScrollbarThemeGtk::hasThumb(Scrollbar& scrollbar)
291 {
292     // This method is just called as a paint-time optimization to see if
293     // painting the thumb can be skipped. We don't have to be exact here.
294     return thumbLength(scrollbar) > 0;
295 }
296
297 #if GTK_CHECK_VERSION(3, 20, 0)
298 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
299 {
300     ASSERT(part == BackButtonStartPart || part == BackButtonEndPart);
301     if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
302         return IntRect();
303
304     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
305     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
306     scrollbarWidget.scrollbar().setState(scrollbarState);
307
308     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
309     if (part == BackButtonStartPart) {
310         auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
311         ASSERT(backwardStepper);
312         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
313         return IntRect(rect.location(), backwardStepper->preferredSize());
314     }
315
316     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
317         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
318         IntSize preferredSize = secondaryForwardStepper->preferredSize();
319         if (scrollbar.orientation() == VerticalScrollbar) {
320             rect.move(0, preferredSize.height());
321             rect.contract(0, preferredSize.height());
322         } else {
323             rect.move(preferredSize.width(), 0);
324             rect.contract(0, preferredSize.width());
325         }
326     }
327
328     if (auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward)) {
329         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
330         if (scrollbar.orientation() == VerticalScrollbar)
331             rect.contract(0, secondaryBackwardStepper->preferredSize().height());
332         else
333             rect.contract(secondaryBackwardStepper->preferredSize().width(), 0);
334     }
335
336     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
337     ASSERT(forwardStepper);
338     forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
339     IntSize preferredSize = forwardStepper->preferredSize();
340     if (scrollbar.orientation() == VerticalScrollbar)
341         rect.move(0, rect.height() - preferredSize.height());
342     else
343         rect.move(rect.width() - preferredSize.width(), 0);
344
345     return IntRect(rect.location(), preferredSize);
346 }
347
348 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
349 {
350     ASSERT(part == ForwardButtonStartPart || part == ForwardButtonEndPart);
351     if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
352         return IntRect();
353
354     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts);
355     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
356     scrollbarWidget.scrollbar().setState(scrollbarState);
357
358     IntRect rect = contentsRectangle(scrollbar, scrollbarWidget);
359     if (auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward)) {
360         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
361         IntSize preferredSize = backwardStepper->preferredSize();
362         if (scrollbar.orientation() == VerticalScrollbar) {
363             rect.move(0, preferredSize.height());
364             rect.contract(0, preferredSize.height());
365         } else {
366             rect.move(preferredSize.width(), 0);
367             rect.contract(preferredSize.width(), 0);
368         }
369     }
370
371     if (auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward)) {
372         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
373         IntSize preferredSize = secondaryForwardStepper->preferredSize();
374         if (part == ForwardButtonStartPart)
375             return IntRect(rect.location(), preferredSize);
376
377         if (scrollbar.orientation() == VerticalScrollbar) {
378             rect.move(0, preferredSize.height());
379             rect.contract(0, preferredSize.height());
380         } else {
381             rect.move(preferredSize.width(), 0);
382             rect.contract(preferredSize.width(), 0);
383         }
384     }
385
386     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
387     ASSERT(forwardStepper);
388     forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
389     IntSize preferredSize = forwardStepper->preferredSize();
390     if (scrollbar.orientation() == VerticalScrollbar)
391         rect.move(0, rect.height() - preferredSize.height());
392     else
393         rect.move(rect.width() - preferredSize.width(), 0);
394
395     return IntRect(rect.location(), preferredSize);
396 }
397 #else
398 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
399 {
400     if ((part == BackButtonEndPart && !m_hasBackButtonEndPart) || (part == BackButtonStartPart && !m_hasBackButtonStartPart))
401         return IntRect();
402
403     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
404     int troughBorderWidth, stepperSize, thumbFat;
405     gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
406     int x = scrollbar.x() + troughBorderWidth;
407     int y = scrollbar.y() + troughBorderWidth;
408     if (part == BackButtonStartPart) {
409         if (scrollbar.orientation() == HorizontalScrollbar)
410             return IntRect(x, y, stepperSize, thumbFat);
411         return IntRect(x, y, thumbFat, stepperSize);
412     }
413
414     // BackButtonEndPart (alternate button)
415     if (scrollbar.orientation() == HorizontalScrollbar)
416         return IntRect(scrollbar.x() + scrollbar.width() - troughBorderWidth - (2 * stepperSize), y, stepperSize, thumbFat);
417
418     // VerticalScrollbar alternate button
419     return IntRect(x, scrollbar.y() + scrollbar.height() - troughBorderWidth - (2 * stepperSize), thumbFat, stepperSize);
420 }
421
422 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool /*painting*/)
423 {
424     if ((part == ForwardButtonStartPart && !m_hasForwardButtonStartPart) || (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart))
425         return IntRect();
426
427     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
428     int troughBorderWidth, stepperSize, thumbFat;
429     gtk_style_context_get_style(styleContext.get(), "trough-border", &troughBorderWidth, "stepper-size", &stepperSize, "slider-width", &thumbFat, nullptr);
430     if (scrollbar.orientation() == HorizontalScrollbar) {
431         int y = scrollbar.y() + troughBorderWidth;
432         if (part == ForwardButtonEndPart)
433             return IntRect(scrollbar.x() + scrollbar.width() - stepperSize - troughBorderWidth, y, stepperSize, thumbFat);
434
435         // ForwardButtonStartPart (alternate button)
436         return IntRect(scrollbar.x() + troughBorderWidth + stepperSize, y, stepperSize, thumbFat);
437     }
438
439     // VerticalScrollbar
440     int x = scrollbar.x() + troughBorderWidth;
441     if (part == ForwardButtonEndPart)
442         return IntRect(x, scrollbar.y() + scrollbar.height() - stepperSize - troughBorderWidth, thumbFat, stepperSize);
443
444     // ForwardButtonStartPart (alternate button)
445     return IntRect(x, scrollbar.y() + troughBorderWidth + stepperSize, thumbFat, stepperSize);
446 }
447 #endif // GTK_CHECK_VERSION(3, 20, 0)
448
449 #if GTK_CHECK_VERSION(3, 20, 0)
450 bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
451 {
452     if (graphicsContext.paintingDisabled())
453         return false;
454
455     if (!scrollbar.enabled())
456         return true;
457
458     double opacity = scrollbar.hoveredPart() == NoPart ? scrollbar.opacity() : 1;
459     if (!opacity)
460         return true;
461
462     IntRect rect = scrollbar.frameRect();
463     if (!rect.intersects(damageRect))
464         return true;
465
466     auto scrollbarState = scrollbarPartStateFlags(scrollbar, AllParts, true);
467     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(widgetTypeForScrollbar(scrollbar, scrollbarState)));
468     auto& scrollbarGadget = scrollbarWidget.scrollbar();
469     scrollbarGadget.setState(scrollbarState);
470     if (m_usesOverlayScrollbars)
471         opacity *= scrollbarGadget.opacity();
472     if (!opacity)
473         return true;
474
475     auto& trough = scrollbarWidget.trough();
476     trough.setState(scrollbarPartStateFlags(scrollbar, BackTrackPart));
477
478     auto* backwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Backward);
479     if (backwardStepper)
480         backwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonStartPart));
481     auto* secondaryForwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryForward);
482     if (secondaryForwardStepper)
483         secondaryForwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonStartPart));
484     auto* secondaryBackwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
485     if (secondaryBackwardStepper)
486         secondaryBackwardStepper->setState(scrollbarPartStateFlags(scrollbar, BackButtonEndPart));
487     auto* forwardStepper = scrollbarWidget.stepper(RenderThemeScrollbarGadget::Steppers::Forward);
488     if (forwardStepper)
489         forwardStepper->setState(scrollbarPartStateFlags(scrollbar, ForwardButtonEndPart));
490
491     IntSize preferredSize = scrollbarWidget.contents().preferredSize();
492     int thumbSize = thumbLength(scrollbar);
493     if (thumbSize) {
494         scrollbarWidget.slider().setState(scrollbarPartStateFlags(scrollbar, ThumbPart));
495         preferredSize = preferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
496     }
497     preferredSize += scrollbarGadget.preferredSize() - scrollbarGadget.minimumSize();
498
499     FloatRect contentsRect(rect);
500     // When using overlay scrollbars we always claim the size of the scrollbar when hovered, so when
501     // drawing the indicator we need to adjust the rectangle to its actual size in indicator mode.
502     if (scrollbar.orientation() == VerticalScrollbar) {
503         if (rect.width() != preferredSize.width()) {
504             if (!scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
505                 contentsRect.move(std::abs(rect.width() - preferredSize.width()), 0);
506             contentsRect.setWidth(preferredSize.width());
507         }
508     } else {
509         if (rect.height() != preferredSize.height()) {
510             contentsRect.move(0, std::abs(rect.height() - preferredSize.height()));
511             contentsRect.setHeight(preferredSize.height());
512         }
513     }
514
515     if (opacity != 1) {
516         graphicsContext.save();
517         graphicsContext.clip(damageRect);
518         graphicsContext.beginTransparencyLayer(opacity);
519     }
520
521     scrollbarGadget.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
522     scrollbarWidget.contents().render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
523
524     if (backwardStepper) {
525         FloatRect buttonRect = contentsRect;
526         if (scrollbar.orientation() == VerticalScrollbar)
527             buttonRect.setHeight(backwardStepper->preferredSize().height());
528         else
529             buttonRect.setWidth(backwardStepper->preferredSize().width());
530         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, backwardStepper,
531             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Backward);
532         if (scrollbar.orientation() == VerticalScrollbar) {
533             contentsRect.move(0, buttonRect.height());
534             contentsRect.contract(0, buttonRect.height());
535         } else {
536             contentsRect.move(buttonRect.width(), 0);
537             contentsRect.contract(buttonRect.width(), 0);
538         }
539     }
540     if (secondaryForwardStepper) {
541         FloatRect buttonRect = contentsRect;
542         if (scrollbar.orientation() == VerticalScrollbar)
543             buttonRect.setHeight(secondaryForwardStepper->preferredSize().height());
544         else
545             buttonRect.setWidth(secondaryForwardStepper->preferredSize().width());
546         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryForwardStepper,
547             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryForward);
548         if (scrollbar.orientation() == VerticalScrollbar) {
549             contentsRect.move(0, buttonRect.height());
550             contentsRect.contract(0, buttonRect.height());
551         } else {
552             contentsRect.move(buttonRect.width(), 0);
553             contentsRect.contract(buttonRect.width(), 0);
554         }
555     }
556     if (secondaryBackwardStepper) {
557         FloatRect buttonRect = contentsRect;
558         if (scrollbar.orientation() == VerticalScrollbar) {
559             buttonRect.setHeight(secondaryBackwardStepper->preferredSize().height());
560             buttonRect.move(0, contentsRect.height() - buttonRect.height());
561         } else {
562             buttonRect.setWidth(secondaryBackwardStepper->preferredSize().width());
563             buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
564         }
565         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, secondaryBackwardStepper,
566             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::SecondaryBackward);
567         if (scrollbar.orientation() == VerticalScrollbar)
568             contentsRect.contract(0, buttonRect.height());
569         else
570             contentsRect.contract(buttonRect.width(), 0);
571     }
572     if (forwardStepper) {
573         FloatRect buttonRect = contentsRect;
574         if (scrollbar.orientation() == VerticalScrollbar) {
575             buttonRect.setHeight(forwardStepper->preferredSize().height());
576             buttonRect.move(0, contentsRect.height() - buttonRect.height());
577         } else {
578             buttonRect.setWidth(forwardStepper->preferredSize().width());
579             buttonRect.move(contentsRect.width() - buttonRect.width(), 0);
580         }
581         static_cast<RenderThemeScrollbarGadget&>(scrollbarGadget).renderStepper(graphicsContext.platformContext()->cr(), buttonRect, forwardStepper,
582             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, RenderThemeScrollbarGadget::Steppers::Forward);
583         if (scrollbar.orientation() == VerticalScrollbar)
584             contentsRect.contract(0, buttonRect.height());
585         else
586             contentsRect.contract(buttonRect.width(), 0);
587     }
588
589     trough.render(graphicsContext.platformContext()->cr(), contentsRect, &contentsRect);
590
591     if (thumbSize) {
592         if (scrollbar.orientation() == VerticalScrollbar) {
593             contentsRect.move(0, thumbPosition(scrollbar));
594             contentsRect.setWidth(scrollbarWidget.slider().preferredSize().width());
595             contentsRect.setHeight(thumbSize);
596         } else {
597             contentsRect.move(thumbPosition(scrollbar), 0);
598             contentsRect.setWidth(thumbSize);
599             contentsRect.setHeight(scrollbarWidget.slider().preferredSize().height());
600         }
601         if (contentsRect.intersects(damageRect))
602             scrollbarWidget.slider().render(graphicsContext.platformContext()->cr(), contentsRect);
603     }
604
605     if (opacity != 1) {
606         graphicsContext.endTransparencyLayer();
607         graphicsContext.restore();
608     }
609
610     return true;
611 }
612 #else
613 static void paintStepper(GtkStyleContext* parentContext, GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part)
614 {
615     ScrollbarOrientation orientation = scrollbar.orientation();
616     GRefPtr<GtkStyleContext> styleContext = createChildStyleContext(parentContext, "button");
617
618     unsigned flags = 0;
619     if ((BackButtonStartPart == part && scrollbar.currentPos())
620         || (BackButtonEndPart == part && scrollbar.currentPos())
621         || (ForwardButtonEndPart == part && scrollbar.currentPos() != scrollbar.maximum())
622         || (ForwardButtonStartPart == part && scrollbar.currentPos() != scrollbar.maximum())) {
623         if (part == scrollbar.pressedPart())
624             flags |= GTK_STATE_FLAG_ACTIVE;
625         if (part == scrollbar.hoveredPart())
626             flags |= GTK_STATE_FLAG_PRELIGHT;
627     } else
628         flags |= GTK_STATE_FLAG_INSENSITIVE;
629     gtk_style_context_set_state(styleContext.get(), static_cast<GtkStateFlags>(flags));
630
631     gtk_render_background(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
632     gtk_render_frame(styleContext.get(), context.platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
633
634     gfloat arrowScaling;
635     gtk_style_context_get_style(styleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
636
637     double arrowSize = std::min(rect.width(), rect.height()) * arrowScaling;
638     FloatPoint arrowPoint(rect.x() + (rect.width() - arrowSize) / 2, rect.y() + (rect.height() - arrowSize) / 2);
639
640     if (flags & GTK_STATE_FLAG_ACTIVE) {
641         gint arrowDisplacementX, arrowDisplacementY;
642         gtk_style_context_get_style(styleContext.get(), "arrow-displacement-x", &arrowDisplacementX, "arrow-displacement-y", &arrowDisplacementY, nullptr);
643         arrowPoint.move(arrowDisplacementX, arrowDisplacementY);
644     }
645
646     gdouble angle;
647     if (orientation == VerticalScrollbar)
648         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI : 0;
649     else
650         angle = (part == ForwardButtonEndPart || part == ForwardButtonStartPart) ? G_PI / 2 : 3 * (G_PI / 2);
651
652     gtk_render_arrow(styleContext.get(), context.platformContext()->cr(), angle, arrowPoint.x(), arrowPoint.y(), arrowSize);
653 }
654
655 static void adjustRectAccordingToMargin(GtkStyleContext* context, IntRect& rect)
656 {
657     GtkBorder margin;
658     gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
659     rect.move(margin.left, margin.top);
660     rect.contract(margin.left + margin.right, margin.top + margin.bottom);
661 }
662
663 bool ScrollbarThemeGtk::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
664 {
665     if (graphicsContext.paintingDisabled())
666         return false;
667
668     GRefPtr<GtkStyleContext> styleContext = createStyleContext(&scrollbar);
669
670     // Create the ScrollbarControlPartMask based on the damageRect
671     ScrollbarControlPartMask scrollMask = NoPart;
672
673     IntRect backButtonStartPaintRect;
674     IntRect backButtonEndPaintRect;
675     IntRect forwardButtonStartPaintRect;
676     IntRect forwardButtonEndPaintRect;
677     if (hasButtons(scrollbar)) {
678         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
679         if (damageRect.intersects(backButtonStartPaintRect))
680             scrollMask |= BackButtonStartPart;
681         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
682         if (damageRect.intersects(backButtonEndPaintRect))
683             scrollMask |= BackButtonEndPart;
684         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
685         if (damageRect.intersects(forwardButtonStartPaintRect))
686             scrollMask |= ForwardButtonStartPart;
687         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
688         if (damageRect.intersects(forwardButtonEndPaintRect))
689             scrollMask |= ForwardButtonEndPart;
690     }
691
692     IntRect trackPaintRect = trackRect(scrollbar, true);
693     if (damageRect.intersects(trackPaintRect))
694         scrollMask |= TrackBGPart;
695
696     gboolean troughUnderSteppers;
697     gtk_style_context_get_style(styleContext.get(), "trough-under-steppers", &troughUnderSteppers, nullptr);
698     if (troughUnderSteppers && (scrollMask & BackButtonStartPart
699             || scrollMask & BackButtonEndPart
700             || scrollMask & ForwardButtonStartPart
701             || scrollMask & ForwardButtonEndPart))
702         scrollMask |= TrackBGPart;
703
704     IntRect currentThumbRect;
705     if (hasThumb(scrollbar)) {
706         IntRect track = trackRect(scrollbar, false);
707         IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, track);
708         int thumbFat;
709         gtk_style_context_get_style(styleContext.get(), "slider-width", &thumbFat, nullptr);
710         if (scrollbar.orientation() == HorizontalScrollbar)
711             currentThumbRect = IntRect(trackRect.x() + thumbPosition(scrollbar), trackRect.y() + (trackRect.height() - thumbFat) / 2, thumbLength(scrollbar), thumbFat);
712         else
713             currentThumbRect = IntRect(trackRect.x() + (trackRect.width() - thumbFat) / 2, trackRect.y() + thumbPosition(scrollbar), thumbFat, thumbLength(scrollbar));
714         if (damageRect.intersects(currentThumbRect))
715             scrollMask |= ThumbPart;
716     }
717
718     if (scrollMask == NoPart)
719         return true;
720
721     ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart | ForwardButtonStartPart | ForwardButtonEndPart;
722
723     // Paint the track background. If the trough-under-steppers property is true, this
724     // should be the full size of the scrollbar, but if is false, it should only be the
725     // track rect.
726     GRefPtr<GtkStyleContext> troughStyleContext = createChildStyleContext(styleContext.get(), "trough");
727     if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons) {
728         IntRect fullScrollbarRect = trackPaintRect;
729         if (troughUnderSteppers)
730             fullScrollbarRect = scrollbar.frameRect();
731
732         IntRect adjustedRect = fullScrollbarRect;
733         adjustRectAccordingToMargin(styleContext.get(), adjustedRect);
734         gtk_render_background(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
735         gtk_render_frame(styleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
736
737         adjustedRect = fullScrollbarRect;
738         adjustRectAccordingToMargin(troughStyleContext.get(), adjustedRect);
739         gtk_render_background(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
740         gtk_render_frame(troughStyleContext.get(), graphicsContext.platformContext()->cr(), adjustedRect.x(), adjustedRect.y(), adjustedRect.width(), adjustedRect.height());
741     }
742
743     // Paint the back and forward buttons.
744     if (scrollMask & BackButtonStartPart)
745         paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
746     if (scrollMask & BackButtonEndPart)
747         paintStepper(styleContext.get(), graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
748     if (scrollMask & ForwardButtonStartPart)
749         paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
750     if (scrollMask & ForwardButtonEndPart)
751         paintStepper(styleContext.get(), graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
752
753     // Paint the thumb.
754     if (scrollMask & ThumbPart) {
755         GRefPtr<GtkStyleContext> thumbStyleContext = createChildStyleContext(troughStyleContext.get(), "slider");
756         unsigned flags = 0;
757         if (scrollbar.pressedPart() == ThumbPart)
758             flags |= GTK_STATE_FLAG_ACTIVE;
759         if (scrollbar.hoveredPart() == ThumbPart)
760             flags |= GTK_STATE_FLAG_PRELIGHT;
761         gtk_style_context_set_state(thumbStyleContext.get(), static_cast<GtkStateFlags>(flags));
762
763         IntRect thumbRect(currentThumbRect);
764         adjustRectAccordingToMargin(thumbStyleContext.get(), thumbRect);
765         gtk_render_slider(thumbStyleContext.get(), graphicsContext.platformContext()->cr(), thumbRect.x(), thumbRect.y(), thumbRect.width(), thumbRect.height(),
766             scrollbar.orientation() == VerticalScrollbar ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
767     }
768
769     return true;
770 }
771 #endif // GTK_CHECK_VERSION(3, 20, 0)
772
773 ScrollbarButtonPressAction ScrollbarThemeGtk::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
774 {
775     switch (pressedPart) {
776     case BackTrackPart:
777     case ForwardTrackPart:
778         if (event.button() == LeftButton)
779             return ScrollbarButtonPressAction::CenterOnThumb;
780         if (event.button() == RightButton)
781             return ScrollbarButtonPressAction::Scroll;
782         break;
783     case ThumbPart:
784         if (event.button() != RightButton)
785             return ScrollbarButtonPressAction::StartDrag;
786         break;
787     case BackButtonStartPart:
788     case ForwardButtonStartPart:
789     case BackButtonEndPart:
790     case ForwardButtonEndPart:
791         return ScrollbarButtonPressAction::Scroll;
792     default:
793         break;
794     }
795
796     return ScrollbarButtonPressAction::None;
797 }
798
799 #if GTK_CHECK_VERSION(3, 20, 0)
800 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
801 {
802     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
803     scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
804     IntSize contentsPreferredSize = scrollbarWidget.contents().preferredSize();
805     contentsPreferredSize = contentsPreferredSize.expandedTo(scrollbarWidget.slider().preferredSize());
806     IntSize preferredSize = contentsPreferredSize + scrollbarWidget.scrollbar().preferredSize() - scrollbarWidget.scrollbar().minimumSize();
807     return preferredSize.width();
808 }
809 #else
810 int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
811 {
812     int thumbFat, troughBorderWidth;
813     gtk_style_context_get_style(createStyleContext().get(), "slider-width", &thumbFat, "trough-border", &troughBorderWidth, nullptr);
814     return thumbFat + 2 * troughBorderWidth;
815 }
816 #endif // GTK_CHECK_VERSION(3, 20, 0)
817
818 #if GTK_CHECK_VERSION(3, 20, 0)
819 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
820 {
821     auto& scrollbarWidget = static_cast<RenderThemeScrollbar&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::VerticalScrollbarRight));
822     scrollbarWidget.scrollbar().setState(GTK_STATE_FLAG_PRELIGHT);
823     IntSize minSize = scrollbarWidget.slider().minimumSize();
824     return scrollbar.orientation() == VerticalScrollbar ? minSize.height() : minSize.width();
825 }
826 #else
827 int ScrollbarThemeGtk::minimumThumbLength(Scrollbar& scrollbar)
828 {
829     int minThumbLength = 0;
830     gtk_style_context_get_style(createStyleContext(&scrollbar).get(), "min-slider-length", &minThumbLength, nullptr);
831     return minThumbLength;
832 }
833 #endif
834
835 #else // GTK_API_VERSION_2
836 bool ScrollbarThemeGtk::hasThumb(Scrollbar&)
837 {
838     return false;
839 }
840
841 IntRect ScrollbarThemeGtk::backButtonRect(Scrollbar&, ScrollbarPart, bool)
842 {
843     return IntRect();
844 }
845
846 IntRect ScrollbarThemeGtk::forwardButtonRect(Scrollbar&, ScrollbarPart, bool)
847 {
848     return IntRect();
849 }
850
851 IntRect ScrollbarThemeGtk::trackRect(Scrollbar&, bool)
852 {
853     return IntRect();
854 }
855
856 bool ScrollbarThemeGtk::hasButtons(Scrollbar&)
857 {
858     return false;
859 }
860 #endif // GTK_API_VERSION_2
861
862 }