org.openqa.selenium.firefox.Preferences Maven / Gradle / Ivy
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.openqa.selenium.firefox;
import static org.openqa.selenium.json.Json.MAP_TYPE;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.Json;
class Preferences {
/**
* The maximum amount of time scripts should be permitted to run. The user may increase this
* timeout, but may not set it below the default value.
*/
private static final String MAX_SCRIPT_RUN_TIME_KEY = "dom.max_script_run_time";
private static final int DEFAULT_MAX_SCRIPT_RUN_TIME = 30;
/**
* This pattern is used to parse preferences in user.js. It is intended to match all preference
* lines in the format generated by Firefox; it won't necessarily match all possible lines that
* Firefox will parse.
*
* e.g. if you have a line with extra spaces after the end-of-line semicolon, this pattern will
* not match that line because Firefox never generates lines like that.
*/
private static final Pattern PREFERENCE_PATTERN =
Pattern.compile("user_pref\\(\"([^\"]+)\", (\"?.+?\"?)\\);");
private final Map immutablePrefs = new HashMap<>();
private final Map allPrefs = new HashMap<>();
public Preferences() {}
public Preferences(Reader defaults) {
readDefaultPreferences(defaults);
}
public Preferences(Reader defaults, File userPrefs) {
readDefaultPreferences(defaults);
try (Reader reader = Files.newBufferedReader(userPrefs.toPath(), Charset.defaultCharset())) {
readPreferences(reader);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
public Preferences(File userPrefs) {
readUserPrefs(userPrefs);
}
public Preferences(Reader defaults, Reader reader) {
readDefaultPreferences(defaults);
try (reader) {
readPreferences(reader);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
private void readUserPrefs(File userPrefs) {
try (Reader reader = Files.newBufferedReader(userPrefs.toPath(), Charset.defaultCharset())) {
readPreferences(reader);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
private void readDefaultPreferences(Reader defaultsReader) {
try {
String rawJson;
try (StringWriter writer = new StringWriter()) {
defaultsReader.transferTo(writer);
rawJson = writer.getBuffer().toString();
}
Map map = new Json().toType(rawJson, MAP_TYPE);
Map frozen = (Map) map.get("frozen");
frozen.forEach(
(key, value) -> {
if (value instanceof Long) {
value = ((Long) value).intValue();
}
setPreference(key, value);
immutablePrefs.put(key, value);
});
Map mutable = (Map) map.get("mutable");
mutable.forEach(this::setPreference);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
public void setPreference(String key, Object value) {
if (value instanceof String) {
if (isStringified((String) value)) {
throw new IllegalArgumentException(
String.format("Preference values must be plain strings: %s: %s", key, value));
}
allPrefs.put(key, value);
} else if (value instanceof Number) {
allPrefs.put(key, ((Number) value).intValue());
} else {
allPrefs.put(key, value);
}
}
private void readPreferences(Reader reader) throws IOException {
BufferedReader allLines = new BufferedReader(reader);
String line = allLines.readLine();
while (line != null) {
Matcher matcher = PREFERENCE_PATTERN.matcher(line);
if (matcher.matches()) {
allPrefs.put(matcher.group(1), preferenceAsValue(matcher.group(2)));
}
line = allLines.readLine();
}
}
public void addTo(Preferences prefs) {
// TODO(simon): Stop being lazy
prefs.allPrefs.putAll(allPrefs);
}
public void writeTo(Writer writer) throws IOException {
for (Map.Entry pref : allPrefs.entrySet()) {
writer.append("user_pref(\"").append(pref.getKey()).append("\", ");
writer.append(valueAsPreference(pref.getValue()));
writer.append(");\n");
}
}
private String valueAsPreference(Object value) {
if (value instanceof String) {
return "\"" + escapeValueAsPreference((String) value) + "\"";
}
return escapeValueAsPreference(String.valueOf(value));
}
private String escapeValueAsPreference(String value) {
return value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"");
}
private Object preferenceAsValue(String toConvert) {
if (toConvert.startsWith("\"") && toConvert.endsWith("\"")) {
return toConvert.substring(1, toConvert.length() - 1).replaceAll("\\\\\\\\", "\\\\");
}
if ("false".equals(toConvert) || "true".equals(toConvert)) {
return Boolean.parseBoolean(toConvert);
}
try {
return Integer.parseInt(toConvert);
} catch (NumberFormatException e) {
throw new WebDriverException(e);
}
}
Object getPreference(String key) {
return allPrefs.get(key);
}
private boolean isStringified(String value) {
// Assume we a string is stringified (i.e. wrapped in " ") when
// the first character == " and the last character == "
return value.startsWith("\"") && value.endsWith("\"");
}
private void checkPreference(String key, Object value) {
Require.nonNull("Key", key);
Require.nonNull("Value", value);
Require.stateCondition(
!immutablePrefs.containsKey(key)
|| (immutablePrefs.containsKey(key) && value.equals(immutablePrefs.get(key))),
"Preference %s may not be overridden: frozen value=%s, requested value=%s",
key,
immutablePrefs.get(key),
value);
if (MAX_SCRIPT_RUN_TIME_KEY.equals(key)) {
int n;
if (value instanceof String) {
n = Integer.parseInt((String) value);
} else if (value instanceof Integer) {
n = (Integer) value;
} else {
throw new IllegalStateException(
String.format(
"%s value must be a number: %s",
MAX_SCRIPT_RUN_TIME_KEY, value.getClass().getName()));
}
Require.stateCondition(
n == 0 || n >= DEFAULT_MAX_SCRIPT_RUN_TIME,
"%s must be == 0 || >= %s",
MAX_SCRIPT_RUN_TIME_KEY,
DEFAULT_MAX_SCRIPT_RUN_TIME);
}
}
}