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 blink { 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::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 128 void 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 140 PassRefPtr<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 156 void 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 188 static 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 213 void 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 259 void 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 267 IntRect 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 299 IntRect 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 323 IntRect 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 342 int 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