1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.selenium.browsers.firefox;
18
19
20 import org.openqa.selenium.Capabilities;
21 import org.openqa.selenium.Platform;
22 import org.openqa.selenium.browserlaunchers.Sleeper;
23 import org.openqa.selenium.browserlaunchers.LauncherUtils;
24 import org.openqa.selenium.browserlaunchers.Proxies;
25 import org.openqa.selenium.browserlaunchers.locators.BrowserInstallation;
26 import org.openqa.selenium.browserlaunchers.locators.CombinedFirefoxLocator;
27 import org.openqa.selenium.os.CommandLine;
28 import org.openqa.selenium.remote.BrowserType;
29 import org.openqa.selenium.server.ApplicationRegistry;
30 import org.openqa.selenium.server.RemoteControlConfiguration;
31 import org.openqa.selenium.server.browserlaunchers.AbstractBrowserLauncher;
32 import org.openqa.selenium.server.browserlaunchers.BrowserOptions;
33 import org.openqa.selenium.server.browserlaunchers.InvalidBrowserExecutableException;
34 import org.openqa.selenium.server.browserlaunchers.ResourceExtractor;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.net.MalformedURLException;
39 import java.net.URL;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42
43
44
45
46
47
48 public class DisplayAwareFirefoxChromeLauncher extends AbstractBrowserLauncher {
49 private static final Logger log = Logger.getLogger(DisplayAwareFirefoxChromeLauncher.class.getName());
50
51 private File customProfileDir = null;
52 private boolean closed = false;
53 private BrowserInstallation browserInstallation;
54 private CommandLine process = null;
55
56 private boolean changeMaxConnections = false;
57
58 public DisplayAwareFirefoxChromeLauncher(Capabilities browserOptions,
59 RemoteControlConfiguration configuration, String sessionId, String browserString)
60 throws InvalidBrowserExecutableException {
61 this(browserOptions, configuration,
62 sessionId, ApplicationRegistry.instance()
63 .browserInstallationCache().locateBrowserInstallation(
64 BrowserType.CHROME, browserString, new CombinedFirefoxLocator()));
65 if (browserInstallation == null) {
66 throw new InvalidBrowserExecutableException(
67 "The specified path to the browser executable is invalid.");
68 }
69 }
70
71 public DisplayAwareFirefoxChromeLauncher(Capabilities browserOptions,
72 RemoteControlConfiguration configuration, String sessionId,
73 BrowserInstallation browserInstallation) {
74 super(sessionId, configuration, browserOptions);
75
76 if (browserInstallation == null) {
77 throw new InvalidBrowserExecutableException(
78 "The specified path to the browser executable is invalid.");
79 }
80 this.browserInstallation = browserInstallation;
81 }
82
83
84
85
86
87
88
89
90
91 @Override
92 protected void launch(String url) {
93 final String profilePath;
94 final String homePage;
95
96 try {
97 homePage = new ChromeUrlConvert().convert(url);
98 profilePath = makeCustomProfile(homePage);
99 populateCustomProfileDirectory(profilePath);
100
101 log.info("Launching Firefox...");
102 process = prepareCommand(
103 browserInstallation.launcherFilePath(),
104 "-profile",
105 profilePath
106 );
107 process.setEnvironmentVariable("NO_EM_RESTART", "1");
108 process.executeAsync();
109 } catch (IOException e) {
110 throw new RuntimeException(e);
111 }
112 }
113
114 private void populateCustomProfileDirectory(String profilePath) {
115
116
117
118
119
120
121 CommandLine command = prepareCommand(browserInstallation.launcherFilePath(),
122 "-profile", profilePath,
123 "-silent"
124 );
125 command.setDynamicLibraryPath(browserInstallation.libraryPath());
126 log.info("Preparing Firefox profile...");
127 command.execute();
128 try {
129 waitForFullProfileToBeCreated(20 * 1000);
130 } catch (RuntimeException e) {
131 command.destroy();
132 throw e;
133 }
134 }
135
136 protected CommandLine prepareCommand(String... commands) {
137 CommandLine command = new CommandLine(commands);
138 command.setEnvironmentVariable("MOZ_NO_REMOTE", "1");
139
140
141 Platform platform = Platform.getCurrent();
142 if (!platform.is(Platform.MAC) || ((platform.is(Platform.MAC))
143 && platform.getMajorVersion() <= 10
144 && platform.getMinorVersion() <= 5)) {
145 command.setDynamicLibraryPath(browserInstallation.libraryPath());
146 }
147
148 if (System.getProperty("DISPLAY") != null) {
149 command.setEnvironmentVariable("DISPLAY", System.getProperty("DISPLAY"));
150 }
151
152 return command;
153 }
154
155 protected void createCustomProfileDir() {
156 customProfileDir = LauncherUtils.createCustomProfileDir(sessionId);
157 }
158
159 protected void copyDirectory(File sourceDir, File destDir) {
160 LauncherUtils.copyDirectory(sourceDir, destDir);
161 }
162
163 protected File initProfileTemplate() {
164 File firefoxProfileTemplate;
165
166 String relativeProfile = BrowserOptions
167 .getProfile(browserConfigurationOptions);
168 if (relativeProfile == null) {
169 relativeProfile = "";
170 }
171
172 File profilesLocation = getConfiguration().getProfilesLocation();
173 if (profilesLocation != null && !"".equals(relativeProfile)) {
174
175 firefoxProfileTemplate = getFileFromParent(profilesLocation, relativeProfile);
176 if (!firefoxProfileTemplate.exists()) {
177 throw new RuntimeException(
178 "The profile specified '" + firefoxProfileTemplate.getAbsolutePath()
179 + "' does not exist");
180 }
181 } else {
182 firefoxProfileTemplate =
183 BrowserOptions.getFile(browserConfigurationOptions, "firefoxProfileTemplate");
184 }
185
186 if (firefoxProfileTemplate != null) {
187 copyDirectory(firefoxProfileTemplate, customProfileDir);
188 }
189
190 return firefoxProfileTemplate;
191 }
192
193 protected void extractProfileFromJar() throws IOException {
194 ResourceExtractor.extractResourcePath(getClass(), "/customProfileDirCUSTFFCHROME",
195 customProfileDir);
196 }
197
198 protected void copySingleFileWithOverwrite(File sourceFile, File destFile) {
199 LauncherUtils.copySingleFileWithOverwrite(sourceFile, destFile, true);
200 }
201
202 protected File getFileFromParent(final File parent, String child) {
203 return new File(parent, child);
204 }
205
206 protected void copyCert8db(final File firefoxProfileTemplate) {
207
208 if (firefoxProfileTemplate != null) {
209 File sourceCertFile = getFileFromParent(firefoxProfileTemplate, "cert8.db");
210 if (sourceCertFile.exists()) {
211 File destCertFile = new File(customProfileDir, "cert8.db");
212 copySingleFileWithOverwrite(sourceCertFile, destCertFile);
213 }
214 }
215 }
216
217 protected void generatePacAndPrefJs(String homePage) throws IOException {
218 browserConfigurationOptions = Proxies.setProxyRequired(browserConfigurationOptions, false);
219 if (browserConfigurationOptions.is("captureNetworkTraffic") ||
220 browserConfigurationOptions.is("addCustomRequestHeaders") ||
221 browserConfigurationOptions.is("trustAllSSLCertificates")) {
222 browserConfigurationOptions = Proxies.setProxyEverything(browserConfigurationOptions, true);
223 browserConfigurationOptions = Proxies.setProxyRequired(browserConfigurationOptions, true);
224 }
225
226 LauncherUtils.generatePacAndPrefJs(customProfileDir, getPort(), homePage,
227 changeMaxConnections, getTimeout(), browserConfigurationOptions);
228 }
229
230 private String makeCustomProfile(String homePage) throws IOException {
231
232 createCustomProfileDir();
233
234 File firefoxProfileTemplate = initProfileTemplate();
235
236 extractProfileFromJar();
237
238 copyCert8db(firefoxProfileTemplate);
239
240 copyRunnerHtmlFiles();
241
242 changeMaxConnections = browserConfigurationOptions.is("changeMaxConnections");
243
244 generatePacAndPrefJs(homePage);
245
246 return customProfileDir.getAbsolutePath();
247 }
248
249
250 private void copyRunnerHtmlFiles() {
251 String guid = "{503A0CD4-EDC8-489b-853B-19E0BAA8F0A4}";
252 File extensionDir = new File(customProfileDir, "extensions/" + guid);
253 File htmlDir = new File(extensionDir, "chrome");
254 htmlDir.mkdirs();
255
256 LauncherUtils.extractHTAFile(htmlDir, getPort(), "/core/TestRunner.html", "TestRunner.html");
257 LauncherUtils.extractHTAFile(htmlDir, getPort(), "/core/TestPrompt.html", "TestPrompt.html");
258 LauncherUtils.extractHTAFile(htmlDir, getPort(), "/core/RemoteRunner.html",
259 "RemoteRunner.html");
260
261 }
262
263
264 public void close() {
265 if (closed) {
266 return;
267 }
268 FileLockRemainedException fileLockException = null;
269 if (process != null) {
270 try {
271 killFirefoxProcess();
272 } catch (FileLockRemainedException flre) {
273 fileLockException = flre;
274 }
275 }
276 if (customProfileDir != null) {
277 try {
278 removeCustomProfileDir();
279 } catch (RuntimeException e) {
280 if (fileLockException != null) {
281 log.log(Level.SEVERE, "Couldn't delete custom Firefox profile directory", e);
282 log.severe("Perhaps caused by this exception:");
283 log.log(Level.SEVERE, "Perhaps caused by this exception:", fileLockException);
284 throw new RuntimeException("Couldn't delete custom Firefox " +
285 "profile directory, presumably because task kill failed; " +
286 "see error LOGGER!", e);
287 }
288 throw e;
289 }
290 }
291 closed = true;
292 }
293
294
295
296
297 protected void removeCustomProfileDir() throws RuntimeException {
298 LauncherUtils.deleteTryTryAgain(customProfileDir, 6);
299 }
300
301
302
303
304 protected void killFirefoxProcess() throws FileLockRemainedException {
305 log.info("Killing Firefox...");
306 int exitValue = process.destroy();
307 if (exitValue == 0) {
308 log.warning("Firefox seems to have ended on its own (did we kill the real browser???)");
309 }
310 waitForFileLockToGoAway(0, 500);
311 }
312
313
314
315
316
317
318
319
320
321 private void waitForFileLockToGoAway(long timeout, long timeToWait)
322 throws FileLockRemainedException {
323 File lock = new File(customProfileDir, "parent.lock");
324 for (long start = System.currentTimeMillis(); System.currentTimeMillis() < start + timeout;) {
325 Sleeper.sleepTight(500);
326 if (!lock.exists() && makeSureFileLockRemainsGone(lock, timeToWait)) {
327 return;
328 }
329 }
330 if (lock.exists()) {
331 throw new FileLockRemainedException("Lock file still present! " + lock.getAbsolutePath());
332 }
333 }
334
335
336
337
338
339
340
341
342
343
344
345 private boolean makeSureFileLockRemainsGone(File lock, long timeToWait) {
346 for (long start = System.currentTimeMillis(); System.currentTimeMillis() < start + timeToWait;) {
347 Sleeper.sleepTight(500);
348 if (lock.exists()) {
349 return false;
350 }
351 }
352 return !lock.exists();
353 }
354
355
356
357
358
359
360
361 private void waitForFullProfileToBeCreated(long timeout) {
362
363 File testFile = new File(customProfileDir, "extensions.ini");
364 long start = System.currentTimeMillis();
365 for (; System.currentTimeMillis() < start + timeout;) {
366
367 Sleeper.sleepTight(500);
368 if (testFile.exists()) {
369 break;
370 }
371 }
372 if (!testFile.exists()) {
373 throw new RuntimeException("Timed out waiting for profile to be created!");
374 }
375
376 long subTimeout = timeout - (System.currentTimeMillis() - start);
377 try {
378 waitForFileLockToGoAway(subTimeout, 500);
379 } catch (FileLockRemainedException e) {
380 throw new RuntimeException("Firefox refused shutdown while preparing a profile", e);
381 }
382 }
383
384
385
386 protected void setCustomProfileDir(File value) {
387 customProfileDir = value;
388 }
389
390
391
392 protected void setCommandLine(CommandLine p) {
393 process = p;
394 }
395
396 protected class FileLockRemainedException extends Exception {
397 FileLockRemainedException(String message) {
398 super(message);
399 }
400 }
401
402 public static class ChromeUrlConvert {
403 public String convert(String httpUrl) throws MalformedURLException {
404 String query = LauncherUtils.getQueryString(httpUrl);
405 String file = new File(new URL(httpUrl).getPath()).getName();
406 return "chrome://src/content/" + file + "?" + query;
407 }
408 }
409
410 @Override
411
412 public void launchHTMLSuite(String suiteUrl, String browserURL) {
413
414 if (suiteUrl != null && suiteUrl.startsWith("TestPrompt.html?")) {
415 suiteUrl =
416 suiteUrl.replaceFirst("^TestPrompt\\.html\\?", "chrome://src/content/TestPrompt.html?");
417 }
418 launch(LauncherUtils.getDefaultHTMLSuiteUrl(browserURL, suiteUrl,
419 (!BrowserOptions.isSingleWindow(browserConfigurationOptions)), getPort()));
420 }
421
422 @Override
423
424 public void launchRemoteSession(String browserURL) {
425 launch(LauncherUtils.getDefaultRemoteSessionUrl(browserURL, sessionId,
426 (!BrowserOptions.isSingleWindow(browserConfigurationOptions)), getPort(),
427 browserConfigurationOptions.is("browserSideLog")));
428 }
429
430 }