1dbbd56cdfdf6e51ae0b71c6b11cb8e115c94f55
[WebKit-https.git] / Source / WebCore / page / WindowFeatures.cpp
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/ASCIICType.h>
28 #include <wtf/Assertions.h>
29 #include <wtf/HashMap.h>
30 #include <wtf/MathExtras.h>
31 #include <wtf/text/StringHash.h>
32
33 namespace WebCore {
34
35 typedef HashMap<String, String, ASCIICaseInsensitiveHash> DialogFeaturesMap;
36
37 static void setWindowFeature(WindowFeatures&, StringView key, StringView value);
38
39 static DialogFeaturesMap parseDialogFeaturesMap(const String&);
40 static Optional<bool> boolFeature(const DialogFeaturesMap&, const char* key);
41 static Optional<float> floatFeature(const DialogFeaturesMap&, const char* key, float min, float max);
42
43 // https://html.spec.whatwg.org/#feature-separator
44 static bool isSeparator(UChar character, FeatureMode mode)
45 {
46     if (mode == FeatureMode::Viewport)
47         return character == ' ' || character == '\t' || character == '\n' || character == '\r' || character == '=' || character == ',';
48
49     return isASCIISpace(character) || character == '=' || character == ',';
50 }
51
52 WindowFeatures parseWindowFeatures(StringView featuresString)
53 {
54     // The IE rule is: all features except for channelmode and fullscreen default to YES, but
55     // if the user specifies a feature string, all features default to NO. (There is no public
56     // standard that applies to this method.)
57     //
58     // <http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/open_0.asp>
59     // We always allow a window to be resized, which is consistent with Firefox.
60
61     WindowFeatures features;
62
63     if (featuresString.isEmpty())
64         return features;
65
66     features.menuBarVisible = false;
67     features.statusBarVisible = false;
68     features.toolBarVisible = false;
69     features.locationBarVisible = false;
70     features.scrollbarsVisible = false;
71     features.noopener = false;
72
73     processFeaturesString(featuresString, FeatureMode::Window, [&features](StringView key, StringView value) {
74         setWindowFeature(features, key, value);
75     });
76
77     return features;
78 }
79
80 // Window: https://html.spec.whatwg.org/#concept-window-open-features-tokenize
81 // Viewport: https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html#//apple_ref/doc/uid/TP40008193-SW6
82 // FIXME: We should considering aligning Viewport feature parsing with Window features parsing.
83 void processFeaturesString(StringView features, FeatureMode mode, const WTF::Function<void(StringView type, StringView value)>& callback)
84 {
85     unsigned length = features.length();
86     for (unsigned i = 0; i < length; ) {
87         // Skip to first non-separator.
88         while (i < length && isSeparator(features[i], mode))
89             ++i;
90         unsigned keyBegin = i;
91
92         // Skip to first separator.
93         while (i < length && !isSeparator(features[i], mode))
94             i++;
95         unsigned keyEnd = i;
96
97         // Skip to first '=', but don't skip past a ',' or a non-separator.
98         while (i < length && features[i] != '=' && features[i] != ',' && (mode == FeatureMode::Viewport || isSeparator(features[i], mode)))
99             ++i;
100
101         // Skip to first non-separator, but don't skip past a ','.
102         if (mode == FeatureMode::Viewport || (i < length && isSeparator(features[i], mode))) {
103             while (i < length && isSeparator(features[i], mode) && features[i] != ',')
104                 ++i;
105             unsigned valueBegin = i;
106
107             // Skip to first separator.
108             while (i < length && !isSeparator(features[i], mode))
109                 ++i;
110             unsigned valueEnd = i;
111             callback(features.substring(keyBegin, keyEnd - keyBegin), features.substring(valueBegin, valueEnd - valueBegin));
112         } else
113             callback(features.substring(keyBegin, keyEnd - keyBegin), StringView());
114     }
115 }
116
117 OptionSet<DisabledAdaptations> parseDisabledAdaptations(const String& disabledAdaptationsString)
118 {
119     OptionSet<DisabledAdaptations> disabledAdaptations;
120     for (auto& name : disabledAdaptationsString.split(',')) {
121         auto normalizedName = name.stripWhiteSpace().convertToASCIILowercase();
122         if (normalizedName == watchAdaptationName())
123             disabledAdaptations.add(DisabledAdaptations::Watch);
124     }
125     return disabledAdaptations;
126 }
127
128 static void setWindowFeature(WindowFeatures& features, StringView key, StringView value)
129 {
130     // Listing a key with no value is shorthand for key=yes
131     int numericValue;
132     if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "yes"))
133         numericValue = 1;
134     else
135         numericValue = value.toInt();
136
137     // We treat key of "resizable" here as an additional feature rather than setting resizeable to true.
138     // This is consistent with Firefox, but could also be handled at another level.
139
140     if (equalLettersIgnoringASCIICase(key, "left") || equalLettersIgnoringASCIICase(key, "screenx"))
141         features.x = numericValue;
142     else if (equalLettersIgnoringASCIICase(key, "top") || equalLettersIgnoringASCIICase(key, "screeny"))
143         features.y = numericValue;
144     else if (equalLettersIgnoringASCIICase(key, "width") || equalLettersIgnoringASCIICase(key, "innerwidth"))
145         features.width = numericValue;
146     else if (equalLettersIgnoringASCIICase(key, "height") || equalLettersIgnoringASCIICase(key, "innerheight"))
147         features.height = numericValue;
148     else if (equalLettersIgnoringASCIICase(key, "menubar"))
149         features.menuBarVisible = numericValue;
150     else if (equalLettersIgnoringASCIICase(key, "toolbar"))
151         features.toolBarVisible = numericValue;
152     else if (equalLettersIgnoringASCIICase(key, "location"))
153         features.locationBarVisible = numericValue;
154     else if (equalLettersIgnoringASCIICase(key, "status"))
155         features.statusBarVisible = numericValue;
156     else if (equalLettersIgnoringASCIICase(key, "fullscreen"))
157         features.fullscreen = numericValue;
158     else if (equalLettersIgnoringASCIICase(key, "scrollbars"))
159         features.scrollbarsVisible = numericValue;
160     else if (equalLettersIgnoringASCIICase(key, "noopener"))
161         features.noopener = numericValue;
162     else if (numericValue == 1)
163         features.additionalFeatures.append(key.toString());
164 }
165
166 WindowFeatures parseDialogFeatures(const String& dialogFeaturesString, const FloatRect& screenAvailableRect)
167 {
168     auto featuresMap = parseDialogFeaturesMap(dialogFeaturesString);
169
170     // The following features from Microsoft's documentation are not implemented:
171     // - default font settings
172     // - width, height, left, and top specified in units other than "px"
173     // - edge (sunken or raised, default is raised)
174     // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print
175     // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?)
176     // - unadorned: trusted && boolFeature(features, "unadorned");
177
178     WindowFeatures features;
179
180     features.menuBarVisible = false;
181     features.toolBarVisible = false;
182     features.locationBarVisible = false;
183     features.dialog = true;
184
185     float width = floatFeature(featuresMap, "dialogwidth", 100, screenAvailableRect.width()).value_or(620); // default here came from frame size of dialog in MacIE
186     float height = floatFeature(featuresMap, "dialogheight", 100, screenAvailableRect.height()).value_or(450); // default here came from frame size of dialog in MacIE
187
188     features.width = width;
189     features.height = height;
190
191     features.x = floatFeature(featuresMap, "dialogleft", screenAvailableRect.x(), screenAvailableRect.maxX() - width);
192     features.y = floatFeature(featuresMap, "dialogtop", screenAvailableRect.y(), screenAvailableRect.maxY() - height);
193
194     if (boolFeature(featuresMap, "center").value_or(true)) {
195         if (!features.x)
196             features.x = screenAvailableRect.x() + (screenAvailableRect.width() - width) / 2;
197         if (!features.y)
198             features.y = screenAvailableRect.y() + (screenAvailableRect.height() - height) / 2;
199     }
200
201     features.resizable = boolFeature(featuresMap, "resizable").value_or(false);
202     features.scrollbarsVisible = boolFeature(featuresMap, "scroll").value_or(true);
203     features.statusBarVisible = boolFeature(featuresMap, "status").value_or(false);
204
205     return features;
206 }
207
208 static Optional<bool> boolFeature(const DialogFeaturesMap& features, const char* key)
209 {
210     auto it = features.find(key);
211     if (it == features.end())
212         return WTF::nullopt;
213
214     auto& value = it->value;
215     return value.isNull()
216         || value == "1"
217         || equalLettersIgnoringASCIICase(value, "yes")
218         || equalLettersIgnoringASCIICase(value, "on");
219 }
220
221 static Optional<float> floatFeature(const DialogFeaturesMap& features, const char* key, float min, float max)
222 {
223     auto it = features.find(key);
224     if (it == features.end())
225         return WTF::nullopt;
226
227     // FIXME: The toDouble function does not offer a way to tell "0q" from string with no digits in it: Both
228     // return the number 0 and false for ok. But "0q" should yield the minimum rather than the default.
229     bool ok;
230     double parsedNumber = it->value.toDouble(&ok);
231     if ((!parsedNumber && !ok) || std::isnan(parsedNumber))
232         return WTF::nullopt;
233     if (parsedNumber < min || max <= min)
234         return min;
235     if (parsedNumber > max)
236         return max;
237
238     // FIXME: Seems strange to cast a double to int and then convert back to a float. Why is this a good idea?
239     return static_cast<int>(parsedNumber);
240 }
241
242 static DialogFeaturesMap parseDialogFeaturesMap(const String& string)
243 {
244     // FIXME: Not clear why we take such a different approach to parsing dialog features
245     // as opposed to window features (using a map, different parsing quirks).
246
247     DialogFeaturesMap features;
248
249     for (auto& featureString : string.split(';')) {
250         size_t separatorPosition = featureString.find('=');
251         size_t colonPosition = featureString.find(':');
252         if (separatorPosition != notFound && colonPosition != notFound)
253             continue; // ignore strings that have both = and :
254         if (separatorPosition == notFound)
255             separatorPosition = colonPosition;
256
257         String key = featureString.left(separatorPosition).stripWhiteSpace();
258
259         // Null string for value indicates key without value.
260         String value;
261         if (separatorPosition != notFound) {
262             value = featureString.substring(separatorPosition + 1).stripWhiteSpace();
263             value = value.left(value.find(' '));
264         }
265
266         features.set(key, value);
267     }
268
269     return features;
270 }
271
272 } // namespace WebCore