1/*
2 * Copyright (C) 2008, 2009, 2013 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "RenderScrollbar.h"
28
29#include "Frame.h"
30#include "FrameView.h"
31#include "RenderScrollbarPart.h"
32#include "RenderScrollbarTheme.h"
33#include "RenderWidget.h"
34#include "StyleInheritedData.h"
35#include "StyleResolver.h"
36
37namespace WebCore {
38
39RefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Element* ownerElement, Frame* owningFrame)
40{
41    return adoptRef(new RenderScrollbar(scrollableArea, orientation, ownerElement, owningFrame));
42}
43
44RenderScrollbar::RenderScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Element* ownerElement, Frame* owningFrame)
45    : Scrollbar(scrollableArea, orientation, RegularScrollbar, RenderScrollbarTheme::renderScrollbarTheme(), true)
46    , m_ownerElement(ownerElement)
47    , m_owningFrame(owningFrame)
48{
49    ASSERT(ownerElement || owningFrame);
50
51    // FIXME: We need to do this because RenderScrollbar::styleChanged is called as soon as the scrollbar is created.
52
53    // Update the scrollbar size.
54    int width = 0;
55    int height = 0;
56    updateScrollbarPart(ScrollbarBGPart);
57    if (RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart)) {
58        part->layout();
59        width = part->width();
60        height = part->height();
61    } else if (this->orientation() == HorizontalScrollbar)
62        width = this->width();
63    else
64        height = this->height();
65
66    setFrameRect(IntRect(0, 0, width, height));
67}
68
69RenderScrollbar::~RenderScrollbar()
70{
71}
72
73RenderBox* RenderScrollbar::owningRenderer() const
74{
75    if (m_owningFrame) {
76        RenderWidget* currentRenderer = m_owningFrame->ownerRenderer();
77        return currentRenderer;
78    }
79    ASSERT(m_ownerElement);
80    if (m_ownerElement->renderer())
81        return &m_ownerElement->renderer()->enclosingBox();
82    return nullptr;
83}
84
85void RenderScrollbar::setParent(ScrollView* parent)
86{
87    Scrollbar::setParent(parent);
88    if (!parent)
89        m_parts.clear();
90}
91
92void RenderScrollbar::setEnabled(bool e)
93{
94    bool wasEnabled = enabled();
95    Scrollbar::setEnabled(e);
96    if (wasEnabled != e)
97        updateScrollbarParts();
98}
99
100void RenderScrollbar::styleChanged()
101{
102    updateScrollbarParts();
103}
104
105void RenderScrollbar::paint(GraphicsContext* context, const IntRect& damageRect)
106{
107    if (context->updatingControlTints()) {
108        updateScrollbarParts();
109        return;
110    }
111    Scrollbar::paint(context, damageRect);
112}
113
114void RenderScrollbar::setHoveredPart(ScrollbarPart part)
115{
116    if (part == m_hoveredPart)
117        return;
118
119    ScrollbarPart oldPart = m_hoveredPart;
120    m_hoveredPart = part;
121
122    updateScrollbarPart(oldPart);
123    updateScrollbarPart(m_hoveredPart);
124
125    updateScrollbarPart(ScrollbarBGPart);
126    updateScrollbarPart(TrackBGPart);
127}
128
129void RenderScrollbar::setPressedPart(ScrollbarPart part)
130{
131    ScrollbarPart oldPart = m_pressedPart;
132    Scrollbar::setPressedPart(part);
133
134    updateScrollbarPart(oldPart);
135    updateScrollbarPart(part);
136
137    updateScrollbarPart(ScrollbarBGPart);
138    updateScrollbarPart(TrackBGPart);
139}
140
141PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId)
142{
143    if (!owningRenderer())
144        return 0;
145
146    RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), &owningRenderer()->style());
147    // Scrollbars for root frames should always have background color
148    // unless explicitly specified as transparent. So we force it.
149    // This is because WebKit assumes scrollbar to be always painted and missing background
150    // causes visual artifact like non-repainted dirty region.
151    if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground())
152        result->setBackgroundColor(Color::white);
153
154    return result;
155}
156
157void RenderScrollbar::updateScrollbarParts()
158{
159    updateScrollbarPart(ScrollbarBGPart);
160    updateScrollbarPart(BackButtonStartPart);
161    updateScrollbarPart(ForwardButtonStartPart);
162    updateScrollbarPart(BackTrackPart);
163    updateScrollbarPart(ThumbPart);
164    updateScrollbarPart(ForwardTrackPart);
165    updateScrollbarPart(BackButtonEndPart);
166    updateScrollbarPart(ForwardButtonEndPart);
167    updateScrollbarPart(TrackBGPart);
168
169    // See if the scrollbar's thickness changed.  If so, we need to mark our owning object as needing a layout.
170    bool isHorizontal = orientation() == HorizontalScrollbar;
171    int oldThickness = isHorizontal ? height() : width();
172    int newThickness = 0;
173    RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart);
174    if (part) {
175        part->layout();
176        newThickness = isHorizontal ? part->height() : part->width();
177    }
178
179    if (newThickness != oldThickness) {
180        setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height())));
181        if (RenderBox* box = owningRenderer())
182            box->setChildNeedsLayout();
183    }
184}
185
186static PseudoId pseudoForScrollbarPart(ScrollbarPart part)
187{
188    switch (part) {
189        case BackButtonStartPart:
190        case ForwardButtonStartPart:
191        case BackButtonEndPart:
192        case ForwardButtonEndPart:
193            return SCROLLBAR_BUTTON;
194        case BackTrackPart:
195        case ForwardTrackPart:
196            return SCROLLBAR_TRACK_PIECE;
197        case ThumbPart:
198            return SCROLLBAR_THUMB;
199        case TrackBGPart:
200            return SCROLLBAR_TRACK;
201        case ScrollbarBGPart:
202            return SCROLLBAR;
203        case NoPart:
204        case AllParts:
205            break;
206    }
207    ASSERT_NOT_REACHED();
208    return SCROLLBAR;
209}
210
211void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType)
212{
213    if (partType == NoPart)
214        return;
215
216    RefPtr<RenderStyle> partStyle = getScrollbarPseudoStyle(partType, pseudoForScrollbarPart(partType));
217    bool needRenderer = partStyle && partStyle->display() != NONE;
218
219    if (needRenderer && partStyle->display() != BLOCK) {
220        // See if we are a button that should not be visible according to OS settings.
221        ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement();
222        switch (partType) {
223            case BackButtonStartPart:
224                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart ||
225                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
226                break;
227            case ForwardButtonStartPart:
228                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth);
229                break;
230            case BackButtonEndPart:
231                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth);
232                break;
233            case ForwardButtonEndPart:
234                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd ||
235                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
236                break;
237            default:
238                break;
239        }
240    }
241
242    if (!needRenderer) {
243        m_parts.remove(partType);
244        return;
245    }
246
247    if (auto& partRendererSlot = m_parts.add(partType, nullptr).iterator->value)
248        partRendererSlot->setStyle(partStyle.releaseNonNull());
249    else {
250        partRendererSlot = createRenderer<RenderScrollbarPart>(owningRenderer()->document(), partStyle.releaseNonNull(), this, partType);
251        partRendererSlot->initializeStyle();
252    }
253}
254
255void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect)
256{
257    RenderScrollbarPart* partRenderer = m_parts.get(partType);
258    if (!partRenderer)
259        return;
260    partRenderer->paintIntoRect(graphicsContext, location(), rect);
261}
262
263IntRect RenderScrollbar::buttonRect(ScrollbarPart partType)
264{
265    RenderScrollbarPart* partRenderer = m_parts.get(partType);
266    if (!partRenderer)
267        return IntRect();
268
269    partRenderer->layout();
270
271    bool isHorizontal = orientation() == HorizontalScrollbar;
272    if (partType == BackButtonStartPart)
273        return IntRect(location(), IntSize(isHorizontal ? partRenderer->pixelSnappedWidth() : width(), isHorizontal ? height() : partRenderer->pixelSnappedHeight()));
274    if (partType == ForwardButtonEndPart)
275        return IntRect(isHorizontal ? x() + width() - partRenderer->pixelSnappedWidth() : x(),
276                       isHorizontal ? y() : y() + height() - partRenderer->pixelSnappedHeight(),
277                       isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
278                       isHorizontal ? height() : partRenderer->pixelSnappedHeight());
279
280    if (partType == ForwardButtonStartPart) {
281        IntRect previousButton = buttonRect(BackButtonStartPart);
282        return IntRect(isHorizontal ? x() + previousButton.width() : x(),
283                       isHorizontal ? y() : y() + previousButton.height(),
284                       isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
285                       isHorizontal ? height() : partRenderer->pixelSnappedHeight());
286    }
287
288    IntRect followingButton = buttonRect(ForwardButtonEndPart);
289    return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->pixelSnappedWidth() : x(),
290                   isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->pixelSnappedHeight(),
291                   isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
292                   isHorizontal ? height() : partRenderer->pixelSnappedHeight());
293}
294
295IntRect RenderScrollbar::trackRect(int startLength, int endLength)
296{
297    RenderScrollbarPart* part = m_parts.get(TrackBGPart);
298    if (part)
299        part->layout();
300
301    if (orientation() == HorizontalScrollbar) {
302        int marginLeft = part ? static_cast<int>(part->marginLeft()) : 0;
303        int marginRight = part ? static_cast<int>(part->marginRight()) : 0;
304        startLength += marginLeft;
305        endLength += marginRight;
306        int totalLength = startLength + endLength;
307        return IntRect(x() + startLength, y(), width() - totalLength, height());
308    }
309
310    int marginTop = part ? static_cast<int>(part->marginTop()) : 0;
311    int marginBottom = part ? static_cast<int>(part->marginBottom()) : 0;
312    startLength += marginTop;
313    endLength += marginBottom;
314    int totalLength = startLength + endLength;
315
316    return IntRect(x(), y() + startLength, width(), height() - totalLength);
317}
318
319IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect)
320{
321    RenderScrollbarPart* partRenderer = m_parts.get(partType);
322    if (!partRenderer)
323        return oldRect;
324
325    partRenderer->layout();
326
327    IntRect rect = oldRect;
328    if (orientation() == HorizontalScrollbar) {
329        rect.setX(rect.x() + partRenderer->marginLeft());
330        rect.setWidth(rect.width() - partRenderer->horizontalMarginExtent());
331    } else {
332        rect.setY(rect.y() + partRenderer->marginTop());
333        rect.setHeight(rect.height() - partRenderer->verticalMarginExtent());
334    }
335    return rect;
336}
337
338int RenderScrollbar::minimumThumbLength()
339{
340    RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart);
341    if (!partRenderer)
342        return 0;
343    partRenderer->layout();
344    return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height();
345}
346
347float RenderScrollbar::opacity()
348{
349    RenderScrollbarPart* partRenderer = m_parts.get(ScrollbarBGPart);
350    if (!partRenderer)
351        return 1;
352
353    return partRenderer->style().opacity();
354}
355
356}
357