1/*
2 *  Copyright (C) 2000 Harri Porten (porten@kde.org)
3 *  Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
4 *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reseved.
5 *  Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 *
7 *  This library is free software; you can redistribute it and/or
8 *  modify it under the terms of the GNU Lesser General Public
9 *  License as published by the Free Software Foundation; either
10 *  version 2 of the License, or (at your option) any later version.
11 *
12 *  This library is distributed in the hope that it will be useful,
13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 *  Lesser General Public License for more details.
16 *
17 *  You should have received a copy of the GNU Lesser General Public
18 *  License along with this library; if not, write to the Free Software
19 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
20 *  USA
21 */
22
23#include "config.h"
24#include "WindowFeatures.h"
25
26#include "FloatRect.h"
27#include <wtf/Assertions.h>
28#include <wtf/MathExtras.h>
29#include <wtf/text/StringHash.h>
30
31namespace WebCore {
32
33// Though isspace() considers \t and \v to be whitespace, Win IE doesn't when parsing window features.
34static bool isWindowFeaturesSeparator(UChar c)
35{
36    return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
37}
38
39WindowFeatures::WindowFeatures(const String& features)
40    : xSet(false)
41    , ySet(false)
42    , widthSet(false)
43    , heightSet(false)
44    , fullscreen(false)
45    , dialog(false)
46{
47    /*
48     The IE rule is: all features except for channelmode and fullscreen default to YES, but
49     if the user specifies a feature string, all features default to NO. (There is no public
50     standard that applies to this method.)
51
52     <http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/open_0.asp>
53     We always allow a window to be resized, which is consistent with Firefox.
54     */
55
56    if (features.length() == 0) {
57        menuBarVisible = true;
58        statusBarVisible = true;
59        toolBarVisible = true;
60        locationBarVisible = true;
61        scrollbarsVisible = true;
62        resizable = true;
63        return;
64    }
65
66    menuBarVisible = false;
67    statusBarVisible = false;
68    toolBarVisible = false;
69    locationBarVisible = false;
70    scrollbarsVisible = false;
71    resizable = true;
72
73    // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior.
74    unsigned keyBegin, keyEnd;
75    unsigned valueBegin, valueEnd;
76
77    String buffer = features.lower();
78    unsigned length = buffer.length();
79    for (unsigned i = 0; i < length; ) {
80        // skip to first non-separator, but don't skip past the end of the string
81        while (isWindowFeaturesSeparator(buffer[i])) {
82            if (i >= length)
83                break;
84            i++;
85        }
86        keyBegin = i;
87
88        // skip to first separator
89        while (!isWindowFeaturesSeparator(buffer[i]))
90            i++;
91        keyEnd = i;
92
93        // skip to first '=', but don't skip past a ',' or the end of the string
94        while (buffer[i] != '=') {
95            if (buffer[i] == ',' || i >= length)
96                break;
97            i++;
98        }
99
100        // skip to first non-separator, but don't skip past a ',' or the end of the string
101        while (isWindowFeaturesSeparator(buffer[i])) {
102            if (buffer[i] == ',' || i >= length)
103                break;
104            i++;
105        }
106        valueBegin = i;
107
108        // skip to first separator
109        while (!isWindowFeaturesSeparator(buffer[i]))
110            i++;
111        valueEnd = i;
112
113        ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
114
115        String keyString(buffer.substring(keyBegin, keyEnd - keyBegin));
116        String valueString(buffer.substring(valueBegin, valueEnd - valueBegin));
117        setWindowFeature(keyString, valueString);
118    }
119}
120
121void WindowFeatures::setWindowFeature(const String& keyString, const String& valueString)
122{
123    int value;
124
125    // Listing a key with no value is shorthand for key=yes
126    if (valueString.isEmpty() || valueString == "yes")
127        value = 1;
128    else
129        value = valueString.toInt();
130
131    // We treat keyString of "resizable" here as an additional feature rather than setting resizeable to true.
132    // This is consistent with Firefox, but could also be handled at another level.
133
134    if (keyString == "left" || keyString == "screenx") {
135        xSet = true;
136        x = value;
137    } else if (keyString == "top" || keyString == "screeny") {
138        ySet = true;
139        y = value;
140    } else if (keyString == "width" || keyString == "innerwidth") {
141        widthSet = true;
142        width = value;
143    } else if (keyString == "height" || keyString == "innerheight") {
144        heightSet = true;
145        height = value;
146    } else if (keyString == "menubar")
147        menuBarVisible = value;
148    else if (keyString == "toolbar")
149        toolBarVisible = value;
150    else if (keyString == "location")
151        locationBarVisible = value;
152    else if (keyString == "status")
153        statusBarVisible = value;
154    else if (keyString == "fullscreen")
155        fullscreen = value;
156    else if (keyString == "scrollbars")
157        scrollbarsVisible = value;
158    else if (value == 1)
159        additionalFeatures.append(keyString);
160}
161
162WindowFeatures::WindowFeatures(const String& dialogFeaturesString, const FloatRect& screenAvailableRect)
163    : widthSet(true)
164    , heightSet(true)
165    , menuBarVisible(false)
166    , toolBarVisible(false)
167    , locationBarVisible(false)
168    , fullscreen(false)
169    , dialog(true)
170{
171    DialogFeaturesMap features;
172    parseDialogFeatures(dialogFeaturesString, features);
173
174    const bool trusted = false;
175
176    // The following features from Microsoft's documentation are not implemented:
177    // - default font settings
178    // - width, height, left, and top specified in units other than "px"
179    // - edge (sunken or raised, default is raised)
180    // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print
181    // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?)
182    // - unadorned: trusted && boolFeature(features, "unadorned");
183
184    width = floatFeature(features, "dialogwidth", 100, screenAvailableRect.width(), 620); // default here came from frame size of dialog in MacIE
185    height = floatFeature(features, "dialogheight", 100, screenAvailableRect.height(), 450); // default here came from frame size of dialog in MacIE
186
187    x = floatFeature(features, "dialogleft", screenAvailableRect.x(), screenAvailableRect.maxX() - width, -1);
188    xSet = x > 0;
189    y = floatFeature(features, "dialogtop", screenAvailableRect.y(), screenAvailableRect.maxY() - height, -1);
190    ySet = y > 0;
191
192    if (boolFeature(features, "center", true)) {
193        if (!xSet) {
194            x = screenAvailableRect.x() + (screenAvailableRect.width() - width) / 2;
195            xSet = true;
196        }
197        if (!ySet) {
198            y = screenAvailableRect.y() + (screenAvailableRect.height() - height) / 2;
199            ySet = true;
200        }
201    }
202
203    resizable = boolFeature(features, "resizable");
204    scrollbarsVisible = boolFeature(features, "scroll", true);
205    statusBarVisible = boolFeature(features, "status", !trusted);
206}
207
208bool WindowFeatures::boolFeature(const DialogFeaturesMap& features, const char* key, bool defaultValue)
209{
210    DialogFeaturesMap::const_iterator it = features.find(key);
211    if (it == features.end())
212        return defaultValue;
213    const String& value = it->value;
214    return value.isNull() || value == "1" || value == "yes" || value == "on";
215}
216
217float WindowFeatures::floatFeature(const DialogFeaturesMap& features, const char* key, float min, float max, float defaultValue)
218{
219    DialogFeaturesMap::const_iterator it = features.find(key);
220    if (it == features.end())
221        return defaultValue;
222    // FIXME: The toDouble function does not offer a way to tell "0q" from string with no digits in it: Both
223    // return the number 0 and false for ok. But "0q" should yield the minimum rather than the default.
224    bool ok;
225    double parsedNumber = it->value.toDouble(&ok);
226    if ((!parsedNumber && !ok) || std::isnan(parsedNumber))
227        return defaultValue;
228    if (parsedNumber < min || max <= min)
229        return min;
230    if (parsedNumber > max)
231        return max;
232    // FIXME: Seems strange to cast a double to int and then convert back to a float. Why is this a good idea?
233    return static_cast<int>(parsedNumber);
234}
235
236void WindowFeatures::parseDialogFeatures(const String& string, DialogFeaturesMap& map)
237{
238    Vector<String> vector;
239    string.split(';', vector);
240    size_t size = vector.size();
241    for (size_t i = 0; i < size; ++i) {
242        const String& featureString = vector[i];
243
244        size_t separatorPosition = featureString.find('=');
245        size_t colonPosition = featureString.find(':');
246        if (separatorPosition != notFound && colonPosition != notFound)
247            continue; // ignore strings that have both = and :
248        if (separatorPosition == notFound)
249            separatorPosition = colonPosition;
250
251        String key = featureString.left(separatorPosition).stripWhiteSpace().lower();
252
253        // Null string for value indicates key without value.
254        String value;
255        if (separatorPosition != notFound) {
256            value = featureString.substring(separatorPosition + 1).stripWhiteSpace().lower();
257            value = value.left(value.find(' '));
258        }
259
260        map.set(key, value);
261    }
262}
263
264} // namespace WebCore
265