1/*
2 * Copyright (C) 2013 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "core/frame/PinchViewport.h"
33
34#include "core/frame/FrameHost.h"
35#include "core/frame/FrameView.h"
36#include "core/frame/LocalFrame.h"
37#include "core/frame/Settings.h"
38#include "core/page/Chrome.h"
39#include "core/page/ChromeClient.h"
40#include "core/page/Page.h"
41#include "core/page/scrolling/ScrollingCoordinator.h"
42#include "core/rendering/RenderView.h"
43#include "core/rendering/compositing/RenderLayerCompositor.h"
44#include "platform/TraceEvent.h"
45#include "platform/geometry/FloatSize.h"
46#include "platform/graphics/GraphicsLayer.h"
47#include "platform/graphics/GraphicsLayerFactory.h"
48#include "platform/scroll/Scrollbar.h"
49#include "public/platform/Platform.h"
50#include "public/platform/WebCompositorSupport.h"
51#include "public/platform/WebLayer.h"
52#include "public/platform/WebLayerTreeView.h"
53#include "public/platform/WebScrollbar.h"
54#include "public/platform/WebScrollbarLayer.h"
55
56using blink::WebLayer;
57using blink::WebLayerTreeView;
58using blink::WebScrollbar;
59using blink::WebScrollbarLayer;
60using WebCore::FrameHost;
61using WebCore::GraphicsLayer;
62using WebCore::GraphicsLayerFactory;
63
64namespace WebCore {
65
66PinchViewport::PinchViewport(FrameHost& owner)
67    : m_frameHost(owner)
68    , m_scale(1)
69{
70    reset();
71}
72
73PinchViewport::~PinchViewport() { }
74
75void PinchViewport::setSize(const IntSize& size)
76{
77    if (m_size == size)
78        return;
79
80    TRACE_EVENT2("webkit", "PinchViewport::setSize", "width", size.width(), "height", size.height());
81    m_size = size;
82
83    // Make sure we clamp the offset to within the new bounds.
84    setLocation(m_offset);
85
86    if (m_innerViewportContainerLayer) {
87        m_innerViewportContainerLayer->setSize(m_size);
88
89        // Need to re-compute sizes for the overlay scrollbars.
90        setupScrollbar(WebScrollbar::Horizontal);
91        setupScrollbar(WebScrollbar::Vertical);
92    }
93}
94
95void PinchViewport::reset()
96{
97    setLocation(FloatPoint());
98    setScale(1);
99}
100
101void PinchViewport::mainFrameDidChangeSize()
102{
103    TRACE_EVENT0("webkit", "PinchViewport::mainFrameDidChangeSize");
104
105    // In unit tests we may not have initialized the layer tree.
106    if (m_innerViewportScrollLayer)
107        m_innerViewportScrollLayer->setSize(contentsSize());
108
109    // Make sure the viewport's offset is clamped within the newly sized main frame.
110    setLocation(m_offset);
111}
112
113FloatRect PinchViewport::visibleRect() const
114{
115    FloatSize scaledSize(m_size);
116    scaledSize.scale(1 / m_scale);
117    return FloatRect(m_offset, scaledSize);
118}
119
120FloatRect PinchViewport::visibleRectInDocument() const
121{
122    if (!mainFrame() || !mainFrame()->view())
123        return FloatRect();
124
125    FloatRect viewRect = mainFrame()->view()->visibleContentRect();
126    FloatRect pinchRect = visibleRect();
127    pinchRect.moveBy(viewRect.location());
128    return pinchRect;
129}
130
131void PinchViewport::scrollIntoView(const FloatRect& rect)
132{
133    if (!mainFrame() || !mainFrame()->view())
134        return;
135
136    FrameView* view = mainFrame()->view();
137
138    float centeringOffsetX = (visibleRect().width() - rect.width()) / 2;
139    float centeringOffsetY = (visibleRect().height() - rect.height()) / 2;
140
141    FloatPoint targetOffset(
142        rect.x() - centeringOffsetX - visibleRect().x(),
143        rect.y() - centeringOffsetY - visibleRect().y());
144
145    view->setScrollPosition(flooredIntPoint(targetOffset));
146
147    FloatPoint remainder = FloatPoint(targetOffset - view->scrollPosition());
148    move(remainder);
149}
150
151void PinchViewport::setLocation(const FloatPoint& newLocation)
152{
153    FloatPoint clampedOffset(clampOffsetToBoundaries(newLocation));
154
155    if (clampedOffset == m_offset)
156        return;
157
158    m_offset = clampedOffset;
159
160    ScrollingCoordinator* coordinator = m_frameHost.page().scrollingCoordinator();
161    ASSERT(coordinator);
162    coordinator->scrollableAreaScrollLayerDidChange(this);
163
164    mainFrame()->loader().saveScrollState();
165}
166
167void PinchViewport::move(const FloatPoint& delta)
168{
169    setLocation(m_offset + delta);
170}
171
172void PinchViewport::setScale(float scale)
173{
174    if (scale == m_scale)
175        return;
176
177    m_scale = scale;
178
179    if (mainFrame())
180        mainFrame()->loader().saveScrollState();
181
182    // Old-style pinch sets scale here but we shouldn't call into the
183    // clamping code below.
184    if (!m_innerViewportScrollLayer)
185        return;
186
187    // Ensure we clamp so we remain within the bounds.
188    setLocation(visibleRect().location());
189
190    // TODO: We should probably be calling scaleDidChange type functions here.
191    // see Page::setPageScaleFactor.
192}
193
194// Modifies the top of the graphics layer tree to add layers needed to support
195// the inner/outer viewport fixed-position model for pinch zoom. When finished,
196// the tree will look like this (with * denoting added layers):
197//
198// *rootTransformLayer
199//  +- *innerViewportContainerLayer (fixed pos container)
200//      +- *pageScaleLayer
201//  |       +- *innerViewportScrollLayer
202//  |           +-- overflowControlsHostLayer (root layer)
203//  |               +-- outerViewportContainerLayer (fixed pos container) [frame container layer in RenderLayerCompositor]
204//  |               |   +-- outerViewportScrollLayer [frame scroll layer in RenderLayerCompositor]
205//  |               |       +-- content layers ...
206//  |               +-- horizontal ScrollbarLayer (non-overlay)
207//  |               +-- verticalScrollbarLayer (non-overlay)
208//  |               +-- scroll corner (non-overlay)
209//  +- *horizontalScrollbarLayer (overlay)
210//  +- *verticalScrollbarLayer (overlay)
211//
212void PinchViewport::attachToLayerTree(GraphicsLayer* currentLayerTreeRoot, GraphicsLayerFactory* graphicsLayerFactory)
213{
214    TRACE_EVENT1("webkit", "PinchViewport::attachToLayerTree", "currentLayerTreeRoot", (bool)currentLayerTreeRoot);
215    if (!currentLayerTreeRoot) {
216        m_innerViewportScrollLayer->removeAllChildren();
217        return;
218    }
219
220    if (currentLayerTreeRoot->parent() && currentLayerTreeRoot->parent() == m_innerViewportScrollLayer)
221        return;
222
223    if (!m_innerViewportScrollLayer) {
224        ASSERT(!m_overlayScrollbarHorizontal
225            && !m_overlayScrollbarVertical
226            && !m_pageScaleLayer
227            && !m_innerViewportContainerLayer);
228
229        // FIXME: The root transform layer should only be created on demand.
230        m_rootTransformLayer = GraphicsLayer::create(graphicsLayerFactory, this);
231        m_innerViewportContainerLayer = GraphicsLayer::create(graphicsLayerFactory, this);
232        m_pageScaleLayer = GraphicsLayer::create(graphicsLayerFactory, this);
233        m_innerViewportScrollLayer = GraphicsLayer::create(graphicsLayerFactory, this);
234        m_overlayScrollbarHorizontal = GraphicsLayer::create(graphicsLayerFactory, this);
235        m_overlayScrollbarVertical = GraphicsLayer::create(graphicsLayerFactory, this);
236
237        WebCore::ScrollingCoordinator* coordinator = m_frameHost.page().scrollingCoordinator();
238        ASSERT(coordinator);
239        coordinator->setLayerIsContainerForFixedPositionLayers(m_innerViewportScrollLayer.get(), true);
240
241        // Set masks to bounds so the compositor doesn't clobber a manually
242        // set inner viewport container layer size.
243        m_innerViewportContainerLayer->setMasksToBounds(m_frameHost.settings().mainFrameClipsContent());
244        m_innerViewportContainerLayer->setSize(m_size);
245
246        m_innerViewportScrollLayer->platformLayer()->setScrollClipLayer(
247            m_innerViewportContainerLayer->platformLayer());
248        m_innerViewportScrollLayer->platformLayer()->setUserScrollable(true, true);
249
250        m_rootTransformLayer->addChild(m_innerViewportContainerLayer.get());
251        m_innerViewportContainerLayer->addChild(m_pageScaleLayer.get());
252        m_pageScaleLayer->addChild(m_innerViewportScrollLayer.get());
253        m_innerViewportContainerLayer->addChild(m_overlayScrollbarHorizontal.get());
254        m_innerViewportContainerLayer->addChild(m_overlayScrollbarVertical.get());
255
256        // Ensure this class is set as the scroll layer's ScrollableArea.
257        coordinator->scrollableAreaScrollLayerDidChange(this);
258
259        // Setup the inner viewport overlay scrollbars.
260        setupScrollbar(WebScrollbar::Horizontal);
261        setupScrollbar(WebScrollbar::Vertical);
262    }
263
264    m_innerViewportScrollLayer->removeAllChildren();
265    m_innerViewportScrollLayer->addChild(currentLayerTreeRoot);
266
267    // We only need to disable the existing (outer viewport) scrollbars
268    // if the existing ones are already overlay.
269    // FIXME: If we knew in advance before the overflowControlsHostLayer goes
270    // away, we would re-enable the drawing of these scrollbars.
271    // FIXME: This doesn't seem to work (at least on Android). Commenting out for now until
272    // I figure out how to access RenderLayerCompositor from here.
273    // if (GraphicsLayer* scrollbar = m_frameHost->compositor()->layerForHorizontalScrollbar())
274    //    scrollbar->setDrawsContent(!page->mainFrame()->view()->hasOverlayScrollbars());
275    // if (GraphicsLayer* scrollbar = m_frameHost->compositor()->layerForVerticalScrollbar())
276    //    scrollbar->setDrawsContent(!page->mainFrame()->view()->hasOverlayScrollbars());
277}
278
279void PinchViewport::setupScrollbar(WebScrollbar::Orientation orientation)
280{
281    bool isHorizontal = orientation == WebScrollbar::Horizontal;
282    GraphicsLayer* scrollbarGraphicsLayer = isHorizontal ?
283        m_overlayScrollbarHorizontal.get() : m_overlayScrollbarVertical.get();
284    OwnPtr<WebScrollbarLayer>& webScrollbarLayer = isHorizontal ?
285        m_webOverlayScrollbarHorizontal : m_webOverlayScrollbarVertical;
286
287    const int overlayScrollbarThickness = m_frameHost.settings().pinchOverlayScrollbarThickness();
288
289    if (!webScrollbarLayer) {
290        ScrollingCoordinator* coordinator = m_frameHost.page().scrollingCoordinator();
291        ASSERT(coordinator);
292        ScrollbarOrientation webcoreOrientation = isHorizontal ? HorizontalScrollbar : VerticalScrollbar;
293        webScrollbarLayer = coordinator->createSolidColorScrollbarLayer(webcoreOrientation, overlayScrollbarThickness, 0, false);
294
295        webScrollbarLayer->setClipLayer(m_innerViewportContainerLayer->platformLayer());
296        scrollbarGraphicsLayer->setContentsToPlatformLayer(webScrollbarLayer->layer());
297        scrollbarGraphicsLayer->setDrawsContent(false);
298    }
299
300    int xPosition = isHorizontal ? 0 : m_innerViewportContainerLayer->size().width() - overlayScrollbarThickness;
301    int yPosition = isHorizontal ? m_innerViewportContainerLayer->size().height() - overlayScrollbarThickness : 0;
302    int width = isHorizontal ? m_innerViewportContainerLayer->size().width() - overlayScrollbarThickness : overlayScrollbarThickness;
303    int height = isHorizontal ? overlayScrollbarThickness : m_innerViewportContainerLayer->size().height() - overlayScrollbarThickness;
304
305    // Use the GraphicsLayer to position the scrollbars.
306    scrollbarGraphicsLayer->setPosition(IntPoint(xPosition, yPosition));
307    scrollbarGraphicsLayer->setSize(IntSize(width, height));
308    scrollbarGraphicsLayer->setContentsRect(IntRect(0, 0, width, height));
309}
310
311void PinchViewport::registerLayersWithTreeView(WebLayerTreeView* layerTreeView) const
312{
313    TRACE_EVENT0("webkit", "PinchViewport::registerLayersWithTreeView");
314    ASSERT(layerTreeView);
315    ASSERT(m_frameHost.page().mainFrame());
316    ASSERT(m_frameHost.page().mainFrame()->isLocalFrame());
317    ASSERT(m_frameHost.page().deprecatedLocalMainFrame()->contentRenderer());
318
319    RenderLayerCompositor* compositor = m_frameHost.page().deprecatedLocalMainFrame()->contentRenderer()->compositor();
320    // Get the outer viewport scroll layer.
321    WebLayer* scrollLayer = compositor->scrollLayer()->platformLayer();
322
323    m_webOverlayScrollbarHorizontal->setScrollLayer(scrollLayer);
324    m_webOverlayScrollbarVertical->setScrollLayer(scrollLayer);
325
326    ASSERT(compositor);
327    layerTreeView->registerViewportLayers(
328        m_pageScaleLayer->platformLayer(),
329        m_innerViewportScrollLayer->platformLayer(),
330        scrollLayer);
331}
332
333void PinchViewport::clearLayersForTreeView(WebLayerTreeView* layerTreeView) const
334{
335    ASSERT(layerTreeView);
336
337    layerTreeView->clearViewportLayers();
338}
339
340int PinchViewport::scrollSize(ScrollbarOrientation orientation) const
341{
342    IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition();
343    return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height();
344}
345
346IntPoint PinchViewport::minimumScrollPosition() const
347{
348    return IntPoint();
349}
350
351IntPoint PinchViewport::maximumScrollPosition() const
352{
353    return flooredIntPoint(FloatSize(contentsSize()) - visibleRect().size());
354}
355
356IntRect PinchViewport::scrollableAreaBoundingBox() const
357{
358    // This method should return the bounding box in the parent view's coordinate
359    // space; however, PinchViewport technically isn't a child of any Frames.
360    // Nonetheless, the PinchViewport always occupies the entire main frame so just
361    // return that.
362    LocalFrame* frame = mainFrame();
363
364    if (!frame || !frame->view())
365        return IntRect();
366
367    return frame->view()->frameRect();
368}
369
370IntSize PinchViewport::contentsSize() const
371{
372    LocalFrame* frame = mainFrame();
373
374    if (!frame || !frame->view())
375        return IntSize();
376
377    ASSERT(frame->view()->visibleContentScaleFactor() == 1);
378    return frame->view()->visibleContentRect(IncludeScrollbars).size();
379}
380
381void PinchViewport::invalidateScrollbarRect(Scrollbar*, const IntRect&)
382{
383    // Do nothing. Pinch scrollbars live on the compositor thread and will
384    // be updated when the viewport is synced to the CC.
385}
386
387void PinchViewport::setScrollOffset(const IntPoint& offset)
388{
389    setLocation(offset);
390}
391
392GraphicsLayer* PinchViewport::layerForContainer() const
393{
394    return m_innerViewportContainerLayer.get();
395}
396
397GraphicsLayer* PinchViewport::layerForScrolling() const
398{
399    return m_innerViewportScrollLayer.get();
400}
401
402GraphicsLayer* PinchViewport::layerForHorizontalScrollbar() const
403{
404    return m_overlayScrollbarHorizontal.get();
405}
406
407GraphicsLayer* PinchViewport::layerForVerticalScrollbar() const
408{
409    return m_overlayScrollbarVertical.get();
410}
411
412void PinchViewport::notifyAnimationStarted(const GraphicsLayer*, double monotonicTime)
413{
414}
415
416void PinchViewport::paintContents(const GraphicsLayer*, GraphicsContext&, GraphicsLayerPaintingPhase, const IntRect& inClip)
417{
418}
419
420LocalFrame* PinchViewport::mainFrame() const
421{
422    return m_frameHost.page().mainFrame() && m_frameHost.page().mainFrame()->isLocalFrame() ? m_frameHost.page().deprecatedLocalMainFrame() : 0;
423}
424
425FloatPoint PinchViewport::clampOffsetToBoundaries(const FloatPoint& offset)
426{
427    FloatPoint clampedOffset(offset);
428    clampedOffset = clampedOffset.shrunkTo(FloatPoint(maximumScrollPosition()));
429    clampedOffset = clampedOffset.expandedTo(FloatPoint(minimumScrollPosition()));
430    return clampedOffset;
431}
432
433String PinchViewport::debugName(const GraphicsLayer* graphicsLayer)
434{
435    String name;
436    if (graphicsLayer == m_innerViewportContainerLayer.get()) {
437        name = "Inner Viewport Container Layer";
438    } else if (graphicsLayer == m_pageScaleLayer.get()) {
439        name =  "Page Scale Layer";
440    } else if (graphicsLayer == m_innerViewportScrollLayer.get()) {
441        name =  "Inner Viewport Scroll Layer";
442    } else if (graphicsLayer == m_overlayScrollbarHorizontal.get()) {
443        name =  "Overlay Scrollbar Horizontal Layer";
444    } else if (graphicsLayer == m_overlayScrollbarVertical.get()) {
445        name =  "Overlay Scrollbar Vertical Layer";
446    } else {
447        ASSERT_NOT_REACHED();
448    }
449
450    return name;
451}
452
453} // namespace WebCore
454