|  | /* | 
|  | *  Copyright (C) 2000 Harri Porten (porten@kde.org) | 
|  | *  Copyright (C) 2006 Jon Shier (jshier@iastate.edu) | 
|  | *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reseved. | 
|  | *  Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) | 
|  | * | 
|  | *  This library is free software; you can redistribute it and/or | 
|  | *  modify it under the terms of the GNU Lesser General Public | 
|  | *  License as published by the Free Software Foundation; either | 
|  | *  version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | *  This library is distributed in the hope that it will be useful, | 
|  | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | *  Lesser General Public License for more details. | 
|  | * | 
|  | *  You should have received a copy of the GNU Lesser General Public | 
|  | *  License along with this library; if not, write to the Free Software | 
|  | *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 | 
|  | *  USA | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "WindowFeatures.h" | 
|  |  | 
|  | #include "FloatRect.h" | 
|  | #include <wtf/Assertions.h> | 
|  | #include <wtf/HashMap.h> | 
|  | #include <wtf/MathExtras.h> | 
|  | #include <wtf/text/StringHash.h> | 
|  |  | 
|  | namespace WebCore { | 
|  |  | 
|  | typedef HashMap<String, String, ASCIICaseInsensitiveHash> DialogFeaturesMap; | 
|  |  | 
|  | static void setWindowFeature(WindowFeatures&, StringView key, StringView value); | 
|  |  | 
|  | static DialogFeaturesMap parseDialogFeaturesMap(const String&); | 
|  | static Optional<bool> boolFeature(const DialogFeaturesMap&, const char* key); | 
|  | static Optional<float> floatFeature(const DialogFeaturesMap&, const char* key, float min, float max); | 
|  |  | 
|  | static bool isSeparator(UChar character) | 
|  | { | 
|  | return character == ' ' || character == '\t' || character == '\n' || character == '\r' || character == '=' || character == ','; | 
|  | } | 
|  |  | 
|  | WindowFeatures parseWindowFeatures(StringView featuresString) | 
|  | { | 
|  | // The IE rule is: all features except for channelmode and fullscreen default to YES, but | 
|  | // if the user specifies a feature string, all features default to NO. (There is no public | 
|  | // standard that applies to this method.) | 
|  | // | 
|  | // <http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/open_0.asp> | 
|  | // We always allow a window to be resized, which is consistent with Firefox. | 
|  |  | 
|  | WindowFeatures features; | 
|  |  | 
|  | if (featuresString.isEmpty()) | 
|  | return features; | 
|  |  | 
|  | features.menuBarVisible = false; | 
|  | features.statusBarVisible = false; | 
|  | features.toolBarVisible = false; | 
|  | features.locationBarVisible = false; | 
|  | features.scrollbarsVisible = false; | 
|  |  | 
|  | processFeaturesString(featuresString, [&features](StringView key, StringView value) { | 
|  | setWindowFeature(features, key, value); | 
|  | }); | 
|  |  | 
|  | return features; | 
|  | } | 
|  |  | 
|  | void processFeaturesString(StringView features, std::function<void(StringView type, StringView value)> callback) | 
|  | { | 
|  | unsigned length = features.length(); | 
|  | for (unsigned i = 0; i < length; ) { | 
|  | // skip to first non-separator | 
|  | while (i < length && isSeparator(features[i])) | 
|  | ++i; | 
|  | unsigned keyBegin = i; | 
|  |  | 
|  | // skip to first separator | 
|  | while (i < length && !isSeparator(features[i])) | 
|  | i++; | 
|  | unsigned keyEnd = i; | 
|  |  | 
|  | // skip to first '=', but don't skip past a ',' | 
|  | while (i < length && features[i] != '=' && features[i] != ',') | 
|  | ++i; | 
|  |  | 
|  | // skip to first non-separator, but don't skip past a ',' | 
|  | while (i < length && isSeparator(features[i]) && features[i] != ',') | 
|  | ++i; | 
|  | unsigned valueBegin = i; | 
|  |  | 
|  | // skip to first separator | 
|  | while (i < length && !isSeparator(features[i])) | 
|  | ++i; | 
|  | unsigned valueEnd = i; | 
|  |  | 
|  | callback(features.substring(keyBegin, keyEnd - keyBegin), features.substring(valueBegin, valueEnd - valueBegin)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void setWindowFeature(WindowFeatures& features, StringView key, StringView value) | 
|  | { | 
|  | // Listing a key with no value is shorthand for key=yes | 
|  | int numericValue; | 
|  | if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "yes")) | 
|  | numericValue = 1; | 
|  | else | 
|  | numericValue = value.toInt(); | 
|  |  | 
|  | // We treat key of "resizable" here as an additional feature rather than setting resizeable to true. | 
|  | // This is consistent with Firefox, but could also be handled at another level. | 
|  |  | 
|  | if (equalLettersIgnoringASCIICase(key, "left") || equalLettersIgnoringASCIICase(key, "screenx")) | 
|  | features.x = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "top") || equalLettersIgnoringASCIICase(key, "screeny")) | 
|  | features.y = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "width") || equalLettersIgnoringASCIICase(key, "innerwidth")) | 
|  | features.width = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "height") || equalLettersIgnoringASCIICase(key, "innerheight")) | 
|  | features.height = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "menubar")) | 
|  | features.menuBarVisible = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "toolbar")) | 
|  | features.toolBarVisible = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "location")) | 
|  | features.locationBarVisible = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "status")) | 
|  | features.statusBarVisible = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "fullscreen")) | 
|  | features.fullscreen = numericValue; | 
|  | else if (equalLettersIgnoringASCIICase(key, "scrollbars")) | 
|  | features.scrollbarsVisible = numericValue; | 
|  | else if (numericValue == 1) | 
|  | features.additionalFeatures.append(key.toString()); | 
|  | } | 
|  |  | 
|  | WindowFeatures parseDialogFeatures(const String& dialogFeaturesString, const FloatRect& screenAvailableRect) | 
|  | { | 
|  | auto featuresMap = parseDialogFeaturesMap(dialogFeaturesString); | 
|  |  | 
|  | // The following features from Microsoft's documentation are not implemented: | 
|  | // - default font settings | 
|  | // - width, height, left, and top specified in units other than "px" | 
|  | // - edge (sunken or raised, default is raised) | 
|  | // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print | 
|  | // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?) | 
|  | // - unadorned: trusted && boolFeature(features, "unadorned"); | 
|  |  | 
|  | WindowFeatures features; | 
|  |  | 
|  | features.menuBarVisible = false; | 
|  | features.toolBarVisible = false; | 
|  | features.locationBarVisible = false; | 
|  | features.dialog = true; | 
|  |  | 
|  | float width = floatFeature(featuresMap, "dialogwidth", 100, screenAvailableRect.width()).valueOr(620); // default here came from frame size of dialog in MacIE | 
|  | float height = floatFeature(featuresMap, "dialogheight", 100, screenAvailableRect.height()).valueOr(450); // default here came from frame size of dialog in MacIE | 
|  |  | 
|  | features.width = width; | 
|  | features.height = height; | 
|  |  | 
|  | features.x = floatFeature(featuresMap, "dialogleft", screenAvailableRect.x(), screenAvailableRect.maxX() - width); | 
|  | features.y = floatFeature(featuresMap, "dialogtop", screenAvailableRect.y(), screenAvailableRect.maxY() - height); | 
|  |  | 
|  | if (boolFeature(featuresMap, "center").valueOr(true)) { | 
|  | if (!features.x) | 
|  | features.x = screenAvailableRect.x() + (screenAvailableRect.width() - width) / 2; | 
|  | if (!features.y) | 
|  | features.y = screenAvailableRect.y() + (screenAvailableRect.height() - height) / 2; | 
|  | } | 
|  |  | 
|  | features.resizable = boolFeature(featuresMap, "resizable").valueOr(false); | 
|  | features.scrollbarsVisible = boolFeature(featuresMap, "scroll").valueOr(true); | 
|  | features.statusBarVisible = boolFeature(featuresMap, "status").valueOr(false); | 
|  |  | 
|  | return features; | 
|  | } | 
|  |  | 
|  | static Optional<bool> boolFeature(const DialogFeaturesMap& features, const char* key) | 
|  | { | 
|  | auto it = features.find(key); | 
|  | if (it == features.end()) | 
|  | return Nullopt; | 
|  |  | 
|  | auto& value = it->value; | 
|  | return value.isNull() | 
|  | || value == "1" | 
|  | || equalLettersIgnoringASCIICase(value, "yes") | 
|  | || equalLettersIgnoringASCIICase(value, "on"); | 
|  | } | 
|  |  | 
|  | static Optional<float> floatFeature(const DialogFeaturesMap& features, const char* key, float min, float max) | 
|  | { | 
|  | auto it = features.find(key); | 
|  | if (it == features.end()) | 
|  | return Nullopt; | 
|  |  | 
|  | // FIXME: The toDouble function does not offer a way to tell "0q" from string with no digits in it: Both | 
|  | // return the number 0 and false for ok. But "0q" should yield the minimum rather than the default. | 
|  | bool ok; | 
|  | double parsedNumber = it->value.toDouble(&ok); | 
|  | if ((!parsedNumber && !ok) || std::isnan(parsedNumber)) | 
|  | return Nullopt; | 
|  | if (parsedNumber < min || max <= min) | 
|  | return min; | 
|  | if (parsedNumber > max) | 
|  | return max; | 
|  |  | 
|  | // FIXME: Seems strange to cast a double to int and then convert back to a float. Why is this a good idea? | 
|  | return static_cast<int>(parsedNumber); | 
|  | } | 
|  |  | 
|  | static DialogFeaturesMap parseDialogFeaturesMap(const String& string) | 
|  | { | 
|  | // FIXME: Not clear why we take such a different approach to parsing dialog features | 
|  | // as opposed to window features (using a map, different parsing quirks). | 
|  |  | 
|  | DialogFeaturesMap features; | 
|  |  | 
|  | Vector<String> vector; | 
|  | string.split(';', vector); | 
|  |  | 
|  | for (auto& featureString : vector) { | 
|  | size_t separatorPosition = featureString.find('='); | 
|  | size_t colonPosition = featureString.find(':'); | 
|  | if (separatorPosition != notFound && colonPosition != notFound) | 
|  | continue; // ignore strings that have both = and : | 
|  | if (separatorPosition == notFound) | 
|  | separatorPosition = colonPosition; | 
|  |  | 
|  | String key = featureString.left(separatorPosition).stripWhiteSpace(); | 
|  |  | 
|  | // Null string for value indicates key without value. | 
|  | String value; | 
|  | if (separatorPosition != notFound) { | 
|  | value = featureString.substring(separatorPosition + 1).stripWhiteSpace(); | 
|  | value = value.left(value.find(' ')); | 
|  | } | 
|  |  | 
|  | features.set(key, value); | 
|  | } | 
|  |  | 
|  | return features; | 
|  | } | 
|  |  | 
|  | } // namespace WebCore |