From 6ad8dc77bf1456ffa1cef502c2f42b9396cb1b8f Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 10 Feb 2026 15:26:32 +0200 Subject: [PATCH] specify nullability in packages `org.openqa.selenium.chrom*` --- .../src/org/openqa/selenium/Capabilities.java | 12 +++ .../selenium/chrome/ChromeDriverInfo.java | 3 - .../openqa/selenium/chrome/package-info.java | 21 +++++ .../selenium/chromium/AddHasCasting.java | 8 +- .../openqa/selenium/chromium/AddHasCdp.java | 7 +- .../chromium/AddHasNetworkConditions.java | 5 +- .../selenium/chromium/ChromiumDriver.java | 13 +-- .../chromium/ChromiumDriverLogLevel.java | 2 +- .../selenium/chromium/ChromiumOptions.java | 84 +++++++++---------- .../openqa/selenium/chromium/HasCasting.java | 2 +- .../openqa/selenium/remote/ExecuteMethod.java | 8 +- .../selenium/remote/FedCmDialogImpl.java | 34 ++++---- .../selenium/remote/LocalExecuteMethod.java | 4 +- .../selenium/remote/RemoteExecuteMethod.java | 5 +- .../selenium/remote/RemoteWebDriver.java | 3 +- 15 files changed, 120 insertions(+), 91 deletions(-) create mode 100644 java/src/org/openqa/selenium/chrome/package-info.java diff --git a/java/src/org/openqa/selenium/Capabilities.java b/java/src/org/openqa/selenium/Capabilities.java index a34bb740b1536..36527a618e55b 100644 --- a/java/src/org/openqa/selenium/Capabilities.java +++ b/java/src/org/openqa/selenium/Capabilities.java @@ -17,6 +17,8 @@ package org.openqa.selenium; +import static java.util.Objects.requireNonNull; + import java.io.Serializable; import java.util.Collections; import java.util.Map; @@ -70,6 +72,16 @@ default String getBrowserVersion() { */ @Nullable Object getCapability(String capabilityName); + @SuppressWarnings("unchecked") + default @Nullable T get(String capabilityName) { + return (T) getCapability(capabilityName); + } + + default T required(String capabilityName) { + return requireNonNull( + get(capabilityName), () -> "Capability " + capabilityName + " is not set"); + } + /** * @param capabilityName The capability to check. * @return Whether the value is not null and not false. diff --git a/java/src/org/openqa/selenium/chrome/ChromeDriverInfo.java b/java/src/org/openqa/selenium/chrome/ChromeDriverInfo.java index 0310ba5cccf31..27c9a823d4a32 100644 --- a/java/src/org/openqa/selenium/chrome/ChromeDriverInfo.java +++ b/java/src/org/openqa/selenium/chrome/ChromeDriverInfo.java @@ -21,7 +21,6 @@ import com.google.auto.service.AutoService; import java.util.Optional; -import java.util.logging.Logger; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.SessionNotCreatedException; @@ -34,8 +33,6 @@ @AutoService(WebDriverInfo.class) public class ChromeDriverInfo extends ChromiumDriverInfo { - private static final Logger LOG = Logger.getLogger(ChromeDriverInfo.class.getName()); - @Override public String getDisplayName() { return "Chrome"; diff --git a/java/src/org/openqa/selenium/chrome/package-info.java b/java/src/org/openqa/selenium/chrome/package-info.java new file mode 100644 index 0000000000000..21abcff6c80d8 --- /dev/null +++ b/java/src/org/openqa/selenium/chrome/package-info.java @@ -0,0 +1,21 @@ +// 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. + +@NullMarked +package org.openqa.selenium.chrome; + +import org.jspecify.annotations.NullMarked; diff --git a/java/src/org/openqa/selenium/chromium/AddHasCasting.java b/java/src/org/openqa/selenium/chromium/AddHasCasting.java index aeb57693110e9..b52d55e50f0dc 100644 --- a/java/src/org/openqa/selenium/chromium/AddHasCasting.java +++ b/java/src/org/openqa/selenium/chromium/AddHasCasting.java @@ -17,6 +17,9 @@ package org.openqa.selenium.chromium; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNullElse; + import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -51,10 +54,9 @@ public Class getDescribedInterface() { @Override public HasCasting getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) { return new HasCasting() { - @SuppressWarnings("unchecked") @Override public List> getCastSinks() { - return (List>) executeMethod.execute(GET_CAST_SINKS, null); + return requireNonNullElse(executeMethod.execute(GET_CAST_SINKS, null), emptyList()); } @Override @@ -80,7 +82,7 @@ public void startTabMirroring(String deviceName) { @Override public String getCastIssueMessage() { - return executeMethod.execute(GET_CAST_ISSUE_MESSAGE, null).toString(); + return executeMethod.executeRequired(GET_CAST_ISSUE_MESSAGE, null).toString(); } @Override diff --git a/java/src/org/openqa/selenium/chromium/AddHasCdp.java b/java/src/org/openqa/selenium/chromium/AddHasCdp.java index 3b6604b54130d..e35998388ffb1 100644 --- a/java/src/org/openqa/selenium/chromium/AddHasCdp.java +++ b/java/src/org/openqa/selenium/chromium/AddHasCdp.java @@ -51,11 +51,8 @@ public HasCdp getImplementation(Capabilities capabilities, ExecuteMethod execute Require.nonNull("Command name", commandName); Require.nonNull("Parameters", parameters); - Map toReturn = - (Map) - executeMethod.execute(EXECUTE_CDP, Map.of("cmd", commandName, "params", parameters)); - - return Map.copyOf(toReturn); + return executeMethod.executeRequired( + EXECUTE_CDP, Map.of("cmd", commandName, "params", parameters)); }; } } diff --git a/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java b/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java index cb976cb2aa87d..3a045c37d8170 100644 --- a/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java +++ b/java/src/org/openqa/selenium/chromium/AddHasNetworkConditions.java @@ -17,7 +17,6 @@ package org.openqa.selenium.chromium; -import static java.util.Objects.requireNonNull; import static org.openqa.selenium.chromium.ChromiumDriver.IS_CHROMIUM_BROWSER; import static org.openqa.selenium.chromium.ChromiumNetworkConditions.DOWNLOAD_THROUGHPUT; import static org.openqa.selenium.chromium.ChromiumNetworkConditions.LATENCY; @@ -77,9 +76,7 @@ public HasNetworkConditions getImplementation( @Override public ChromiumNetworkConditions getNetworkConditions() { @SuppressWarnings("unchecked") - Map result = - (Map) - requireNonNull(executeMethod.execute(GET_NETWORK_CONDITIONS, null)); + Map result = executeMethod.executeRequired(GET_NETWORK_CONDITIONS, null); return new ChromiumNetworkConditions() .setOffline((Boolean) result.getOrDefault(OFFLINE, false)) .setLatency(Duration.ofMillis((Long) result.getOrDefault(LATENCY, 0))) diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java index e5cd3cb48b710..3a3ec3a1bdbc1 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java @@ -17,6 +17,7 @@ package org.openqa.selenium.chromium; +import static java.util.Collections.emptyList; import static org.openqa.selenium.remote.Browser.CHROME; import static org.openqa.selenium.remote.Browser.EDGE; import static org.openqa.selenium.remote.Browser.OPERA; @@ -32,7 +33,6 @@ import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; -import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.openqa.selenium.BuildInfo; import org.openqa.selenium.Capabilities; @@ -69,7 +69,6 @@ * A {@link WebDriver} implementation that controls a Chromium browser running on the local machine. * It is used as the base class for Chromium-based browser drivers (Chrome, Edge). */ -@NullMarked public class ChromiumDriver extends RemoteWebDriver implements HasAuthentication, HasBiDi, @@ -250,8 +249,9 @@ public void unpin(ScriptKey key) { "Page.removeScriptToEvaluateOnNewDocument", Map.of("identifier", key.getIdentifier()))); } + @Nullable @Override - public Object executeScript(ScriptKey key, Object... args) { + public Object executeScript(ScriptKey key, @Nullable Object... args) { int hashCode = getScriptId(key); String scriptToUse = @@ -342,7 +342,7 @@ public Optional maybeGetBiDi() { @Override public List> getCastSinks() { if (this.casting == null) { - return List.of(); + return emptyList(); } return casting.getCastSinks(); @@ -415,9 +415,4 @@ public void setNetworkConditions(ChromiumNetworkConditions networkConditions) { public void deleteNetworkConditions() { networkConditions.deleteNetworkConditions(); } - - @Override - public void quit() { - super.quit(); - } } diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriverLogLevel.java b/java/src/org/openqa/selenium/chromium/ChromiumDriverLogLevel.java index 0a8dd108fe4c6..fc5d2ec615a99 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriverLogLevel.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriverLogLevel.java @@ -52,7 +52,7 @@ public String toString() { } @Nullable - public static ChromiumDriverLogLevel fromString(String text) { + public static ChromiumDriverLogLevel fromString(@Nullable String text) { if (text != null) { for (ChromiumDriverLogLevel b : ChromiumDriverLogLevel.values()) { if (text.equalsIgnoreCase(b.toString())) { diff --git a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java index ac780d57cd4fa..0aa1b30f81d20 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java @@ -17,9 +17,8 @@ package org.openqa.selenium.chromium; -import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toUnmodifiableList; import java.io.File; import java.io.IOException; @@ -30,7 +29,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; @@ -64,12 +62,12 @@ public class ChromiumOptions> extends AbstractDriverOptions> { - private String binary; + private @Nullable String binary; private final List args = new ArrayList<>(); private final List extensionFiles = new ArrayList<>(); private final List extensions = new ArrayList<>(); private final Map experimentalOptions = new HashMap<>(); - private Map androidOptions = new HashMap<>(); + private final Map androidOptions = new HashMap<>(); private final String capabilityName; @@ -87,7 +85,7 @@ public ChromiumOptions(String capabilityType, String browserType, String capabil */ public T setBinary(File path) { binary = Require.nonNull("Path to the chrome executable", path).getPath(); - return (T) this; + return self(); } /** @@ -99,7 +97,7 @@ public T setBinary(File path) { */ public T setBinary(String path) { binary = Require.nonNull("Path to the chrome executable", path); - return (T) this; + return self(); } /** @@ -108,7 +106,7 @@ public T setBinary(String path) { */ public T addArguments(String... arguments) { addArguments(List.of(arguments)); - return (T) this; + return self(); } /** @@ -127,7 +125,7 @@ public T addArguments(String... arguments) { */ public T addArguments(List arguments) { args.addAll(arguments); - return (T) this; + return self(); } /** @@ -136,7 +134,7 @@ public T addArguments(List arguments) { */ public T addExtensions(File... paths) { addExtensions(List.of(paths)); - return (T) this; + return self(); } /** @@ -148,7 +146,7 @@ public T addExtensions(File... paths) { public T addExtensions(List paths) { paths.forEach(path -> Require.argument("Extension", path.toPath()).isFile()); extensionFiles.addAll(paths); - return (T) this; + return self(); } /** @@ -157,7 +155,7 @@ public T addExtensions(List paths) { */ public T addEncodedExtensions(String... encoded) { addEncodedExtensions(List.of(encoded)); - return (T) this; + return self(); } /** @@ -171,12 +169,12 @@ public T addEncodedExtensions(List encoded) { Require.nonNull("Encoded extension", extension); } extensions.addAll(encoded); - return (T) this; + return self(); } public T enableBiDi() { setCapability("webSocketUrl", true); - return (T) this; + return self(); } /** @@ -188,7 +186,7 @@ public T enableBiDi() { */ public T setExperimentalOption(String name, Object value) { experimentalOptions.put(Require.nonNull("Option name", name), value); - return (T) this; + return self(); } public T setAndroidPackage(String androidPackage) { @@ -222,9 +220,12 @@ public T setAndroidProcess(String processName) { private T setAndroidCapability(String name, Object value) { Require.nonNull("Name", name); Require.nonNull("Value", value); - Map newOptions = new TreeMap<>(androidOptions); - newOptions.put(name, value); - androidOptions = Collections.unmodifiableMap(newOptions); + androidOptions.put(name, value); + return self(); + } + + @SuppressWarnings("unchecked") + protected T self() { return (T) this; } @@ -247,30 +248,28 @@ protected Object getExtraCapability(String capabilityName) { options.put("binary", binary); } - options.put("args", unmodifiableList(new ArrayList<>(args))); - - options.put( - "extensions", - unmodifiableList( - Stream.concat( - extensionFiles.stream() - .map( - file -> { - try { - return Base64.getEncoder() - .encodeToString(Files.readAllBytes(file.toPath())); - } catch (IOException e) { - throw new SessionNotCreatedException(e.getMessage(), e); - } - }), - extensions.stream()) - .collect(toList()))); - + options.put("args", List.copyOf(args)); + options.put("extensions", extensionsArgument()); options.putAll(androidOptions); return unmodifiableMap(options); } + private List extensionsArgument() { + return Stream.concat(extensionFiles.stream().map(this::fileContentBase64), extensions.stream()) + .collect(toUnmodifiableList()); + } + + private String fileContentBase64(File file) { + try { + byte[] fileContent = Files.readAllBytes(file.toPath()); + return Base64.getEncoder().encodeToString(fileContent); + } catch (IOException e) { + throw new SessionNotCreatedException( + "Failed to read extension file " + file.getAbsolutePath(), e); + } + } + protected void mergeInPlace(Capabilities capabilities) { Require.nonNull("Capabilities to merge", capabilities); @@ -280,7 +279,7 @@ protected void mergeInPlace(Capabilities capabilities) { } if (name.equals("args") && capabilities.getCapability(name) != null) { - List arguments = (List) (capabilities.getCapability(("args"))); + List arguments = capabilities.required("args"); arguments.forEach( arg -> { if (!args.contains(arg)) { @@ -290,7 +289,7 @@ protected void mergeInPlace(Capabilities capabilities) { } if (name.equals("extensions") && capabilities.getCapability(name) != null) { - List extensionList = (List) (capabilities.getCapability(("extensions"))); + List extensionList = capabilities.required("extensions"); extensionList.forEach( extension -> { if (!extensions.contains(extension)) { @@ -323,12 +322,9 @@ protected void mergeInPlace(Capabilities capabilities) { addExtensions(options.extensionFiles); addEncodedExtensions(options.extensions); - Optional.ofNullable(options.binary).ifPresent(this::setBinary); - + if (options.binary != null) setBinary(options.binary); options.experimentalOptions.forEach(this::setExperimentalOption); - - Optional.ofNullable(options.androidOptions) - .ifPresent(opts -> opts.forEach(this::setAndroidCapability)); + options.androidOptions.forEach(this::setAndroidCapability); } } diff --git a/java/src/org/openqa/selenium/chromium/HasCasting.java b/java/src/org/openqa/selenium/chromium/HasCasting.java index a9f3ca5b95b69..513122e1847b5 100644 --- a/java/src/org/openqa/selenium/chromium/HasCasting.java +++ b/java/src/org/openqa/selenium/chromium/HasCasting.java @@ -28,7 +28,7 @@ public interface HasCasting { /** * Returns the list of cast sinks (Cast devices) available to the Chrome media router. * - * @return array of ID / Name pairs of available cast sink targets + * @return list of ID / Name pairs of available cast sink targets */ List> getCastSinks(); diff --git a/java/src/org/openqa/selenium/remote/ExecuteMethod.java b/java/src/org/openqa/selenium/remote/ExecuteMethod.java index de1caaefe8ca8..e348e5daf4604 100644 --- a/java/src/org/openqa/selenium/remote/ExecuteMethod.java +++ b/java/src/org/openqa/selenium/remote/ExecuteMethod.java @@ -17,6 +17,8 @@ package org.openqa.selenium.remote; +import static java.util.Objects.requireNonNull; + import java.util.Map; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -35,5 +37,9 @@ public interface ExecuteMethod { * @param parameters The parameters to execute that command with * @return The result of {@link Response#getValue()}. */ - @Nullable Object execute(String commandName, @Nullable Map parameters); + @Nullable T execute(String commandName, @Nullable Map parameters); + + default T executeRequired(String commandName, @Nullable Map parameters) { + return requireNonNull(execute(commandName, parameters)); + } } diff --git a/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java b/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java index 59e832f84e367..ed50cfab338fe 100644 --- a/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java +++ b/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java @@ -17,12 +17,15 @@ package org.openqa.selenium.remote; -import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementAccount; import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementDialog; +@NullMarked class FedCmDialogImpl implements FederatedCredentialManagementDialog { private final ExecuteMethod executeMethod; @@ -40,9 +43,10 @@ public void selectAccount(int index) { executeMethod.execute(DriverCommand.SELECT_ACCOUNT, Map.of("accountIndex", index)); } + @Nullable @Override public String getDialogType() { - return (String) executeMethod.execute(DriverCommand.GET_FEDCM_DIALOG_TYPE, null); + return executeMethod.execute(DriverCommand.GET_FEDCM_DIALOG_TYPE, null); } @Override @@ -51,29 +55,27 @@ public void clickDialog() { DriverCommand.CLICK_DIALOG, Map.of("dialogButton", "ConfirmIdpLoginContinue")); } + @Nullable @Override public String getTitle() { - Map result = - (Map) executeMethod.execute(DriverCommand.GET_FEDCM_TITLE, null); - return (String) result.getOrDefault("title", null); + Map result = executeMethod.executeRequired(DriverCommand.GET_FEDCM_TITLE, null); + return result.get("title"); } + @Nullable @Override public String getSubtitle() { - Map result = - (Map) executeMethod.execute(DriverCommand.GET_FEDCM_TITLE, null); - return (String) result.getOrDefault("subtitle", null); + Map result = executeMethod.executeRequired(DriverCommand.GET_FEDCM_TITLE, null); + return result.get("subtitle"); } @Override public List getAccounts() { - List> list = - (List>) executeMethod.execute(DriverCommand.GET_ACCOUNTS, null); - ArrayList accounts = - new ArrayList(); - for (Map map : list) { - accounts.add(new FederatedCredentialManagementAccount(map)); - } - return accounts; + List> accounts = + executeMethod.executeRequired(DriverCommand.GET_ACCOUNTS, null); + + return accounts.stream() + .map(map -> new FederatedCredentialManagementAccount(map)) + .collect(Collectors.toList()); } } diff --git a/java/src/org/openqa/selenium/remote/LocalExecuteMethod.java b/java/src/org/openqa/selenium/remote/LocalExecuteMethod.java index 944b9365d8169..0d2b4e4cb5c36 100644 --- a/java/src/org/openqa/selenium/remote/LocalExecuteMethod.java +++ b/java/src/org/openqa/selenium/remote/LocalExecuteMethod.java @@ -18,13 +18,15 @@ package org.openqa.selenium.remote; import java.util.Map; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebDriverException; +@NullMarked class LocalExecuteMethod implements ExecuteMethod { @Nullable @Override - public Object execute(String commandName, @Nullable Map parameters) { + public T execute(String commandName, @Nullable Map parameters) { throw new WebDriverException("Cannot execute remote command: " + commandName); } } diff --git a/java/src/org/openqa/selenium/remote/RemoteExecuteMethod.java b/java/src/org/openqa/selenium/remote/RemoteExecuteMethod.java index 9be7a20bfd2be..8f28193c5718e 100644 --- a/java/src/org/openqa/selenium/remote/RemoteExecuteMethod.java +++ b/java/src/org/openqa/selenium/remote/RemoteExecuteMethod.java @@ -31,8 +31,9 @@ public RemoteExecuteMethod(RemoteWebDriver driver) { this.driver = Require.nonNull("Remote WebDriver", driver); } + @SuppressWarnings("unchecked") @Override - public @Nullable Object execute(String commandName, @Nullable Map parameters) { + public @Nullable T execute(String commandName, @Nullable Map parameters) { Response response; if (parameters == null || parameters.isEmpty()) { @@ -41,7 +42,7 @@ public RemoteExecuteMethod(RemoteWebDriver driver) { response = driver.execute(commandName, parameters); } - return response.getValue(); + return (T) response.getValue(); } @Override diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index c419961c9b5d1..0ed6e13909865 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -47,6 +47,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.AcceptedW3CCapabilityKeys; import org.openqa.selenium.Alert; import org.openqa.selenium.Beta; @@ -528,7 +529,7 @@ public String getWindowHandle() { } @Override - public Object executeScript(String script, Object... args) { + public @Nullable Object executeScript(@NonNull String script, @Nullable Object... args) { List convertedArgs = Stream.of(args).map(new WebElementToJsonConverter()).collect(Collectors.toList());