3333
3434import org .openqa .selenium .Capabilities ;
3535import org .openqa .selenium .SessionNotCreatedException ;
36+ import org .openqa .selenium .WebDriver ;
3637import org .openqa .selenium .WebDriverException ;
3738import org .openqa .selenium .firefox .internal .ProfilesIni ;
3839import org .openqa .selenium .logging .LoggingPreferences ;
3940import org .openqa .selenium .remote .CapabilityType ;
4041import org .openqa .selenium .remote .DesiredCapabilities ;
4142
43+ import sun .awt .image .ByteInterleavedRaster ;
44+
4245import java .io .File ;
4346import java .io .IOException ;
4447import java .nio .file .Path ;
4548import java .nio .file .Paths ;
4649import java .util .ArrayList ;
4750import java .util .Collection ;
51+ import java .util .Collections ;
4852import java .util .HashMap ;
4953import java .util .List ;
5054import java .util .Map ;
5155import java .util .Optional ;
5256import java .util .logging .Level ;
57+ import java .util .logging .Logger ;
5358import java .util .stream .Collectors ;
5459import java .util .stream .Stream ;
5560
6873public class FirefoxOptions {
6974
7075 public final static String FIREFOX_OPTIONS = "moz:firefoxOptions" ;
76+ private final static Logger LOG = Logger .getLogger (FirefoxOptions .class .getName ());
7177
7278 private String binaryPath ;
7379 private FirefoxBinary actualBinary ;
@@ -159,18 +165,24 @@ public boolean isLegacy() {
159165 public FirefoxOptions setBinary (FirefoxBinary binary ) {
160166 this .actualBinary = Preconditions .checkNotNull (binary );
161167 binary .amendOptions (this );
168+ desiredCapabilities .setCapability (BINARY , binary );
162169 this .binaryPath = null ;
163170 return this ;
164171 }
165172
166173 public FirefoxOptions setBinary (Path path ) {
167174 // Default to UNIX-style paths, even on Windows.
168- StringBuilder builder = new StringBuilder (path .isAbsolute () ? "/" : "" );
169- this .binaryPath = Joiner .on ("/" ).appendTo (builder , path ).toString ();
175+ this .binaryPath = asUnixPath (path );
170176 this .actualBinary = null ;
177+ desiredCapabilities .setCapability (BINARY , new FirefoxBinary (path .toFile ()));
171178 return this ;
172179 }
173180
181+ private String asUnixPath (Path path ) {
182+ StringBuilder builder = new StringBuilder (path .isAbsolute () ? "/" : "" );
183+ return Joiner .on ("/" ).appendTo (builder , path ).toString ();
184+ }
185+
174186 public FirefoxOptions setBinary (String path ) {
175187 return setBinary (Paths .get (checkNotNull (path )));
176188 }
@@ -228,6 +240,16 @@ private Optional<FirefoxBinary> determineBinaryFromCapabilities(Capabilities cap
228240
229241 public FirefoxOptions setProfile (FirefoxProfile profile ) {
230242 this .profile = profile ;
243+
244+ if (!booleanPrefs .isEmpty () || !intPrefs .isEmpty () || !stringPrefs .isEmpty ()) {
245+ LOG .info ("Will update profile with preferences from these options." );
246+ booleanPrefs .entrySet ().forEach (e -> profile .setPreference (e .getKey (), e .getValue ()));
247+ intPrefs .entrySet ().forEach (e -> profile .setPreference (e .getKey (), e .getValue ()));
248+ stringPrefs .entrySet ().forEach (e -> profile .setPreference (e .getKey (), e .getValue ()));
249+ }
250+
251+ desiredCapabilities .setCapability (PROFILE , profile );
252+
231253 return this ;
232254 }
233255
@@ -289,14 +311,6 @@ private static void populateProfile(FirefoxProfile profile, Capabilities capabil
289311 }
290312 }
291313
292- // Confusing API. Keeping package visible only
293- FirefoxOptions setProfileSafely (FirefoxProfile profile ) {
294- if (profile == null ) {
295- return this ;
296- }
297- return setProfile (profile );
298- }
299-
300314 public FirefoxOptions addArguments (String ... arguments ) {
301315 addArguments (ImmutableList .copyOf (arguments ));
302316 return this ;
@@ -309,16 +323,25 @@ public FirefoxOptions addArguments(List<String> arguments) {
309323
310324 public FirefoxOptions addPreference (String key , boolean value ) {
311325 booleanPrefs .put (checkNotNull (key ), value );
326+ if (profile != null ) {
327+ profile .setPreference (key , value );
328+ }
312329 return this ;
313330 }
314331
315332 public FirefoxOptions addPreference (String key , int value ) {
316333 intPrefs .put (checkNotNull (key ), value );
334+ if (profile != null ) {
335+ profile .setPreference (key , value );
336+ }
317337 return this ;
318338 }
319339
320340 public FirefoxOptions addPreference (String key , String value ) {
321341 stringPrefs .put (checkNotNull (key ), checkNotNull (value ));
342+ if (profile != null ) {
343+ profile .setPreference (key , value );
344+ }
322345 return this ;
323346 }
324347
@@ -328,28 +351,33 @@ public FirefoxOptions setLogLevel(Level logLevel) {
328351 }
329352
330353 public FirefoxOptions addDesiredCapabilities (Capabilities desiredCapabilities ) {
331- if (desiredCapabilities == null ) {
354+ return validateAndAmendUsing (this .desiredCapabilities , desiredCapabilities );
355+ }
356+
357+ public FirefoxOptions addRequiredCapabilities (Capabilities requiredCapabilities ) {
358+ return validateAndAmendUsing (this .requiredCapabilities , requiredCapabilities );
359+ }
360+
361+ private FirefoxOptions validateAndAmendUsing (DesiredCapabilities existing , Capabilities caps ) {
362+ if (caps == null ) {
332363 return this ;
333364 }
334365
335- this . desiredCapabilities . merge (desiredCapabilities );
366+ existing . merge (caps );
336367
337- FirefoxProfile suggestedProfile = extractProfile (desiredCapabilities );
338- if (suggestedProfile != null ) {
339- if (!booleanPrefs .isEmpty () || !intPrefs .isEmpty () || !stringPrefs .isEmpty ()) {
340- throw new IllegalStateException (
341- "Unable to determine if preferences set on this option " +
342- "are the same as the profile in the capabilities" );
343- }
344- if (profile != null && !suggestedProfile .equals (profile )) {
345- throw new IllegalStateException (
346- "Profile has been set on both the capabilities and these options, but they're " +
347- "different. Unable to determine which one you want to use." );
348- }
349- profile = suggestedProfile ;
368+ FirefoxProfile newProfile = extractProfile (caps );
369+ if (profile != null && newProfile != null && !profile .equals (newProfile )) {
370+ LOG .info ("Found a profile on these options and the capabilities. Will assume you " +
371+ "want the profile already set here. If you're seeing this in the logs of the " +
372+ "standalone server, we've probably just deserialized the same options twice and " +
373+ "it's likely that there's nothing to worry about." );
350374 }
351375
352- Object binary = desiredCapabilities .getCapability (BINARY );
376+ if (newProfile != null ) {
377+ setProfile (newProfile );
378+ }
379+
380+ Object binary = existing .getCapability (BINARY );
353381 if (binary != null ) {
354382 if (binary instanceof File ) {
355383 setBinary (((File ) binary ).toPath ());
@@ -365,27 +393,6 @@ public FirefoxOptions addDesiredCapabilities(Capabilities desiredCapabilities) {
365393 return this ;
366394 }
367395
368- public FirefoxOptions addRequiredCapabilities (Capabilities requiredCapabilities ) {
369- this .requiredCapabilities .merge (requiredCapabilities );
370-
371- FirefoxProfile suggestedProfile = extractProfile (desiredCapabilities );
372- if (suggestedProfile != null ) {
373- if (!booleanPrefs .isEmpty () || !intPrefs .isEmpty () || !stringPrefs .isEmpty ()) {
374- throw new IllegalStateException (
375- "Unable to determine if preferences set on this option " +
376- "are the same as the profile in the capabilities" );
377- }
378- if (profile != null && !suggestedProfile .equals (profile )) {
379- throw new IllegalStateException (
380- "Profile has been set on both the capabilities and these options, but they're " +
381- "different. Unable to determine which one you want to use." );
382- }
383- profile = suggestedProfile ;
384- }
385-
386- return this ;
387- }
388-
389396 private FirefoxProfile extractProfile (Capabilities capabilities ) {
390397 if (capabilities == null ) {
391398 return null ;
@@ -409,7 +416,15 @@ private FirefoxProfile extractProfile(Capabilities capabilities) {
409416 }
410417
411418 public Capabilities toDesiredCapabilities () {
412- DesiredCapabilities capabilities = new DesiredCapabilities (desiredCapabilities );
419+ return toCapabilities (desiredCapabilities );
420+ }
421+
422+ public Capabilities toRequiredCapabilities () {
423+ return toCapabilities (requiredCapabilities );
424+ }
425+
426+ private Capabilities toCapabilities (Capabilities source ) {
427+ DesiredCapabilities capabilities = new DesiredCapabilities (source );
413428
414429 if (isLegacy ()) {
415430 capabilities .setCapability (FirefoxDriver .MARIONETTE , false );
@@ -418,40 +433,54 @@ public Capabilities toDesiredCapabilities() {
418433 Object priorBinary = capabilities .getCapability (BINARY );
419434 if (priorBinary instanceof Path ) {
420435 // Again, unix-style path
421- priorBinary = Joiner . on ( "/" ). join ((Path ) priorBinary );
436+ priorBinary = asUnixPath ((Path ) priorBinary );
422437 }
423438 if (priorBinary instanceof String ) {
424- if (actualBinary != null || !priorBinary .equals (binaryPath )) {
425- throw new IllegalStateException (String .format (
426- "Binary already set in capabilities, but is different from the one set here: %s, %s" ,
427- priorBinary ,
428- binaryPath != null ? binaryPath : actualBinary ));
429- }
439+ priorBinary = asUnixPath (Paths .get ((String ) priorBinary ));
430440 }
431441 if (priorBinary instanceof FirefoxBinary ) {
432- // Relies on instance equality, which is fragile.
433- if (binaryPath != null || !priorBinary .equals (actualBinary )) {
434- throw new IllegalStateException (String .format (
435- "Binary already set in capabilities, but is different from the one set here: %s, %s" ,
436- priorBinary ,
437- actualBinary != null ? actualBinary : binaryPath ));
438- }
442+ priorBinary = asUnixPath (((FirefoxBinary ) priorBinary ).getFile ().toPath ());
443+ }
444+
445+ if ((actualBinary != null && !actualBinary .getFile ().toPath ().equals (priorBinary )) ||
446+ (binaryPath != null && !binaryPath .equals (priorBinary ))) {
447+ LOG .info (String .format (
448+ "Preferring the firefox binary in these options (%s rather than %s)" ,
449+ actualBinary != null ? actualBinary .getPath () : binaryPath ,
450+ priorBinary ));
451+ }
452+ if (actualBinary != null && binaryPath == null ) {
453+ capabilities .setCapability (BINARY , actualBinary );
454+ } else if (binaryPath != null && actualBinary == null ) {
455+ capabilities .setCapability (BINARY , new FirefoxBinary (new File (binaryPath )));
439456 }
440457
441458 Object priorProfile = capabilities .getCapability (PROFILE );
459+ if (priorProfile instanceof String ) {
460+ try {
461+ priorProfile = FirefoxProfile .fromJson ((String ) priorProfile );
462+ } catch (IOException e ) {
463+ throw new WebDriverException (e );
464+ }
465+ }
442466 if (priorProfile != null ) {
443467 if (!booleanPrefs .isEmpty () || !intPrefs .isEmpty () || !stringPrefs .isEmpty ()) {
444- throw new IllegalStateException (
445- "Unable to determine if preferences set on this option " +
446- "are the same as the profile in the capabilities" );
468+ LOG .info ("Setting our our preferences on the existing profile" );
447469 }
448470 if (profile != null && !priorProfile .equals (profile )) {
449- throw new IllegalStateException (
450- "Profile has been set on both the capabilities and these options, but they're " +
451- "different. Unable to determine which one you want to use." );
471+ LOG .info ("Found a profile on these options and the capabilities. Will assume you " +
472+ "want the profile already set here. If you're seeing this in the logs of the " +
473+ "standalone server, we've probably just deserialized the same options twice and " +
474+ "it's likely that there's nothing to worry about." );
475+ }
476+ if (profile == null ) {
477+ if (priorProfile instanceof FirefoxProfile ) {
478+ profile = (FirefoxProfile ) priorProfile ;
479+ } else {
480+ LOG .info ("Unable to use profile: " + priorProfile .getClass ());
481+ }
452482 }
453483 }
454-
455484 capabilities .setCapability (FIREFOX_OPTIONS , this );
456485
457486 if (actualBinary != null ) {
@@ -469,12 +498,9 @@ public Capabilities toDesiredCapabilities() {
469498 return capabilities ;
470499 }
471500
472- public Capabilities toRequiredCapabilities () {
473- return requiredCapabilities ;
474- }
475-
476501 public DesiredCapabilities addTo (DesiredCapabilities capabilities ) {
477502 capabilities .merge (toDesiredCapabilities ());
503+ capabilities .merge (toRequiredCapabilities ());
478504 return capabilities ;
479505 }
480506
0 commit comments