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