Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class ChromeRequest {
private final Integer id;
private String method;
private Map<String, Object> params;
private String sessionId;

public ChromeRequest(String method) {
this.id = requestNumber.getAndIncrement();
Expand All @@ -35,6 +36,10 @@ public Integer getId() {
return id;
}

public String getSessionId() {
return sessionId;
}

public String getMethod() {
return method;
}
Expand All @@ -55,4 +60,8 @@ public ChromeRequest putParams(String key, Object value) {
}
return this;
}

public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.hubspot.chrome.devtools.base;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.immutables.value.Value;

@Value.Immutable
@ChromeStyle
public interface ChromeVersionInfoIF {
@JsonProperty("Browser")
String getBrowser();

@JsonProperty("Protocol-Version")
String getProtocolVersion();

@JsonProperty("User-Agent")
String getUserAgent();

@JsonProperty("V8-Version")
String getV8Version();

@JsonProperty("WebKit-Version")
String getWebKitVersion();

@JsonProperty("webSocketDebuggerUrl")
String getWebSocketDebuggerUrl();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.hubspot.chrome.devtools.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hubspot.chrome.devtools.base.ChromeRequest;
import com.hubspot.chrome.devtools.client.core.browser.BrowserContextID;
import com.hubspot.chrome.devtools.client.core.target.SessionID;
import com.hubspot.chrome.devtools.client.core.target.Target;
import com.hubspot.chrome.devtools.client.core.target.TargetID;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ChromeDevToolsBrowserContext extends ChromeDevToolsSession {

private static final Logger LOG = LoggerFactory.getLogger(
ChromeDevToolsBrowserContext.class
);
private static final String BLANK_TAB = "about:blank";
private BrowserContextID browserContextId;
private SessionID sessionId;

ChromeDevToolsBrowserContext(
final URI uri,
final ObjectMapper objectMapper,
final ExecutorService executorService,
final long actionTimeoutMillis
) {
super(uri, objectMapper, executorService, actionTimeoutMillis);
this.browserContextId = null;
this.sessionId = null;
}

public void attach() {
if (sessionId == null) {
final Target target = getTarget();
browserContextId = target.createBrowserContext();
final TargetID targetId = target.createTarget(
BLANK_TAB,
null, // left
null, // top
null, // width
null, // height
null, // windowState
browserContextId
);

sessionId = target.attachToTarget(targetId, true);
} else {
LOG.warn("Already attached. sessionId={}", sessionId);
}
}

public BrowserContextID getBrowserContextId() {
return browserContextId;
}

@Override
public void close() throws Exception {
if (browserContextId != null) {
sessionId = null;
getTarget().disposeBrowserContext(browserContextId);
browserContextId = null;
super.close();
} else {
LOG.debug("Not attached");
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!super.equals(o)) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
final ChromeDevToolsBrowserContext other = (ChromeDevToolsBrowserContext) o;
return (
Objects.equals(
browserContextId != null ? browserContextId.getValue() : null,
other.browserContextId != null ? other.browserContextId.getValue() : null
) &&
Objects.equals(
sessionId != null ? sessionId.getValue() : null,
other.sessionId != null ? other.sessionId.getValue() : null
)
);
}

@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
browserContextId != null ? browserContextId.getValue() : null,
sessionId != null ? sessionId.getValue() : null
);
}

@Override
void sendChromeRequest(ChromeRequest request) {
addBrowserContextSessionIdIfRequired(request);
super.sendChromeRequest(request);
}

private void addBrowserContextSessionIdIfRequired(ChromeRequest request) {
if (sessionId != null) {
request.setSessionId(sessionId.getValue());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.hubspot.chrome.devtools.base.ChromeSessionInfo;
import com.hubspot.chrome.devtools.base.ChromeVersionInfo;
import com.hubspot.chrome.devtools.client.core.target.TargetID;
import com.hubspot.chrome.devtools.client.exceptions.ChromeDevToolsException;
import com.hubspot.horizon.HttpClient;
Expand Down Expand Up @@ -82,6 +83,22 @@ public ChromeDevToolsSession connect(String host, int port) throws URISyntaxExce
);
}

public ChromeDevToolsBrowserContext createBrowserContext(String host, int port)
throws URISyntaxException {
final String wsUri = getWebSocketDebuggerUrl(host, port);
return createBrowserContext(wsUri);
}

public ChromeDevToolsBrowserContext createBrowserContext(final String wsUri)
throws URISyntaxException {
return new ChromeDevToolsBrowserContext(
new URI(wsUri),
objectMapper,
executorService,
actionTimeoutMillis
);
}

@Override
public void close() {
try {
Expand All @@ -92,6 +109,24 @@ public void close() {
}
}

private String getWebSocketDebuggerUrl(String host, int port) {
final String url = String.format("http://%s:%d/json/version", host, port);
final HttpRequest httpRequest = HttpRequest
.newBuilder()
.setUrl(url)
.setMethod(Method.GET)
.build();

final HttpResponse response = httpClient.execute(httpRequest);

if (response.isError()) {
throw new ChromeDevToolsException("Unable to find available chrome version info.");
}

final ChromeVersionInfo versionInfo = response.getAs(new TypeReference<>() {});
return versionInfo.getWebSocketDebuggerUrl();
}

private TargetID getFirstAvailableTargetId(String host, int port) {
if (defaultStartNewTarget) {
return startNewTarget(host, port);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public <T> CompletableFuture<T> sendAsync(
);
}

private void sendChromeRequest(ChromeRequest request) {
void sendChromeRequest(ChromeRequest request) {
try {
String json = objectMapper.writeValueAsString(request);
LOG.trace("Sending request: {}", json);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.hubspot.chrome.devtools.client.examples;

import com.hubspot.chrome.devtools.client.ChromeDevToolsBrowserContext;
import com.hubspot.chrome.devtools.client.ChromeDevToolsClient;
import com.hubspot.chrome.devtools.client.core.EventType;
import com.hubspot.chrome.devtools.client.core.browser.BrowserContextID;
import com.hubspot.chrome.devtools.client.core.network.Cookie;
import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.Semaphore;

// Run chrome with args --headless --disable-gpu --remote-debugging-port=9292
public class BrowserContextExample {

private static final String URL = "https://www.example.com/";

public static void main(String[] args) throws URISyntaxException {
// Create the client
ChromeDevToolsClient client = ChromeDevToolsClient.defaultClient();

// Get a browser context
try (
ChromeDevToolsBrowserContext context = client.createBrowserContext(
"127.0.0.1",
9292
)
) {
context.attach();

final Semaphore sem = new Semaphore(0);
context.addEventConsumer(EventType.PAGE_LOAD_EVENT_FIRED, event -> sem.release());
context.getPage().enable();

context.navigate(URL);
final boolean loaded = sem.tryAcquire(1);
if (loaded) {
System.out.println("Loaded: " + URL);

final BrowserContextID browserContextId = context.getBrowserContextId();
List<Cookie> cookies = context.getStorage().getCookies(browserContextId);
for (var cookie : cookies) {
System.out.println("cookie: " + cookie.getName() + "=" + cookie.getValue());
}
} else {
System.out.println("Failed to load: " + URL);
}
} catch (final Exception e) {
System.out.println("Exception: " + e);
}

// Close the client when we are done with it to cleanly shut down executors
client.close();

System.exit(0);
}
}
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,26 @@ client.close();

The `ChromeDevToolsSession` class provides all methods for interacting with Chrome. There are synchronous and asynchronous versions available for each method.

You can also check out our [examples](TODO) for more.
You can also check out our [examples](ChromeDevToolsClient/src/main/java/com/hubspot/chrome/devtools/client/examples) for more.

## Usage with Browser Contexts

Browser Contexts can be used to run multiple independent browser sessions; thus providing session isolation.
The context is used in the same manner as the `ChromeDevToolsSession` described previously, however sessions are isolated in that cookie, local storage and caches are not shared.

```java
// Create the client
ChromeDevToolsClient client = ChromeDevToolsClient.defaultClient();

// Connect to Chrome Dev Tools Running on port 9292 on your local machine and create a browser context
try (ChromeDevToolsBrowserContext context = client.createBrowserContext("127.0.0.1", 9292)) {
context.attach();
context.navigate("https://www.hubspot.com/");
}

// Close the client when your finished
client.close();
```

## Configuring ChromeDevToolsClient

Expand Down
2 changes: 1 addition & 1 deletion release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ https://source.chromium.org/chromium/chromium/src/+/refs/tags/91.0.4472.114:thir

Copy the content of this file to CodeGeneration/src/main/resources/browser_protocol.pdl

Run the pdl_to_json.py scipt to update the corresponding json file:
Run the pdl_to_json.py script to update the corresponding json file:

```
python pdl_to_json.py --pdl_file ../CodeGeneration/src/main/resources/browser_protocol.pdl --json_file ../CodeGeneration/src/main/resources/browser_protocol.json
Expand Down