1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * (C) 2006 Alexey Proskuryakov (ap (at) webkit.org) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. 7 * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 8 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) 9 * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Library General Public 13 * License as published by the Free Software Foundation; either 14 * version 2 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Library General Public License for more details. 20 * 21 * You should have received a copy of the GNU Library General Public License 22 * along with this library; see the file COPYING.LIB. If not, write to 23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 24 * Boston, MA 02110-1301, USA. 25 * 26 */ 27 28 #include "config.h" 29 #include "core/dom/ViewportArguments.h" 30 31 #include "core/dom/Document.h" 32 #include "core/page/Page.h" 33 #include "core/page/Settings.h" 34 #include "wtf/text/WTFString.h" 35 36 using namespace std; 37 38 namespace WebCore { 39 40 static const float& compareIgnoringAuto(const float& value1, const float& value2, const float& (*compare) (const float&, const float&)) 41 { 42 if (value1 == ViewportArguments::ValueAuto) 43 return value2; 44 45 if (value2 == ViewportArguments::ValueAuto) 46 return value1; 47 48 return compare(value1, value2); 49 } 50 51 static inline float clampLengthValue(float value) 52 { 53 ASSERT(value != ViewportArguments::ValueDeviceWidth); 54 ASSERT(value != ViewportArguments::ValueDeviceHeight); 55 56 // Limits as defined in the css-device-adapt spec. 57 if (value != ViewportArguments::ValueAuto) 58 return min(float(10000), max(value, float(1))); 59 return value; 60 } 61 62 static inline float clampScaleValue(float value) 63 { 64 ASSERT(value != ViewportArguments::ValueDeviceWidth); 65 ASSERT(value != ViewportArguments::ValueDeviceHeight); 66 67 // Limits as defined in the css-device-adapt spec. 68 if (value != ViewportArguments::ValueAuto) 69 return min(float(10), max(value, float(0.1))); 70 return value; 71 } 72 73 float ViewportArguments::resolveViewportLength(const Length& length, const FloatSize& initialViewportSize, Direction direction) 74 { 75 if (length.isAuto()) 76 return ViewportArguments::ValueAuto; 77 78 if (length.isFixed()) 79 return length.getFloatValue(); 80 81 if (length.type() == ExtendToZoom) 82 return ViewportArguments::ValueExtendToZoom; 83 84 if ((length.type() == Percent && direction == Horizontal) || length.type() == ViewportPercentageWidth) 85 return initialViewportSize.width() * length.getFloatValue() / 100.0f; 86 87 if ((length.type() == Percent && direction == Vertical) || length.type() == ViewportPercentageHeight) 88 return initialViewportSize.height() * length.getFloatValue() / 100.0f; 89 90 if (length.type() == ViewportPercentageMin) 91 return min(initialViewportSize.width(), initialViewportSize.height()) * length.viewportPercentageLength() / 100.0f; 92 93 if (length.type() == ViewportPercentageMax) 94 return max(initialViewportSize.width(), initialViewportSize.height()) * length.viewportPercentageLength() / 100.0f; 95 96 ASSERT_NOT_REACHED(); 97 return ViewportArguments::ValueAuto; 98 } 99 100 PageScaleConstraints ViewportArguments::resolve(const FloatSize& initialViewportSize, const FloatSize& deviceSize, int defaultWidth) const 101 { 102 float resultWidth = width; 103 float resultHeight = height; 104 105 float resultZoom = zoom; 106 float resultMinZoom = minZoom; 107 float resultMaxZoom = maxZoom; 108 float resultUserZoom = userZoom; 109 110 if (type == ViewportArguments::CSSDeviceAdaptation) { 111 112 float resultMaxWidth = resolveViewportLength(maxWidth, initialViewportSize, Horizontal); 113 float resultMinWidth = resolveViewportLength(minWidth, initialViewportSize, Horizontal); 114 float resultMaxHeight = resolveViewportLength(maxHeight, initialViewportSize, Vertical); 115 float resultMinHeight = resolveViewportLength(minHeight, initialViewportSize, Vertical); 116 117 // 1. Resolve min-zoom and max-zoom values. 118 if (resultMinZoom != ViewportArguments::ValueAuto && resultMaxZoom != ViewportArguments::ValueAuto) 119 resultMaxZoom = max(resultMinZoom, resultMaxZoom); 120 121 // 2. Constrain zoom value to the [min-zoom, max-zoom] range. 122 if (resultZoom != ViewportArguments::ValueAuto) 123 resultZoom = compareIgnoringAuto(resultMinZoom, compareIgnoringAuto(resultMaxZoom, resultZoom, min), max); 124 125 float extendZoom = compareIgnoringAuto(resultZoom, resultMaxZoom, min); 126 127 if (extendZoom == ViewportArguments::ValueAuto) { 128 if (resultMaxWidth == ViewportArguments::ValueExtendToZoom) 129 resultMaxWidth = ViewportArguments::ValueAuto; 130 131 if (resultMaxHeight == ViewportArguments::ValueExtendToZoom) 132 resultMaxHeight = ViewportArguments::ValueAuto; 133 134 if (resultMinWidth == ViewportArguments::ValueExtendToZoom) 135 resultMinWidth = resultMaxWidth; 136 137 if (resultMinHeight == ViewportArguments::ValueExtendToZoom) 138 resultMinHeight = resultMaxHeight; 139 } else { 140 float extendWidth = initialViewportSize.width() / extendZoom; 141 float extendHeight = initialViewportSize.height() / extendZoom; 142 143 if (resultMaxWidth == ViewportArguments::ValueExtendToZoom) 144 resultMaxWidth = extendWidth; 145 146 if (resultMaxHeight == ViewportArguments::ValueExtendToZoom) 147 resultMaxHeight = extendHeight; 148 149 if (resultMinWidth == ViewportArguments::ValueExtendToZoom) 150 resultMinWidth = compareIgnoringAuto(extendWidth, resultMaxWidth, max); 151 152 if (resultMinHeight == ViewportArguments::ValueExtendToZoom) 153 resultMinHeight = compareIgnoringAuto(extendHeight, resultMaxHeight, max); 154 } 155 156 // 4. Resolve initial width from min/max descriptors. 157 if (resultMinWidth != ViewportArguments::ValueAuto || resultMaxWidth != ViewportArguments::ValueAuto) 158 resultWidth = compareIgnoringAuto(resultMinWidth, compareIgnoringAuto(resultMaxWidth, initialViewportSize.width(), min), max); 159 160 // 5. Resolve initial height from min/max descriptors. 161 if (resultMinHeight != ViewportArguments::ValueAuto || resultMaxHeight != ViewportArguments::ValueAuto) 162 resultHeight = compareIgnoringAuto(resultMinHeight, compareIgnoringAuto(resultMaxHeight, initialViewportSize.height(), min), max); 163 164 // 6-7. Resolve width value. 165 if (resultWidth == ViewportArguments::ValueAuto) { 166 if (resultHeight == ViewportArguments::ValueAuto || !initialViewportSize.height()) 167 resultWidth = initialViewportSize.width(); 168 else 169 resultWidth = resultHeight * (initialViewportSize.width() / initialViewportSize.height()); 170 } 171 172 // 8. Resolve height value. 173 if (resultHeight == ViewportArguments::ValueAuto) { 174 if (!initialViewportSize.width()) 175 resultHeight = initialViewportSize.height(); 176 else 177 resultHeight = resultWidth * initialViewportSize.height() / initialViewportSize.width(); 178 } 179 180 PageScaleConstraints result; 181 result.minimumScale = resultMinZoom; 182 result.maximumScale = resultMaxZoom; 183 result.initialScale = resultZoom; 184 result.layoutSize.setWidth(resultWidth); 185 result.layoutSize.setHeight(resultHeight); 186 return result; 187 } 188 189 switch (static_cast<int>(resultWidth)) { 190 case ViewportArguments::ValueDeviceWidth: 191 resultWidth = deviceSize.width(); 192 break; 193 case ViewportArguments::ValueDeviceHeight: 194 resultWidth = deviceSize.height(); 195 break; 196 } 197 198 switch (static_cast<int>(resultHeight)) { 199 case ViewportArguments::ValueDeviceWidth: 200 resultHeight = deviceSize.width(); 201 break; 202 case ViewportArguments::ValueDeviceHeight: 203 resultHeight = deviceSize.height(); 204 break; 205 } 206 207 if (type != ViewportArguments::Implicit) { 208 // Clamp values to a valid range, but not for @viewport since is 209 // not mandated by the specification. 210 resultWidth = clampLengthValue(resultWidth); 211 resultHeight = clampLengthValue(resultHeight); 212 resultZoom = clampScaleValue(resultZoom); 213 resultMinZoom = clampScaleValue(resultMinZoom); 214 resultMaxZoom = clampScaleValue(resultMaxZoom); 215 } 216 217 PageScaleConstraints result; 218 219 // Resolve minimum-scale and maximum-scale values according to spec. 220 if (resultMinZoom == ViewportArguments::ValueAuto) 221 result.minimumScale = float(0.25); 222 else 223 result.minimumScale = resultMinZoom; 224 225 if (resultMaxZoom == ViewportArguments::ValueAuto) { 226 result.maximumScale = float(5.0); 227 result.minimumScale = min(float(5.0), result.minimumScale); 228 } else 229 result.maximumScale = resultMaxZoom; 230 result.maximumScale = max(result.minimumScale, result.maximumScale); 231 232 // Resolve initial-scale value. 233 result.initialScale = resultZoom; 234 if (resultZoom == ViewportArguments::ValueAuto) { 235 result.initialScale = initialViewportSize.width() / defaultWidth; 236 if (resultWidth != ViewportArguments::ValueAuto && resultWidth > 0) 237 result.initialScale = initialViewportSize.width() / resultWidth; 238 if (resultHeight != ViewportArguments::ValueAuto && resultHeight > 0) { 239 // if 'auto', the initial-scale will be negative here and thus ignored. 240 result.initialScale = max<float>(result.initialScale, initialViewportSize.height() / resultHeight); 241 } 242 } 243 244 // Constrain initial-scale value to minimum-scale/maximum-scale range. 245 result.initialScale = min(result.maximumScale, max(result.minimumScale, result.initialScale)); 246 247 // Resolve width value. 248 if (resultWidth == ViewportArguments::ValueAuto) { 249 if (resultZoom == ViewportArguments::ValueAuto) 250 resultWidth = defaultWidth; 251 else if (resultHeight != ViewportArguments::ValueAuto) 252 resultWidth = resultHeight * (initialViewportSize.width() / initialViewportSize.height()); 253 else 254 resultWidth = initialViewportSize.width() / result.initialScale; 255 } 256 257 // Resolve height value. 258 if (resultHeight == ViewportArguments::ValueAuto) 259 resultHeight = resultWidth * (initialViewportSize.height() / initialViewportSize.width()); 260 261 if (type == ViewportArguments::ViewportMeta) { 262 // Extend width and height to fill the visual viewport for the resolved initial-scale. 263 resultWidth = max<float>(resultWidth, initialViewportSize.width() / result.initialScale); 264 resultHeight = max<float>(resultHeight, initialViewportSize.height() / result.initialScale); 265 } 266 267 result.layoutSize.setWidth(resultWidth); 268 result.layoutSize.setHeight(resultHeight); 269 270 // If user-scalable = no, lock the min/max scale to the computed initial 271 // scale. 272 if (!resultUserZoom) 273 result.maximumScale = result.minimumScale = result.initialScale; 274 275 // Only set initialScale to a value if it was explicitly set. 276 if (resultZoom == ViewportArguments::ValueAuto) 277 result.initialScale = ViewportArguments::ValueAuto; 278 279 return result; 280 } 281 282 static float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok = 0) 283 { 284 size_t parsedLength; 285 float value; 286 if (valueString.is8Bit()) 287 value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength); 288 else 289 value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength); 290 if (!parsedLength) { 291 reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString); 292 if (ok) 293 *ok = false; 294 return 0; 295 } 296 if (parsedLength < valueString.length()) 297 reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString); 298 if (ok) 299 *ok = true; 300 return value; 301 } 302 303 static float findSizeValue(const String& keyString, const String& valueString, Document* document) 304 { 305 // 1) Non-negative number values are translated to px lengths. 306 // 2) Negative number values are translated to auto. 307 // 3) device-width and device-height are used as keywords. 308 // 4) Other keywords and unknown values translate to 0.0. 309 310 if (equalIgnoringCase(valueString, "device-width")) 311 return ViewportArguments::ValueDeviceWidth; 312 if (equalIgnoringCase(valueString, "device-height")) 313 return ViewportArguments::ValueDeviceHeight; 314 315 float value = numericPrefix(keyString, valueString, document); 316 317 if (value < 0) 318 return ViewportArguments::ValueAuto; 319 320 return value; 321 } 322 323 static float findScaleValue(const String& keyString, const String& valueString, Document* document) 324 { 325 // 1) Non-negative number values are translated to <number> values. 326 // 2) Negative number values are translated to auto. 327 // 3) yes is translated to 1.0. 328 // 4) device-width and device-height are translated to 10.0. 329 // 5) no and unknown values are translated to 0.0 330 331 if (equalIgnoringCase(valueString, "yes")) 332 return 1; 333 if (equalIgnoringCase(valueString, "no")) 334 return 0; 335 if (equalIgnoringCase(valueString, "device-width")) 336 return 10; 337 if (equalIgnoringCase(valueString, "device-height")) 338 return 10; 339 340 float value = numericPrefix(keyString, valueString, document); 341 342 if (value < 0) 343 return ViewportArguments::ValueAuto; 344 345 if (value > 10.0) 346 reportViewportWarning(document, MaximumScaleTooLargeError, String(), String()); 347 348 if (!value && document->page() && document->page()->settings()->viewportMetaZeroValuesQuirk()) 349 return ViewportArguments::ValueAuto; 350 351 return value; 352 } 353 354 static float findUserScalableValue(const String& keyString, const String& valueString, Document* document) 355 { 356 // yes and no are used as keywords. 357 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes. 358 // Numbers in the range <-1, 1>, and unknown values, are mapped to no. 359 360 if (equalIgnoringCase(valueString, "yes")) 361 return 1; 362 if (equalIgnoringCase(valueString, "no")) 363 return 0; 364 if (equalIgnoringCase(valueString, "device-width")) 365 return 1; 366 if (equalIgnoringCase(valueString, "device-height")) 367 return 1; 368 369 float value = numericPrefix(keyString, valueString, document); 370 371 if (fabs(value) < 1) 372 return 0; 373 374 return 1; 375 } 376 377 static float findTargetDensityDPIValue(const String& keyString, const String& valueString, Document* document) 378 { 379 if (equalIgnoringCase(valueString, "device-dpi")) 380 return ViewportArguments::ValueDeviceDPI; 381 if (equalIgnoringCase(valueString, "low-dpi")) 382 return ViewportArguments::ValueLowDPI; 383 if (equalIgnoringCase(valueString, "medium-dpi")) 384 return ViewportArguments::ValueMediumDPI; 385 if (equalIgnoringCase(valueString, "high-dpi")) 386 return ViewportArguments::ValueHighDPI; 387 388 bool ok; 389 float value = numericPrefix(keyString, valueString, document, &ok); 390 if (!ok || value < 70 || value > 400) 391 return ViewportArguments::ValueAuto; 392 393 return value; 394 } 395 396 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data) 397 { 398 ViewportArguments* arguments = static_cast<ViewportArguments*>(data); 399 400 if (keyString == "width") 401 arguments->width = findSizeValue(keyString, valueString, document); 402 else if (keyString == "height") 403 arguments->height = findSizeValue(keyString, valueString, document); 404 else if (keyString == "initial-scale") 405 arguments->zoom = findScaleValue(keyString, valueString, document); 406 else if (keyString == "minimum-scale") 407 arguments->minZoom = findScaleValue(keyString, valueString, document); 408 else if (keyString == "maximum-scale") 409 arguments->maxZoom = findScaleValue(keyString, valueString, document); 410 else if (keyString == "user-scalable") 411 arguments->userZoom = findUserScalableValue(keyString, valueString, document); 412 else if (keyString == "target-densitydpi") { 413 arguments->deprecatedTargetDensityDPI = findTargetDensityDPIValue(keyString, valueString, document); 414 reportViewportWarning(document, TargetDensityDpiUnsupported, String(), String()); 415 } else 416 reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String()); 417 } 418 419 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode) 420 { 421 static const char* const errors[] = { 422 "Viewport argument key \"%replacement1\" not recognized and ignored.", 423 "Viewport argument value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.", 424 "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.", 425 "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0.", 426 "Viewport target-densitydpi is not supported.", 427 }; 428 429 return errors[errorCode]; 430 } 431 432 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode) 433 { 434 switch (errorCode) { 435 case TruncatedViewportArgumentValueError: 436 case TargetDensityDpiUnsupported: 437 return WarningMessageLevel; 438 case UnrecognizedViewportArgumentKeyError: 439 case UnrecognizedViewportArgumentValueError: 440 case MaximumScaleTooLargeError: 441 return ErrorMessageLevel; 442 } 443 444 ASSERT_NOT_REACHED(); 445 return ErrorMessageLevel; 446 } 447 448 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2) 449 { 450 Frame* frame = document->frame(); 451 if (!frame) 452 return; 453 454 String message = viewportErrorMessageTemplate(errorCode); 455 if (!replacement1.isNull()) 456 message.replace("%replacement1", replacement1); 457 if (!replacement2.isNull()) 458 message.replace("%replacement2", replacement2); 459 460 if ((errorCode == UnrecognizedViewportArgumentValueError || errorCode == TruncatedViewportArgumentValueError) && replacement1.find(';') != WTF::notFound) 461 message.append(" Note that ';' is not a separator in viewport values. The list should be comma-separated."); 462 463 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. 464 document->addConsoleMessage(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message); 465 } 466 467 } // namespace WebCore 468