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