1 /* 2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. 3 * 4 * Portions are Copyright (C) 1998 Netscape Communications Corporation. 5 * 6 * Other contributors: 7 * Robert O'Callahan <roc+@cs.cmu.edu> 8 * David Baron <dbaron (at) fas.harvard.edu> 9 * Christian Biesinger <cbiesinger (at) web.de> 10 * Randall Jesup <rjesup (at) wgate.com> 11 * Roland Mainz <roland.mainz (at) informatik.med.uni-giessen.de> 12 * Josh Soref <timeless (at) mac.com> 13 * Boris Zbarsky <bzbarsky (at) mit.edu> 14 * 15 * This library is free software; you can redistribute it and/or 16 * modify it under the terms of the GNU Lesser General Public 17 * License as published by the Free Software Foundation; either 18 * version 2.1 of the License, or (at your option) any later version. 19 * 20 * This library is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 * Lesser General Public License for more details. 24 * 25 * You should have received a copy of the GNU Lesser General Public 26 * License along with this library; if not, write to the Free Software 27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 28 * 29 * Alternatively, the contents of this file may be used under the terms 30 * of either the Mozilla Public License Version 1.1, found at 31 * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public 32 * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html 33 * (the "GPL"), in which case the provisions of the MPL or the GPL are 34 * applicable instead of those above. If you wish to allow use of your 35 * version of this file only under the terms of one of those two 36 * licenses (the MPL or the GPL) and not to allow others to use your 37 * version of this file under the LGPL, indicate your decision by 38 * deletingthe provisions above and replace them with the notice and 39 * other provisions required by the MPL or the GPL, as the case may be. 40 * If you do not delete the provisions above, a recipient may use your 41 * version of this file under any of the LGPL, the MPL or the GPL. 42 */ 43 44 #include "config.h" 45 #include "core/rendering/RenderLayer.h" 46 47 #include "core/css/PseudoStyleRequest.h" 48 #include "core/dom/shadow/ShadowRoot.h" 49 #include "core/editing/FrameSelection.h" 50 #include "core/html/HTMLFrameOwnerElement.h" 51 #include "core/inspector/InspectorInstrumentation.h" 52 #include "core/page/EventHandler.h" 53 #include "core/page/FocusController.h" 54 #include "core/frame/Frame.h" 55 #include "core/frame/FrameView.h" 56 #include "core/page/Page.h" 57 #include "core/page/scrolling/ScrollingCoordinator.h" 58 #include "core/rendering/CompositedLayerMapping.h" 59 #include "core/rendering/RenderGeometryMap.h" 60 #include "core/rendering/RenderLayerCompositor.h" 61 #include "core/rendering/RenderScrollbar.h" 62 #include "core/rendering/RenderScrollbarPart.h" 63 #include "core/rendering/RenderView.h" 64 #include "platform/PlatformGestureEvent.h" 65 #include "platform/PlatformMouseEvent.h" 66 #include "platform/graphics/GraphicsContextStateSaver.h" 67 #include "platform/graphics/GraphicsLayer.h" 68 #include "platform/scroll/ScrollAnimator.h" 69 #include "platform/scroll/ScrollbarTheme.h" 70 #include "public/platform/Platform.h" 71 72 namespace WebCore { 73 74 const int ResizerControlExpandRatioForTouch = 2; 75 76 RenderLayerScrollableArea::RenderLayerScrollableArea(RenderBox* box) 77 : m_box(box) 78 , m_inResizeMode(false) 79 , m_scrollDimensionsDirty(true) 80 , m_inOverflowRelayout(false) 81 , m_needsCompositedScrolling(false) 82 , m_willUseCompositedScrollingHasBeenRecorded(false) 83 , m_isScrollableAreaHasBeenRecorded(false) 84 , m_forceNeedsCompositedScrolling(DoNotForceCompositedScrolling) 85 , m_scrollCorner(0) 86 , m_resizer(0) 87 { 88 ScrollableArea::setConstrainsScrollingToContentEdge(false); 89 90 Node* node = m_box->node(); 91 if (node && node->isElementNode()) { 92 // We save and restore only the scrollOffset as the other scroll values are recalculated. 93 Element* element = toElement(node); 94 m_scrollOffset = element->savedLayerScrollOffset(); 95 if (!m_scrollOffset.isZero()) 96 scrollAnimator()->setCurrentPosition(FloatPoint(m_scrollOffset.width(), m_scrollOffset.height())); 97 element->setSavedLayerScrollOffset(IntSize()); 98 } 99 100 updateResizerAreaSet(); 101 } 102 103 RenderLayerScrollableArea::~RenderLayerScrollableArea() 104 { 105 if (inResizeMode() && !m_box->documentBeingDestroyed()) { 106 if (Frame* frame = m_box->frame()) 107 frame->eventHandler().resizeScrollableAreaDestroyed(); 108 } 109 110 if (Frame* frame = m_box->frame()) { 111 if (FrameView* frameView = frame->view()) { 112 frameView->removeScrollableArea(this); 113 } 114 } 115 116 if (m_box->frame() && m_box->frame()->page()) { 117 if (ScrollingCoordinator* scrollingCoordinator = m_box->frame()->page()->scrollingCoordinator()) 118 scrollingCoordinator->willDestroyScrollableArea(this); 119 } 120 121 if (!m_box->documentBeingDestroyed()) { 122 Node* node = m_box->node(); 123 if (node && node->isElementNode()) 124 toElement(node)->setSavedLayerScrollOffset(m_scrollOffset); 125 } 126 127 if (Frame* frame = m_box->frame()) { 128 if (FrameView* frameView = frame->view()) 129 frameView->removeResizerArea(m_box); 130 } 131 132 destroyScrollbar(HorizontalScrollbar); 133 destroyScrollbar(VerticalScrollbar); 134 135 if (m_scrollCorner) 136 m_scrollCorner->destroy(); 137 if (m_resizer) 138 m_resizer->destroy(); 139 } 140 141 ScrollableArea* RenderLayerScrollableArea::enclosingScrollableArea() const 142 { 143 if (RenderBox* enclosingScrollableBox = m_box->enclosingScrollableBox()) 144 return enclosingScrollableBox->layer()->scrollableArea(); 145 146 // FIXME: We should return the frame view here (or possibly an ancestor frame view, 147 // if the frame view isn't scrollable. 148 return 0; 149 } 150 151 GraphicsLayer* RenderLayerScrollableArea::layerForScrolling() const 152 { 153 return m_box->hasCompositedLayerMapping() ? m_box->compositedLayerMapping()->scrollingContentsLayer() : 0; 154 } 155 156 GraphicsLayer* RenderLayerScrollableArea::layerForHorizontalScrollbar() const 157 { 158 return m_box->hasCompositedLayerMapping() ? m_box->compositedLayerMapping()->layerForHorizontalScrollbar() : 0; 159 } 160 161 GraphicsLayer* RenderLayerScrollableArea::layerForVerticalScrollbar() const 162 { 163 return m_box->hasCompositedLayerMapping() ? m_box->compositedLayerMapping()->layerForVerticalScrollbar() : 0; 164 } 165 166 GraphicsLayer* RenderLayerScrollableArea::layerForScrollCorner() const 167 { 168 return m_box->hasCompositedLayerMapping() ? m_box->compositedLayerMapping()->layerForScrollCorner() : 0; 169 } 170 171 void RenderLayerScrollableArea::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) 172 { 173 if (scrollbar == m_vBar.get()) { 174 if (GraphicsLayer* layer = layerForVerticalScrollbar()) { 175 layer->setNeedsDisplayInRect(rect); 176 return; 177 } 178 } else { 179 if (GraphicsLayer* layer = layerForHorizontalScrollbar()) { 180 layer->setNeedsDisplayInRect(rect); 181 return; 182 } 183 } 184 185 IntRect scrollRect = rect; 186 // If we are not yet inserted into the tree, there is no need to repaint. 187 if (!m_box->parent()) 188 return; 189 190 if (scrollbar == m_vBar.get()) 191 scrollRect.move(verticalScrollbarStart(0, m_box->width()), m_box->borderTop()); 192 else 193 scrollRect.move(horizontalScrollbarStart(0), m_box->height() - m_box->borderBottom() - scrollbar->height()); 194 m_box->repaintRectangle(scrollRect); 195 } 196 197 void RenderLayerScrollableArea::invalidateScrollCornerRect(const IntRect& rect) 198 { 199 if (GraphicsLayer* layer = layerForScrollCorner()) { 200 layer->setNeedsDisplayInRect(rect); 201 return; 202 } 203 204 if (m_scrollCorner) 205 m_scrollCorner->repaintRectangle(rect); 206 if (m_resizer) 207 m_resizer->repaintRectangle(rect); 208 } 209 210 bool RenderLayerScrollableArea::isActive() const 211 { 212 Page* page = m_box->frame()->page(); 213 return page && page->focusController().isActive(); 214 } 215 216 bool RenderLayerScrollableArea::isScrollCornerVisible() const 217 { 218 return !scrollCornerRect().isEmpty(); 219 } 220 221 static int cornerStart(const RenderStyle* style, int minX, int maxX, int thickness) 222 { 223 if (style->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) 224 return minX + style->borderLeftWidth(); 225 return maxX - thickness - style->borderRightWidth(); 226 } 227 228 static IntRect cornerRect(const RenderStyle* style, const Scrollbar* horizontalScrollbar, const Scrollbar* verticalScrollbar, const IntRect& bounds) 229 { 230 int horizontalThickness; 231 int verticalThickness; 232 if (!verticalScrollbar && !horizontalScrollbar) { 233 // FIXME: This isn't right. We need to know the thickness of custom scrollbars 234 // even when they don't exist in order to set the resizer square size properly. 235 horizontalThickness = ScrollbarTheme::theme()->scrollbarThickness(); 236 verticalThickness = horizontalThickness; 237 } else if (verticalScrollbar && !horizontalScrollbar) { 238 horizontalThickness = verticalScrollbar->width(); 239 verticalThickness = horizontalThickness; 240 } else if (horizontalScrollbar && !verticalScrollbar) { 241 verticalThickness = horizontalScrollbar->height(); 242 horizontalThickness = verticalThickness; 243 } else { 244 horizontalThickness = verticalScrollbar->width(); 245 verticalThickness = horizontalScrollbar->height(); 246 } 247 return IntRect(cornerStart(style, bounds.x(), bounds.maxX(), horizontalThickness), 248 bounds.maxY() - verticalThickness - style->borderBottomWidth(), 249 horizontalThickness, verticalThickness); 250 } 251 252 253 IntRect RenderLayerScrollableArea::scrollCornerRect() const 254 { 255 // We have a scrollbar corner when a scrollbar is visible and not filling the entire length of the box. 256 // This happens when: 257 // (a) A resizer is present and at least one scrollbar is present 258 // (b) Both scrollbars are present. 259 bool hasHorizontalBar = horizontalScrollbar(); 260 bool hasVerticalBar = verticalScrollbar(); 261 bool hasResizer = m_box->style()->resize() != RESIZE_NONE; 262 if ((hasHorizontalBar && hasVerticalBar) || (hasResizer && (hasHorizontalBar || hasVerticalBar))) 263 return cornerRect(m_box->style(), horizontalScrollbar(), verticalScrollbar(), m_box->pixelSnappedBorderBoxRect()); 264 return IntRect(); 265 } 266 267 IntRect RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const 268 { 269 RenderView* view = m_box->view(); 270 if (!view) 271 return scrollbarRect; 272 273 IntRect rect = scrollbarRect; 274 rect.move(scrollbarOffset(scrollbar)); 275 276 return view->frameView()->convertFromRenderer(m_box, rect); 277 } 278 279 IntRect RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const 280 { 281 RenderView* view = m_box->view(); 282 if (!view) 283 return parentRect; 284 285 IntRect rect = view->frameView()->convertToRenderer(m_box, parentRect); 286 rect.move(-scrollbarOffset(scrollbar)); 287 return rect; 288 } 289 290 IntPoint RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const 291 { 292 RenderView* view = m_box->view(); 293 if (!view) 294 return scrollbarPoint; 295 296 IntPoint point = scrollbarPoint; 297 point.move(scrollbarOffset(scrollbar)); 298 return view->frameView()->convertFromRenderer(m_box, point); 299 } 300 301 IntPoint RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const 302 { 303 RenderView* view = m_box->view(); 304 if (!view) 305 return parentPoint; 306 307 IntPoint point = view->frameView()->convertToRenderer(m_box, parentPoint); 308 309 point.move(-scrollbarOffset(scrollbar)); 310 return point; 311 } 312 313 int RenderLayerScrollableArea::scrollSize(ScrollbarOrientation orientation) const 314 { 315 IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition(); 316 return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height(); 317 } 318 319 void RenderLayerScrollableArea::setScrollOffset(const IntPoint& newScrollOffset) 320 { 321 if (!m_box->isMarquee()) { 322 // Ensure that the dimensions will be computed if they need to be (for overflow:hidden blocks). 323 if (m_scrollDimensionsDirty) 324 computeScrollDimensions(); 325 } 326 327 if (scrollOffset() == toIntSize(newScrollOffset)) 328 return; 329 330 setScrollOffset(toIntSize(newScrollOffset)); 331 332 Frame* frame = m_box->frame(); 333 InspectorInstrumentation::willScrollLayer(m_box); 334 335 RenderView* view = m_box->view(); 336 337 // We should have a RenderView if we're trying to scroll. 338 ASSERT(view); 339 340 // Update the positions of our child layers (if needed as only fixed layers should be impacted by a scroll). 341 // We don't update compositing layers, because we need to do a deep update from the compositing ancestor. 342 bool inLayout = view ? view->frameView()->isInLayout() : false; 343 if (!inLayout) { 344 // If we're in the middle of layout, we'll just update layers once layout has finished. 345 layer()->updateLayerPositionsAfterOverflowScroll(); 346 if (view) { 347 // Update regions, scrolling may change the clip of a particular region. 348 view->frameView()->updateAnnotatedRegions(); 349 view->updateWidgetPositions(); 350 } 351 352 updateCompositingLayersAfterScroll(); 353 } 354 355 RenderLayerModelObject* repaintContainer = m_box->containerForRepaint(); 356 if (frame) { 357 // The caret rect needs to be invalidated after scrolling 358 frame->selection().setCaretRectNeedsUpdate(); 359 360 FloatQuad quadForFakeMouseMoveEvent = FloatQuad(layer()->repainter().repaintRect()); 361 if (repaintContainer) 362 quadForFakeMouseMoveEvent = repaintContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent); 363 frame->eventHandler().dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent); 364 } 365 366 bool requiresRepaint = true; 367 368 if (m_box->view()->compositor()->inCompositingMode()) { 369 bool onlyScrolledCompositedLayers = scrollsOverflow() 370 && !layer()->hasVisibleNonLayerContent() 371 && !layer()->hasNonCompositedChild() 372 && !layer()->hasBlockSelectionGapBounds() 373 && !m_box->isMarquee(); 374 375 if (usesCompositedScrolling() || onlyScrolledCompositedLayers) 376 requiresRepaint = false; 377 } 378 379 // Just schedule a full repaint of our object. 380 if (view && requiresRepaint) 381 m_box->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(layer()->repainter().repaintRect())); 382 383 // Schedule the scroll DOM event. 384 if (m_box->node()) 385 m_box->node()->document().enqueueScrollEventForNode(m_box->node()); 386 387 InspectorInstrumentation::didScrollLayer(m_box); 388 } 389 390 IntPoint RenderLayerScrollableArea::scrollPosition() const 391 { 392 return IntPoint(m_scrollOffset); 393 } 394 395 IntPoint RenderLayerScrollableArea::minimumScrollPosition() const 396 { 397 return -scrollOrigin(); 398 } 399 400 IntPoint RenderLayerScrollableArea::maximumScrollPosition() const 401 { 402 if (!m_box->hasOverflowClip()) 403 return -scrollOrigin(); 404 405 return -scrollOrigin() + enclosingIntRect(m_overflowRect).size() - enclosingIntRect(m_box->clientBoxRect()).size(); 406 } 407 408 IntRect RenderLayerScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const 409 { 410 int verticalScrollbarWidth = 0; 411 int horizontalScrollbarHeight = 0; 412 if (scrollbarInclusion == IncludeScrollbars) { 413 verticalScrollbarWidth = (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) ? verticalScrollbar()->width() : 0; 414 horizontalScrollbarHeight = (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar()) ? horizontalScrollbar()->height() : 0; 415 } 416 417 return IntRect(IntPoint(scrollXOffset(), scrollYOffset()), 418 IntSize(max(0, layer()->size().width() - verticalScrollbarWidth), max(0, layer()->size().height() - horizontalScrollbarHeight))); 419 } 420 421 int RenderLayerScrollableArea::visibleHeight() const 422 { 423 return layer()->size().height(); 424 } 425 426 int RenderLayerScrollableArea::visibleWidth() const 427 { 428 return layer()->size().width(); 429 } 430 431 IntSize RenderLayerScrollableArea::contentsSize() const 432 { 433 return IntSize(scrollWidth(), scrollHeight()); 434 } 435 436 IntSize RenderLayerScrollableArea::overhangAmount() const 437 { 438 return IntSize(); 439 } 440 441 IntPoint RenderLayerScrollableArea::lastKnownMousePosition() const 442 { 443 return m_box->frame() ? m_box->frame()->eventHandler().lastKnownMousePosition() : IntPoint(); 444 } 445 446 bool RenderLayerScrollableArea::shouldSuspendScrollAnimations() const 447 { 448 RenderView* view = m_box->view(); 449 if (!view) 450 return true; 451 return view->frameView()->shouldSuspendScrollAnimations(); 452 } 453 454 bool RenderLayerScrollableArea::scrollbarsCanBeActive() const 455 { 456 RenderView* view = m_box->view(); 457 if (!view) 458 return false; 459 return view->frameView()->scrollbarsCanBeActive(); 460 } 461 462 IntRect RenderLayerScrollableArea::scrollableAreaBoundingBox() const 463 { 464 return m_box->absoluteBoundingBoxRect(); 465 } 466 467 bool RenderLayerScrollableArea::userInputScrollable(ScrollbarOrientation orientation) const 468 { 469 if (m_box->isIntristicallyScrollable(orientation)) 470 return true; 471 472 EOverflow overflowStyle = (orientation == HorizontalScrollbar) ? 473 m_box->style()->overflowX() : m_box->style()->overflowY(); 474 return (overflowStyle == OSCROLL || overflowStyle == OAUTO || overflowStyle == OOVERLAY); 475 } 476 477 bool RenderLayerScrollableArea::shouldPlaceVerticalScrollbarOnLeft() const 478 { 479 return m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft(); 480 } 481 482 int RenderLayerScrollableArea::pageStep(ScrollbarOrientation orientation) const 483 { 484 int length = (orientation == HorizontalScrollbar) ? 485 m_box->pixelSnappedClientWidth() : m_box->pixelSnappedClientHeight(); 486 int minPageStep = static_cast<float>(length) * ScrollableArea::minFractionToStepWhenPaging(); 487 int pageStep = max(minPageStep, length - ScrollableArea::maxOverlapBetweenPages()); 488 489 return max(pageStep, 1); 490 } 491 492 RenderLayer* RenderLayerScrollableArea::layer() const 493 { 494 return m_box->layer(); 495 } 496 497 int RenderLayerScrollableArea::scrollWidth() const 498 { 499 if (m_scrollDimensionsDirty) 500 const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions(); 501 return snapSizeToPixel(m_overflowRect.width(), m_box->clientLeft() + m_box->x()); 502 } 503 504 int RenderLayerScrollableArea::scrollHeight() const 505 { 506 if (m_scrollDimensionsDirty) 507 const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions(); 508 return snapSizeToPixel(m_overflowRect.height(), m_box->clientTop() + m_box->y()); 509 } 510 511 void RenderLayerScrollableArea::computeScrollDimensions() 512 { 513 m_scrollDimensionsDirty = false; 514 515 m_overflowRect = m_box->layoutOverflowRect(); 516 m_box->flipForWritingMode(m_overflowRect); 517 518 int scrollableLeftOverflow = m_overflowRect.x() - m_box->borderLeft() - (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? m_box->verticalScrollbarWidth() : 0); 519 int scrollableTopOverflow = m_overflowRect.y() - m_box->borderTop(); 520 setScrollOrigin(IntPoint(-scrollableLeftOverflow, -scrollableTopOverflow)); 521 } 522 523 void RenderLayerScrollableArea::scrollToOffset(const IntSize& scrollOffset, ScrollOffsetClamping clamp) 524 { 525 IntSize newScrollOffset = clamp == ScrollOffsetClamped ? clampScrollOffset(scrollOffset) : scrollOffset; 526 if (newScrollOffset != adjustedScrollOffset()) 527 scrollToOffsetWithoutAnimation(-scrollOrigin() + newScrollOffset); 528 } 529 530 void RenderLayerScrollableArea::updateAfterLayout() 531 { 532 // List box parts handle the scrollbars by themselves so we have nothing to do. 533 if (m_box->style()->appearance() == ListboxPart) 534 return; 535 536 m_scrollDimensionsDirty = true; 537 IntSize originalScrollOffset = adjustedScrollOffset(); 538 539 computeScrollDimensions(); 540 541 if (!m_box->isMarquee()) { 542 // Layout may cause us to be at an invalid scroll position. In this case we need 543 // to pull our scroll offsets back to the max (or push them up to the min). 544 IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset()); 545 if (clampedScrollOffset != adjustedScrollOffset()) 546 scrollToOffset(clampedScrollOffset); 547 } 548 549 if (originalScrollOffset != adjustedScrollOffset()) 550 scrollToOffsetWithoutAnimation(-scrollOrigin() + adjustedScrollOffset()); 551 552 bool hasHorizontalOverflow = this->hasHorizontalOverflow(); 553 bool hasVerticalOverflow = this->hasVerticalOverflow(); 554 555 // overflow:scroll should just enable/disable. 556 if (m_box->style()->overflowX() == OSCROLL) 557 horizontalScrollbar()->setEnabled(hasHorizontalOverflow); 558 if (m_box->style()->overflowY() == OSCROLL) 559 verticalScrollbar()->setEnabled(hasVerticalOverflow); 560 561 // overflow:auto may need to lay out again if scrollbars got added/removed. 562 bool autoHorizontalScrollBarChanged = m_box->hasAutoHorizontalScrollbar() && (hasHorizontalScrollbar() != hasHorizontalOverflow); 563 bool autoVerticalScrollBarChanged = m_box->hasAutoVerticalScrollbar() && (hasVerticalScrollbar() != hasVerticalOverflow); 564 565 if (autoHorizontalScrollBarChanged || autoVerticalScrollBarChanged) { 566 if (m_box->hasAutoHorizontalScrollbar()) 567 setHasHorizontalScrollbar(hasHorizontalOverflow); 568 if (m_box->hasAutoVerticalScrollbar()) 569 setHasVerticalScrollbar(hasVerticalOverflow); 570 571 layer()->updateSelfPaintingLayer(); 572 573 // Force an update since we know the scrollbars have changed things. 574 if (m_box->document().hasAnnotatedRegions()) 575 m_box->document().setAnnotatedRegionsDirty(true); 576 577 m_box->repaint(); 578 579 if (m_box->style()->overflowX() == OAUTO || m_box->style()->overflowY() == OAUTO) { 580 if (!m_inOverflowRelayout) { 581 // Our proprietary overflow: overlay value doesn't trigger a layout. 582 m_inOverflowRelayout = true; 583 SubtreeLayoutScope layoutScope(m_box); 584 layoutScope.setNeedsLayout(m_box); 585 if (m_box->isRenderBlock()) { 586 RenderBlock* block = toRenderBlock(m_box); 587 block->scrollbarsChanged(autoHorizontalScrollBarChanged, autoVerticalScrollBarChanged); 588 block->layoutBlock(true); 589 } else { 590 m_box->layout(); 591 } 592 m_inOverflowRelayout = false; 593 } 594 } 595 } 596 597 // Set up the range (and page step/line step). 598 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { 599 int clientWidth = m_box->pixelSnappedClientWidth(); 600 horizontalScrollbar->setProportion(clientWidth, overflowRect().width()); 601 } 602 if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) { 603 int clientHeight = m_box->pixelSnappedClientHeight(); 604 verticalScrollbar->setProportion(clientHeight, overflowRect().height()); 605 } 606 607 updateScrollableAreaSet(hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow()); 608 609 // Composited scrolling may need to be enabled or disabled if the amount of overflow changed. 610 if (m_box->view() && m_box->view()->compositor()->updateLayerCompositingState(m_box->layer())) 611 m_box->view()->compositor()->setCompositingLayersNeedRebuild(); 612 } 613 614 bool RenderLayerScrollableArea::hasHorizontalOverflow() const 615 { 616 ASSERT(!m_scrollDimensionsDirty); 617 618 return scrollWidth() > m_box->pixelSnappedClientWidth(); 619 } 620 621 bool RenderLayerScrollableArea::hasVerticalOverflow() const 622 { 623 ASSERT(!m_scrollDimensionsDirty); 624 625 return scrollHeight() > m_box->pixelSnappedClientHeight(); 626 } 627 628 bool RenderLayerScrollableArea::hasScrollableHorizontalOverflow() const 629 { 630 return hasHorizontalOverflow() && m_box->scrollsOverflowX(); 631 } 632 633 bool RenderLayerScrollableArea::hasScrollableVerticalOverflow() const 634 { 635 return hasVerticalOverflow() && m_box->scrollsOverflowY(); 636 } 637 638 static bool overflowRequiresScrollbar(EOverflow overflow) 639 { 640 return overflow == OSCROLL; 641 } 642 643 static bool overflowDefinesAutomaticScrollbar(EOverflow overflow) 644 { 645 return overflow == OAUTO || overflow == OOVERLAY; 646 } 647 648 void RenderLayerScrollableArea::updateAfterStyleChange(const RenderStyle* oldStyle) 649 { 650 // List box parts handle the scrollbars by themselves so we have nothing to do. 651 if (m_box->style()->appearance() == ListboxPart) 652 return; 653 654 if (!m_scrollDimensionsDirty) 655 updateScrollableAreaSet(hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow()); 656 657 EOverflow overflowX = m_box->style()->overflowX(); 658 EOverflow overflowY = m_box->style()->overflowY(); 659 660 // To avoid doing a relayout in updateScrollbarsAfterLayout, we try to keep any automatic scrollbar that was already present. 661 bool needsHorizontalScrollbar = (hasHorizontalScrollbar() && overflowDefinesAutomaticScrollbar(overflowX)) || overflowRequiresScrollbar(overflowX); 662 bool needsVerticalScrollbar = (hasVerticalScrollbar() && overflowDefinesAutomaticScrollbar(overflowY)) || overflowRequiresScrollbar(overflowY); 663 setHasHorizontalScrollbar(needsHorizontalScrollbar); 664 setHasVerticalScrollbar(needsVerticalScrollbar); 665 666 // With overflow: scroll, scrollbars are always visible but may be disabled. 667 // When switching to another value, we need to re-enable them (see bug 11985). 668 if (needsHorizontalScrollbar && oldStyle && oldStyle->overflowX() == OSCROLL && overflowX != OSCROLL) { 669 ASSERT(hasHorizontalScrollbar()); 670 m_hBar->setEnabled(true); 671 } 672 673 if (needsVerticalScrollbar && oldStyle && oldStyle->overflowY() == OSCROLL && overflowY != OSCROLL) { 674 ASSERT(hasVerticalScrollbar()); 675 m_vBar->setEnabled(true); 676 } 677 678 // FIXME: Need to detect a swap from custom to native scrollbars (and vice versa). 679 if (m_hBar) 680 m_hBar->styleChanged(); 681 if (m_vBar) 682 m_vBar->styleChanged(); 683 684 updateScrollCornerStyle(); 685 updateResizerAreaSet(); 686 updateResizerStyle(); 687 } 688 689 IntSize RenderLayerScrollableArea::clampScrollOffset(const IntSize& scrollOffset) const 690 { 691 int maxX = scrollWidth() - m_box->pixelSnappedClientWidth(); 692 int maxY = scrollHeight() - m_box->pixelSnappedClientHeight(); 693 694 int x = std::max(std::min(scrollOffset.width(), maxX), 0); 695 int y = std::max(std::min(scrollOffset.height(), maxY), 0); 696 return IntSize(x, y); 697 } 698 699 IntRect RenderLayerScrollableArea::rectForHorizontalScrollbar(const IntRect& borderBoxRect) const 700 { 701 if (!m_hBar) 702 return IntRect(); 703 704 const IntRect& scrollCorner = scrollCornerRect(); 705 706 return IntRect(horizontalScrollbarStart(borderBoxRect.x()), 707 borderBoxRect.maxY() - m_box->borderBottom() - m_hBar->height(), 708 borderBoxRect.width() - (m_box->borderLeft() + m_box->borderRight()) - scrollCorner.width(), 709 m_hBar->height()); 710 } 711 712 IntRect RenderLayerScrollableArea::rectForVerticalScrollbar(const IntRect& borderBoxRect) const 713 { 714 if (!m_vBar) 715 return IntRect(); 716 717 const IntRect& scrollCorner = scrollCornerRect(); 718 719 return IntRect(verticalScrollbarStart(borderBoxRect.x(), borderBoxRect.maxX()), 720 borderBoxRect.y() + m_box->borderTop(), 721 m_vBar->width(), 722 borderBoxRect.height() - (m_box->borderTop() + m_box->borderBottom()) - scrollCorner.height()); 723 } 724 725 LayoutUnit RenderLayerScrollableArea::verticalScrollbarStart(int minX, int maxX) const 726 { 727 if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) 728 return minX + m_box->borderLeft(); 729 return maxX - m_box->borderRight() - m_vBar->width(); 730 } 731 732 LayoutUnit RenderLayerScrollableArea::horizontalScrollbarStart(int minX) const 733 { 734 int x = minX + m_box->borderLeft(); 735 if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) 736 x += m_vBar ? m_vBar->width() : resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer).width(); 737 return x; 738 } 739 740 IntSize RenderLayerScrollableArea::scrollbarOffset(const Scrollbar* scrollbar) const 741 { 742 if (scrollbar == m_vBar.get()) 743 return IntSize(verticalScrollbarStart(0, m_box->width()), m_box->borderTop()); 744 745 if (scrollbar == m_hBar.get()) 746 return IntSize(horizontalScrollbarStart(0), m_box->height() - m_box->borderBottom() - scrollbar->height()); 747 748 ASSERT_NOT_REACHED(); 749 return IntSize(); 750 } 751 752 static inline RenderObject* rendererForScrollbar(RenderObject* renderer) 753 { 754 if (Node* node = renderer->node()) { 755 if (ShadowRoot* shadowRoot = node->containingShadowRoot()) { 756 if (shadowRoot->type() == ShadowRoot::UserAgentShadowRoot) 757 return shadowRoot->host()->renderer(); 758 } 759 } 760 761 return renderer; 762 } 763 764 PassRefPtr<Scrollbar> RenderLayerScrollableArea::createScrollbar(ScrollbarOrientation orientation) 765 { 766 RefPtr<Scrollbar> widget; 767 RenderObject* actualRenderer = rendererForScrollbar(m_box); 768 bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR); 769 if (hasCustomScrollbarStyle) { 770 widget = RenderScrollbar::createCustomScrollbar(this, orientation, actualRenderer->node()); 771 } else { 772 widget = Scrollbar::create(this, orientation, RegularScrollbar); 773 if (orientation == HorizontalScrollbar) 774 didAddScrollbar(widget.get(), HorizontalScrollbar); 775 else 776 didAddScrollbar(widget.get(), VerticalScrollbar); 777 } 778 m_box->document().view()->addChild(widget.get()); 779 return widget.release(); 780 } 781 782 void RenderLayerScrollableArea::destroyScrollbar(ScrollbarOrientation orientation) 783 { 784 RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_hBar : m_vBar; 785 if (!scrollbar) 786 return; 787 788 if (!scrollbar->isCustomScrollbar()) 789 willRemoveScrollbar(scrollbar.get(), orientation); 790 791 scrollbar->removeFromParent(); 792 scrollbar->disconnectFromScrollableArea(); 793 scrollbar = 0; 794 } 795 796 void RenderLayerScrollableArea::setHasHorizontalScrollbar(bool hasScrollbar) 797 { 798 if (hasScrollbar == hasHorizontalScrollbar()) 799 return; 800 801 if (hasScrollbar) 802 m_hBar = createScrollbar(HorizontalScrollbar); 803 else 804 destroyScrollbar(HorizontalScrollbar); 805 806 // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style. 807 if (m_hBar) 808 m_hBar->styleChanged(); 809 if (m_vBar) 810 m_vBar->styleChanged(); 811 812 // Force an update since we know the scrollbars have changed things. 813 if (m_box->document().hasAnnotatedRegions()) 814 m_box->document().setAnnotatedRegionsDirty(true); 815 } 816 817 void RenderLayerScrollableArea::setHasVerticalScrollbar(bool hasScrollbar) 818 { 819 if (hasScrollbar == hasVerticalScrollbar()) 820 return; 821 822 if (hasScrollbar) 823 m_vBar = createScrollbar(VerticalScrollbar); 824 else 825 destroyScrollbar(VerticalScrollbar); 826 827 // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style. 828 if (m_hBar) 829 m_hBar->styleChanged(); 830 if (m_vBar) 831 m_vBar->styleChanged(); 832 833 // Force an update since we know the scrollbars have changed things. 834 if (m_box->document().hasAnnotatedRegions()) 835 m_box->document().setAnnotatedRegionsDirty(true); 836 } 837 838 int RenderLayerScrollableArea::verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const 839 { 840 if (!m_vBar || (m_vBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_vBar->shouldParticipateInHitTesting()))) 841 return 0; 842 return m_vBar->width(); 843 } 844 845 int RenderLayerScrollableArea::horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const 846 { 847 if (!m_hBar || (m_hBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_hBar->shouldParticipateInHitTesting()))) 848 return 0; 849 return m_hBar->height(); 850 } 851 852 void RenderLayerScrollableArea::positionOverflowControls() 853 { 854 RenderGeometryMap geometryMap(UseTransforms); 855 RenderView* view = m_box->view(); 856 if (m_box->layer() != view->layer() && m_box->layer()->parent()) 857 geometryMap.pushMappingsToAncestor(m_box->layer()->parent(), 0); 858 859 LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint())); 860 positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot))); 861 } 862 863 void RenderLayerScrollableArea::positionOverflowControls(const IntSize& offsetFromRoot) 864 { 865 if (!hasScrollbar() && !m_box->canResize()) 866 return; 867 868 const IntRect borderBox = m_box->pixelSnappedBorderBoxRect(); 869 if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) { 870 IntRect vBarRect = rectForVerticalScrollbar(borderBox); 871 vBarRect.move(offsetFromRoot); 872 verticalScrollbar->setFrameRect(vBarRect); 873 } 874 875 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { 876 IntRect hBarRect = rectForHorizontalScrollbar(borderBox); 877 hBarRect.move(offsetFromRoot); 878 horizontalScrollbar->setFrameRect(hBarRect); 879 } 880 881 const IntRect& scrollCorner = scrollCornerRect(); 882 if (m_scrollCorner) 883 m_scrollCorner->setFrameRect(scrollCorner); 884 885 if (m_resizer) 886 m_resizer->setFrameRect(resizerCornerRect(borderBox, ResizerForPointer)); 887 888 // FIXME, this should eventually be removed, once we are certain that composited 889 // controls get correctly positioned on a compositor update. For now, conservatively 890 // leaving this unchanged. 891 if (m_box->hasCompositedLayerMapping()) 892 m_box->compositedLayerMapping()->positionOverflowControlsLayers(offsetFromRoot); 893 } 894 895 bool RenderLayerScrollableArea::scrollsOverflow() const 896 { 897 if (FrameView* frameView = m_box->view()->frameView()) 898 return frameView->containsScrollableArea(this); 899 900 return false; 901 } 902 903 void RenderLayerScrollableArea::updateScrollCornerStyle() 904 { 905 RenderObject* actualRenderer = rendererForScrollbar(m_box); 906 RefPtr<RenderStyle> corner = m_box->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(SCROLLBAR_CORNER), actualRenderer->style()) : PassRefPtr<RenderStyle>(0); 907 if (corner) { 908 if (!m_scrollCorner) { 909 m_scrollCorner = RenderScrollbarPart::createAnonymous(&m_box->document()); 910 m_scrollCorner->setParent(m_box); 911 } 912 m_scrollCorner->setStyle(corner.release()); 913 } else if (m_scrollCorner) { 914 m_scrollCorner->destroy(); 915 m_scrollCorner = 0; 916 } 917 } 918 919 void RenderLayerScrollableArea::paintOverflowControls(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect, bool paintingOverlayControls) 920 { 921 // Don't do anything if we have no overflow. 922 if (!m_box->hasOverflowClip()) 923 return; 924 925 IntPoint adjustedPaintOffset = paintOffset; 926 if (paintingOverlayControls) 927 adjustedPaintOffset = m_cachedOverlayScrollbarOffset; 928 929 // Move the scrollbar widgets if necessary. We normally move and resize widgets during layout, 930 // but sometimes widgets can move without layout occurring (most notably when you scroll a 931 // document that contains fixed positioned elements). 932 positionOverflowControls(toIntSize(adjustedPaintOffset)); 933 934 // Overlay scrollbars paint in a second pass through the layer tree so that they will paint 935 // on top of everything else. If this is the normal painting pass, paintingOverlayControls 936 // will be false, and we should just tell the root layer that there are overlay scrollbars 937 // that need to be painted. That will cause the second pass through the layer tree to run, 938 // and we'll paint the scrollbars then. In the meantime, cache tx and ty so that the 939 // second pass doesn't need to re-enter the RenderTree to get it right. 940 if (hasOverlayScrollbars() && !paintingOverlayControls) { 941 m_cachedOverlayScrollbarOffset = paintOffset; 942 // It's not necessary to do the second pass if the scrollbars paint into layers. 943 if ((m_hBar && layerForHorizontalScrollbar()) || (m_vBar && layerForVerticalScrollbar())) 944 return; 945 IntRect localDamgeRect = damageRect; 946 localDamgeRect.moveBy(-paintOffset); 947 if (!overflowControlsIntersectRect(localDamgeRect)) 948 return; 949 950 RenderView* renderView = m_box->view(); 951 952 RenderLayer* paintingRoot = layer()->enclosingCompositingLayer(); 953 if (!paintingRoot) 954 paintingRoot = renderView->layer(); 955 956 paintingRoot->setContainsDirtyOverlayScrollbars(true); 957 return; 958 } 959 960 // This check is required to avoid painting custom CSS scrollbars twice. 961 if (paintingOverlayControls && !hasOverlayScrollbars()) 962 return; 963 964 // Now that we're sure the scrollbars are in the right place, paint them. 965 if (m_hBar && !layerForHorizontalScrollbar()) 966 m_hBar->paint(context, damageRect); 967 if (m_vBar && !layerForVerticalScrollbar()) 968 m_vBar->paint(context, damageRect); 969 970 if (layerForScrollCorner()) 971 return; 972 973 // We fill our scroll corner with white if we have a scrollbar that doesn't run all the way up to the 974 // edge of the box. 975 paintScrollCorner(context, adjustedPaintOffset, damageRect); 976 977 // Paint our resizer last, since it sits on top of the scroll corner. 978 paintResizer(context, adjustedPaintOffset, damageRect); 979 } 980 981 void RenderLayerScrollableArea::paintScrollCorner(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect) 982 { 983 IntRect absRect = scrollCornerRect(); 984 absRect.moveBy(paintOffset); 985 if (!absRect.intersects(damageRect)) 986 return; 987 988 if (context->updatingControlTints()) { 989 updateScrollCornerStyle(); 990 return; 991 } 992 993 if (m_scrollCorner) { 994 m_scrollCorner->paintIntoRect(context, paintOffset, absRect); 995 return; 996 } 997 998 // We don't want to paint white if we have overlay scrollbars, since we need 999 // to see what is behind it. 1000 if (!hasOverlayScrollbars()) 1001 context->fillRect(absRect, Color::white); 1002 } 1003 1004 bool RenderLayerScrollableArea::hitTestOverflowControls(HitTestResult& result, const IntPoint& localPoint) 1005 { 1006 if (!hasScrollbar() && !m_box->canResize()) 1007 return false; 1008 1009 IntRect resizeControlRect; 1010 if (m_box->style()->resize() != RESIZE_NONE) { 1011 resizeControlRect = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer); 1012 if (resizeControlRect.contains(localPoint)) 1013 return true; 1014 } 1015 1016 int resizeControlSize = max(resizeControlRect.height(), 0); 1017 if (m_vBar && m_vBar->shouldParticipateInHitTesting()) { 1018 LayoutRect vBarRect(verticalScrollbarStart(0, m_box->width()), 1019 m_box->borderTop(), 1020 m_vBar->width(), 1021 m_box->height() - (m_box->borderTop() + m_box->borderBottom()) - (m_hBar ? m_hBar->height() : resizeControlSize)); 1022 if (vBarRect.contains(localPoint)) { 1023 result.setScrollbar(m_vBar.get()); 1024 return true; 1025 } 1026 } 1027 1028 resizeControlSize = max(resizeControlRect.width(), 0); 1029 if (m_hBar && m_hBar->shouldParticipateInHitTesting()) { 1030 LayoutRect hBarRect(horizontalScrollbarStart(0), 1031 m_box->height() - m_box->borderBottom() - m_hBar->height(), 1032 m_box->width() - (m_box->borderLeft() + m_box->borderRight()) - (m_vBar ? m_vBar->width() : resizeControlSize), 1033 m_hBar->height()); 1034 if (hBarRect.contains(localPoint)) { 1035 result.setScrollbar(m_hBar.get()); 1036 return true; 1037 } 1038 } 1039 1040 // FIXME: We should hit test the m_scrollCorner and pass it back through the result. 1041 1042 return false; 1043 } 1044 1045 IntRect RenderLayerScrollableArea::resizerCornerRect(const IntRect& bounds, ResizerHitTestType resizerHitTestType) const 1046 { 1047 if (m_box->style()->resize() == RESIZE_NONE) 1048 return IntRect(); 1049 IntRect corner = cornerRect(m_box->style(), horizontalScrollbar(), verticalScrollbar(), bounds); 1050 1051 if (resizerHitTestType == ResizerForTouch) { 1052 // We make the resizer virtually larger for touch hit testing. With the 1053 // expanding ratio k = ResizerControlExpandRatioForTouch, we first move 1054 // the resizer rect (of width w & height h), by (-w * (k-1), -h * (k-1)), 1055 // then expand the rect by new_w/h = w/h * k. 1056 int expandRatio = ResizerControlExpandRatioForTouch - 1; 1057 corner.move(-corner.width() * expandRatio, -corner.height() * expandRatio); 1058 corner.expand(corner.width() * expandRatio, corner.height() * expandRatio); 1059 } 1060 1061 return corner; 1062 } 1063 1064 IntRect RenderLayerScrollableArea::scrollCornerAndResizerRect() const 1065 { 1066 IntRect scrollCornerAndResizer = scrollCornerRect(); 1067 if (scrollCornerAndResizer.isEmpty()) 1068 scrollCornerAndResizer = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer); 1069 return scrollCornerAndResizer; 1070 } 1071 1072 bool RenderLayerScrollableArea::overflowControlsIntersectRect(const IntRect& localRect) const 1073 { 1074 const IntRect borderBox = m_box->pixelSnappedBorderBoxRect(); 1075 1076 if (rectForHorizontalScrollbar(borderBox).intersects(localRect)) 1077 return true; 1078 1079 if (rectForVerticalScrollbar(borderBox).intersects(localRect)) 1080 return true; 1081 1082 if (scrollCornerRect().intersects(localRect)) 1083 return true; 1084 1085 if (resizerCornerRect(borderBox, ResizerForPointer).intersects(localRect)) 1086 return true; 1087 1088 return false; 1089 } 1090 1091 void RenderLayerScrollableArea::paintResizer(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect) 1092 { 1093 if (m_box->style()->resize() == RESIZE_NONE) 1094 return; 1095 1096 IntRect absRect = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer); 1097 absRect.moveBy(paintOffset); 1098 if (!absRect.intersects(damageRect)) 1099 return; 1100 1101 if (context->updatingControlTints()) { 1102 updateResizerStyle(); 1103 return; 1104 } 1105 1106 if (m_resizer) { 1107 m_resizer->paintIntoRect(context, paintOffset, absRect); 1108 return; 1109 } 1110 1111 drawPlatformResizerImage(context, absRect); 1112 1113 // Draw a frame around the resizer (1px grey line) if there are any scrollbars present. 1114 // Clipping will exclude the right and bottom edges of this frame. 1115 if (!hasOverlayScrollbars() && hasScrollbar()) { 1116 GraphicsContextStateSaver stateSaver(*context); 1117 context->clip(absRect); 1118 IntRect largerCorner = absRect; 1119 largerCorner.setSize(IntSize(largerCorner.width() + 1, largerCorner.height() + 1)); 1120 context->setStrokeColor(Color(217, 217, 217)); 1121 context->setStrokeThickness(1.0f); 1122 context->setFillColor(Color::transparent); 1123 context->drawRect(largerCorner); 1124 } 1125 } 1126 1127 bool RenderLayerScrollableArea::isPointInResizeControl(const IntPoint& absolutePoint, ResizerHitTestType resizerHitTestType) const 1128 { 1129 if (!m_box->canResize()) 1130 return false; 1131 1132 IntPoint localPoint = roundedIntPoint(m_box->absoluteToLocal(absolutePoint, UseTransforms)); 1133 IntRect localBounds(0, 0, m_box->pixelSnappedWidth(), m_box->pixelSnappedHeight()); 1134 return resizerCornerRect(localBounds, resizerHitTestType).contains(localPoint); 1135 } 1136 1137 bool RenderLayerScrollableArea::hitTestResizerInFragments(const LayerFragments& layerFragments, const HitTestLocation& hitTestLocation) const 1138 { 1139 if (!m_box->canResize()) 1140 return false; 1141 1142 if (layerFragments.isEmpty()) 1143 return false; 1144 1145 for (int i = layerFragments.size() - 1; i >= 0; --i) { 1146 const LayerFragment& fragment = layerFragments.at(i); 1147 if (fragment.backgroundRect.intersects(hitTestLocation) && resizerCornerRect(pixelSnappedIntRect(fragment.layerBounds), ResizerForPointer).contains(hitTestLocation.roundedPoint())) 1148 return true; 1149 } 1150 1151 return false; 1152 } 1153 1154 void RenderLayerScrollableArea::updateResizerAreaSet() 1155 { 1156 Frame* frame = m_box->frame(); 1157 if (!frame) 1158 return; 1159 FrameView* frameView = frame->view(); 1160 if (!frameView) 1161 return; 1162 if (m_box->canResize()) 1163 frameView->addResizerArea(m_box); 1164 else 1165 frameView->removeResizerArea(m_box); 1166 } 1167 1168 void RenderLayerScrollableArea::updateResizerStyle() 1169 { 1170 RenderObject* actualRenderer = rendererForScrollbar(m_box); 1171 RefPtr<RenderStyle> resizer = m_box->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(RESIZER), actualRenderer->style()) : PassRefPtr<RenderStyle>(0); 1172 if (resizer) { 1173 if (!m_resizer) { 1174 m_resizer = RenderScrollbarPart::createAnonymous(&m_box->document()); 1175 m_resizer->setParent(m_box); 1176 } 1177 m_resizer->setStyle(resizer.release()); 1178 } else if (m_resizer) { 1179 m_resizer->destroy(); 1180 m_resizer = 0; 1181 } 1182 } 1183 1184 void RenderLayerScrollableArea::drawPlatformResizerImage(GraphicsContext* context, IntRect resizerCornerRect) 1185 { 1186 float deviceScaleFactor = WebCore::deviceScaleFactor(m_box->frame()); 1187 1188 RefPtr<Image> resizeCornerImage; 1189 IntSize cornerResizerSize; 1190 if (deviceScaleFactor >= 2) { 1191 DEFINE_STATIC_REF(Image, resizeCornerImageHiRes, (Image::loadPlatformResource("textAreaResizeCorner@2x"))); 1192 resizeCornerImage = resizeCornerImageHiRes; 1193 cornerResizerSize = resizeCornerImage->size(); 1194 cornerResizerSize.scale(0.5f); 1195 } else { 1196 DEFINE_STATIC_REF(Image, resizeCornerImageLoRes, (Image::loadPlatformResource("textAreaResizeCorner"))); 1197 resizeCornerImage = resizeCornerImageLoRes; 1198 cornerResizerSize = resizeCornerImage->size(); 1199 } 1200 1201 if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { 1202 context->save(); 1203 context->translate(resizerCornerRect.x() + cornerResizerSize.width(), resizerCornerRect.y() + resizerCornerRect.height() - cornerResizerSize.height()); 1204 context->scale(FloatSize(-1.0, 1.0)); 1205 context->drawImage(resizeCornerImage.get(), IntRect(IntPoint(), cornerResizerSize)); 1206 context->restore(); 1207 return; 1208 } 1209 IntRect imageRect(resizerCornerRect.maxXMaxYCorner() - cornerResizerSize, cornerResizerSize); 1210 context->drawImage(resizeCornerImage.get(), imageRect); 1211 } 1212 1213 IntSize RenderLayerScrollableArea::offsetFromResizeCorner(const IntPoint& absolutePoint) const 1214 { 1215 // Currently the resize corner is either the bottom right corner or the bottom left corner. 1216 // FIXME: This assumes the location is 0, 0. Is this guaranteed to always be the case? 1217 IntSize elementSize = layer()->size(); 1218 if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) 1219 elementSize.setWidth(0); 1220 IntPoint resizerPoint = IntPoint(elementSize); 1221 IntPoint localPoint = roundedIntPoint(m_box->absoluteToLocal(absolutePoint, UseTransforms)); 1222 return localPoint - resizerPoint; 1223 } 1224 1225 void RenderLayerScrollableArea::resize(const PlatformEvent& evt, const LayoutSize& oldOffset) 1226 { 1227 // FIXME: This should be possible on generated content but is not right now. 1228 if (!inResizeMode() || !m_box->canResize() || !m_box->node()) 1229 return; 1230 1231 ASSERT(m_box->node()->isElementNode()); 1232 Element* element = toElement(m_box->node()); 1233 1234 Document& document = element->document(); 1235 1236 IntPoint pos; 1237 const PlatformGestureEvent* gevt = 0; 1238 1239 switch (evt.type()) { 1240 case PlatformEvent::MouseMoved: 1241 if (!document.frame()->eventHandler().mousePressed()) 1242 return; 1243 pos = static_cast<const PlatformMouseEvent*>(&evt)->position(); 1244 break; 1245 case PlatformEvent::GestureScrollUpdate: 1246 case PlatformEvent::GestureScrollUpdateWithoutPropagation: 1247 pos = static_cast<const PlatformGestureEvent*>(&evt)->position(); 1248 gevt = static_cast<const PlatformGestureEvent*>(&evt); 1249 pos = gevt->position(); 1250 pos.move(gevt->deltaX(), gevt->deltaY()); 1251 break; 1252 default: 1253 ASSERT_NOT_REACHED(); 1254 } 1255 1256 float zoomFactor = m_box->style()->effectiveZoom(); 1257 1258 LayoutSize newOffset = offsetFromResizeCorner(document.view()->windowToContents(pos)); 1259 newOffset.setWidth(newOffset.width() / zoomFactor); 1260 newOffset.setHeight(newOffset.height() / zoomFactor); 1261 1262 LayoutSize currentSize = LayoutSize(m_box->width() / zoomFactor, m_box->height() / zoomFactor); 1263 LayoutSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize); 1264 element->setMinimumSizeForResizing(minimumSize); 1265 1266 LayoutSize adjustedOldOffset = LayoutSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor); 1267 if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { 1268 newOffset.setWidth(-newOffset.width()); 1269 adjustedOldOffset.setWidth(-adjustedOldOffset.width()); 1270 } 1271 1272 LayoutSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize; 1273 1274 bool isBoxSizingBorder = m_box->style()->boxSizing() == BORDER_BOX; 1275 1276 EResize resize = m_box->style()->resize(); 1277 if (resize != RESIZE_VERTICAL && difference.width()) { 1278 if (element->isFormControlElement()) { 1279 // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>). 1280 element->setInlineStyleProperty(CSSPropertyMarginLeft, m_box->marginLeft() / zoomFactor, CSSPrimitiveValue::CSS_PX); 1281 element->setInlineStyleProperty(CSSPropertyMarginRight, m_box->marginRight() / zoomFactor, CSSPrimitiveValue::CSS_PX); 1282 } 1283 LayoutUnit baseWidth = m_box->width() - (isBoxSizingBorder ? LayoutUnit() : m_box->borderAndPaddingWidth()); 1284 baseWidth = baseWidth / zoomFactor; 1285 element->setInlineStyleProperty(CSSPropertyWidth, roundToInt(baseWidth + difference.width()), CSSPrimitiveValue::CSS_PX); 1286 } 1287 1288 if (resize != RESIZE_HORIZONTAL && difference.height()) { 1289 if (element->isFormControlElement()) { 1290 // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>). 1291 element->setInlineStyleProperty(CSSPropertyMarginTop, m_box->marginTop() / zoomFactor, CSSPrimitiveValue::CSS_PX); 1292 element->setInlineStyleProperty(CSSPropertyMarginBottom, m_box->marginBottom() / zoomFactor, CSSPrimitiveValue::CSS_PX); 1293 } 1294 LayoutUnit baseHeight = m_box->height() - (isBoxSizingBorder ? LayoutUnit() : m_box->borderAndPaddingHeight()); 1295 baseHeight = baseHeight / zoomFactor; 1296 element->setInlineStyleProperty(CSSPropertyHeight, roundToInt(baseHeight + difference.height()), CSSPrimitiveValue::CSS_PX); 1297 } 1298 1299 document.updateLayout(); 1300 1301 // FIXME (Radar 4118564): We should also autoscroll the window as necessary to keep the point under the cursor in view. 1302 } 1303 1304 LayoutRect RenderLayerScrollableArea::exposeRect(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) 1305 { 1306 LayoutRect localExposeRect(m_box->absoluteToLocalQuad(FloatQuad(FloatRect(rect)), UseTransforms).boundingBox()); 1307 LayoutRect layerBounds(0, 0, m_box->clientWidth(), m_box->clientHeight()); 1308 LayoutRect r = ScrollAlignment::getRectToExpose(layerBounds, localExposeRect, alignX, alignY); 1309 1310 IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset() + toIntSize(roundedIntRect(r).location())); 1311 if (clampedScrollOffset == adjustedScrollOffset()) 1312 return rect; 1313 1314 IntSize oldScrollOffset = adjustedScrollOffset(); 1315 scrollToOffset(clampedScrollOffset); 1316 IntSize scrollOffsetDifference = adjustedScrollOffset() - oldScrollOffset; 1317 localExposeRect.move(-scrollOffsetDifference); 1318 return LayoutRect(m_box->localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox()); 1319 } 1320 1321 void RenderLayerScrollableArea::updateScrollableAreaSet(bool hasOverflow) 1322 { 1323 Frame* frame = m_box->frame(); 1324 if (!frame) 1325 return; 1326 1327 FrameView* frameView = frame->view(); 1328 if (!frameView) 1329 return; 1330 1331 bool isVisibleToHitTest = m_box->visibleToHitTesting(); 1332 if (HTMLFrameOwnerElement* owner = frame->ownerElement()) 1333 isVisibleToHitTest &= owner->renderer() && owner->renderer()->visibleToHitTesting(); 1334 1335 bool requiresScrollableArea = hasOverflow && isVisibleToHitTest; 1336 bool updatedScrollableAreaSet = false; 1337 if (requiresScrollableArea) { 1338 if (frameView->addScrollableArea(this)) 1339 updatedScrollableAreaSet = true; 1340 } else { 1341 if (frameView->removeScrollableArea(this)) 1342 updatedScrollableAreaSet = true; 1343 } 1344 1345 if (updatedScrollableAreaSet) { 1346 // Count the total number of RenderLayers that are scrollable areas for 1347 // any period. We only want to record this at most once per RenderLayer. 1348 if (requiresScrollableArea && !m_isScrollableAreaHasBeenRecorded) { 1349 blink::Platform::current()->histogramEnumeration("Renderer.CompositedScrolling", RenderLayer::IsScrollableAreaBucket, RenderLayer::CompositedScrollingHistogramMax); 1350 m_isScrollableAreaHasBeenRecorded = true; 1351 } 1352 1353 // We always want composited scrolling if compositor driven accelerated 1354 // scrolling is enabled. Since we will not update needs composited scrolling 1355 // in this case, we must force our state to update. 1356 if (layer()->compositorDrivenAcceleratedScrollingEnabled()) 1357 layer()->didUpdateNeedsCompositedScrolling(); 1358 else if (requiresScrollableArea) 1359 m_box->view()->compositor()->setNeedsUpdateCompositingRequirementsState(); 1360 else 1361 setNeedsCompositedScrolling(false); 1362 } 1363 } 1364 1365 void RenderLayerScrollableArea::updateNeedsCompositedScrolling() 1366 { 1367 TRACE_EVENT0("comp-scroll", "RenderLayer::updateNeedsCompositedScrolling"); 1368 1369 layer()->stackingNode()->updateDescendantsAreContiguousInStackingOrder(); 1370 layer()->updateDescendantDependentFlags(); 1371 1372 ASSERT(scrollsOverflow()); 1373 const bool needsToBeStackingContainer = layer()->acceleratedCompositingForOverflowScrollEnabled() 1374 && layer()->stackingNode()->descendantsAreContiguousInStackingOrder() 1375 && !layer()->hasUnclippedDescendant(); 1376 1377 const bool needsToBeStackingContainerDidChange = layer()->stackingNode()->setNeedsToBeStackingContainer(needsToBeStackingContainer); 1378 1379 const bool needsCompositedScrolling = needsToBeStackingContainer 1380 || layer()->compositorDrivenAcceleratedScrollingEnabled(); 1381 1382 // We gather a boolean value for use with Google UMA histograms to 1383 // quantify the actual effects of a set of patches attempting to 1384 // relax composited scrolling requirements, thereby increasing the 1385 // number of composited overflow divs. 1386 if (layer()->acceleratedCompositingForOverflowScrollEnabled()) 1387 blink::Platform::current()->histogramEnumeration("Renderer.NeedsCompositedScrolling", needsCompositedScrolling, 2); 1388 1389 const bool needsCompositedScrollingDidChange = setNeedsCompositedScrolling(needsCompositedScrolling); 1390 1391 if (needsToBeStackingContainerDidChange || needsCompositedScrollingDidChange) { 1392 // Note, the z-order lists may need to be rebuilt, but our code guarantees 1393 // that we have not affected stacking, so we will not dirty 1394 // m_descendantsAreContiguousInStackingOrder for either us or our stacking 1395 // context or container. 1396 layer()->didUpdateNeedsCompositedScrolling(); 1397 } 1398 } 1399 1400 bool RenderLayerScrollableArea::setNeedsCompositedScrolling(bool needsCompositedScrolling) 1401 { 1402 if (this->needsCompositedScrolling() == needsCompositedScrolling) 1403 return false; 1404 1405 // Count the total number of RenderLayers which need composited scrolling at 1406 // some point. This should be recorded at most once per RenderLayer, so we 1407 // check m_willUseCompositedScrollingHasBeenRecorded. 1408 if (layer()->acceleratedCompositingForOverflowScrollEnabled() && !m_willUseCompositedScrollingHasBeenRecorded) { 1409 blink::Platform::current()->histogramEnumeration("Renderer.CompositedScrolling", RenderLayer::WillUseCompositedScrollingBucket, RenderLayer::CompositedScrollingHistogramMax); 1410 m_willUseCompositedScrollingHasBeenRecorded = true; 1411 } 1412 1413 m_needsCompositedScrolling = needsCompositedScrolling; 1414 1415 return true; 1416 } 1417 1418 void RenderLayerScrollableArea::updateHasVisibleNonLayerContent() 1419 { 1420 layer()->updateHasVisibleNonLayerContent(); 1421 } 1422 1423 void RenderLayerScrollableArea::updateCompositingLayersAfterScroll() 1424 { 1425 RenderLayerCompositor* compositor = m_box->view()->compositor(); 1426 if (compositor->inCompositingMode()) { 1427 // FIXME: Our stacking container is guaranteed to contain all of our descendants that may need 1428 // repositioning, so we should be able to enqueue a partial update compositing layers from there. 1429 // this feature was overridden for now by deferred compositing updates. 1430 if (usesCompositedScrolling()) 1431 compositor->updateCompositingLayers(CompositingUpdateOnCompositedScroll); 1432 else 1433 compositor->updateCompositingLayers(CompositingUpdateOnScroll); 1434 } 1435 } 1436 1437 bool RenderLayerScrollableArea::usesCompositedScrolling() const 1438 { 1439 // Scroll form controls on the main thread so they exhibit correct touch scroll event bubbling 1440 if (m_box && (m_box->isIntristicallyScrollable(VerticalScrollbar) || m_box->isIntristicallyScrollable(HorizontalScrollbar))) 1441 return false; 1442 1443 return m_box->hasCompositedLayerMapping() && m_box->compositedLayerMapping()->scrollingLayer(); 1444 } 1445 1446 bool RenderLayerScrollableArea::adjustForForceCompositedScrollingMode(bool value) const 1447 { 1448 switch (m_forceNeedsCompositedScrolling) { 1449 case DoNotForceCompositedScrolling: 1450 return value; 1451 case CompositedScrollingAlwaysOn: 1452 return true; 1453 case CompositedScrollingAlwaysOff: 1454 return false; 1455 } 1456 1457 ASSERT_NOT_REACHED(); 1458 return value; 1459 } 1460 1461 bool RenderLayerScrollableArea::needsCompositedScrolling() const 1462 { 1463 return adjustForForceCompositedScrollingMode(m_needsCompositedScrolling); 1464 } 1465 1466 void RenderLayerScrollableArea::setForceNeedsCompositedScrolling(ForceNeedsCompositedScrollingMode mode) 1467 { 1468 if (m_forceNeedsCompositedScrolling == mode) 1469 return; 1470 1471 m_forceNeedsCompositedScrolling = mode; 1472 layer()->didUpdateNeedsCompositedScrolling(); 1473 } 1474 1475 } // Namespace WebCore 1476