/*
* Copyright 2006 ThoughtWorks, Inc.
*
* Licensed 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.server.browserlaunchers;
import org.apache.commons.logging.Log;
import org.mortbay.log.LogFactory;
import org.openqa.selenium.server.BrowserConfigurationOptions;
import org.openqa.selenium.server.RemoteControlConfiguration;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;
public class OperaCustomProfileLauncher extends AbstractBrowserLauncher {
static Log log = LogFactory.getLog(OperaCustomProfileLauncher.class);
// TODO What is this really?
private static final String DEFAULT_NONWINDOWS_LOCATION = "/Applications/Opera.app/Contents/MacOS/opera";
private static boolean simple = false;
private File customProfileDir;
private String[] cmdarray;
private boolean closed = false;
private String commandPath;
private Process process;
private static AsyncExecute exe = new AsyncExecute();
// Opera has been a real pain for me (Lightbody), and so I'm adding a simple hook in the browser launcher that lets
// me define, in an amazingly ghetto way, define additional browser settings to write out to the ini file. For
// example, I will use this to pre-determine the window size and location simply because it appears Opera 9.2
// has a regression in which window.resizeTo() and window.moveTo() simply do not work!
private static String additionalSettings = "";
protected File locateBinaryInPath(String commandPath) {
return AsyncExecute.whichExec(commandPath);
}
public OperaCustomProfileLauncher(BrowserConfigurationOptions browserOptions, RemoteControlConfiguration configuration, String sessionId, String browserLaunchLocation) {
super(sessionId, configuration, browserOptions);
commandPath = browserLaunchLocation == null ? findBrowserLaunchLocation() : browserLaunchLocation;
this.sessionId = sessionId;
if (!WindowsUtils.thisIsWindows()) {
// On unix, add command's directory to LD_LIBRARY_PATH
File operaBin = locateBinaryInPath(commandPath);
if (operaBin == null) {
File execDirect = new File(commandPath);
if (execDirect.isAbsolute() && execDirect.exists()) operaBin = execDirect;
}
if (operaBin != null) {
String libPathKey = SystemUtils.libraryPathEnvironmentVariable();
String libPath = WindowsUtils.loadEnvironment().getProperty(libPathKey);
exe.setEnvironment(new String[]{
"MOZ_NO_REMOTE=1",
libPathKey + "=" + libPath + ":" + operaBin.getParent(),
});
}
}
}
public static void setAdditionalSettings(String additionalSettings) {
OperaCustomProfileLauncher.additionalSettings = additionalSettings;
}
protected String findBrowserLaunchLocation() {
String defaultPath = System.getProperty("operaDefaultPath");
if (defaultPath == null) {
if (WindowsUtils.thisIsWindows()) {
defaultPath = WindowsUtils.getProgramFilesPath() + "\\Opera\\opera.exe";
} else {
defaultPath = DEFAULT_NONWINDOWS_LOCATION;
}
}
File defaultLocation = new File(defaultPath);
if (defaultLocation.exists()) {
return defaultLocation.getAbsolutePath();
}
if (WindowsUtils.thisIsWindows()) {
File operaEXE = AsyncExecute.whichExec("opera.exe");
if (operaEXE != null) return operaEXE.getAbsolutePath();
throw new RuntimeException("Opera could not be found in the path!\n" +
"Please add the directory containing opera.exe to your PATH environment\n" +
"variable, or explicitly specify a path to Opera like this:\n" +
"*opera c:\\blah\\opera.exe");
}
// On unix, prefer operaBin if it's on the path
File operaBin = AsyncExecute.whichExec("opera");
if (operaBin != null) {
return operaBin.getAbsolutePath();
}
throw new RuntimeException("Opera could not be found in the path!\n" +
"Please add the directory containing 'opera' to your PATH environment\n" +
"variable, or explicitly specify a path to Opera like this:\n" +
"*opera /blah/blah/opera");
}
static final Pattern JAVA_STYLE_UNC_URL = Pattern.compile("^file:////([^/]+/.*)$");
static final Pattern JAVA_STYLE_LOCAL_URL = Pattern.compile("^file:/([A-Z]:/.*)$");
protected void launch(String url) {
try {
File opera6ini = makeCustomProfile();
log.info("Launching Opera...");
if (WindowsUtils.thisIsWindows()) {
cmdarray = new String[]{commandPath, "/settings", opera6ini.getAbsolutePath(), url};
} else {
cmdarray = new String[]{commandPath, "-nosession", "-personaldir", opera6ini.getParentFile().getAbsolutePath(), url};
}
exe.setCommandline(cmdarray);
process = exe.asyncSpawn();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File makeCustomProfile() throws IOException {
customProfileDir = LauncherUtils.createCustomProfileDir(sessionId);
ResourceExtractor.extractResourcePath(getClass(), "/opera", customProfileDir);
if (simple) return customProfileDir;
File proxyPAC = LauncherUtils.makeProxyPAC(customProfileDir, getPort(), browserConfigurationOptions.is("avoidProxy"));
// TODO Do we want to make these preferences configurable somehow?
File opera6ini = new File(customProfileDir, "opera6.ini");
PrintStream out = new PrintStream(new FileOutputStream(opera6ini));
// Configure us as the local proxy
// TODO Proxy.pac file doesn't seem to want to work correctly
// browser starts and just sits there on a blank page!
out.println("[Proxy]");
out.println("HTTP server=localhost:" + getPort());
out.println("Enable HTTP 1.1 for proxy=1");
out.println("Use Proxy On Local Names Check=1");
out.println("Use HTTP=1"); //TODO This forces the proxy to be used all the time!
out.println("Use HTTPS=1");
out.println("Use FTP=0");
out.println("Use GOPHER=0");
out.println("Use WAIS=0");
out.println("Use Automatic Proxy Configuration=0");
out.println("HTTPS server=localhost:" + getPort());
out.println("FTP server=localhost:" + getPort());
out.println("Gopher server=localhost:" + getPort());
out.println("WAIS server");
out.println("Automatic Proxy Configuration URL=" + proxyPAC.getAbsolutePath());
out.println("No Proxy Servers");
out.println("No Proxy Servers Check=0");
out.println("");
out.println("[Cache]");
out.println("Cache Docs=0");
out.println("Document=0");
out.println("Figure=0");
out.println("Cache Figs=0");
out.println("");
out.println("[Disk Cache]");
out.println("Cache Docs=0");
out.println("Enabled=0");
out.println("Size=0");
out.println("Docs Modification=1");
out.println("Figs Modification=1");
out.println("Other Modification=1");
out.println("");
out.println("[State]");
out.println("Run=0");
// Accept License (unix only)
out.println("Accept License=1");
out.println("[User Prefs]");
out.println("Automatic RAM Cache=0");
// Put our Opera profile in the custom profile dir
out.println("Opera Directory=" + customProfileDir.getAbsolutePath());
out.println("Shown First Time Setup=1");
out.println("One Time Page=");
out.println("Show Setupdialog On Start=0");
// Disable "do you want to remember this password?"
out.println("Enable Wand=0");
// Disable pop-up blocking
out.println("Ignore Unrequested Popups=0");
// Don't ask if we want to switch default browsers
out.println("Show Default Browser Dialog=0");
out.println("Check For New Opera=0");
out.println("Show Startup Dialog=0");
// Don't open any pages on startup (except for command line URL)
out.println("Startup Type=4");
// Don't tell us KDE detected (unix only)
out.println("Has Shown KDE Shortcut Message=1");
//new windows mode
out.println("SDI=1");
out.println("Maximize New Windows=3");
out.println(additionalSettings);
out.println("[Install]");
out.println("HELLO=NO");
out.println("Newest Used Version=2000");
// TODO Disable security warnings
out.close();
return opera6ini;
}
public void close() {
if (closed) return;
if (process == null) return;
log.info("Killing Opera...");
Exception taskKillException = null;
Exception fileLockException = null;
if (false) {
try {
// try to kill with windows taskkill
WindowsUtils.kill(cmdarray);
} catch (Exception e) {
taskKillException = e;
}
}
int exitValue = AsyncExecute.killProcess(process);
if (exitValue == 0) {
log.warn("Opera seems to have ended on its own (did we kill the real browser???)");
}
try {
waitForFileLockToGoAway(5 * 000, 500);
} catch (FileLockRemainedException e1) {
fileLockException = e1;
}
try {
LauncherUtils.deleteTryTryAgain(customProfileDir, 6);
} catch (RuntimeException e) {
if (taskKillException != null || fileLockException != null) {
log.error("Couldn't delete custom Opera profile directory", e);
log.error("Perhaps caused by this exception:");
if (taskKillException != null) log.error("Perhaps caused by this exception:", taskKillException);
if (fileLockException != null) log.error("Perhaps caused by this exception:", fileLockException);
throw new RuntimeException("Couldn't delete custom Opera " +
"profile directory, presumably because task kill failed; " +
"see error log!", e);
}
throw e;
}
closed = true;
}
public Process getProcess() {
return process;
}
/**
* Opera knows it's running by using a "parent.lock" file in
* the profile directory. Wait for this file to go away (and stay gone)
*
* @param timeout max time to wait for the file to go away
* @param timeToWait minimum time to wait to make sure the file is gone
* @throws FileLockRemainedException
*/
private void waitForFileLockToGoAway(long timeout, long timeToWait) throws FileLockRemainedException {
File lock = new File(customProfileDir, "parent.lock");
for (long start = System.currentTimeMillis(); System.currentTimeMillis() < start + timeout;) {
AsyncExecute.sleepTight(500);
if (!lock.exists() && makeSureFileLockRemainsGone(lock, timeToWait)) return;
}
if (lock.exists()) throw new FileLockRemainedException("Lock file still present! " + lock.getAbsolutePath());
}
/**
* When initializing the profile, Opera rapidly starts, stops, restarts and
* stops again; we need to wait a bit to make sure the file lock is really gone.
*
* @param lock the parent.lock file in the profile directory
* @param timeToWait minimum time to wait to see if the file shows back
* up again. This is not a timeout; we will always wait this amount of time or more.
* @return true if the file stayed gone for the entire timeToWait; false if the
* file exists (or came back)
*/
private boolean makeSureFileLockRemainsGone(File lock, long timeToWait) {
for (long start = System.currentTimeMillis(); System.currentTimeMillis() < start + timeToWait;) {
AsyncExecute.sleepTight(500);
if (lock.exists()) return false;
}
return !lock.exists();
}
public static void main(String[] args) throws Exception {
OperaCustomProfileLauncher l = new OperaCustomProfileLauncher(new BrowserConfigurationOptions(), new RemoteControlConfiguration(), "CUSTFF", null);
l.launch("http://www.google.com");
int seconds = 15;
System.out.println("Killing browser in " + Integer.toString(seconds) + " seconds");
AsyncExecute.sleepTight(seconds * 1000);
l.close();
System.out.println("He's dead now, right?");
}
private class FileLockRemainedException extends Exception {
FileLockRemainedException(String message) {
super(message);
}
}
public String getCommandPath() {
return commandPath;
}
public void setCommandPath(String commandPath) {
this.commandPath = commandPath;
}
}