Skip to content

Commit

Permalink
Allow commands to be loaded via the ServiceLoader (#9854)
Browse files Browse the repository at this point in the history
When adding commands via the Augmenter, it is sometimes helpful to
provide an actual command too. This change allows drivers to specify
new commands and add them as necessary.
  • Loading branch information
shs96c authored Sep 20, 2021
1 parent 46fc208 commit 15dfc62
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 20 deletions.
29 changes: 23 additions & 6 deletions java/src/org/openqa/selenium/firefox/AddHasExtensions.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,34 @@
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.remote.AdditionalHttpCommands;
import org.openqa.selenium.remote.AugmenterProvider;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.ExecuteMethod;
import org.openqa.selenium.remote.http.HttpMethod;

import java.nio.file.Path;
import java.util.Map;
import java.util.function.Predicate;

import static java.util.Collections.singletonMap;
import static org.openqa.selenium.remote.BrowserType.FIREFOX;

@AutoService(AugmenterProvider.class)
public class AddHasExtensions implements AugmenterProvider<HasExtensions> {
@AutoService({AdditionalHttpCommands.class, AugmenterProvider.class})
public class AddHasExtensions implements AugmenterProvider<HasExtensions>, AdditionalHttpCommands {

public static final String INSTALL_EXTENSION = "installExtension";
public static final String UNINSTALL_EXTENSION = "uninstallExtension";

private static final Map<String, CommandInfo> COMMANDS = ImmutableMap.of(
INSTALL_EXTENSION, new CommandInfo("/session/:sessionId/moz/addon/install",HttpMethod.POST),
UNINSTALL_EXTENSION, new CommandInfo("/session/:sessionId/moz/addon/uninstall", HttpMethod.POST));

@Override
public Map<String, CommandInfo> getAdditionalCommands() {
return COMMANDS;
}

@Override
public Predicate<Capabilities> isApplicable() {
return caps -> FIREFOX.equals(caps.getBrowserName());
Expand All @@ -46,15 +63,15 @@ public HasExtensions getImplementation(Capabilities capabilities, ExecuteMethod
return new HasExtensions() {
@Override
public String installExtension(Path path) {
return (String) executeMethod.execute(FirefoxDriver.ExtraCommands.INSTALL_EXTENSION,
ImmutableMap.of("path", path.toAbsolutePath().toString(),
"temporary", false));
return (String) executeMethod.execute(
INSTALL_EXTENSION,
ImmutableMap.of("path", path.toAbsolutePath().toString(), "temporary", false));

}

@Override
public void uninstallExtension(String extensionId) {
executeMethod.execute(FirefoxDriver.ExtraCommands.UNINSTALL_EXTENSION, singletonMap("id", extensionId));
executeMethod.execute(UNINSTALL_EXTENSION, singletonMap("id", extensionId));
}
};
}
Expand Down
27 changes: 15 additions & 12 deletions java/src/org/openqa/selenium/firefox/FirefoxDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@

import java.net.URI;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.StreamSupport;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonMap;
import static org.openqa.selenium.remote.CapabilityType.PROXY;

/**
Expand Down Expand Up @@ -141,29 +141,31 @@ public static final class Capability {
}

static class ExtraCommands {
static String INSTALL_EXTENSION = "installExtension";
static String UNINSTALL_EXTENSION = "uninstallExtension";
static String FULL_PAGE_SCREENSHOT = "fullPageScreenshot";
}

private static final ImmutableMap<String, CommandInfo> EXTRA_COMMANDS = ImmutableMap.of(
ExtraCommands.INSTALL_EXTENSION,
new CommandInfo("/session/:sessionId/moz/addon/install", HttpMethod.POST),
ExtraCommands.UNINSTALL_EXTENSION,
new CommandInfo("/session/:sessionId/moz/addon/uninstall", HttpMethod.POST),
ExtraCommands.FULL_PAGE_SCREENSHOT,
new CommandInfo("/session/:sessionId/moz/screenshot/full", HttpMethod.GET)
);

private static class FirefoxDriverCommandExecutor extends DriverCommandExecutor {
public FirefoxDriverCommandExecutor(DriverService service) {
super(service, EXTRA_COMMANDS);
super(service, getExtraCommands());
}

private static Map<String, CommandInfo> getExtraCommands() {
return ImmutableMap.<String, CommandInfo>builder()
.putAll(EXTRA_COMMANDS)
.putAll(new AddHasExtensions().getAdditionalCommands())
.build();
}
}

private final Capabilities capabilities;
protected FirefoxBinary binary;
private final RemoteWebStorage webStorage;
private final HasExtensions extensions;
private final Optional<URI> cdpUri;
private DevTools devTools;

Expand Down Expand Up @@ -204,6 +206,7 @@ public FirefoxDriver(FirefoxDriverService service, FirefoxOptions options) {
private FirefoxDriver(FirefoxDriverCommandExecutor executor, FirefoxOptions options) {
super(executor, dropCapabilities(options));
webStorage = new RemoteWebStorage(getExecuteMethod());
extensions = new AddHasExtensions().getImplementation(getCapabilities(), getExecuteMethod());

Capabilities capabilities = super.getCapabilities();
HttpClient.Factory clientFactory = HttpClient.Factory.createDefault();
Expand Down Expand Up @@ -269,14 +272,14 @@ private static boolean isLegacy(Capabilities desiredCapabilities) {

@Override
public String installExtension(Path path) {
return (String) execute(ExtraCommands.INSTALL_EXTENSION,
ImmutableMap.of("path", path.toAbsolutePath().toString(),
"temporary", false)).getValue();
Require.nonNull("Path", path);
return extensions.installExtension(path);
}

@Override
public void uninstallExtension(String extensionId) {
execute(ExtraCommands.UNINSTALL_EXTENSION, singletonMap("id", extensionId));
Require.nonNull("Extension ID", extensionId);
extensions.uninstallExtension(extensionId);
}

/**
Expand Down
33 changes: 33 additions & 0 deletions java/src/org/openqa/selenium/remote/AdditionalHttpCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.remote;

import java.util.Map;

/**
* Used to allow a {@link CommandExecutor} using HTTP to find additional
* commands that should be supported. Implementations of this interface
* are found using the {@link java.util.ServiceLoader}
*/
@FunctionalInterface
public interface AdditionalHttpCommands {
/**
* @return Additional commands to add to the {@link CommandExecutor}.
*/
Map<String, CommandInfo> getAdditionalCommands();
}
5 changes: 5 additions & 0 deletions java/src/org/openqa/selenium/remote/CommandCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
*/
public interface CommandCodec<T> {

/**
* @return Whether this {@link CommandCodec} supports the given command name.
*/
boolean isSupported(String commandName);

/**
* Encodes a command.
*
Expand Down
18 changes: 16 additions & 2 deletions java/src/org/openqa/selenium/remote/Dialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec;
import org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec;

import java.util.ServiceLoader;

public enum Dialect {
OSS {
@Override
public CommandCodec<HttpRequest> getCommandCodec() {
return new JsonHttpCommandCodec();
return bindAdditionalCommands(new JsonHttpCommandCodec());
}

@Override
Expand All @@ -49,7 +51,7 @@ public String getShadowRootElementKey() {
W3C {
@Override
public CommandCodec<HttpRequest> getCommandCodec() {
return new W3CHttpCommandCodec();
return bindAdditionalCommands(new W3CHttpCommandCodec());
}

@Override
Expand All @@ -72,4 +74,16 @@ public String getShadowRootElementKey() {
public abstract ResponseCodec<HttpResponse> getResponseCodec();
public abstract String getEncodedElementKey();
public abstract String getShadowRootElementKey();

private static CommandCodec<HttpRequest> bindAdditionalCommands(CommandCodec<HttpRequest> toCodec) {
ServiceLoader.load(AdditionalHttpCommands.class).forEach(cmds -> {
cmds.getAdditionalCommands().forEach((name, info) -> {
if (!toCodec.isSupported(name)) {
toCodec.defineCommand(name, info.getMethod(), info.getUrl());
}
});
});

return toCodec;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ public AbstractHttpCommandCodec() {
defineCommand(SET_USER_VERIFIED, post(webauthnId + "/uv"));
}

@Override
public boolean isSupported(String commandName) {
Require.nonNull("Command name", commandName);
return nameToSpec.containsKey(commandName) || aliases.containsKey(commandName);
}

@Override
public HttpRequest encode(Command command) {
String name = aliases.getOrDefault(command.getName(), command.getName());
Expand Down

0 comments on commit 15dfc62

Please sign in to comment.