1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#include "ScrollingCoordinator.h"
29
30#include "Document.h"
31#include "FrameView.h"
32#include "GraphicsLayer.h"
33#include "IntRect.h"
34#include "MainFrame.h"
35#include "Page.h"
36#include "PlatformWheelEvent.h"
37#include "PluginViewBase.h"
38#include "Region.h"
39#include "RenderLayerCompositor.h"
40#include "RenderView.h"
41#include "ScrollAnimator.h"
42#include "Settings.h"
43#include <wtf/MainThread.h>
44#include <wtf/text/StringBuilder.h>
45
46#if USE(COORDINATED_GRAPHICS)
47#include "ScrollingCoordinatorCoordinatedGraphics.h"
48#endif
49
50#if ENABLE(WEB_REPLAY)
51#include "ReplayController.h"
52#include <replay/InputCursor.h>
53#endif
54
55namespace WebCore {
56
57#if !PLATFORM(COCOA)
58PassRefPtr<ScrollingCoordinator> ScrollingCoordinator::create(Page* page)
59{
60#if USE(COORDINATED_GRAPHICS)
61    return adoptRef(new ScrollingCoordinatorCoordinatedGraphics(page));
62#endif
63
64    return adoptRef(new ScrollingCoordinator(page));
65}
66#endif
67
68ScrollingCoordinator::ScrollingCoordinator(Page* page)
69    : m_page(page)
70    , m_forceSynchronousScrollLayerPositionUpdates(false)
71{
72}
73
74ScrollingCoordinator::~ScrollingCoordinator()
75{
76    ASSERT(!m_page);
77}
78
79void ScrollingCoordinator::pageDestroyed()
80{
81    ASSERT(m_page);
82    m_page = 0;
83}
84
85bool ScrollingCoordinator::coordinatesScrollingForFrameView(FrameView* frameView) const
86{
87    ASSERT(isMainThread());
88    ASSERT(m_page);
89
90    if (!frameView->frame().isMainFrame() && !m_page->settings().scrollingTreeIncludesFrames())
91        return false;
92
93    RenderView* renderView = m_page->mainFrame().contentRenderer();
94    if (!renderView)
95        return false;
96    return renderView->usesCompositing();
97}
98
99Region ScrollingCoordinator::computeNonFastScrollableRegion(const Frame* frame, const IntPoint& frameLocation) const
100{
101#if PLATFORM(IOS)
102    // On iOS, we use nonFastScrollableRegion to represent the region covered by elements with touch event handlers.
103    ASSERT(frame->isMainFrame());
104    UNUSED_PARAM(frameLocation);
105
106    Document* document = frame->document();
107    if (!document)
108        return Region();
109
110    Vector<IntRect> touchRects;
111    document->getTouchRects(touchRects);
112
113    Region touchRegion;
114    for (const auto& rect : touchRects)
115        touchRegion.unite(rect);
116
117    return touchRegion;
118#else
119    Region nonFastScrollableRegion;
120    FrameView* frameView = frame->view();
121    if (!frameView)
122        return nonFastScrollableRegion;
123
124    IntPoint offset = frameLocation;
125    offset.moveBy(frameView->frameRect().location());
126    offset.move(0, frameView->topContentInset());
127
128    if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) {
129        for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) {
130            ScrollableArea* scrollableArea = *it;
131            // Composited scrollable areas can be scrolled off the main thread.
132            if (scrollableArea->usesCompositedScrolling())
133                continue;
134            IntRect box = scrollableArea->scrollableAreaBoundingBox();
135            box.moveBy(offset);
136            nonFastScrollableRegion.unite(box);
137        }
138    }
139
140    for (const auto& child : frameView->children()) {
141        if (!child->isPluginViewBase())
142            continue;
143        PluginViewBase* pluginViewBase = toPluginViewBase(child.get());
144        if (pluginViewBase->wantsWheelEvents())
145            nonFastScrollableRegion.unite(pluginViewBase->frameRect());
146    }
147
148    for (Frame* subframe = frame->tree().firstChild(); subframe; subframe = subframe->tree().nextSibling())
149        nonFastScrollableRegion.unite(computeNonFastScrollableRegion(subframe, offset));
150
151    return nonFastScrollableRegion;
152#endif
153}
154
155unsigned ScrollingCoordinator::computeCurrentWheelEventHandlerCount()
156{
157    unsigned wheelEventHandlerCount = 0;
158
159    for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
160        if (frame->document())
161            wheelEventHandlerCount += frame->document()->wheelEventHandlerCount();
162    }
163
164    return wheelEventHandlerCount;
165}
166
167void ScrollingCoordinator::frameViewWheelEventHandlerCountChanged(FrameView* frameView)
168{
169    ASSERT(isMainThread());
170    ASSERT(m_page);
171
172    recomputeWheelEventHandlerCountForFrameView(frameView);
173}
174
175void ScrollingCoordinator::frameViewHasSlowRepaintObjectsDidChange(FrameView* frameView)
176{
177    ASSERT(isMainThread());
178    ASSERT(m_page);
179
180    if (!coordinatesScrollingForFrameView(frameView))
181        return;
182
183    updateSynchronousScrollingReasons(frameView);
184}
185
186void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView* frameView)
187{
188    ASSERT(isMainThread());
189    ASSERT(m_page);
190
191    if (!coordinatesScrollingForFrameView(frameView))
192        return;
193
194    updateSynchronousScrollingReasons(frameView);
195}
196
197GraphicsLayer* ScrollingCoordinator::scrollLayerForScrollableArea(ScrollableArea* scrollableArea)
198{
199    return scrollableArea->layerForScrolling();
200}
201
202GraphicsLayer* ScrollingCoordinator::horizontalScrollbarLayerForScrollableArea(ScrollableArea* scrollableArea)
203{
204    return scrollableArea->layerForHorizontalScrollbar();
205}
206
207GraphicsLayer* ScrollingCoordinator::verticalScrollbarLayerForScrollableArea(ScrollableArea* scrollableArea)
208{
209    return scrollableArea->layerForVerticalScrollbar();
210}
211
212GraphicsLayer* ScrollingCoordinator::scrollLayerForFrameView(FrameView* frameView)
213{
214    if (RenderView* renderView = frameView->frame().contentRenderer())
215        return renderView->compositor().scrollLayer();
216    return nullptr;
217}
218
219GraphicsLayer* ScrollingCoordinator::headerLayerForFrameView(FrameView* frameView)
220{
221#if ENABLE(RUBBER_BANDING)
222    if (RenderView* renderView = frameView->frame().contentRenderer())
223        return renderView->compositor().headerLayer();
224    return nullptr;
225#else
226    UNUSED_PARAM(frameView);
227    return nullptr;
228#endif
229}
230
231GraphicsLayer* ScrollingCoordinator::footerLayerForFrameView(FrameView* frameView)
232{
233#if ENABLE(RUBBER_BANDING)
234    if (RenderView* renderView = frameView->frame().contentRenderer())
235        return renderView->compositor().footerLayer();
236    return nullptr;
237#else
238    UNUSED_PARAM(frameView);
239    return nullptr;
240#endif
241}
242
243GraphicsLayer* ScrollingCoordinator::counterScrollingLayerForFrameView(FrameView* frameView)
244{
245    if (RenderView* renderView = frameView->frame().contentRenderer())
246        return renderView->compositor().fixedRootBackgroundLayer();
247    return nullptr;
248}
249
250GraphicsLayer* ScrollingCoordinator::insetClipLayerForFrameView(FrameView* frameView)
251{
252    if (RenderView* renderView = frameView->frame().contentRenderer())
253        return renderView->compositor().clipLayer();
254    return nullptr;
255}
256
257GraphicsLayer* ScrollingCoordinator::contentShadowLayerForFrameView(FrameView* frameView)
258{
259#if ENABLE(RUBBER_BANDING)
260    if (RenderView* renderView = frameView->frame().contentRenderer())
261        return renderView->compositor().layerForContentShadow();
262
263    return nullptr;
264#else
265    UNUSED_PARAM(frameView);
266    return nullptr;
267#endif
268}
269
270GraphicsLayer* ScrollingCoordinator::rootContentLayerForFrameView(FrameView* frameView)
271{
272    if (RenderView* renderView = frameView->frame().contentRenderer())
273        return renderView->compositor().rootContentLayer();
274    return nullptr;
275}
276
277void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView* frameView)
278{
279    ASSERT(isMainThread());
280    ASSERT(m_page);
281
282    if (!coordinatesScrollingForFrameView(frameView))
283        return;
284
285    frameViewLayoutUpdated(frameView);
286    recomputeWheelEventHandlerCountForFrameView(frameView);
287    updateSynchronousScrollingReasons(frameView);
288}
289
290#if PLATFORM(COCOA)
291void ScrollingCoordinator::handleWheelEventPhase(PlatformWheelEventPhase phase)
292{
293    ASSERT(isMainThread());
294
295    if (!m_page)
296        return;
297
298    FrameView* frameView = m_page->mainFrame().view();
299    if (!frameView)
300        return;
301
302    frameView->scrollAnimator()->handleWheelEventPhase(phase);
303}
304#endif
305
306bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(FrameView* frameView) const
307{
308    const FrameView::ViewportConstrainedObjectSet* viewportConstrainedObjects = frameView->viewportConstrainedObjects();
309    if (!viewportConstrainedObjects)
310        return false;
311
312    for (FrameView::ViewportConstrainedObjectSet::const_iterator it = viewportConstrainedObjects->begin(), end = viewportConstrainedObjects->end(); it != end; ++it) {
313        RenderObject* viewportConstrainedObject = *it;
314        if (!viewportConstrainedObject->isBoxModelObject() || !viewportConstrainedObject->hasLayer())
315            return true;
316        RenderLayer* layer = toRenderBoxModelObject(viewportConstrainedObject)->layer();
317        // Any explicit reason that a fixed position element is not composited shouldn't cause slow scrolling.
318        if (!layer->isComposited() && layer->viewportConstrainedNotCompositedReason() == RenderLayer::NoNotCompositedReason)
319            return true;
320    }
321    return false;
322}
323
324SynchronousScrollingReasons ScrollingCoordinator::synchronousScrollingReasons(FrameView* frameView) const
325{
326    if (!frameView)
327        return static_cast<SynchronousScrollingReasons>(0);
328
329    SynchronousScrollingReasons synchronousScrollingReasons = (SynchronousScrollingReasons)0;
330
331    if (m_forceSynchronousScrollLayerPositionUpdates)
332        synchronousScrollingReasons |= ForcedOnMainThread;
333#if ENABLE(WEB_REPLAY)
334    InputCursor& cursor = m_page->replayController().activeInputCursor();
335    if (cursor.isCapturing() || cursor.isReplaying())
336        synchronousScrollingReasons |= ForcedOnMainThread;
337#endif
338    if (frameView->hasSlowRepaintObjects())
339        synchronousScrollingReasons |= HasSlowRepaintObjects;
340    if (!supportsFixedPositionLayers() && frameView->hasViewportConstrainedObjects())
341        synchronousScrollingReasons |= HasViewportConstrainedObjectsWithoutSupportingFixedLayers;
342    if (supportsFixedPositionLayers() && hasVisibleSlowRepaintViewportConstrainedObjects(frameView))
343        synchronousScrollingReasons |= HasNonLayerViewportConstrainedObjects;
344    if (frameView->frame().mainFrame().document() && frameView->frame().document()->isImageDocument())
345        synchronousScrollingReasons |= IsImageDocument;
346
347    return synchronousScrollingReasons;
348}
349
350void ScrollingCoordinator::updateSynchronousScrollingReasons(FrameView* frameView)
351{
352    // FIXME: Once we support async scrolling of iframes, we'll have to track the synchronous scrolling
353    // reasons per frame (maybe on scrolling tree nodes).
354    if (!frameView->frame().isMainFrame())
355        return;
356
357    setSynchronousScrollingReasons(synchronousScrollingReasons(frameView));
358}
359
360void ScrollingCoordinator::setForceSynchronousScrollLayerPositionUpdates(bool forceSynchronousScrollLayerPositionUpdates)
361{
362    if (m_forceSynchronousScrollLayerPositionUpdates == forceSynchronousScrollLayerPositionUpdates)
363        return;
364
365    m_forceSynchronousScrollLayerPositionUpdates = forceSynchronousScrollLayerPositionUpdates;
366    updateSynchronousScrollingReasons(m_page->mainFrame().view());
367}
368
369bool ScrollingCoordinator::shouldUpdateScrollLayerPositionSynchronously() const
370{
371    return synchronousScrollingReasons(m_page->mainFrame().view());
372}
373
374#if ENABLE(WEB_REPLAY)
375void ScrollingCoordinator::replaySessionStateDidChange()
376{
377    // FIXME: Once we support async scrolling of iframes, this should go through all subframes.
378    updateSynchronousScrollingReasons(m_page->mainFrame().view());
379}
380#endif
381
382ScrollingNodeID ScrollingCoordinator::uniqueScrollLayerID()
383{
384    static ScrollingNodeID uniqueScrollLayerID = 1;
385    return uniqueScrollLayerID++;
386}
387
388String ScrollingCoordinator::scrollingStateTreeAsText() const
389{
390    return String();
391}
392
393String ScrollingCoordinator::synchronousScrollingReasonsAsText(SynchronousScrollingReasons reasons)
394{
395    StringBuilder stringBuilder;
396
397    if (reasons & ScrollingCoordinator::ForcedOnMainThread)
398        stringBuilder.append("Forced on main thread, ");
399    if (reasons & ScrollingCoordinator::HasSlowRepaintObjects)
400        stringBuilder.append("Has slow repaint objects, ");
401    if (reasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
402        stringBuilder.append("Has viewport constrained objects without supporting fixed layers, ");
403    if (reasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
404        stringBuilder.append("Has non-layer viewport-constrained objects, ");
405    if (reasons & ScrollingCoordinator::IsImageDocument)
406        stringBuilder.append("Is image document, ");
407
408    if (stringBuilder.length())
409        stringBuilder.resize(stringBuilder.length() - 2);
410    return stringBuilder.toString();
411}
412
413String ScrollingCoordinator::synchronousScrollingReasonsAsText() const
414{
415    return synchronousScrollingReasonsAsText(synchronousScrollingReasons(m_page->mainFrame().view()));
416}
417
418} // namespace WebCore
419