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