Home | History | Annotate | Download | only in dom
      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