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