1/*
2 * Copyright (C) 2008, 2009 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 "core/rendering/RenderScrollbar.h"
28
29#include "core/css/PseudoStyleRequest.h"
30#include "core/frame/FrameView.h"
31#include "core/frame/LocalFrame.h"
32#include "core/rendering/RenderPart.h"
33#include "core/rendering/RenderScrollbarPart.h"
34#include "core/rendering/RenderScrollbarTheme.h"
35#include "platform/graphics/GraphicsContext.h"
36
37namespace blink {
38
39PassRefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, LocalFrame* owningFrame)
40{
41    return adoptRef(new RenderScrollbar(scrollableArea, orientation, ownerNode, owningFrame));
42}
43
44RenderScrollbar::RenderScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, LocalFrame* owningFrame)
45    : Scrollbar(scrollableArea, orientation, RegularScrollbar, RenderScrollbarTheme::renderScrollbarTheme())
46    , m_owner(ownerNode)
47    , m_owningFrame(owningFrame)
48{
49    ASSERT(ownerNode || 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    if (!m_parts.isEmpty()) {
72        // When a scrollbar is detached from its parent (causing all parts removal) and
73        // ready to be destroyed, its destruction can be delayed because of RefPtr
74        // maintained in other classes such as EventHandler (m_lastScrollbarUnderMouse).
75        // Meanwhile, we can have a call to updateScrollbarPart which recreates the
76        // scrollbar part. So, we need to destroy these parts since we don't want them
77        // to call on a destroyed scrollbar. See webkit bug 68009.
78        updateScrollbarParts(true);
79    }
80}
81
82RenderBox* RenderScrollbar::owningRenderer() const
83{
84    if (m_owningFrame) {
85        RenderBox* currentRenderer = m_owningFrame->ownerRenderer();
86        return currentRenderer;
87    }
88    return m_owner && m_owner->renderer() ? m_owner->renderer()->enclosingBox() : 0;
89}
90
91void RenderScrollbar::setParent(Widget* parent)
92{
93    Scrollbar::setParent(parent);
94    if (!parent) {
95        // Destroy all of the scrollbar's RenderBoxes.
96        updateScrollbarParts(true);
97    }
98}
99
100void RenderScrollbar::setEnabled(bool e)
101{
102    bool wasEnabled = enabled();
103    Scrollbar::setEnabled(e);
104    if (wasEnabled != e)
105        updateScrollbarParts();
106}
107
108void RenderScrollbar::styleChanged()
109{
110    updateScrollbarParts();
111}
112
113void RenderScrollbar::setHoveredPart(ScrollbarPart part)
114{
115    if (part == m_hoveredPart)
116        return;
117
118    ScrollbarPart oldPart = m_hoveredPart;
119    m_hoveredPart = part;
120
121    updateScrollbarPart(oldPart);
122    updateScrollbarPart(m_hoveredPart);
123
124    updateScrollbarPart(ScrollbarBGPart);
125    updateScrollbarPart(TrackBGPart);
126}
127
128void RenderScrollbar::setPressedPart(ScrollbarPart part)
129{
130    ScrollbarPart oldPart = m_pressedPart;
131    Scrollbar::setPressedPart(part);
132
133    updateScrollbarPart(oldPart);
134    updateScrollbarPart(part);
135
136    updateScrollbarPart(ScrollbarBGPart);
137    updateScrollbarPart(TrackBGPart);
138}
139
140PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId)
141{
142    if (!owningRenderer())
143        return nullptr;
144
145    RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), owningRenderer()->style());
146    // Scrollbars for root frames should always have background color
147    // unless explicitly specified as transparent. So we force it.
148    // This is because WebKit assumes scrollbar to be always painted and missing background
149    // causes visual artifact like non-paint invalidated dirty region.
150    if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground())
151        result->setBackgroundColor(StyleColor(Color::white));
152
153    return result;
154}
155
156void RenderScrollbar::updateScrollbarParts(bool destroy)
157{
158    updateScrollbarPart(ScrollbarBGPart, destroy);
159    updateScrollbarPart(BackButtonStartPart, destroy);
160    updateScrollbarPart(ForwardButtonStartPart, destroy);
161    updateScrollbarPart(BackTrackPart, destroy);
162    updateScrollbarPart(ThumbPart, destroy);
163    updateScrollbarPart(ForwardTrackPart, destroy);
164    updateScrollbarPart(BackButtonEndPart, destroy);
165    updateScrollbarPart(ForwardButtonEndPart, destroy);
166    updateScrollbarPart(TrackBGPart, destroy);
167
168    if (destroy)
169        return;
170
171    // See if the scrollbar's thickness changed.  If so, we need to mark our owning object as needing a layout.
172    bool isHorizontal = orientation() == HorizontalScrollbar;
173    int oldThickness = isHorizontal ? height() : width();
174    int newThickness = 0;
175    RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart);
176    if (part) {
177        part->layout();
178        newThickness = isHorizontal ? part->height() : part->width();
179    }
180
181    if (newThickness != oldThickness) {
182        setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height())));
183        if (RenderBox* box = owningRenderer())
184            box->setChildNeedsLayout();
185    }
186}
187
188static PseudoId pseudoForScrollbarPart(ScrollbarPart part)
189{
190    switch (part) {
191        case BackButtonStartPart:
192        case ForwardButtonStartPart:
193        case BackButtonEndPart:
194        case ForwardButtonEndPart:
195            return SCROLLBAR_BUTTON;
196        case BackTrackPart:
197        case ForwardTrackPart:
198            return SCROLLBAR_TRACK_PIECE;
199        case ThumbPart:
200            return SCROLLBAR_THUMB;
201        case TrackBGPart:
202            return SCROLLBAR_TRACK;
203        case ScrollbarBGPart:
204            return SCROLLBAR;
205        case NoPart:
206        case AllParts:
207            break;
208    }
209    ASSERT_NOT_REACHED();
210    return SCROLLBAR;
211}
212
213void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy)
214{
215    if (partType == NoPart)
216        return;
217
218    RefPtr<RenderStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType,  pseudoForScrollbarPart(partType)) : PassRefPtr<RenderStyle>(nullptr);
219
220    bool needRenderer = !destroy && partStyle && partStyle->display() != NONE;
221
222    if (needRenderer && partStyle->display() != BLOCK) {
223        // See if we are a button that should not be visible according to OS settings.
224        ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement();
225        switch (partType) {
226            case BackButtonStartPart:
227                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart ||
228                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
229                break;
230            case ForwardButtonStartPart:
231                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth);
232                break;
233            case BackButtonEndPart:
234                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth);
235                break;
236            case ForwardButtonEndPart:
237                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd ||
238                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
239                break;
240            default:
241                break;
242        }
243    }
244
245    RenderScrollbarPart* partRenderer = m_parts.get(partType);
246    if (!partRenderer && needRenderer) {
247        partRenderer = RenderScrollbarPart::createAnonymous(&owningRenderer()->document(), this, partType);
248        m_parts.set(partType, partRenderer);
249    } else if (partRenderer && !needRenderer) {
250        m_parts.remove(partType);
251        partRenderer->destroy();
252        partRenderer = 0;
253    }
254
255    if (partRenderer)
256        partRenderer->setStyle(partStyle.release());
257}
258
259void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect)
260{
261    RenderScrollbarPart* partRenderer = m_parts.get(partType);
262    if (!partRenderer)
263        return;
264    partRenderer->paintIntoRect(graphicsContext, location(), rect);
265}
266
267IntRect RenderScrollbar::buttonRect(ScrollbarPart partType)
268{
269    RenderScrollbarPart* partRenderer = m_parts.get(partType);
270    if (!partRenderer)
271        return IntRect();
272
273    partRenderer->layout();
274
275    bool isHorizontal = orientation() == HorizontalScrollbar;
276    if (partType == BackButtonStartPart)
277        return IntRect(location(), IntSize(isHorizontal ? partRenderer->pixelSnappedWidth() : width(), isHorizontal ? height() : partRenderer->pixelSnappedHeight()));
278    if (partType == ForwardButtonEndPart)
279        return IntRect(isHorizontal ? x() + width() - partRenderer->pixelSnappedWidth() : x(),
280                       isHorizontal ? y() : y() + height() - partRenderer->pixelSnappedHeight(),
281                       isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
282                       isHorizontal ? height() : partRenderer->pixelSnappedHeight());
283
284    if (partType == ForwardButtonStartPart) {
285        IntRect previousButton = buttonRect(BackButtonStartPart);
286        return IntRect(isHorizontal ? x() + previousButton.width() : x(),
287                       isHorizontal ? y() : y() + previousButton.height(),
288                       isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
289                       isHorizontal ? height() : partRenderer->pixelSnappedHeight());
290    }
291
292    IntRect followingButton = buttonRect(ForwardButtonEndPart);
293    return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->pixelSnappedWidth() : x(),
294                   isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->pixelSnappedHeight(),
295                   isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
296                   isHorizontal ? height() : partRenderer->pixelSnappedHeight());
297}
298
299IntRect RenderScrollbar::trackRect(int startLength, int endLength)
300{
301    RenderScrollbarPart* part = m_parts.get(TrackBGPart);
302    if (part)
303        part->layout();
304
305    if (orientation() == HorizontalScrollbar) {
306        int marginLeft = part ? static_cast<int>(part->marginLeft()) : 0;
307        int marginRight = part ? static_cast<int>(part->marginRight()) : 0;
308        startLength += marginLeft;
309        endLength += marginRight;
310        int totalLength = startLength + endLength;
311        return IntRect(x() + startLength, y(), width() - totalLength, height());
312    }
313
314    int marginTop = part ? static_cast<int>(part->marginTop()) : 0;
315    int marginBottom = part ? static_cast<int>(part->marginBottom()) : 0;
316    startLength += marginTop;
317    endLength += marginBottom;
318    int totalLength = startLength + endLength;
319
320    return IntRect(x(), y() + startLength, width(), height() - totalLength);
321}
322
323IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect)
324{
325    RenderScrollbarPart* partRenderer = m_parts.get(partType);
326    if (!partRenderer)
327        return oldRect;
328
329    partRenderer->layout();
330
331    IntRect rect = oldRect;
332    if (orientation() == HorizontalScrollbar) {
333        rect.setX(rect.x() + partRenderer->marginLeft());
334        rect.setWidth(rect.width() - partRenderer->marginWidth());
335    } else {
336        rect.setY(rect.y() + partRenderer->marginTop());
337        rect.setHeight(rect.height() - partRenderer->marginHeight());
338    }
339    return rect;
340}
341
342int RenderScrollbar::minimumThumbLength()
343{
344    RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart);
345    if (!partRenderer)
346        return 0;
347    partRenderer->layout();
348    return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height();
349}
350
351}
352