1/*
2 * Copyright (C) 2008, 2009, 2013, 2015 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
39Ref<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())
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() = default;
70
71RenderBox* RenderScrollbar::owningRenderer() const
72{
73 if (m_owningFrame) {
74 RenderWidget* currentRenderer = m_owningFrame->ownerRenderer();
75 return currentRenderer;
76 }
77 ASSERT(m_ownerElement);
78 if (m_ownerElement->renderer())
79 return &m_ownerElement->renderer()->enclosingBox();
80 return nullptr;
81}
82
83void RenderScrollbar::setParent(ScrollView* parent)
84{
85 Scrollbar::setParent(parent);
86 if (!parent)
87 m_parts.clear();
88}
89
90void RenderScrollbar::setEnabled(bool e)
91{
92 bool wasEnabled = enabled();
93 Scrollbar::setEnabled(e);
94 if (wasEnabled != e)
95 updateScrollbarParts();
96}
97
98void RenderScrollbar::styleChanged()
99{
100 updateScrollbarParts();
101}
102
103void RenderScrollbar::paint(GraphicsContext& context, const IntRect& damageRect, Widget::SecurityOriginPaintPolicy)
104{
105 if (context.invalidatingControlTints()) {
106 updateScrollbarParts();
107 return;
108 }
109 Scrollbar::paint(context, damageRect);
110}
111
112void RenderScrollbar::setHoveredPart(ScrollbarPart part)
113{
114 if (part == m_hoveredPart)
115 return;
116
117 ScrollbarPart oldPart = m_hoveredPart;
118 m_hoveredPart = part;
119
120 updateScrollbarPart(oldPart);
121 updateScrollbarPart(m_hoveredPart);
122
123 updateScrollbarPart(ScrollbarBGPart);
124 updateScrollbarPart(TrackBGPart);
125}
126
127void RenderScrollbar::setPressedPart(ScrollbarPart part)
128{
129 ScrollbarPart oldPart = m_pressedPart;
130 Scrollbar::setPressedPart(part);
131
132 updateScrollbarPart(oldPart);
133 updateScrollbarPart(part);
134
135 updateScrollbarPart(ScrollbarBGPart);
136 updateScrollbarPart(TrackBGPart);
137}
138
139std::unique_ptr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId)
140{
141 if (!owningRenderer())
142 return 0;
143
144 std::unique_ptr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), &owningRenderer()->style());
145 // Scrollbars for root frames should always have background color
146 // unless explicitly specified as transparent. So we force it.
147 // This is because WebKit assumes scrollbar to be always painted and missing background
148 // causes visual artifact like non-repainted dirty region.
149 if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground())
150 result->setBackgroundColor(Color::white);
151
152 return result;
153}
154
155void RenderScrollbar::updateScrollbarParts()
156{
157 updateScrollbarPart(ScrollbarBGPart);
158 updateScrollbarPart(BackButtonStartPart);
159 updateScrollbarPart(ForwardButtonStartPart);
160 updateScrollbarPart(BackTrackPart);
161 updateScrollbarPart(ThumbPart);
162 updateScrollbarPart(ForwardTrackPart);
163 updateScrollbarPart(BackButtonEndPart);
164 updateScrollbarPart(ForwardButtonEndPart);
165 updateScrollbarPart(TrackBGPart);
166
167 // See if the scrollbar's thickness changed. If so, we need to mark our owning object as needing a layout.
168 bool isHorizontal = orientation() == HorizontalScrollbar;
169 int oldThickness = isHorizontal ? height() : width();
170 int newThickness = 0;
171 RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart);
172 if (part) {
173 part->layout();
174 newThickness = isHorizontal ? part->height() : part->width();
175 }
176
177 if (newThickness != oldThickness) {
178 setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height())));
179 if (RenderBox* box = owningRenderer())
180 box->setChildNeedsLayout();
181 }
182}
183
184static PseudoId pseudoForScrollbarPart(ScrollbarPart part)
185{
186 switch (part) {
187 case BackButtonStartPart:
188 case ForwardButtonStartPart:
189 case BackButtonEndPart:
190 case ForwardButtonEndPart:
191 return PseudoId::ScrollbarButton;
192 case BackTrackPart:
193 case ForwardTrackPart:
194 return PseudoId::ScrollbarTrackPiece;
195 case ThumbPart:
196 return PseudoId::ScrollbarThumb;
197 case TrackBGPart:
198 return PseudoId::ScrollbarTrack;
199 case ScrollbarBGPart:
200 return PseudoId::Scrollbar;
201 case NoPart:
202 case AllParts:
203 break;
204 }
205 ASSERT_NOT_REACHED();
206 return PseudoId::Scrollbar;
207}
208
209void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType)
210{
211 if (partType == NoPart)
212 return;
213
214 std::unique_ptr<RenderStyle> partStyle = getScrollbarPseudoStyle(partType, pseudoForScrollbarPart(partType));
215 bool needRenderer = partStyle && partStyle->display() != DisplayType::None;
216
217 if (needRenderer && partStyle->display() != DisplayType::Block) {
218 // See if we are a button that should not be visible according to OS settings.
219 ScrollbarButtonsPlacement buttonsPlacement = theme().buttonsPlacement();
220 switch (partType) {
221 case BackButtonStartPart:
222 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart ||
223 buttonsPlacement == ScrollbarButtonsDoubleBoth);
224 break;
225 case ForwardButtonStartPart:
226 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth);
227 break;
228 case BackButtonEndPart:
229 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth);
230 break;
231 case ForwardButtonEndPart:
232 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd ||
233 buttonsPlacement == ScrollbarButtonsDoubleBoth);
234 break;
235 default:
236 break;
237 }
238 }
239
240 if (!needRenderer) {
241 m_parts.remove(partType);
242 return;
243 }
244
245 if (auto& partRendererSlot = m_parts.add(partType, nullptr).iterator->value)
246 partRendererSlot->setStyle(WTFMove(*partStyle));
247 else {
248 partRendererSlot = createRenderer<RenderScrollbarPart>(owningRenderer()->document(), WTFMove(*partStyle), this, partType);
249 partRendererSlot->initializeStyle();
250 }
251}
252
253void RenderScrollbar::paintPart(GraphicsContext& graphicsContext, ScrollbarPart partType, const IntRect& rect)
254{
255 RenderScrollbarPart* partRenderer = m_parts.get(partType);
256 if (!partRenderer)
257 return;
258 partRenderer->paintIntoRect(graphicsContext, location(), rect);
259}
260
261IntRect RenderScrollbar::buttonRect(ScrollbarPart partType)
262{
263 RenderScrollbarPart* partRenderer = m_parts.get(partType);
264 if (!partRenderer)
265 return IntRect();
266
267 partRenderer->layout();
268
269 bool isHorizontal = orientation() == HorizontalScrollbar;
270 IntSize pixelSnappedIntSize = snappedIntRect(partRenderer->frameRect()).size();
271 if (partType == BackButtonStartPart)
272 return IntRect(location(), IntSize(isHorizontal ? pixelSnappedIntSize.width() : width(), isHorizontal ? height() : pixelSnappedIntSize.height()));
273 if (partType == ForwardButtonEndPart)
274 return IntRect(isHorizontal ? x() + width() - pixelSnappedIntSize.width() : x(), isHorizontal ? y() : y() + height() - pixelSnappedIntSize.height(),
275 isHorizontal ? pixelSnappedIntSize.width() : width(),
276 isHorizontal ? height() : pixelSnappedIntSize.height());
277
278 if (partType == ForwardButtonStartPart) {
279 IntRect previousButton = buttonRect(BackButtonStartPart);
280 return IntRect(isHorizontal ? x() + previousButton.width() : x(),
281 isHorizontal ? y() : y() + previousButton.height(),
282 isHorizontal ? pixelSnappedIntSize.width() : width(),
283 isHorizontal ? height() : pixelSnappedIntSize.height());
284 }
285
286 IntRect followingButton = buttonRect(ForwardButtonEndPart);
287 return IntRect(isHorizontal ? x() + width() - followingButton.width() - pixelSnappedIntSize.width() : x(),
288 isHorizontal ? y() : y() + height() - followingButton.height() - pixelSnappedIntSize.height(),
289 isHorizontal ? pixelSnappedIntSize.width() : width(),
290 isHorizontal ? height() : pixelSnappedIntSize.height());
291}
292
293IntRect RenderScrollbar::trackRect(int startLength, int endLength)
294{
295 RenderScrollbarPart* part = m_parts.get(TrackBGPart);
296 if (part)
297 part->layout();
298
299 if (orientation() == HorizontalScrollbar) {
300 int marginLeft = part ? static_cast<int>(part->marginLeft()) : 0;
301 int marginRight = part ? static_cast<int>(part->marginRight()) : 0;
302 startLength += marginLeft;
303 endLength += marginRight;
304 int totalLength = startLength + endLength;
305 return IntRect(x() + startLength, y(), width() - totalLength, height());
306 }
307
308 int marginTop = part ? static_cast<int>(part->marginTop()) : 0;
309 int marginBottom = part ? static_cast<int>(part->marginBottom()) : 0;
310 startLength += marginTop;
311 endLength += marginBottom;
312 int totalLength = startLength + endLength;
313
314 return IntRect(x(), y() + startLength, width(), height() - totalLength);
315}
316
317IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect)
318{
319 RenderScrollbarPart* partRenderer = m_parts.get(partType);
320 if (!partRenderer)
321 return oldRect;
322
323 partRenderer->layout();
324
325 IntRect rect = oldRect;
326 if (orientation() == HorizontalScrollbar) {
327 rect.setX(rect.x() + partRenderer->marginLeft());
328 rect.setWidth(rect.width() - partRenderer->horizontalMarginExtent());
329 } else {
330 rect.setY(rect.y() + partRenderer->marginTop());
331 rect.setHeight(rect.height() - partRenderer->verticalMarginExtent());
332 }
333 return rect;
334}
335
336int RenderScrollbar::minimumThumbLength()
337{
338 RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart);
339 if (!partRenderer)
340 return 0;
341 partRenderer->layout();
342 return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height();
343}
344
345float RenderScrollbar::opacity()
346{
347 RenderScrollbarPart* partRenderer = m_parts.get(ScrollbarBGPart);
348 if (!partRenderer)
349 return 1;
350
351 return partRenderer->style().opacity();
352}
353
354}
355