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