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 37 namespace WebCore { 38 39 PassRefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, LocalFrame* owningFrame) 40 { 41 return adoptRef(new RenderScrollbar(scrollableArea, orientation, ownerNode, owningFrame)); 42 } 43 44 RenderScrollbar::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 69 RenderScrollbar::~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 82 RenderBox* 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 91 void 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 100 void RenderScrollbar::setEnabled(bool e) 101 { 102 bool wasEnabled = enabled(); 103 Scrollbar::setEnabled(e); 104 if (wasEnabled != e) 105 updateScrollbarParts(); 106 } 107 108 void RenderScrollbar::styleChanged() 109 { 110 updateScrollbarParts(); 111 } 112 113 void RenderScrollbar::paint(GraphicsContext* context, const IntRect& damageRect) 114 { 115 if (context->updatingControlTints()) { 116 updateScrollbarParts(); 117 return; 118 } 119 Scrollbar::paint(context, damageRect); 120 } 121 122 void RenderScrollbar::setHoveredPart(ScrollbarPart part) 123 { 124 if (part == m_hoveredPart) 125 return; 126 127 ScrollbarPart oldPart = m_hoveredPart; 128 m_hoveredPart = part; 129 130 updateScrollbarPart(oldPart); 131 updateScrollbarPart(m_hoveredPart); 132 133 updateScrollbarPart(ScrollbarBGPart); 134 updateScrollbarPart(TrackBGPart); 135 } 136 137 void RenderScrollbar::setPressedPart(ScrollbarPart part) 138 { 139 ScrollbarPart oldPart = m_pressedPart; 140 Scrollbar::setPressedPart(part); 141 142 updateScrollbarPart(oldPart); 143 updateScrollbarPart(part); 144 145 updateScrollbarPart(ScrollbarBGPart); 146 updateScrollbarPart(TrackBGPart); 147 } 148 149 PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId) 150 { 151 if (!owningRenderer()) 152 return nullptr; 153 154 RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), owningRenderer()->style()); 155 // Scrollbars for root frames should always have background color 156 // unless explicitly specified as transparent. So we force it. 157 // This is because WebKit assumes scrollbar to be always painted and missing background 158 // causes visual artifact like non-repainted dirty region. 159 if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground()) 160 result->setBackgroundColor(StyleColor(Color::white)); 161 162 return result; 163 } 164 165 void RenderScrollbar::updateScrollbarParts(bool destroy) 166 { 167 updateScrollbarPart(ScrollbarBGPart, destroy); 168 updateScrollbarPart(BackButtonStartPart, destroy); 169 updateScrollbarPart(ForwardButtonStartPart, destroy); 170 updateScrollbarPart(BackTrackPart, destroy); 171 updateScrollbarPart(ThumbPart, destroy); 172 updateScrollbarPart(ForwardTrackPart, destroy); 173 updateScrollbarPart(BackButtonEndPart, destroy); 174 updateScrollbarPart(ForwardButtonEndPart, destroy); 175 updateScrollbarPart(TrackBGPart, destroy); 176 177 if (destroy) 178 return; 179 180 // See if the scrollbar's thickness changed. If so, we need to mark our owning object as needing a layout. 181 bool isHorizontal = orientation() == HorizontalScrollbar; 182 int oldThickness = isHorizontal ? height() : width(); 183 int newThickness = 0; 184 RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart); 185 if (part) { 186 part->layout(); 187 newThickness = isHorizontal ? part->height() : part->width(); 188 } 189 190 if (newThickness != oldThickness) { 191 setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height()))); 192 if (RenderBox* box = owningRenderer()) 193 box->setChildNeedsLayout(); 194 } 195 } 196 197 static PseudoId pseudoForScrollbarPart(ScrollbarPart part) 198 { 199 switch (part) { 200 case BackButtonStartPart: 201 case ForwardButtonStartPart: 202 case BackButtonEndPart: 203 case ForwardButtonEndPart: 204 return SCROLLBAR_BUTTON; 205 case BackTrackPart: 206 case ForwardTrackPart: 207 return SCROLLBAR_TRACK_PIECE; 208 case ThumbPart: 209 return SCROLLBAR_THUMB; 210 case TrackBGPart: 211 return SCROLLBAR_TRACK; 212 case ScrollbarBGPart: 213 return SCROLLBAR; 214 case NoPart: 215 case AllParts: 216 break; 217 } 218 ASSERT_NOT_REACHED(); 219 return SCROLLBAR; 220 } 221 222 void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy) 223 { 224 if (partType == NoPart) 225 return; 226 227 RefPtr<RenderStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType, pseudoForScrollbarPart(partType)) : PassRefPtr<RenderStyle>(nullptr); 228 229 bool needRenderer = !destroy && partStyle && partStyle->display() != NONE; 230 231 if (needRenderer && partStyle->display() != BLOCK) { 232 // See if we are a button that should not be visible according to OS settings. 233 ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement(); 234 switch (partType) { 235 case BackButtonStartPart: 236 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart || 237 buttonsPlacement == ScrollbarButtonsDoubleBoth); 238 break; 239 case ForwardButtonStartPart: 240 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth); 241 break; 242 case BackButtonEndPart: 243 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth); 244 break; 245 case ForwardButtonEndPart: 246 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd || 247 buttonsPlacement == ScrollbarButtonsDoubleBoth); 248 break; 249 default: 250 break; 251 } 252 } 253 254 RenderScrollbarPart* partRenderer = m_parts.get(partType); 255 if (!partRenderer && needRenderer) { 256 partRenderer = RenderScrollbarPart::createAnonymous(&owningRenderer()->document(), this, partType); 257 m_parts.set(partType, partRenderer); 258 } else if (partRenderer && !needRenderer) { 259 m_parts.remove(partType); 260 partRenderer->destroy(); 261 partRenderer = 0; 262 } 263 264 if (partRenderer) 265 partRenderer->setStyle(partStyle.release()); 266 } 267 268 void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect) 269 { 270 RenderScrollbarPart* partRenderer = m_parts.get(partType); 271 if (!partRenderer) 272 return; 273 partRenderer->paintIntoRect(graphicsContext, location(), rect); 274 } 275 276 IntRect RenderScrollbar::buttonRect(ScrollbarPart partType) 277 { 278 RenderScrollbarPart* partRenderer = m_parts.get(partType); 279 if (!partRenderer) 280 return IntRect(); 281 282 partRenderer->layout(); 283 284 bool isHorizontal = orientation() == HorizontalScrollbar; 285 if (partType == BackButtonStartPart) 286 return IntRect(location(), IntSize(isHorizontal ? partRenderer->pixelSnappedWidth() : width(), isHorizontal ? height() : partRenderer->pixelSnappedHeight())); 287 if (partType == ForwardButtonEndPart) 288 return IntRect(isHorizontal ? x() + width() - partRenderer->pixelSnappedWidth() : x(), 289 isHorizontal ? y() : y() + height() - partRenderer->pixelSnappedHeight(), 290 isHorizontal ? partRenderer->pixelSnappedWidth() : width(), 291 isHorizontal ? height() : partRenderer->pixelSnappedHeight()); 292 293 if (partType == ForwardButtonStartPart) { 294 IntRect previousButton = buttonRect(BackButtonStartPart); 295 return IntRect(isHorizontal ? x() + previousButton.width() : x(), 296 isHorizontal ? y() : y() + previousButton.height(), 297 isHorizontal ? partRenderer->pixelSnappedWidth() : width(), 298 isHorizontal ? height() : partRenderer->pixelSnappedHeight()); 299 } 300 301 IntRect followingButton = buttonRect(ForwardButtonEndPart); 302 return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->pixelSnappedWidth() : x(), 303 isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->pixelSnappedHeight(), 304 isHorizontal ? partRenderer->pixelSnappedWidth() : width(), 305 isHorizontal ? height() : partRenderer->pixelSnappedHeight()); 306 } 307 308 IntRect RenderScrollbar::trackRect(int startLength, int endLength) 309 { 310 RenderScrollbarPart* part = m_parts.get(TrackBGPart); 311 if (part) 312 part->layout(); 313 314 if (orientation() == HorizontalScrollbar) { 315 int marginLeft = part ? static_cast<int>(part->marginLeft()) : 0; 316 int marginRight = part ? static_cast<int>(part->marginRight()) : 0; 317 startLength += marginLeft; 318 endLength += marginRight; 319 int totalLength = startLength + endLength; 320 return IntRect(x() + startLength, y(), width() - totalLength, height()); 321 } 322 323 int marginTop = part ? static_cast<int>(part->marginTop()) : 0; 324 int marginBottom = part ? static_cast<int>(part->marginBottom()) : 0; 325 startLength += marginTop; 326 endLength += marginBottom; 327 int totalLength = startLength + endLength; 328 329 return IntRect(x(), y() + startLength, width(), height() - totalLength); 330 } 331 332 IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect) 333 { 334 RenderScrollbarPart* partRenderer = m_parts.get(partType); 335 if (!partRenderer) 336 return oldRect; 337 338 partRenderer->layout(); 339 340 IntRect rect = oldRect; 341 if (orientation() == HorizontalScrollbar) { 342 rect.setX(rect.x() + partRenderer->marginLeft()); 343 rect.setWidth(rect.width() - partRenderer->marginWidth()); 344 } else { 345 rect.setY(rect.y() + partRenderer->marginTop()); 346 rect.setHeight(rect.height() - partRenderer->marginHeight()); 347 } 348 return rect; 349 } 350 351 int RenderScrollbar::minimumThumbLength() 352 { 353 RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart); 354 if (!partRenderer) 355 return 0; 356 partRenderer->layout(); 357 return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height(); 358 } 359 360 } 361