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  *
     10  * This library is free software; you can redistribute it and/or
     11  * modify it under the terms of the GNU Library General Public
     12  * License as published by the Free Software Foundation; either
     13  * version 2 of the License, or (at your option) any later version.
     14  *
     15  * This library is distributed in the hope that it will be useful,
     16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     18  * Library General Public License for more details.
     19  *
     20  * You should have received a copy of the GNU Library General Public License
     21  * along with this library; see the file COPYING.LIB.  If not, write to
     22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     23  * Boston, MA 02110-1301, USA.
     24  *
     25  */
     26 
     27 #include "config.h"
     28 #include "ViewportArguments.h"
     29 
     30 #include "Chrome.h"
     31 #include "Console.h"
     32 #include "DOMWindow.h"
     33 #include "Document.h"
     34 #include "Frame.h"
     35 #include "IntSize.h"
     36 #include "Page.h"
     37 #include "PlatformString.h"
     38 #include "ScriptableDocumentParser.h"
     39 
     40 using namespace std;
     41 
     42 namespace WebCore {
     43 
     44 ViewportAttributes computeViewportAttributes(ViewportArguments args, int desktopWidth, int deviceWidth, int deviceHeight, int deviceDPI, IntSize visibleViewport)
     45 {
     46     ViewportAttributes result;
     47 
     48     float availableWidth = visibleViewport.width();
     49     float availableHeight = visibleViewport.height();
     50 
     51     ASSERT(availableWidth > 0 && availableHeight > 0);
     52 
     53     switch (int(args.targetDensityDpi)) {
     54     case ViewportArguments::ValueDeviceDPI:
     55         args.targetDensityDpi = deviceDPI;
     56         break;
     57     case ViewportArguments::ValueLowDPI:
     58         args.targetDensityDpi = 120;
     59         break;
     60     case ViewportArguments::ValueAuto:
     61     case ViewportArguments::ValueMediumDPI:
     62         args.targetDensityDpi = 160;
     63         break;
     64     case ViewportArguments::ValueHighDPI:
     65         args.targetDensityDpi = 240;
     66         break;
     67     }
     68 
     69     result.devicePixelRatio = float(deviceDPI / args.targetDensityDpi);
     70 
     71     // Resolve non-'auto' width and height to pixel values.
     72     if (result.devicePixelRatio != 1.0) {
     73         availableWidth /= result.devicePixelRatio;
     74         availableHeight /= result.devicePixelRatio;
     75         deviceWidth /= result.devicePixelRatio;
     76         deviceHeight /= result.devicePixelRatio;
     77     }
     78 
     79     switch (int(args.width)) {
     80     case ViewportArguments::ValueDesktopWidth:
     81         args.width = desktopWidth;
     82         break;
     83     case ViewportArguments::ValueDeviceWidth:
     84         args.width = deviceWidth;
     85         break;
     86     case ViewportArguments::ValueDeviceHeight:
     87         args.width = deviceHeight;
     88         break;
     89     }
     90 
     91     switch (int(args.height)) {
     92     case ViewportArguments::ValueDesktopWidth:
     93         args.height = desktopWidth;
     94         break;
     95     case ViewportArguments::ValueDeviceWidth:
     96         args.height = deviceWidth;
     97         break;
     98     case ViewportArguments::ValueDeviceHeight:
     99         args.height = deviceHeight;
    100         break;
    101     }
    102 
    103     // Clamp values to range defined by spec and resolve minimum-scale and maximum-scale values
    104     if (args.width != ViewportArguments::ValueAuto)
    105         args.width = min(float(10000), max(args.width, float(1)));
    106     if (args.height != ViewportArguments::ValueAuto)
    107         args.height = min(float(10000), max(args.height, float(1)));
    108 
    109     if (args.initialScale != ViewportArguments::ValueAuto)
    110         args.initialScale = min(float(10), max(args.initialScale, float(0.1)));
    111     if (args.minimumScale != ViewportArguments::ValueAuto)
    112         args.minimumScale = min(float(10), max(args.minimumScale, float(0.1)));
    113     if (args.maximumScale != ViewportArguments::ValueAuto)
    114         args.maximumScale = min(float(10), max(args.maximumScale, float(0.1)));
    115 
    116     // Resolve minimum-scale and maximum-scale values according to spec.
    117     if (args.minimumScale == ViewportArguments::ValueAuto)
    118         result.minimumScale = float(0.25);
    119     else
    120         result.minimumScale = args.minimumScale;
    121 
    122     if (args.maximumScale == ViewportArguments::ValueAuto) {
    123         result.maximumScale = float(5.0);
    124         result.minimumScale = min(float(5.0), result.minimumScale);
    125     } else
    126         result.maximumScale = args.maximumScale;
    127     result.maximumScale = max(result.minimumScale, result.maximumScale);
    128 
    129     // Resolve initial-scale value.
    130     result.initialScale = args.initialScale;
    131     if (result.initialScale == ViewportArguments::ValueAuto) {
    132         result.initialScale = availableWidth / desktopWidth;
    133         if (args.width != ViewportArguments::ValueAuto)
    134             result.initialScale = availableWidth / args.width;
    135         if (args.height != ViewportArguments::ValueAuto) {
    136             // if 'auto', the initial-scale will be negative here and thus ignored.
    137             result.initialScale = max(result.initialScale, availableHeight / args.height);
    138         }
    139     }
    140 
    141     // Constrain initial-scale value to minimum-scale/maximum-scale range.
    142     result.initialScale = min(result.maximumScale, max(result.minimumScale, result.initialScale));
    143 
    144     // Resolve width value.
    145     float width;
    146     if (args.width != ViewportArguments::ValueAuto)
    147         width = args.width;
    148     else {
    149         if (args.initialScale == ViewportArguments::ValueAuto)
    150             width = desktopWidth;
    151         else if (args.height != ViewportArguments::ValueAuto)
    152             width = args.height * (availableWidth / availableHeight);
    153         else
    154             width = availableWidth / result.initialScale;
    155     }
    156 
    157     // Resolve height value.
    158     float height;
    159     if (args.height != ViewportArguments::ValueAuto)
    160         height = args.height;
    161     else
    162         height = width * availableHeight / availableWidth;
    163 
    164     // Extend width and height to fill the visual viewport for the resolved initial-scale.
    165     width = max(width, availableWidth / result.initialScale);
    166     height = max(height, availableHeight / result.initialScale);
    167     result.layoutSize.setWidth(width);
    168     result.layoutSize.setHeight(height);
    169 
    170     // Update minimum scale factor, to never allow zooming out more than viewport
    171     result.minimumScale = max(result.minimumScale, max(availableWidth / width, availableHeight / height));
    172 
    173     result.userScalable = args.userScalable;
    174     // Make maximum and minimum scale equal to the initial scale if user is not allowed to zoom in/out.
    175     if (!args.userScalable)
    176         result.maximumScale = result.minimumScale = result.initialScale;
    177 
    178     return result;
    179 }
    180 
    181 static float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok)
    182 {
    183     // If a prefix of property-value can be converted to a number using strtod,
    184     // the value will be that number. The remainder of the string is ignored.
    185     // So when String::toFloat says there is an error, it may be a false positive,
    186     // and we should check if the valueString prefix was a number.
    187 
    188     bool didReadNumber;
    189     float value = valueString.toFloat(ok, &didReadNumber);
    190     if (!*ok) {
    191         if (!didReadNumber) {
    192             ASSERT(!value);
    193             reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString);
    194             return value;
    195         }
    196         *ok = true;
    197         reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString);
    198     }
    199     return value;
    200 }
    201 
    202 static float findSizeValue(const String& keyString, const String& valueString, Document* document)
    203 {
    204     // 1) Non-negative number values are translated to px lengths.
    205     // 2) Negative number values are translated to auto.
    206     // 3) device-width and device-height are used as keywords.
    207     // 4) Other keywords and unknown values translate to 0.0.
    208 
    209     if (equalIgnoringCase(valueString, "desktop-width"))
    210         return ViewportArguments::ValueDesktopWidth;
    211     if (equalIgnoringCase(valueString, "device-width"))
    212         return ViewportArguments::ValueDeviceWidth;
    213     if (equalIgnoringCase(valueString, "device-height"))
    214         return ViewportArguments::ValueDeviceHeight;
    215 
    216     bool ok;
    217     float value = numericPrefix(keyString, valueString, document, &ok);
    218     if (!ok)
    219         return float(0.0);
    220 
    221     if (value < 0)
    222         return ViewportArguments::ValueAuto;
    223 
    224     return value;
    225 }
    226 
    227 static float findScaleValue(const String& keyString, const String& valueString, Document* document)
    228 {
    229     // 1) Non-negative number values are translated to <number> values.
    230     // 2) Negative number values are translated to auto.
    231     // 3) yes is translated to 1.0.
    232     // 4) device-width and device-height are translated to 10.0.
    233     // 5) no and unknown values are translated to 0.0
    234 
    235     if (equalIgnoringCase(valueString, "yes"))
    236         return float(1.0);
    237     if (equalIgnoringCase(valueString, "no"))
    238         return float(0.0);
    239     if (equalIgnoringCase(valueString, "desktop-width"))
    240         return float(10.0);
    241     if (equalIgnoringCase(valueString, "device-width"))
    242         return float(10.0);
    243     if (equalIgnoringCase(valueString, "device-height"))
    244         return float(10.0);
    245 
    246     bool ok;
    247     float value = numericPrefix(keyString, valueString, document, &ok);
    248     if (!ok)
    249         return float(0.0);
    250 
    251     if (value < 0)
    252         return ViewportArguments::ValueAuto;
    253 
    254     if (value > 10.0)
    255         reportViewportWarning(document, MaximumScaleTooLargeError, String(), String());
    256 
    257     return value;
    258 }
    259 
    260 static float findUserScalableValue(const String& keyString, const String& valueString, Document* document)
    261 {
    262     // yes and no are used as keywords.
    263     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
    264     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
    265 
    266     if (equalIgnoringCase(valueString, "yes"))
    267         return 1;
    268     if (equalIgnoringCase(valueString, "no"))
    269         return 0;
    270     if (equalIgnoringCase(valueString, "desktop-width"))
    271         return 1;
    272     if (equalIgnoringCase(valueString, "device-width"))
    273         return 1;
    274     if (equalIgnoringCase(valueString, "device-height"))
    275         return 1;
    276 
    277     bool ok;
    278     float value = numericPrefix(keyString, valueString, document, &ok);
    279     if (!ok)
    280         return 0;
    281 
    282     if (fabs(value) < 1)
    283         return 0;
    284 
    285     return 1;
    286 }
    287 
    288 static float findTargetDensityDPIValue(const String& keyString, const String& valueString, Document* document)
    289 {
    290     if (equalIgnoringCase(valueString, "device-dpi"))
    291         return ViewportArguments::ValueDeviceDPI;
    292     if (equalIgnoringCase(valueString, "low-dpi"))
    293         return ViewportArguments::ValueLowDPI;
    294     if (equalIgnoringCase(valueString, "medium-dpi"))
    295         return ViewportArguments::ValueMediumDPI;
    296     if (equalIgnoringCase(valueString, "high-dpi"))
    297         return ViewportArguments::ValueHighDPI;
    298 
    299     bool ok;
    300     float value = numericPrefix(keyString, valueString, document, &ok);
    301     if (!ok)
    302         return ViewportArguments::ValueAuto;
    303 
    304      if (value < 70 || value > 400) {
    305         reportViewportWarning(document, TargetDensityDpiTooSmallOrLargeError, String(), String());
    306         return ViewportArguments::ValueAuto;
    307     }
    308 
    309     return value;
    310 }
    311 
    312 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data)
    313 {
    314     ViewportArguments* arguments = static_cast<ViewportArguments*>(data);
    315 
    316     if (keyString == "width")
    317         arguments->width = findSizeValue(keyString, valueString, document);
    318     else if (keyString == "height")
    319         arguments->height = findSizeValue(keyString, valueString, document);
    320     else if (keyString == "initial-scale")
    321         arguments->initialScale = findScaleValue(keyString, valueString, document);
    322     else if (keyString == "minimum-scale")
    323         arguments->minimumScale = findScaleValue(keyString, valueString, document);
    324     else if (keyString == "maximum-scale")
    325         arguments->maximumScale = findScaleValue(keyString, valueString, document);
    326     else if (keyString == "user-scalable")
    327         arguments->userScalable = findUserScalableValue(keyString, valueString, document);
    328     else if (keyString == "target-densitydpi")
    329         arguments->targetDensityDpi = findTargetDensityDPIValue(keyString, valueString, document);
    330     else
    331         reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String());
    332 }
    333 
    334 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
    335 {
    336     static const char* const errors[] = {
    337         "Viewport argument key \"%replacement1\" not recognized and ignored.",
    338         "Viewport argument value \"%replacement1\" for key \"%replacement2\" not recognized. Content ignored.",
    339         "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
    340         "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0.",
    341         "Viewport target-densitydpi has to take a number between 70 and 400 as a valid target dpi, try using \"device-dpi\", \"low-dpi\", \"medium-dpi\" or \"high-dpi\" instead for future compatibility."
    342     };
    343 
    344     return errors[errorCode];
    345 }
    346 
    347 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
    348 {
    349     switch (errorCode) {
    350     case TruncatedViewportArgumentValueError:
    351     case TargetDensityDpiTooSmallOrLargeError:
    352         return TipMessageLevel;
    353     case UnrecognizedViewportArgumentKeyError:
    354     case UnrecognizedViewportArgumentValueError:
    355     case MaximumScaleTooLargeError:
    356         return ErrorMessageLevel;
    357     }
    358 
    359     ASSERT_NOT_REACHED();
    360     return ErrorMessageLevel;
    361 }
    362 
    363 // FIXME: Why is this different from SVGDocumentExtensions parserLineNumber?
    364 // FIXME: Callers should probably use ScriptController::eventHandlerLineNumber()
    365 static int parserLineNumber(Document* document)
    366 {
    367     if (!document)
    368         return 0;
    369     ScriptableDocumentParser* parser = document->scriptableDocumentParser();
    370     if (!parser)
    371         return 0;
    372     return parser->lineNumber() + 1;
    373 }
    374 
    375 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
    376 {
    377     Frame* frame = document->frame();
    378     if (!frame)
    379         return;
    380 
    381     String message = viewportErrorMessageTemplate(errorCode);
    382     if (!replacement1.isNull())
    383         message.replace("%replacement1", replacement1);
    384     if (!replacement2.isNull())
    385         message.replace("%replacement2", replacement2);
    386 
    387     frame->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, viewportErrorMessageLevel(errorCode), message, parserLineNumber(document), document->url().string());
    388 }
    389 
    390 } // namespace WebCore
    391