1/*
2 * Copyright (C) 2011, 2012, 2013 Research In Motion Limited. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19#include "config.h"
20#include "InRegionScroller.h"
21
22#include "BackingStoreClient.h"
23#include "DOMSupport.h"
24#include "Frame.h"
25#include "HTMLFrameOwnerElement.h"
26#include "HitTestResult.h"
27#include "InRegionScrollableArea.h"
28#include "InRegionScroller_p.h"
29#include "LayerCompositingThread.h"
30#include "LayerWebKitThread.h"
31#include "Page.h"
32#include "RenderBox.h"
33#include "RenderLayer.h"
34#include "RenderLayerBacking.h"
35#include "RenderLayerCompositor.h"
36#include "RenderObject.h"
37#include "RenderView.h"
38#include "SelectionHandler.h"
39#include "WebKitThreadViewportAccessor.h"
40#include "WebPage_p.h"
41
42#include <BlackBerryPlatformViewportAccessor.h>
43
44using namespace WebCore;
45
46namespace BlackBerry {
47namespace WebKit {
48
49static bool canScrollInnerFrame(Frame*);
50static RenderLayer* parentLayer(RenderLayer*);
51static bool isNonRenderViewFixedPositionedContainer(RenderLayer*);
52
53InRegionScroller::InRegionScroller(WebPagePrivate* webPagePrivate)
54    : d(new InRegionScrollerPrivate(webPagePrivate))
55{
56    ASSERT(webPagePrivate);
57}
58
59InRegionScroller::~InRegionScroller()
60{
61    delete d;
62}
63
64bool InRegionScroller::setDocumentScrollPositionCompositingThread(unsigned camouflagedLayer, const Platform::IntPoint& documentScrollPosition)
65{
66    ASSERT(Platform::userInterfaceThreadMessageClient()->isCurrentThread());
67
68    return d->setScrollPositionCompositingThread(camouflagedLayer, documentScrollPosition);
69}
70
71bool InRegionScroller::setDocumentScrollPositionWebKitThread(unsigned camouflagedLayer, const Platform::IntPoint& documentScrollPosition,
72    bool supportsAcceleratedScrolling, Platform::ScrollViewBase::ScrollTarget scrollTarget)
73{
74    ASSERT(Platform::webKitThreadMessageClient()->isCurrentThread());
75
76    return d->setScrollPositionWebKitThread(camouflagedLayer, documentScrollPosition, supportsAcceleratedScrolling, scrollTarget);
77}
78
79InRegionScrollerPrivate::InRegionScrollerPrivate(WebPagePrivate* webPagePrivate)
80    : m_webPage(webPagePrivate)
81    , m_needsActiveScrollableAreaCalculation(false)
82    , m_selectionScrollView(0)
83{
84}
85
86void InRegionScrollerPrivate::reset()
87{
88    // Notify the client side to clear InRegion scrollable areas before we destroy them here.
89    std::vector<Platform::ScrollViewBase*> emptyInRegionScrollableAreas;
90    m_webPage->m_client->notifyInRegionScrollableAreasChanged(emptyInRegionScrollableAreas);
91
92    m_needsActiveScrollableAreaCalculation = false;
93    for (size_t i = 0; i < m_activeInRegionScrollableAreas.size(); ++i)
94        delete m_activeInRegionScrollableAreas[i];
95    m_activeInRegionScrollableAreas.clear();
96}
97
98void InRegionScrollerPrivate::resetSelectionScrollView()
99{
100    m_webPage->m_client->notifySelectionScrollView(0);
101    m_webPage->m_selectionHandler->setSelectionSubframeViewportRect(WebCore::IntRect());
102    if (m_selectionScrollView) {
103        delete m_selectionScrollView;
104        m_selectionScrollView = 0;
105    }
106}
107
108bool InRegionScrollerPrivate::isActive() const
109{
110    return m_activeInRegionScrollableAreas.size() > 0;
111}
112
113void InRegionScrollerPrivate::clearDocumentData(const Document* documentGoingAway)
114{
115    InRegionScrollableArea* scrollableArea = static_cast<InRegionScrollableArea*>(m_selectionScrollView);
116    if (scrollableArea && scrollableArea->document() == documentGoingAway)
117        resetSelectionScrollView();
118
119    if (m_needsActiveScrollableAreaCalculation) {
120        reset();
121        return;
122    }
123
124    scrollableArea = static_cast<InRegionScrollableArea*>(m_activeInRegionScrollableAreas[0]);
125    ASSERT(scrollableArea);
126    if (scrollableArea->document() == documentGoingAway)
127        reset();
128}
129
130bool InRegionScrollerPrivate::setScrollPositionCompositingThread(unsigned camouflagedLayer, const WebCore::IntPoint& scrollPosition)
131{
132    LayerWebKitThread* layerWebKitThread = reinterpret_cast<LayerWebKitThread*>(camouflagedLayer);
133    if (!isValidScrollableLayerWebKitThread(layerWebKitThread))
134        return false;
135
136    LayerCompositingThread* scrollLayer = layerWebKitThread->layerCompositingThread();
137
138    // FIXME: Clamp maximum and minimum scroll positions as a last attempt to fix round errors.
139    FloatPoint anchor;
140    if (scrollLayer->override()->isAnchorPointSet())
141        anchor = scrollLayer->override()->anchorPoint();
142    else
143        anchor = scrollLayer->anchorPoint();
144
145    FloatSize bounds;
146    if (scrollLayer->override()->isBoundsSet())
147        bounds = scrollLayer->override()->bounds();
148    else
149        bounds = scrollLayer->bounds();
150
151    // Position is offset on the layer by the layer anchor point.
152    FloatPoint layerPosition(-scrollPosition.x() + anchor.x() * bounds.width(), -scrollPosition.y() + anchor.y() * bounds.height());
153
154    scrollLayer->override()->setPosition(FloatPoint(layerPosition.x(), layerPosition.y()));
155
156    // The client is going to blitVisibleContens, which allow us benefit from "defer blits" technique.
157    return true;
158}
159
160bool InRegionScrollerPrivate::setScrollPositionWebKitThread(unsigned camouflagedLayer, const WebCore::IntPoint& scrollPosition,
161    bool supportsAcceleratedScrolling, Platform::ScrollViewBase::ScrollTarget scrollTarget)
162{
163    RenderLayer* layer = 0;
164
165    if (supportsAcceleratedScrolling) {
166        LayerWebKitThread* layerWebKitThread = reinterpret_cast<LayerWebKitThread*>(camouflagedLayer);
167        if (!isValidScrollableLayerWebKitThread(layerWebKitThread))
168            return false;
169
170        if (layerWebKitThread->owner()) {
171            GraphicsLayer* graphicsLayer = layerWebKitThread->owner();
172
173            if (scrollTarget == Platform::ScrollViewBase::BlockElement) {
174                RenderLayerBacking* backing = static_cast<RenderLayerBacking*>(graphicsLayer->client());
175                layer = backing->owningLayer();
176            } else {
177                RenderLayerCompositor* compositor = static_cast<RenderLayerCompositor*>(graphicsLayer->client());
178                layer = compositor->rootRenderLayer();
179            }
180        }
181    } else {
182        Node* node = reinterpret_cast<Node*>(camouflagedLayer);
183        if (!isValidScrollableNode(node) || !node->renderer())
184            return false;
185
186        layer = node->renderer()->enclosingLayer();
187    }
188
189    if (!layer)
190        return false;
191
192    calculateActiveAndShrinkCachedScrollableAreas(layer);
193
194    // FIXME: Clamp maximum and minimum scroll positions as a last attempt to fix round errors.
195    return setLayerScrollPosition(layer, scrollPosition);
196}
197
198void InRegionScrollerPrivate::calculateActiveAndShrinkCachedScrollableAreas(RenderLayer* layer)
199{
200    if (!m_needsActiveScrollableAreaCalculation)
201        return;
202
203    ASSERT(layer);
204    std::vector<Platform::ScrollViewBase*>::iterator end = m_activeInRegionScrollableAreas.end();
205    std::vector<Platform::ScrollViewBase*>::iterator it = m_activeInRegionScrollableAreas.begin();
206    while (it != end) {
207        InRegionScrollableArea* curr = static_cast<InRegionScrollableArea*>(*it);
208
209        if (layer == curr->layer()) {
210            ++it;
211            continue;
212        }
213
214        delete *it;
215        it = m_activeInRegionScrollableAreas.erase(it);
216        // ::erase invalidates the iterators.
217        end = m_activeInRegionScrollableAreas.end();
218    }
219
220    ASSERT(m_activeInRegionScrollableAreas.size() == 1);
221    m_needsActiveScrollableAreaCalculation = false;
222}
223
224WebCore::IntRect InRegionScrollerPrivate::clipToRect(const WebCore::IntRect& clippingRect, InRegionScrollableArea* scrollable)
225{
226    RenderLayer* layer = scrollable->layer();
227    if (!layer)
228        return clippingRect;
229
230    const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
231
232    if (layer->renderer()->isRenderView()) { // #document case
233        FrameView* view = toRenderView(layer->renderer())->frameView();
234        ASSERT(view);
235        ASSERT(canScrollInnerFrame(view->frame()));
236
237        WebCore::IntRect frameWindowRect = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatRect(m_webPage->getRecursiveVisibleWindowRect(view)));
238        frameWindowRect.intersect(clippingRect);
239        return frameWindowRect;
240    }
241
242    RenderBox* box = layer->renderBox();
243    ASSERT(box);
244    ASSERT(canScrollRenderBox(box));
245
246    // We want the window rect in pixel viewport coordinates clipped to the clipping rect.
247    WebCore::IntRect visibleWindowRect = enclosingIntRect(box->absoluteClippedOverflowRect());
248    visibleWindowRect = box->frame()->view()->contentsToWindow(visibleWindowRect);
249    visibleWindowRect = viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatRect(visibleWindowRect));
250    visibleWindowRect.intersect(clippingRect);
251    return visibleWindowRect;
252}
253
254void InRegionScrollerPrivate::calculateInRegionScrollableAreasForPoint(const WebCore::IntPoint& documentPoint)
255{
256    ASSERT(m_activeInRegionScrollableAreas.empty());
257    m_needsActiveScrollableAreaCalculation = false;
258    const HitTestResult& result = m_webPage->hitTestResult(documentPoint);
259    Node* node = result.innerNonSharedNode();
260    if (!node || !node->renderer())
261        return;
262
263    RenderLayer* layer = node->renderer()->enclosingLayer();
264    if (!layer)
265        return;
266
267    do {
268
269        RenderObject* renderer = layer->renderer();
270
271        if (renderer && renderer->isRenderView()) {
272            if (RenderView* renderView = toRenderView(renderer)) {
273                FrameView* view = renderView->frameView();
274                if (!view) {
275                    reset();
276                    return;
277                }
278
279                if (!renderView->compositor()->scrollLayer())
280                    continue;
281
282                if (canScrollInnerFrame(view->frame())) {
283                    pushBackInRegionScrollable(new InRegionScrollableArea(m_webPage, layer));
284                    continue;
285                }
286            }
287        } else if (canScrollRenderBox(layer->renderBox())) {
288            pushBackInRegionScrollable(new InRegionScrollableArea(m_webPage, layer));
289            continue;
290        }
291
292        // If we run into a fix positioned layer, set the last scrollable in-region object
293        // as not able to propagate scroll to its parent scrollable.
294        if (isNonRenderViewFixedPositionedContainer(layer) && m_activeInRegionScrollableAreas.size()) {
295            Platform::ScrollViewBase* end = m_activeInRegionScrollableAreas.back();
296            end->setCanPropagateScrollingToEnclosingScrollable(false);
297        }
298
299    } while ((layer = parentLayer(layer)));
300
301    if (m_activeInRegionScrollableAreas.empty())
302        return;
303
304    m_needsActiveScrollableAreaCalculation = true;
305
306    // Post-calculate the visible window rects in reverse hit test order so
307    // we account for all and any clipping rects.
308    WebCore::IntRect recursiveClippingRect(WebCore::IntPoint::zero(), m_webPage->transformedViewportSize());
309
310    for (int i = m_activeInRegionScrollableAreas.size() - 1; i >= 0; --i) {
311        InRegionScrollableArea* scrollable = static_cast<InRegionScrollableArea*>(m_activeInRegionScrollableAreas[i]);
312        scrollable->setVisibleWindowRect(clipToRect(recursiveClippingRect, scrollable));
313        recursiveClippingRect = scrollable->visibleWindowRect();
314    }
315}
316
317void InRegionScrollerPrivate::updateSelectionScrollView(const Node* node)
318{
319    // TODO: don't notify the client if the node didn't change.
320    m_selectionScrollView = firstScrollableInRegionForNode(node);
321    m_webPage->m_client->notifySelectionScrollView(m_selectionScrollView);
322    // If there's no subframe set an empty rect so that we default to the main frame.
323    m_webPage->m_selectionHandler->setSelectionSubframeViewportRect(m_selectionScrollView ? WebCore::IntRect(m_selectionScrollView->documentViewportRect()) : WebCore::IntRect());
324}
325
326Platform::ScrollViewBase* InRegionScrollerPrivate::firstScrollableInRegionForNode(const Node* node)
327{
328    if (!node || !node->renderer())
329        return 0;
330
331    RenderLayer* layer = node->renderer()->enclosingLayer();
332    if (!layer)
333        return 0;
334    do {
335        RenderObject* renderer = layer->renderer();
336
337        if (renderer->isRenderView()) {
338            if (RenderView* renderView = toRenderView(renderer)) {
339                FrameView* view = renderView->frameView();
340                if (!view) {
341                    resetSelectionScrollView();
342                    return 0;
343                }
344
345                if (!renderView->compositor()->scrollLayer())
346                    continue;
347
348                if (canScrollInnerFrame(view->frame()))
349                    return clipAndCreateInRegionScrollableArea(layer);
350            }
351        } else if (canScrollRenderBox(layer->renderBox()))
352            return clipAndCreateInRegionScrollableArea(layer);
353
354        // If we run into a fix positioned layer, set the last scrollable in-region object
355        // as not able to propagate scroll to its parent scrollable.
356        if (isNonRenderViewFixedPositionedContainer(layer) && m_activeInRegionScrollableAreas.size()) {
357            Platform::ScrollViewBase* end = m_activeInRegionScrollableAreas.back();
358            end->setCanPropagateScrollingToEnclosingScrollable(false);
359        }
360
361    } while ((layer = parentLayer(layer)));
362    return 0;
363}
364
365Platform::ScrollViewBase* InRegionScrollerPrivate::clipAndCreateInRegionScrollableArea(RenderLayer* layer)
366{
367    WebCore::IntRect recursiveClippingRect(WebCore::IntPoint::zero(), m_webPage->transformedViewportSize());
368    InRegionScrollableArea* scrollable = new InRegionScrollableArea(m_webPage, layer);
369    scrollable->setVisibleWindowRect(clipToRect(recursiveClippingRect, scrollable));
370    return scrollable;
371}
372
373const std::vector<Platform::ScrollViewBase*>& InRegionScrollerPrivate::activeInRegionScrollableAreas() const
374{
375    return m_activeInRegionScrollableAreas;
376}
377
378bool InRegionScrollerPrivate::setLayerScrollPosition(RenderLayer* layer, const IntPoint& scrollPosition)
379{
380    RenderObject* layerRenderer = layer->renderer();
381    ASSERT(layerRenderer);
382
383    if (layerRenderer->isRenderView()) { // #document case.
384        FrameView* view = toRenderView(layerRenderer)->frameView();
385        ASSERT(view);
386
387        Frame* frame = view->frame();
388        ASSERT_UNUSED(frame, frame);
389        ASSERT(canScrollInnerFrame(frame));
390
391        view->setCanBlitOnScroll(false);
392        view->setScrollPosition(scrollPosition);
393
394    } else {
395
396        // RenderBox-based elements case (scrollable boxes (div's, p's, textarea's, etc)).
397        layer->scrollToOffset(toIntSize(scrollPosition));
398    }
399
400    layer->renderer()->frame()->selection()->updateAppearance();
401    // FIXME: We have code in place to handle scrolling and clipping tap highlight
402    // on in-region scrolling. As soon as it is fast enough (i.e. we have it backed by
403    // a backing store), we can reliably make use of it in the real world.
404    // m_touchEventHandler->drawTapHighlight();
405    return true;
406}
407
408void InRegionScrollerPrivate::adjustScrollDelta(const WebCore::IntPoint& maxOffset, const WebCore::IntPoint& currentOffset, WebCore::IntSize& delta) const
409{
410    if (currentOffset.x() + delta.width() > maxOffset.x())
411        delta.setWidth(std::min(maxOffset.x() - currentOffset.x(), delta.width()));
412
413    if (currentOffset.x() + delta.width() < 0)
414        delta.setWidth(std::max(-currentOffset.x(), delta.width()));
415
416    if (currentOffset.y() + delta.height() > maxOffset.y())
417        delta.setHeight(std::min(maxOffset.y() - currentOffset.y(), delta.height()));
418
419    if (currentOffset.y() + delta.height() < 0)
420        delta.setHeight(std::max(-currentOffset.y(), delta.height()));
421}
422
423static bool canScrollInnerFrame(Frame* frame)
424{
425    if (!frame || !frame->view())
426        return false;
427
428    // Not having an owner element means that we are on the mainframe.
429    if (!frame->ownerElement())
430        return false;
431
432    ASSERT(frame != frame->page()->mainFrame());
433
434    IntSize visibleSize = frame->view()->visibleContentRect().size();
435    IntSize contentsSize = frame->view()->contentsSize();
436
437    bool canBeScrolled = contentsSize.height() > visibleSize.height() || contentsSize.width() > visibleSize.width();
438
439    // Lets also consider the 'overflow-{x,y} property set directly to the {i}frame tag.
440    return canBeScrolled && (frame->ownerElement()->scrollingMode() != ScrollbarAlwaysOff);
441}
442
443// The RenderBox::canbeScrolledAndHasScrollableArea method returns true for the
444// following scenario, for example:
445// (1) a div that has a vertical overflow but no horizontal overflow
446//     with overflow-y: hidden and overflow-x: auto set.
447// The version below fixes it.
448// FIXME: Fix RenderBox::canBeScrolledAndHasScrollableArea method instead.
449bool InRegionScrollerPrivate::canScrollRenderBox(RenderBox* box)
450{
451    if (!box)
452        return false;
453
454    // We use this to make non-overflown contents layers to actually
455    // be overscrollable.
456    if (box->layer() && box->layer()->usesCompositedScrolling()) {
457        if (box->style()->overflowScrolling() == OSBlackberryTouch)
458            return true;
459    }
460
461    if (!box->hasOverflowClip())
462        return false;
463
464    if (box->scrollHeight() == box->clientHeight() && box->scrollWidth() == box->clientWidth())
465        return false;
466
467    if ((box->scrollsOverflowX() && (box->scrollWidth() != box->clientWidth()))
468        || (box->scrollsOverflowY() && (box->scrollHeight() != box->clientHeight())))
469        return true;
470
471    Node* node = box->node();
472    return node && (DOMSupport::isShadowHostTextInputElement(node) || node->isDocumentNode());
473}
474
475static RenderLayer* parentLayer(RenderLayer* layer)
476{
477    ASSERT(layer);
478    if (layer->parent())
479        return layer->parent();
480
481    RenderObject* renderer = layer->renderer();
482    Document* document = renderer->document();
483    if (document) {
484        HTMLFrameOwnerElement* ownerElement = document->ownerElement();
485        if (ownerElement) {
486            RenderObject* subRenderer = ownerElement->renderer();
487            if (subRenderer)
488                return subRenderer->enclosingLayer();
489        }
490    }
491
492    return 0;
493}
494
495static bool isNonRenderViewFixedPositionedContainer(RenderLayer* layer)
496{
497    RenderObject* o = layer->renderer();
498    if (o->isRenderView())
499        return false;
500
501    return o->isOutOfFlowPositioned() && o->style()->position() == FixedPosition;
502}
503
504void InRegionScrollerPrivate::pushBackInRegionScrollable(InRegionScrollableArea* scrollableArea)
505{
506    ASSERT(!scrollableArea->isNull());
507
508    scrollableArea->setCanPropagateScrollingToEnclosingScrollable(!isNonRenderViewFixedPositionedContainer(scrollableArea->layer()));
509    m_activeInRegionScrollableAreas.push_back(scrollableArea);
510}
511
512bool InRegionScrollerPrivate::isValidScrollableLayerWebKitThread(LayerWebKitThread* layerWebKitThread) const
513{
514    if (!layerWebKitThread)
515        return false;
516
517    for (unsigned i = 0; i < m_activeInRegionScrollableAreas.size(); ++i) {
518        if (static_cast<InRegionScrollableArea*>(m_activeInRegionScrollableAreas[i])->cachedScrollableLayer() == layerWebKitThread)
519            return true;
520    }
521
522    return false;
523}
524
525bool InRegionScrollerPrivate::isValidScrollableNode(Node* node) const
526{
527    if (!node)
528        return false;
529
530    for (unsigned i = 0; i < m_activeInRegionScrollableAreas.size(); ++i) {
531        if (static_cast<InRegionScrollableArea*>(m_activeInRegionScrollableAreas[i])->cachedScrollableNode() == node)
532            return true;
533    }
534
535    return false;
536}
537
538}
539}
540