1/* 2 * Copyright (C) 2005-2014 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#include "ViewportConfiguration.h" 28 29#include <WebCore/TextStream.h> 30#include <wtf/Assertions.h> 31#include <wtf/MathExtras.h> 32#include <wtf/text/CString.h> 33 34#if PLATFORM(IOS) 35#include "WebCoreSystemInterface.h" 36#endif 37 38namespace WebCore { 39 40#if !ASSERT_DISABLED 41static bool constraintsAreAllRelative(const ViewportConfiguration::Parameters& configuration) 42{ 43 return !configuration.widthIsSet && !configuration.heightIsSet && !configuration.initialScaleIsSet; 44} 45#endif 46 47ViewportConfiguration::ViewportConfiguration() 48 : m_minimumLayoutSize(1024, 768) 49 , m_minimumLayoutSizeForMinimalUI(m_minimumLayoutSize) 50 , m_usesMinimalUI(false) 51 , m_pageDidFinishDocumentLoad(false) 52{ 53 // Setup a reasonable default configuration to avoid computing infinite scale/sizes. 54 // Those are the original iPhone configuration. 55 m_defaultConfiguration = ViewportConfiguration::webpageParameters(); 56 updateConfiguration(); 57} 58 59void ViewportConfiguration::setDefaultConfiguration(const ViewportConfiguration::Parameters& defaultConfiguration) 60{ 61 ASSERT(!constraintsAreAllRelative(m_configuration)); 62 ASSERT(!m_defaultConfiguration.initialScaleIsSet || defaultConfiguration.initialScale > 0); 63 ASSERT(defaultConfiguration.minimumScale > 0); 64 ASSERT(defaultConfiguration.maximumScale >= defaultConfiguration.minimumScale); 65 66 m_defaultConfiguration = defaultConfiguration; 67 updateConfiguration(); 68} 69 70void ViewportConfiguration::setContentsSize(const IntSize& contentSize) 71{ 72 if (m_contentSize == contentSize) 73 return; 74 75 m_contentSize = contentSize; 76 updateConfiguration(); 77} 78 79void ViewportConfiguration::setMinimumLayoutSize(const FloatSize& minimumLayoutSize) 80{ 81 if (m_minimumLayoutSize == minimumLayoutSize) 82 return; 83 84 m_minimumLayoutSize = minimumLayoutSize; 85} 86 87void ViewportConfiguration::setMinimumLayoutSizeForMinimalUI(const FloatSize& minimumLayoutSizeForMinimalUI) 88{ 89 if (m_minimumLayoutSizeForMinimalUI == minimumLayoutSizeForMinimalUI) 90 return; 91 92 m_minimumLayoutSizeForMinimalUI = minimumLayoutSizeForMinimalUI; 93} 94 95const FloatSize& ViewportConfiguration::activeMinimumLayoutSizeInScrollViewCoordinates() const 96{ 97 if (m_usesMinimalUI) 98 return m_minimumLayoutSizeForMinimalUI; 99 return m_minimumLayoutSize; 100} 101 102void ViewportConfiguration::setViewportArguments(const ViewportArguments& viewportArguments) 103{ 104 if (m_viewportArguments == viewportArguments) 105 return; 106 107 m_viewportArguments = viewportArguments; 108 updateConfiguration(); 109} 110 111void ViewportConfiguration::resetMinimalUI() 112{ 113 m_usesMinimalUI = false; 114 m_pageDidFinishDocumentLoad = false; 115} 116 117void ViewportConfiguration::didFinishDocumentLoad() 118{ 119 m_pageDidFinishDocumentLoad = true; 120} 121 122IntSize ViewportConfiguration::layoutSize() const 123{ 124 return IntSize(layoutWidth(), layoutHeight()); 125} 126 127double ViewportConfiguration::initialScale() const 128{ 129 ASSERT(!constraintsAreAllRelative(m_configuration)); 130 131 // If the document has specified its own initial scale, use it regardless. 132 // This is guaranteed to be sanity checked already, so no need for MIN/MAX. 133 if (m_configuration.initialScaleIsSet) 134 return m_configuration.initialScale; 135 136 // If not, it is up to us to determine the initial scale. 137 // We want a scale small enough to fit the document width-wise. 138 const FloatSize& minimumLayoutSize = activeMinimumLayoutSizeInScrollViewCoordinates(); 139 double width = m_contentSize.width() > 0 ? m_contentSize.width() : layoutWidth(); 140 double initialScale = 0; 141 if (width > 0) 142 initialScale = minimumLayoutSize.width() / width; 143 144 // Prevent the intial scale from shrinking to a height smaller than our view's minimum height. 145 double height = m_contentSize.height() > 0 ? m_contentSize.height() : layoutHeight(); 146 if (height > 0 && height * initialScale < minimumLayoutSize.height()) 147 initialScale = minimumLayoutSize.height() / height; 148 return std::min(std::max(initialScale, m_configuration.minimumScale), m_configuration.maximumScale); 149} 150 151double ViewportConfiguration::minimumScale() const 152{ 153 // If we scale to fit, then this is our minimum scale as well. 154 if (!m_configuration.initialScaleIsSet) 155 return initialScale(); 156 157 // If not, we still need to sanity check our value. 158 double minimumScale = m_configuration.minimumScale; 159 160 const FloatSize& minimumLayoutSize = activeMinimumLayoutSizeInScrollViewCoordinates(); 161 double contentWidth = m_contentSize.width(); 162 if (contentWidth > 0 && contentWidth * minimumScale < minimumLayoutSize.width()) 163 minimumScale = minimumLayoutSize.width() / contentWidth; 164 165 double contentHeight = m_contentSize.height(); 166 if (contentHeight > 0 && contentHeight * minimumScale < minimumLayoutSize.height()) 167 minimumScale = minimumLayoutSize.height() / contentHeight; 168 169 minimumScale = std::min(std::max(minimumScale, m_configuration.minimumScale), m_configuration.maximumScale); 170 171 return minimumScale; 172} 173 174ViewportConfiguration::Parameters ViewportConfiguration::webpageParameters() 175{ 176 Parameters parameters; 177 parameters.width = 980; 178 parameters.widthIsSet = true; 179 parameters.allowsUserScaling = true; 180 parameters.minimumScale = 0.25; 181 parameters.maximumScale = 5; 182 return parameters; 183} 184 185ViewportConfiguration::Parameters ViewportConfiguration::textDocumentParameters() 186{ 187 Parameters parameters; 188 189#if PLATFORM(IOS) 190 parameters.width = static_cast<int>(wkGetScreenSize().width); 191#else 192 // FIXME: this needs to be unified with ViewportArguments on all ports. 193 parameters.width = 320; 194#endif 195 196 parameters.widthIsSet = true; 197 parameters.allowsUserScaling = true; 198 parameters.minimumScale = 0.25; 199 parameters.maximumScale = 5; 200 return parameters; 201} 202 203ViewportConfiguration::Parameters ViewportConfiguration::imageDocumentParameters() 204{ 205 Parameters parameters; 206 parameters.width = 980; 207 parameters.widthIsSet = true; 208 parameters.allowsUserScaling = true; 209 parameters.minimumScale = 0.01; 210 parameters.maximumScale = 5; 211 return parameters; 212} 213 214ViewportConfiguration::Parameters ViewportConfiguration::xhtmlMobileParameters() 215{ 216 Parameters parameters = webpageParameters(); 217 parameters.width = 320; 218 return parameters; 219} 220 221ViewportConfiguration::Parameters ViewportConfiguration::testingParameters() 222{ 223 Parameters parameters; 224 parameters.initialScale = 1; 225 parameters.initialScaleIsSet = true; 226 parameters.minimumScale = 1; 227 parameters.maximumScale = 5; 228 return parameters; 229} 230 231static inline bool viewportArgumentValueIsValid(float value) 232{ 233 return value > 0; 234} 235 236template<typename ValueType, typename ViewportArgumentsType> 237static inline void applyViewportArgument(ValueType& value, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum) 238{ 239 if (viewportArgumentValueIsValid(viewportArgumentValue)) 240 value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue))); 241} 242 243template<typename ValueType, typename ViewportArgumentsType> 244static inline void applyViewportArgument(ValueType& value, bool& valueIsSet, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum) 245{ 246 if (viewportArgumentValueIsValid(viewportArgumentValue)) { 247 value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue))); 248 valueIsSet = true; 249 } else 250 valueIsSet = false; 251} 252 253static inline bool viewportArgumentUserZoomIsSet(float value) 254{ 255 return !value || value == 1; 256} 257 258void ViewportConfiguration::updateConfiguration() 259{ 260 m_configuration = m_defaultConfiguration; 261 262 const double minimumViewportArgumentsScaleFactor = 0.1; 263 const double maximumViewportArgumentsScaleFactor = 10.0; 264 265 bool viewportArgumentsOverridesInitialScale; 266 bool viewportArgumentsOverridesWidth; 267 bool viewportArgumentsOverridesHeight; 268 269 applyViewportArgument(m_configuration.minimumScale, m_viewportArguments.minZoom, minimumViewportArgumentsScaleFactor, maximumViewportArgumentsScaleFactor); 270 applyViewportArgument(m_configuration.maximumScale, m_viewportArguments.maxZoom, m_configuration.minimumScale, maximumViewportArgumentsScaleFactor); 271 applyViewportArgument(m_configuration.initialScale, viewportArgumentsOverridesInitialScale, m_viewportArguments.zoom, m_configuration.minimumScale, m_configuration.maximumScale); 272 273 double minimumViewportArgumentsDimension = 10; 274 double maximumViewportArgumentsDimension = 10000; 275 applyViewportArgument(m_configuration.width, viewportArgumentsOverridesWidth, m_viewportArguments.width, minimumViewportArgumentsDimension, maximumViewportArgumentsDimension); 276 applyViewportArgument(m_configuration.height, viewportArgumentsOverridesHeight, m_viewportArguments.height, minimumViewportArgumentsDimension, maximumViewportArgumentsDimension); 277 278 if (viewportArgumentsOverridesInitialScale || viewportArgumentsOverridesWidth || viewportArgumentsOverridesHeight) { 279 m_configuration.initialScaleIsSet = viewportArgumentsOverridesInitialScale; 280 m_configuration.widthIsSet = viewportArgumentsOverridesWidth; 281 m_configuration.heightIsSet = viewportArgumentsOverridesHeight; 282 } 283 284 if (viewportArgumentUserZoomIsSet(m_viewportArguments.userZoom)) 285 m_configuration.allowsUserScaling = m_viewportArguments.userZoom != 0.; 286 287#if PLATFORM(IOS) 288 if (!m_pageDidFinishDocumentLoad) 289 m_usesMinimalUI = m_usesMinimalUI || m_viewportArguments.minimalUI; 290#endif 291} 292 293int ViewportConfiguration::layoutWidth() const 294{ 295 ASSERT(!constraintsAreAllRelative(m_configuration)); 296 297 const FloatSize& minimumLayoutSize = activeMinimumLayoutSizeInScrollViewCoordinates(); 298 if (m_configuration.widthIsSet) { 299 // If we scale to fit, then accept the viewport width with sanity checking. 300 if (!m_configuration.initialScaleIsSet) { 301 double maximumScale = this->maximumScale(); 302 double maximumContentWidthInViewportCoordinate = maximumScale * m_configuration.width; 303 if (maximumContentWidthInViewportCoordinate < minimumLayoutSize.width()) { 304 // The content zoomed to maxScale does not fit the the view. Return the minimum width 305 // satisfying the constraint maximumScale. 306 return std::round(minimumLayoutSize.width() / maximumScale); 307 } 308 return std::round(m_configuration.width); 309 } 310 311 // If not, make sure the viewport width and initial scale can co-exist. 312 double initialContentWidthInViewportCoordinate = m_configuration.width * m_configuration.initialScale; 313 if (initialContentWidthInViewportCoordinate < minimumLayoutSize.width()) { 314 // The specified width does not fit in viewport. Return the minimum width that satisfy the initialScale constraint. 315 return std::round(minimumLayoutSize.width() / m_configuration.initialScale); 316 } 317 return std::round(m_configuration.width); 318 } 319 320 // If the page has a real scale, then just return the minimum size over the initial scale. 321 if (m_configuration.initialScaleIsSet && !m_configuration.heightIsSet) 322 return std::round(minimumLayoutSize.width() / m_configuration.initialScale); 323 324 if (minimumLayoutSize.height() > 0) 325 return std::round(minimumLayoutSize.width() * layoutHeight() / minimumLayoutSize.height()); 326 return minimumLayoutSize.width(); 327} 328 329int ViewportConfiguration::layoutHeight() const 330{ 331 ASSERT(!constraintsAreAllRelative(m_configuration)); 332 333 const FloatSize& minimumLayoutSize = activeMinimumLayoutSizeInScrollViewCoordinates(); 334 if (m_configuration.heightIsSet) { 335 // If we scale to fit, then accept the viewport height with sanity checking. 336 if (!m_configuration.initialScaleIsSet) { 337 double maximumScale = this->maximumScale(); 338 double maximumContentHeightInViewportCoordinate = maximumScale * m_configuration.height; 339 if (maximumContentHeightInViewportCoordinate < minimumLayoutSize.height()) { 340 // The content zoomed to maxScale does not fit the the view. Return the minimum height that 341 // satisfy the constraint maximumScale. 342 return std::round(minimumLayoutSize.height() / maximumScale); 343 } 344 return std::round(m_configuration.height); 345 } 346 347 // If not, make sure the viewport width and initial scale can co-exist. 348 double initialContentHeightInViewportCoordinate = m_configuration.height * m_configuration.initialScale; 349 if (initialContentHeightInViewportCoordinate < minimumLayoutSize.height()) { 350 // The specified width does not fit in viewport. Return the minimum height that satisfy the initialScale constraint. 351 return std::round(minimumLayoutSize.height() / m_configuration.initialScale); 352 } 353 return std::round(m_configuration.height); 354 } 355 356 // If the page has a real scale, then just return the minimum size over the initial scale. 357 if (m_configuration.initialScaleIsSet && !m_configuration.widthIsSet) 358 return std::round(minimumLayoutSize.height() / m_configuration.initialScale); 359 360 if (minimumLayoutSize.width() > 0) 361 return std::round(minimumLayoutSize.height() * layoutWidth() / minimumLayoutSize.width()); 362 return minimumLayoutSize.height(); 363} 364 365#ifndef NDEBUG 366class ViewportConfigurationTextStream : public TextStream { 367public: 368 ViewportConfigurationTextStream() 369 : m_indent(0) 370 { 371 } 372 373 using TextStream::operator<<; 374 375 ViewportConfigurationTextStream& operator<<(const ViewportConfiguration::Parameters&); 376 ViewportConfigurationTextStream& operator<<(const ViewportArguments&); 377 378 void increaseIndent() { ++m_indent; } 379 void decreaseIndent() { --m_indent; ASSERT(m_indent >= 0); } 380 381 void writeIndent(); 382 383private: 384 int m_indent; 385}; 386 387template <typename T> 388static void dumpProperty(ViewportConfigurationTextStream& ts, String name, T value) 389{ 390 ts << "\n"; 391 ts.increaseIndent(); 392 ts.writeIndent(); 393 ts << "(" << name << " "; 394 ts << value << ")"; 395 ts.decreaseIndent(); 396} 397 398void ViewportConfigurationTextStream::writeIndent() 399{ 400 for (int i = 0; i < m_indent; ++i) 401 *this << " "; 402} 403 404ViewportConfigurationTextStream& ViewportConfigurationTextStream::operator<<(const ViewportConfiguration::Parameters& parameters) 405{ 406 ViewportConfigurationTextStream& ts = *this; 407 408 ts.increaseIndent(); 409 ts << "\n"; 410 ts.writeIndent(); 411 ts << "(width " << parameters.width << ", set: " << (parameters.widthIsSet ? "true" : "false") << ")"; 412 413 ts << "\n"; 414 ts.writeIndent(); 415 ts << "(height " << parameters.height << ", set: " << (parameters.heightIsSet ? "true" : "false") << ")"; 416 417 ts << "\n"; 418 ts.writeIndent(); 419 ts << "(initialScale " << parameters.width << ", set: " << (parameters.initialScaleIsSet ? "true" : "false") << ")"; 420 ts.decreaseIndent(); 421 422 dumpProperty(ts, "minimumScale", parameters.minimumScale); 423 dumpProperty(ts, "maximumScale", parameters.maximumScale); 424 dumpProperty(ts, "allowsUserScaling", parameters.allowsUserScaling); 425 426 return ts; 427} 428 429ViewportConfigurationTextStream& ViewportConfigurationTextStream::operator<<(const ViewportArguments& viewportArguments) 430{ 431 ViewportConfigurationTextStream& ts = *this; 432 433 ts.increaseIndent(); 434 435 ts << "\n"; 436 ts.writeIndent(); 437 ts << "(width " << viewportArguments.width << ", minWidth " << viewportArguments.minWidth << ", maxWidth " << viewportArguments.maxWidth << ")"; 438 439 ts << "\n"; 440 ts.writeIndent(); 441 ts << "(height " << viewportArguments.height << ", minHeight " << viewportArguments.minHeight << ", maxHeight " << viewportArguments.maxHeight << ")"; 442 443 ts << "\n"; 444 ts.writeIndent(); 445 ts << "(zoom " << viewportArguments.zoom << ", minZoom " << viewportArguments.minZoom << ", maxZoom " << viewportArguments.maxZoom << ")"; 446 ts.decreaseIndent(); 447 448#if PLATFORM(IOS) 449 dumpProperty(ts, "minimalUI", viewportArguments.minimalUI); 450#endif 451 return ts; 452} 453 454CString ViewportConfiguration::description() const 455{ 456 ViewportConfigurationTextStream ts; 457 458 ts << "(viewport-configuration " << (void*)this; 459 ts << "\n"; 460 ts.increaseIndent(); 461 ts.writeIndent(); 462 ts << "(viewport arguments"; 463 ts << m_viewportArguments; 464 ts << ")"; 465 ts.decreaseIndent(); 466 467 ts << "\n"; 468 ts.increaseIndent(); 469 ts.writeIndent(); 470 ts << "(configuration"; 471 ts << m_configuration; 472 ts << ")"; 473 ts.decreaseIndent(); 474 475 ts << "\n"; 476 ts.increaseIndent(); 477 ts.writeIndent(); 478 ts << "(default configuration"; 479 ts << m_defaultConfiguration; 480 ts << ")"; 481 ts.decreaseIndent(); 482 483 dumpProperty(ts, "contentSize", m_contentSize); 484 dumpProperty(ts, "minimumLayoutSize", m_minimumLayoutSize); 485 dumpProperty(ts, "minimumLayoutSizeForMinimalUI", m_minimumLayoutSizeForMinimalUI); 486 ts << "(uses minimal UI " << m_usesMinimalUI << ")"; 487 488 ts << "\n"; 489 ts.increaseIndent(); 490 ts.writeIndent(); 491 ts << "(computed initial scale " << initialScale() << ")\n"; 492 ts.writeIndent(); 493 ts << "(computed minimum scale " << minimumScale() << ")\n"; 494 ts.writeIndent(); 495 ts << "(computed layout size " << layoutSize() << ")"; 496 ts.decreaseIndent(); 497 498 ts << ")\n"; 499 500 return ts.release().utf8(); 501} 502 503void ViewportConfiguration::dump() const 504{ 505 fprintf(stderr, "%s", description().data()); 506} 507 508#endif 509 510} // namespace WebCore 511