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