1/* 2 * Copyright (C) 2011, 2012 Nokia Corporation and/or its subsidiary(-ies) 3 * Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org> 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this program; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 * 20 */ 21 22#include "config.h" 23 24#if USE(ACCELERATED_COMPOSITING) 25#include "PageViewportController.h" 26 27#include "PageViewportControllerClient.h" 28#include "WebPageProxy.h" 29#include <WebCore/FloatRect.h> 30#include <WebCore/FloatSize.h> 31#include <wtf/MathExtras.h> 32 33using namespace WebCore; 34 35namespace WebKit { 36 37bool fuzzyCompare(float a, float b, float epsilon) 38{ 39 return std::abs(a - b) < epsilon; 40} 41 42PageViewportController::PageViewportController(WebKit::WebPageProxy* proxy, PageViewportControllerClient* client) 43 : m_webPageProxy(proxy) 44 , m_client(client) 45 , m_allowsUserScaling(false) 46 , m_minimumScaleToFit(1) 47 , m_initiallyFitToViewport(true) 48 , m_hadUserInteraction(false) 49 , m_pageScaleFactor(1) 50 , m_pendingPositionChange(false) 51 , m_pendingScaleChange(false) 52{ 53 // Initializing Viewport Raw Attributes to avoid random negative or infinity scale factors 54 // if there is a race condition between the first layout and setting the viewport attributes for the first time. 55 m_rawAttributes.minimumScale = 1; 56 m_rawAttributes.maximumScale = 1; 57 m_rawAttributes.userScalable = m_allowsUserScaling; 58 59 // The initial scale might be implicit and set to -1, in this case we have to infer it 60 // using the viewport size and the final layout size. 61 // To be able to assert for valid scale we initialize it to -1. 62 m_rawAttributes.initialScale = -1; 63 64 ASSERT(m_client); 65 m_client->setController(this); 66} 67 68float PageViewportController::innerBoundedViewportScale(float viewportScale) const 69{ 70 return clampTo(viewportScale, m_minimumScaleToFit, m_rawAttributes.maximumScale); 71} 72 73float PageViewportController::outerBoundedViewportScale(float viewportScale) const 74{ 75 if (m_allowsUserScaling) { 76 // Bounded by [0.1, 10.0] like the viewport meta code in WebCore. 77 float hardMin = std::max<float>(0.1, 0.5 * m_minimumScaleToFit); 78 float hardMax = std::min<float>(10, 2 * m_rawAttributes.maximumScale); 79 return clampTo(viewportScale, hardMin, hardMax); 80 } 81 return innerBoundedViewportScale(viewportScale); 82} 83 84float PageViewportController::deviceScaleFactor() const 85{ 86 return m_webPageProxy->deviceScaleFactor(); 87} 88 89static inline bool isIntegral(float value) 90{ 91 return static_cast<int>(value) == value; 92} 93 94FloatPoint PageViewportController::pixelAlignedFloatPoint(const FloatPoint& framePosition) 95{ 96#if PLATFORM(EFL) 97 float effectiveScale = m_pageScaleFactor * deviceScaleFactor(); 98 if (!isIntegral(effectiveScale)) { 99 // To avoid blurryness, modify the position so that it maps into a discrete device position. 100 FloatPoint scaledPos(framePosition); 101 102 // Scale by the effective scale factor to compute the screen-relative position. 103 scaledPos.scale(effectiveScale, effectiveScale); 104 105 // Round to integer boundaries. 106 FloatPoint alignedPos = roundedIntPoint(scaledPos); 107 108 // Convert back to CSS coordinates. 109 alignedPos.scale(1 / effectiveScale, 1 / effectiveScale); 110 111 return alignedPos; 112 } 113#endif 114 115 return framePosition; 116} 117 118FloatPoint PageViewportController::boundContentsPositionAtScale(const WebCore::FloatPoint& framePosition, float scale) 119{ 120 // We need to floor the viewport here as to allow aligning the content in device units. If not, 121 // it might not be possible to scroll the last pixel and that affects fixed position elements. 122 FloatRect bounds; 123 bounds.setWidth(std::max(0.f, m_contentsSize.width() - floorf(m_viewportSize.width() / scale))); 124 bounds.setHeight(std::max(0.f, m_contentsSize.height() - floorf(m_viewportSize.height() / scale))); 125 126 FloatPoint position; 127 position.setX(clampTo(framePosition.x(), bounds.x(), bounds.width())); 128 position.setY(clampTo(framePosition.y(), bounds.y(), bounds.height())); 129 130 return position; 131} 132 133FloatPoint PageViewportController::boundContentsPosition(const WebCore::FloatPoint& framePosition) 134{ 135 return boundContentsPositionAtScale(framePosition, m_pageScaleFactor); 136} 137 138void PageViewportController::didCommitLoad() 139{ 140 // Do not count the previous committed page contents as covered. 141 m_lastFrameCoveredRect = FloatRect(); 142 143 // Do not continue to use the content size of the previous page. 144 m_contentsSize = IntSize(); 145 146 // Reset the position to the top, page/history scroll requests may override this before we re-enable rendering. 147 applyPositionAfterRenderingContents(FloatPoint()); 148} 149 150void PageViewportController::didChangeContentsSize(const IntSize& newSize) 151{ 152 m_contentsSize = newSize; 153 154 bool minimumScaleUpdated = updateMinimumScaleToFit(false); 155 156 if (m_initiallyFitToViewport) { 157 // Restrict scale factors to m_minimumScaleToFit. 158 ASSERT(m_minimumScaleToFit > 0); 159 m_rawAttributes.initialScale = m_minimumScaleToFit; 160 WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(m_rawAttributes); 161 } 162 163 if (minimumScaleUpdated) 164 m_client->didChangeViewportAttributes(); 165 166 // We might have pending position change which is now possible. 167 syncVisibleContents(); 168} 169 170void PageViewportController::didRenderFrame(const IntSize& contentsSize, const IntRect& coveredRect) 171{ 172 if (m_clientContentsSize != contentsSize) { 173 m_clientContentsSize = contentsSize; 174 // Only update the viewport's contents dimensions along with its render if the 175 // size actually changed since animations on the page trigger DidRenderFrame 176 // messages without causing dimension changes. 177 m_client->didChangeContentsSize(contentsSize); 178 } 179 180 m_lastFrameCoveredRect = coveredRect; 181 182 // Apply any scale or scroll position we locked to be set on the viewport 183 // only when there is something to display there. The scale goes first to 184 // avoid offsetting our deferred position by scaling at the viewport center. 185 // All position and scale changes resulting from a web process event should 186 // go through here to be applied on the viewport to avoid showing incomplete 187 // tiles to the user during a few milliseconds. 188 189 if (m_pendingScaleChange) { 190 m_pendingScaleChange = false; 191 m_client->setPageScaleFactor(m_pageScaleFactor); 192 193 // The scale changed, we have to re-pixel align. 194 m_pendingPositionChange = true; 195 FloatPoint currentDiscretePos = roundedIntPoint(m_contentsPosition); 196 FloatPoint pixelAlignedPos = pixelAlignedFloatPoint(currentDiscretePos); 197 m_contentsPosition = boundContentsPosition(pixelAlignedPos); 198 199 m_webPageProxy->scalePage(m_pageScaleFactor, roundedIntPoint(m_contentsPosition)); 200 } 201 202 // There might be rendered frames not covering our requested position yet, wait for it. 203 FloatRect endVisibleContentRect(m_contentsPosition, visibleContentsSize()); 204 if (m_pendingPositionChange && endVisibleContentRect.intersects(coveredRect)) { 205 m_client->setViewportPosition(m_contentsPosition); 206 m_pendingPositionChange = false; 207 } 208} 209 210void PageViewportController::pageTransitionViewportReady() 211{ 212 if (!m_rawAttributes.layoutSize.isEmpty()) { 213 m_hadUserInteraction = false; 214 float initialScale = m_initiallyFitToViewport ? m_minimumScaleToFit : m_rawAttributes.initialScale; 215 applyScaleAfterRenderingContents(innerBoundedViewportScale(initialScale)); 216 } 217 218 // At this point we should already have received the first viewport arguments and the requested scroll 219 // position for the newly loaded page and sent our reactions to the web process. It's now safe to tell 220 // the web process to start rendering the new page contents and possibly re-use the current tiles. 221 // This assumes that all messages have been handled in order and that nothing has been pushed back on the event loop. 222 m_webPageProxy->commitPageTransitionViewport(); 223} 224 225void PageViewportController::pageDidRequestScroll(const IntPoint& cssPosition) 226{ 227 // Ignore the request if suspended. Can only happen due to delay in event delivery. 228 if (m_webPageProxy->areActiveDOMObjectsAndAnimationsSuspended()) 229 return; 230 231 FloatPoint boundPosition = boundContentsPosition(FloatPoint(cssPosition)); 232 FloatPoint alignedPosition = pixelAlignedFloatPoint(boundPosition); 233 FloatRect endVisibleContentRect(alignedPosition, visibleContentsSize()); 234 235 if (m_lastFrameCoveredRect.intersects(endVisibleContentRect)) 236 m_client->setViewportPosition(alignedPosition); 237 else { 238 // Keep the unbound position in case the contents size is changed later on. 239 FloatPoint position = pixelAlignedFloatPoint(FloatPoint(cssPosition)); 240 applyPositionAfterRenderingContents(position); 241 } 242} 243 244void PageViewportController::didChangeViewportSize(const FloatSize& newSize) 245{ 246 if (newSize.isEmpty()) 247 return; 248 249 m_viewportSize = newSize; 250 251 // Let the WebProcess know about the new viewport size, so that 252 // it can resize the content accordingly. 253 m_webPageProxy->drawingArea()->setSize(roundedIntSize(newSize), IntSize(), IntSize()); 254} 255 256void PageViewportController::didChangeContentsVisibility(const FloatPoint& position, float scale, const FloatPoint& trajectoryVector) 257{ 258 if (!m_pendingPositionChange) 259 m_contentsPosition = position; 260 if (!m_pendingScaleChange) 261 applyScaleAfterRenderingContents(scale); 262 263 syncVisibleContents(trajectoryVector); 264} 265 266void PageViewportController::syncVisibleContents(const FloatPoint& trajectoryVector) 267{ 268 DrawingAreaProxy* drawingArea = m_webPageProxy->drawingArea(); 269 if (!drawingArea || m_viewportSize.isEmpty() || m_contentsSize.isEmpty()) 270 return; 271 272 FloatRect visibleContentsRect(boundContentsPosition(m_contentsPosition), visibleContentsSize()); 273 visibleContentsRect.intersect(FloatRect(FloatPoint::zero(), m_contentsSize)); 274 drawingArea->setVisibleContentsRect(visibleContentsRect, trajectoryVector); 275 276 m_client->didChangeVisibleContents(); 277} 278 279void PageViewportController::didChangeViewportAttributes(const WebCore::ViewportAttributes& newAttributes) 280{ 281 if (newAttributes.layoutSize.isEmpty()) 282 return; 283 284 m_rawAttributes = newAttributes; 285 m_allowsUserScaling = !!m_rawAttributes.userScalable; 286 m_initiallyFitToViewport = (m_rawAttributes.initialScale < 0); 287 288 if (!m_initiallyFitToViewport) 289 WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(m_rawAttributes); 290 291 updateMinimumScaleToFit(true); 292 293 // As the viewport attributes are calculated when loading pages, after load, or after 294 // viewport resize, it is important that we inform the client of the new scale and 295 // position, so that the content can be positioned correctly and pixel aligned. 296 m_pendingPositionChange = true; 297 m_pendingScaleChange = true; 298 299 m_client->didChangeViewportAttributes(); 300} 301 302FloatSize PageViewportController::visibleContentsSize() const 303{ 304 return FloatSize(m_viewportSize.width() / m_pageScaleFactor, m_viewportSize.height() / m_pageScaleFactor); 305} 306 307void PageViewportController::applyScaleAfterRenderingContents(float scale) 308{ 309 if (m_pageScaleFactor == scale) 310 return; 311 312 m_pageScaleFactor = scale; 313 m_pendingScaleChange = true; 314 syncVisibleContents(); 315} 316 317void PageViewportController::applyPositionAfterRenderingContents(const FloatPoint& pos) 318{ 319 if (m_contentsPosition == pos) 320 return; 321 322 m_contentsPosition = pos; 323 m_pendingPositionChange = true; 324 syncVisibleContents(); 325} 326 327bool PageViewportController::updateMinimumScaleToFit(bool userInitiatedUpdate) 328{ 329 if (m_viewportSize.isEmpty() || m_contentsSize.isEmpty()) 330 return false; 331 332 bool currentlyScaledToFit = fuzzyCompare(m_pageScaleFactor, m_minimumScaleToFit, 0.0001); 333 334 float minimumScale = WebCore::computeMinimumScaleFactorForContentContained(m_rawAttributes, WebCore::roundedIntSize(m_viewportSize), WebCore::roundedIntSize(m_contentsSize)); 335 336 if (minimumScale <= 0) 337 return false; 338 339 if (!fuzzyCompare(minimumScale, m_minimumScaleToFit, 0.0001)) { 340 m_minimumScaleToFit = minimumScale; 341 342 if (!m_webPageProxy->areActiveDOMObjectsAndAnimationsSuspended()) { 343 if (!m_hadUserInteraction || (userInitiatedUpdate && currentlyScaledToFit)) 344 applyScaleAfterRenderingContents(m_minimumScaleToFit); 345 else { 346 // Ensure the effective scale stays within bounds. 347 float boundedScale = innerBoundedViewportScale(m_pageScaleFactor); 348 if (!fuzzyCompare(boundedScale, m_pageScaleFactor, 0.0001)) 349 applyScaleAfterRenderingContents(boundedScale); 350 } 351 } 352 353 return true; 354 } 355 return false; 356} 357 358} // namespace WebKit 359 360#endif 361