org.openqa.selenium.firefox.XpiDriverService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of selenium-firefox-driver Show documentation
Show all versions of selenium-firefox-driver Show documentation
Selenium automates browsers. That's it! What you do with that power is entirely up to you.
// 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 java.util.concurrent.TimeUnit.SECONDS;
import static org.openqa.selenium.firefox.FirefoxOptions.FIREFOX_OPTIONS;
import static org.openqa.selenium.firefox.FirefoxProfile.PORT_PREFERENCE;
import com.google.auto.service.AutoService;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.firefox.internal.ClasspathExtension;
import org.openqa.selenium.firefox.internal.Extension;
import org.openqa.selenium.firefox.internal.FileExtension;
import org.openqa.selenium.io.FileHandler;
import org.openqa.selenium.net.UrlChecker;
import org.openqa.selenium.os.CommandLine;
import org.openqa.selenium.remote.service.DriverService;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class XpiDriverService extends DriverService {
private static final String NO_FOCUS_LIBRARY_NAME = "x_ignore_nofocus.so";
private static final String PATH_PREFIX =
"/" + XpiDriverService.class.getPackage().getName().replace(".", "/") + "/";
private final Lock lock = new ReentrantLock();
private final int port;
private final FirefoxBinary binary;
private final FirefoxProfile profile;
private File profileDir;
private XpiDriverService(
File executable,
int port,
ImmutableList args,
ImmutableMap environment,
FirefoxBinary binary,
FirefoxProfile profile,
File logFile)
throws IOException {
super(executable, port, args, environment);
Preconditions.checkState(port > 0, "Port must be set");
this.port = port;
this.binary = binary;
this.profile = profile;
String firefoxLogFile = System.getProperty(FirefoxDriver.SystemProperty.BROWSER_LOGFILE);
if (firefoxLogFile != null) { // System property has higher precedence
if ("/dev/stdout".equals(firefoxLogFile)) {
sendOutputTo(System.out);
} else if ("/dev/stderr".equals(firefoxLogFile)) {
sendOutputTo(System.err);
} else if ("/dev/null".equals(firefoxLogFile)) {
sendOutputTo(ByteStreams.nullOutputStream());
} else {
// TODO: This stream is leaked.
sendOutputTo(new FileOutputStream(firefoxLogFile));
}
} else {
if (logFile != null) {
// TODO: This stream is leaked.
sendOutputTo(new FileOutputStream(logFile));
} else {
sendOutputTo(ByteStreams.nullOutputStream());
}
}
}
@Override
protected URL getUrl(int port) throws MalformedURLException {
return new URL("http", "localhost", port, "/hub");
}
@Override
public void start() throws IOException {
lock.lock();
try {
profile.setPreference(PORT_PREFERENCE, port);
addWebDriverExtension(profile);
profileDir = profile.layoutOnDisk();
binary.setOutputWatcher(getOutputStream());
ImmutableMap.Builder envBuilder = new ImmutableMap.Builder()
.putAll(getEnvironment())
.put("XRE_PROFILE_PATH", profileDir.getAbsolutePath())
.put("MOZ_NO_REMOTE", "1")
.put("MOZ_CRASHREPORTER_DISABLE", "1") // Disable Breakpad
.put("NO_EM_RESTART", "1"); // Prevent the binary from detaching from the console
if (Platform.getCurrent().is(Platform.LINUX) && profile.shouldLoadNoFocusLib()) {
modifyLinkLibraryPath(envBuilder, profileDir);
}
Map env = envBuilder.build();
List cmdArray = new ArrayList<>(getArgs());
cmdArray.addAll(binary.getExtraOptions());
cmdArray.add("-foreground");
process = new CommandLine(binary.getPath(), Iterables.toArray(cmdArray, String.class));
process.setEnvironmentVariables(env);
process.updateDynamicLibraryPath(env.get(CommandLine.getLibraryPathPropertyName()));
// On Snow Leopard, beware of problems the sqlite library
if (! (Platform.getCurrent().is(Platform.MAC) && Platform.getCurrent().getMinorVersion() > 5)) {
String firefoxLibraryPath = System.getProperty(
FirefoxDriver.SystemProperty.BROWSER_LIBRARY_PATH,
binary.getFile().getAbsoluteFile().getParentFile().getAbsolutePath());
process.updateDynamicLibraryPath(firefoxLibraryPath);
}
process.copyOutputTo(getActualOutputStream());
process.executeAsync();
waitUntilAvailable();
} finally {
lock.unlock();
}
}
private void modifyLinkLibraryPath(ImmutableMap.Builder envBuilder, File profileDir) {
// Extract x_ignore_nofocus.so from x86, amd64 directories inside
// the jar into a real place in the filesystem and change LD_LIBRARY_PATH
// to reflect that.
String existingLdLibPath = System.getenv("LD_LIBRARY_PATH");
// The returned new ld lib path is terminated with ':'
String newLdLibPath =
extractAndCheck(profileDir, NO_FOCUS_LIBRARY_NAME, PATH_PREFIX + "x86", PATH_PREFIX +
"amd64");
if (existingLdLibPath != null && !existingLdLibPath.equals("")) {
newLdLibPath += existingLdLibPath;
}
envBuilder.put("LD_LIBRARY_PATH", newLdLibPath);
// Set LD_PRELOAD to x_ignore_nofocus.so - this will be taken automagically
// from the LD_LIBRARY_PATH
envBuilder.put("LD_PRELOAD", NO_FOCUS_LIBRARY_NAME);
}
private String extractAndCheck(File profileDir, String noFocusSoName,
String jarPath32Bit, String jarPath64Bit) {
// 1. Extract x86/x_ignore_nofocus.so to profile.getLibsDir32bit
// 2. Extract amd64/x_ignore_nofocus.so to profile.getLibsDir64bit
// 3. Create a new LD_LIB_PATH string to contain:
// profile.getLibsDir32bit + ":" + profile.getLibsDir64bit
Set pathsSet = new HashSet<>();
pathsSet.add(jarPath32Bit);
pathsSet.add(jarPath64Bit);
StringBuilder builtPath = new StringBuilder();
for (String path : pathsSet) {
try {
FileHandler.copyResource(profileDir, getClass(), path + File.separator + noFocusSoName);
} catch (IOException e) {
if (Boolean.getBoolean("webdriver.development")) {
System.err.println(
"Exception unpacking required library, but in development mode. Continuing");
} else {
throw new WebDriverException(e);
}
} // End catch.
String outSoPath = profileDir.getAbsolutePath() + File.separator + path;
File file = new File(outSoPath, noFocusSoName);
if (!file.exists()) {
throw new WebDriverException("Could not locate " + path + ": "
+ "native events will not work.");
}
builtPath.append(outSoPath).append(":");
}
return builtPath.toString();
}
private OutputStream getActualOutputStream() throws FileNotFoundException {
String firefoxLogFile = System.getProperty(FirefoxDriver.SystemProperty.BROWSER_LOGFILE);
if (firefoxLogFile != null) {
if ("/dev/stdout".equals(firefoxLogFile)) {
return System.out;
}
return new FileOutputStream(firefoxLogFile);
}
return getOutputStream();
}
@Override
protected void waitUntilAvailable() throws MalformedURLException {
try {
// Use a longer timeout, because 45 seconds was the default timeout in the predecessor to
// XpiDriverService. This has to wait for Firefox to start, not just a service, and some users
// may be running tests on really slow machines.
URL status = new URL(getUrl(port).toString() + "/status");
new UrlChecker().waitUntilAvailable(45, SECONDS, status);
} catch (UrlChecker.TimeoutException e) {
throw new WebDriverException("Timed out waiting 45 seconds for Firefox to start.", e);
}
}
@Override
public void stop() {
lock.lock();
try {
if (process != null) {
process.destroy();
}
profile.cleanTemporaryModel();
profile.clean(profileDir);
} finally {
lock.unlock();
}
}
private void addWebDriverExtension(FirefoxProfile profile) {
if (profile.containsWebDriverExtension()) {
return;
}
profile.addExtension("webdriver", loadCustomExtension().orElse(loadDefaultExtension()));
}
private Optional loadCustomExtension() {
String xpiProperty = System.getProperty(FirefoxDriver.SystemProperty.DRIVER_XPI_PROPERTY);
if (xpiProperty != null) {
File xpi = new File(xpiProperty);
return Optional.of(new FileExtension(xpi));
}
return Optional.empty();
}
private static Extension loadDefaultExtension() {
return new ClasspathExtension(
FirefoxProfile.class,
"/" + FirefoxProfile.class.getPackage().getName().replace(".", "/") + "/webdriver.xpi");
}
/**
* Configures and returns a new {@link XpiDriverService} using the default configuration. In
* this configuration, the service will use the firefox executable identified by the
* {@link FirefoxDriver.SystemProperty#BROWSER_BINARY} system property on a free port.
*
* @return A new XpiDriverService using the default configuration.
*/
public static XpiDriverService createDefaultService() {
try {
return new Builder().build();
} catch (WebDriverException e) {
throw new IllegalStateException(e.getMessage(), e.getCause());
}
}
@SuppressWarnings("unchecked")
static XpiDriverService createDefaultService(Capabilities caps) {
Builder builder = new Builder().usingAnyFreePort();
FirefoxProfile profile = Stream.>of(
() -> (FirefoxProfile) caps.getCapability(FirefoxDriver.PROFILE),
() -> FirefoxProfile.fromJson((String) caps.getCapability(FirefoxDriver.PROFILE)),
() -> ((FirefoxOptions) caps).getProfile(),
() -> (FirefoxProfile) ((Map) caps.getCapability(FIREFOX_OPTIONS)).get("profile"),
() -> FirefoxProfile.fromJson((String) ((Map) caps.getCapability(FIREFOX_OPTIONS)).get("profile")),
() -> {
Map options = (Map) caps.getCapability(FIREFOX_OPTIONS);
FirefoxProfile toReturn = new FirefoxProfile();
((Map) options.get("prefs")).forEach((key, value) -> {
if (value instanceof Boolean) { toReturn.setPreference(key, (Boolean) value); }
if (value instanceof Integer) { toReturn.setPreference(key, (Integer) value); }
if (value instanceof String) { toReturn.setPreference(key, (String) value); }
});
return toReturn;
})
.map(supplier -> {
try {
return supplier.get();
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (profile != null) {
builder.withProfile(profile);
}
Object binary = caps.getCapability(FirefoxDriver.BINARY);
if (binary != null) {
FirefoxBinary actualBinary;
if (binary instanceof FirefoxBinary) {
actualBinary = (FirefoxBinary) binary;
} else if (binary instanceof String) {
actualBinary = new FirefoxBinary(new File(String.valueOf(binary)));
} else {
throw new IllegalArgumentException(
"Expected binary to be a string or a binary: " + binary);
}
builder.withBinary(actualBinary);
}
return builder.build();
}
public static Builder builder() {
return new Builder();
}
@AutoService(DriverService.Builder.class)
public static class Builder extends DriverService.Builder {
private FirefoxBinary binary = null;
private FirefoxProfile profile = null;
@Override
public int score(Capabilities capabilites) {
if (!capabilites.is(FirefoxDriver.MARIONETTE)) {
return 0;
}
int score = 1;
if (capabilites.getCapability(FirefoxDriver.BINARY) != null) {
score++;
}
if (capabilites.getCapability(FirefoxDriver.PROFILE) != null) {
score++;
}
return score;
}
public Builder withBinary(FirefoxBinary binary) {
this.binary = Preconditions.checkNotNull(binary);
return this;
}
public Builder withProfile(FirefoxProfile profile) {
this.profile = Preconditions.checkNotNull(profile);
return this;
}
@Override
protected File findDefaultExecutable() {
if (binary == null) {
return new FirefoxBinary().getFile();
}
return binary.getFile();
}
@Override
protected ImmutableList createArgs() {
return ImmutableList.of("-foreground");
}
@Override
protected XpiDriverService createDriverService(
File exe,
int port,
ImmutableList args,
ImmutableMap environment) {
try {
return new XpiDriverService(
exe,
port,
args,
environment,
binary == null ? new FirefoxBinary() : binary,
profile == null ? new FirefoxProfile() : profile,
getLogFile());
} catch (IOException e) {
throw new WebDriverException(e);
}
}
}
@FunctionalInterface
private interface ThrowingSupplier extends Supplier {
V throwingGet() throws Exception;
@Override
default V get() {
try {
return throwingGet();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
}
}
}
}