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@fas.harvard.edu>
9 * Christian Biesinger <cbiesinger@web.de>
10 * Randall Jesup <rjesup@wgate.com>
11 * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
12 * Josh Soref <timeless@mac.com>
13 * Boris Zbarsky <bzbarsky@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
RenderLayerScrollableArea(RenderLayer & layer)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
~RenderLayerScrollableArea()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
hostWindow() const145 HostWindow* RenderLayerScrollableArea::hostWindow() const
146 {
147 if (Page* page = box().frame()->page())
148 return &page->chrome();
149 return nullptr;
150 }
151
layerForScrolling() const152 GraphicsLayer* RenderLayerScrollableArea::layerForScrolling() const
153 {
154 return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->scrollingContentsLayer() : 0;
155 }
156
layerForHorizontalScrollbar() const157 GraphicsLayer* RenderLayerScrollableArea::layerForHorizontalScrollbar() const
158 {
159 // See crbug.com/343132.
160 DisableCompositingQueryAsserts disabler;
161
162 return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForHorizontalScrollbar() : 0;
163 }
164
layerForVerticalScrollbar() const165 GraphicsLayer* RenderLayerScrollableArea::layerForVerticalScrollbar() const
166 {
167 // See crbug.com/343132.
168 DisableCompositingQueryAsserts disabler;
169
170 return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForVerticalScrollbar() : 0;
171 }
172
layerForScrollCorner() const173 GraphicsLayer* RenderLayerScrollableArea::layerForScrollCorner() const
174 {
175 // See crbug.com/343132.
176 DisableCompositingQueryAsserts disabler;
177
178 return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForScrollCorner() : 0;
179 }
180
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)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
invalidateScrollCornerRect(const IntRect & rect)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
isActive() const235 bool RenderLayerScrollableArea::isActive() const
236 {
237 Page* page = box().frame()->page();
238 return page && page->focusController().isActive();
239 }
240
isScrollCornerVisible() const241 bool RenderLayerScrollableArea::isScrollCornerVisible() const
242 {
243 return !scrollCornerRect().isEmpty();
244 }
245
cornerStart(const RenderStyle * style,int minX,int maxX,int thickness)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
cornerRect(const RenderStyle * style,const Scrollbar * horizontalScrollbar,const Scrollbar * verticalScrollbar,const IntRect & bounds)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
scrollCornerRect() const278 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
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntRect & scrollbarRect) const292 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
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntRect & parentRect) const304 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
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntPoint & scrollbarPoint) const315 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
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntPoint & parentPoint) const326 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
scrollSize(ScrollbarOrientation orientation) const338 int RenderLayerScrollableArea::scrollSize(ScrollbarOrientation orientation) const
339 {
340 IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition();
341 return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height();
342 }
343
setScrollOffset(const IntPoint & newScrollOffset)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
scrollPosition() const417 IntPoint RenderLayerScrollableArea::scrollPosition() const
418 {
419 return IntPoint(m_scrollOffset);
420 }
421
minimumScrollPosition() const422 IntPoint RenderLayerScrollableArea::minimumScrollPosition() const
423 {
424 return -scrollOrigin();
425 }
426
maximumScrollPosition() const427 IntPoint RenderLayerScrollableArea::maximumScrollPosition() const
428 {
429 if (!box().hasOverflowClip())
430 return -scrollOrigin();
431 return -scrollOrigin() + IntPoint(pixelSnappedScrollWidth(), pixelSnappedScrollHeight()) - enclosingIntRect(box().clientBoxRect()).size();
432 }
433
visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const434 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
visibleHeight() const447 int RenderLayerScrollableArea::visibleHeight() const
448 {
449 return layer()->size().height();
450 }
451
visibleWidth() const452 int RenderLayerScrollableArea::visibleWidth() const
453 {
454 return layer()->size().width();
455 }
456
contentsSize() const457 IntSize RenderLayerScrollableArea::contentsSize() const
458 {
459 return IntSize(scrollWidth(), scrollHeight());
460 }
461
overhangAmount() const462 IntSize RenderLayerScrollableArea::overhangAmount() const
463 {
464 return IntSize();
465 }
466
lastKnownMousePosition() const467 IntPoint RenderLayerScrollableArea::lastKnownMousePosition() const
468 {
469 return box().frame() ? box().frame()->eventHandler().lastKnownMousePosition() : IntPoint();
470 }
471
shouldSuspendScrollAnimations() const472 bool RenderLayerScrollableArea::shouldSuspendScrollAnimations() const
473 {
474 RenderView* view = box().view();
475 if (!view)
476 return true;
477 return view->frameView()->shouldSuspendScrollAnimations();
478 }
479
scrollbarsCanBeActive() const480 bool RenderLayerScrollableArea::scrollbarsCanBeActive() const
481 {
482 RenderView* view = box().view();
483 if (!view)
484 return false;
485 return view->frameView()->scrollbarsCanBeActive();
486 }
487
scrollableAreaBoundingBox() const488 IntRect RenderLayerScrollableArea::scrollableAreaBoundingBox() const
489 {
490 return box().absoluteBoundingBoxRect();
491 }
492
userInputScrollable(ScrollbarOrientation orientation) const493 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
shouldPlaceVerticalScrollbarOnLeft() const503 bool RenderLayerScrollableArea::shouldPlaceVerticalScrollbarOnLeft() const
504 {
505 return box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft();
506 }
507
pageStep(ScrollbarOrientation orientation) const508 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
box() const518 RenderBox& RenderLayerScrollableArea::box() const
519 {
520 return *m_layer.renderBox();
521 }
522
layer() const523 RenderLayer* RenderLayerScrollableArea::layer() const
524 {
525 return &m_layer;
526 }
527
scrollWidth() const528 LayoutUnit RenderLayerScrollableArea::scrollWidth() const
529 {
530 if (m_scrollDimensionsDirty)
531 const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
532 return m_overflowRect.width();
533 }
534
scrollHeight() const535 LayoutUnit RenderLayerScrollableArea::scrollHeight() const
536 {
537 if (m_scrollDimensionsDirty)
538 const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
539 return m_overflowRect.height();
540 }
541
pixelSnappedScrollWidth() const542 int RenderLayerScrollableArea::pixelSnappedScrollWidth() const
543 {
544 return snapSizeToPixel(scrollWidth(), box().clientLeft() + box().x());
545 }
546
pixelSnappedScrollHeight() const547 int RenderLayerScrollableArea::pixelSnappedScrollHeight() const
548 {
549 return snapSizeToPixel(scrollHeight(), box().clientTop() + box().y());
550 }
551
computeScrollDimensions()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
scrollToOffset(const IntSize & scrollOffset,ScrollOffsetClamping clamp)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
updateAfterLayout()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
hasHorizontalOverflow() const664 bool RenderLayerScrollableArea::hasHorizontalOverflow() const
665 {
666 ASSERT(!m_scrollDimensionsDirty);
667
668 return pixelSnappedScrollWidth() > box().pixelSnappedClientWidth();
669 }
670
hasVerticalOverflow() const671 bool RenderLayerScrollableArea::hasVerticalOverflow() const
672 {
673 ASSERT(!m_scrollDimensionsDirty);
674
675 return pixelSnappedScrollHeight() > box().pixelSnappedClientHeight();
676 }
677
hasScrollableHorizontalOverflow() const678 bool RenderLayerScrollableArea::hasScrollableHorizontalOverflow() const
679 {
680 return hasHorizontalOverflow() && box().scrollsOverflowX();
681 }
682
hasScrollableVerticalOverflow() const683 bool RenderLayerScrollableArea::hasScrollableVerticalOverflow() const
684 {
685 return hasVerticalOverflow() && box().scrollsOverflowY();
686 }
687
overflowRequiresScrollbar(EOverflow overflow)688 static bool overflowRequiresScrollbar(EOverflow overflow)
689 {
690 return overflow == OSCROLL;
691 }
692
overflowDefinesAutomaticScrollbar(EOverflow overflow)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.
canHaveOverflowScrollbars(const RenderBox & box)703 static bool canHaveOverflowScrollbars(const RenderBox& box)
704 {
705 return !box.isRenderView() && box.document().viewportDefiningElement() != box.node();
706 }
707
updateAfterStyleChange(const RenderStyle * oldStyle)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
updateAfterCompositingChange()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
updateAfterOverflowRecalc()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
clampScrollOffset(const IntSize & scrollOffset) const777 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
rectForHorizontalScrollbar(const IntRect & borderBoxRect) const787 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
rectForVerticalScrollbar(const IntRect & borderBoxRect) const800 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
verticalScrollbarStart(int minX,int maxX) const813 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
horizontalScrollbarStart(int minX) const820 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
scrollbarOffset(const Scrollbar * scrollbar) const828 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
rendererForScrollbar(RenderObject & renderer)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
createScrollbar(ScrollbarOrientation orientation)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
destroyScrollbar(ScrollbarOrientation orientation)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
setHasHorizontalScrollbar(bool hasScrollbar)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
setHasVerticalScrollbar(bool hasScrollbar)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
verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const936 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
horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const943 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
positionOverflowControls(const IntSize & offsetFromRoot)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
updateScrollCornerStyle()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
paintOverflowControls(GraphicsContext * context,const IntPoint & paintOffset,const IntRect & damageRect,bool paintingOverlayControls)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
paintScrollCorner(GraphicsContext * context,const IntPoint & paintOffset,const IntRect & damageRect)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
hitTestOverflowControls(HitTestResult & result,const IntPoint & localPoint)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
resizerCornerRect(const IntRect & bounds,ResizerHitTestType resizerHitTestType) const1124 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
scrollCornerAndResizerRect() const1143 IntRect RenderLayerScrollableArea::scrollCornerAndResizerRect() const
1144 {
1145 IntRect scrollCornerAndResizer = scrollCornerRect();
1146 if (scrollCornerAndResizer.isEmpty())
1147 scrollCornerAndResizer = resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer);
1148 return scrollCornerAndResizer;
1149 }
1150
overflowControlsIntersectRect(const IntRect & localRect) const1151 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
paintResizer(GraphicsContext * context,const IntPoint & paintOffset,const IntRect & damageRect)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
isPointInResizeControl(const IntPoint & absolutePoint,ResizerHitTestType resizerHitTestType) const1201 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
hitTestResizerInFragments(const LayerFragments & layerFragments,const HitTestLocation & hitTestLocation) const1211 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
updateResizerAreaSet()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
updateResizerStyle()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
drawPlatformResizerImage(GraphicsContext * context,IntRect resizerCornerRect)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
offsetFromResizeCorner(const IntPoint & absolutePoint) const1290 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
resize(const PlatformEvent & evt,const LayoutSize & oldOffset)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
exposeRect(const LayoutRect & rect,const ScrollAlignment & alignX,const ScrollAlignment & alignY)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
updateScrollableAreaSet(bool hasOverflow)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
updateCompositingLayersAfterScroll()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
usesCompositedScrolling() const1440 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
layerNeedsCompositedScrolling(const RenderLayer * layer)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
updateNeedsCompositedScrolling()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
setTopmostScrollChild(RenderLayer * scrollChild)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