diff --git a/playwright/pom.xml b/playwright/pom.xml
index 90526687..ec7ba219 100644
--- a/playwright/pom.xml
+++ b/playwright/pom.xml
@@ -61,6 +61,10 @@
com.google.code.gson
gson
+
+ org.java-websocket
+ Java-WebSocket
+
org.junit.jupiter
junit-jupiter-engine
@@ -73,10 +77,5 @@
com.microsoft.playwright
driver-bundle
-
- org.java-websocket
- Java-WebSocket
- test
-
diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
index 9359ba46..f9f6af9d 100644
--- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
+++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
@@ -41,6 +41,27 @@ import java.util.*;
* }
*/
public interface BrowserType {
+ class ConnectOptions {
+ /**
+ * Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
+ * Defaults to 0.
+ */
+ public Double slowMo;
+ /**
+ * Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
+ * disable timeout.
+ */
+ public Double timeout;
+
+ public ConnectOptions withSlowMo(double slowMo) {
+ this.slowMo = slowMo;
+ return this;
+ }
+ public ConnectOptions withTimeout(double timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+ }
class LaunchOptions {
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found contexts = new HashSet<>();
private final ListenerCollection listeners = new ListenerCollection<>();
+ public boolean isRemote;
private boolean isConnected = true;
enum EventType {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index 75fd236d..b32528ec 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -18,10 +18,15 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.file.Path;
+import java.time.Duration;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -44,6 +49,38 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
}
+ @Override
+ public Browser connect(String wsEndpoint, ConnectOptions options) {
+ return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
+ }
+
+ private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
+ try {
+ Duration timeout = Duration.ofDays(1);
+ if (options != null && options.timeout != null) {
+ timeout = Duration.ofMillis(Math.round(options.timeout));
+ }
+ Connection connection = new Connection(new WebSocketTransport(new URI(wsEndpoint), timeout));
+ RemoteBrowser remoteBrowser = (RemoteBrowser) connection.waitForObjectWithKnownName("remoteBrowser");
+ PlaywrightImpl playwright = this.connection.getExistingObject("Playwright");
+ SelectorsImpl selectors = remoteBrowser.selectors();
+ playwright.sharedSelectors.addChannel(selectors);
+ BrowserImpl browser = remoteBrowser.browser();
+ browser.isRemote = true;
+ browser.onDisconnected(b -> {
+ playwright.sharedSelectors.removeChannel(selectors);
+ try {
+ connection.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ return browser;
+ } catch (URISyntaxException e) {
+ throw new PlaywrightException("Failed to connect", e);
+ }
+ }
+
public String executablePath() {
return initializer.get("executablePath").getAsString();
}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
index fe8ae9c1..0461b9d4 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
@@ -64,8 +64,8 @@ public class Connection {
}
}
- public Connection(InputStream in, OutputStream out) {
- transport = new Transport(in, out);
+ Connection(Transport transport) {
+ this.transport = transport;
root = new Root(this);
}
@@ -232,6 +232,9 @@ public class Connection {
case "Request":
result = new RequestImpl(parent, type, guid, initializer);
break;
+ case "RemoteBrowser":
+ result = new RemoteBrowser(parent, type, guid, initializer);
+ break;
case "Response":
result = new ResponseImpl(parent, type, guid, initializer);
break;
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java
index 8bb2b419..abe524c8 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java
@@ -16,16 +16,27 @@
package com.microsoft.playwright.impl;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Download;
+import com.microsoft.playwright.PlaywrightException;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
+import static com.microsoft.playwright.impl.Utils.writeToFile;
+
public class DownloadImpl extends ChannelOwner implements Download {
+ private final BrowserImpl browser;
+
public DownloadImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
+ browser = ((BrowserContextImpl) parent).browser();
}
@Override
@@ -71,6 +82,9 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public Path path() {
return withLogging("Download.path", () -> {
+ if (browser != null && browser.isRemote) {
+ throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
+ }
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
});
@@ -79,6 +93,13 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public void saveAs(Path path) {
withLogging("Download.saveAs", () -> {
+ if (browser != null && browser.isRemote) {
+ JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
+ Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
+ writeToFile(stream.stream(), path);
+ return;
+ }
+
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PipeTransport.java b/playwright/src/main/java/com/microsoft/playwright/impl/PipeTransport.java
new file mode 100644
index 00000000..4fe86f65
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PipeTransport.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed 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 com.microsoft.playwright.impl;
+
+import com.microsoft.playwright.PlaywrightException;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class PipeTransport implements Transport {
+ private final BlockingQueue incoming = new ArrayBlockingQueue<>(1000);
+ private final BlockingQueue outgoing= new ArrayBlockingQueue<>(1000);
+
+ private final ReaderThread readerThread;
+ private final WriterThread writerThread;
+
+ private boolean isClosed;
+
+ PipeTransport(InputStream input, OutputStream output) {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(input));
+ readerThread = new ReaderThread(in, incoming);
+ readerThread.start();
+ writerThread = new WriterThread(output, outgoing);
+ writerThread.start();
+ }
+
+ @Override
+ public void send(String message) {
+ if (isClosed) {
+ throw new PlaywrightException("Playwright connection closed");
+ }
+ try {
+ outgoing.put(message);
+ } catch (InterruptedException e) {
+ throw new PlaywrightException("Failed to send message", e);
+ }
+ }
+
+ @Override
+ public String poll(Duration timeout) {
+ if (isClosed) {
+ throw new PlaywrightException("Playwright connection closed");
+ }
+ try {
+ return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new PlaywrightException("Failed to read message", e);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+ isClosed = true;
+ // We interrupt only the outgoing pipe and keep reader thread running as
+ // otherwise child process may block on writing to its stdout and never
+ // exit (observed on Windows).
+ readerThread.isClosing = true;
+ writerThread.out.close();
+ writerThread.interrupt();
+ }
+}
+
+class ReaderThread extends Thread {
+ private final DataInputStream in;
+ private final BlockingQueue queue;
+ volatile boolean isClosing;
+
+ private static int readIntLE(DataInputStream in) throws IOException {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ int ch3 = in.read();
+ int ch4 = in.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0) {
+ throw new EOFException();
+ } else {
+ return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
+ }
+ }
+
+ ReaderThread(DataInputStream in, BlockingQueue queue) {
+ this.in = in;
+ this.queue = queue;
+ }
+
+ @Override
+ public void run() {
+ while (!isInterrupted()) {
+ try {
+ queue.put(readMessage());
+ } catch (IOException e) {
+ if (!isInterrupted() && !isClosing) {
+ e.printStackTrace();
+ }
+ break;
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+
+ private String readMessage() throws IOException {
+ int len = readIntLE(in);
+ byte[] raw = new byte[len];
+ in.readFully(raw, 0, len);
+ return new String(raw, StandardCharsets.UTF_8);
+ }
+}
+
+class WriterThread extends Thread {
+ final OutputStream out;
+ private final BlockingQueue queue;
+
+ private static void writeIntLE(OutputStream out, int v) throws IOException {
+ out.write(v >>> 0 & 255);
+ out.write(v >>> 8 & 255);
+ out.write(v >>> 16 & 255);
+ out.write(v >>> 24 & 255);
+ }
+
+ WriterThread(OutputStream out, BlockingQueue queue) {
+ this.out = out;
+ this.queue = queue;
+ }
+
+ @Override
+ public void run() {
+ while (!isInterrupted()) {
+ try {
+ if (queue.isEmpty())
+ out.flush();
+ sendMessage(queue.take());
+ } catch (IOException e) {
+ if (!isInterrupted())
+ e.printStackTrace();
+ break;
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+
+ private void sendMessage(String message) throws IOException {
+ byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
+ writeIntLE(out, bytes.length);
+ out.write(bytes);
+ }
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
index ae45571e..30552d55 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
@@ -35,7 +35,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
// pb.environment().put("DEBUG", "pw:pro*");
Process p = pb.start();
- Connection connection = new Connection(p.getInputStream(), p.getOutputStream());
+ Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
result.driverProcess = p;
return result;
@@ -47,14 +47,15 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
- private final Selectors selectors;
+ final SharedSelectors sharedSelectors = new SharedSelectors();;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
- selectors = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
+ SelectorsImpl channel = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
+ sharedSelectors.addChannel(channel);
}
@Override
@@ -74,7 +75,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
@Override
public Selectors selectors() {
- return selectors;
+ return sharedSelectors;
}
@Override
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java b/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java
new file mode 100644
index 00000000..0dc5011d
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed 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 com.microsoft.playwright.impl;
+
+import com.google.gson.JsonObject;
+
+public class RemoteBrowser extends ChannelOwner {
+ RemoteBrowser(ChannelOwner parent, String type, String guid, JsonObject initializer) {
+ super(parent, type, guid, initializer);
+ }
+
+ BrowserImpl browser() {
+ return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
+ }
+
+ SelectorsImpl selectors() {
+ return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
+ }
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
index 531cf695..e1e4a063 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
@@ -27,34 +27,18 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
import static java.nio.charset.StandardCharsets.UTF_8;
-class SelectorsImpl extends ChannelOwner implements Selectors {
+class SelectorsImpl extends ChannelOwner {
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
- @Override
- public void register(String name, String script, RegisterOptions options) {
- withLogging("Selectors.register", () -> registerImpl(name, script, options));
- }
-
- private void registerImpl(String name, String script, RegisterOptions options) {
+ void registerImpl(String name, String script, Selectors.RegisterOptions options) {
if (options == null) {
- options = new RegisterOptions();
+ options = new Selectors.RegisterOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
params.addProperty("source", script);
sendMessage("register", params);
}
-
- @Override
- public void register(String name, Path path, RegisterOptions options) {
- byte[] buffer;
- try {
- buffer = Files.readAllBytes(path);
- } catch (IOException e) {
- throw new PlaywrightException("Failed to read selector from file: " + path, e);
- }
- register(name, new String(buffer, UTF_8), options);
- }
}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
new file mode 100644
index 00000000..0da01512
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed 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 com.microsoft.playwright.impl;
+
+import com.microsoft.playwright.PlaywrightException;
+import com.microsoft.playwright.Selectors;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class SharedSelectors extends LoggingSupport implements Selectors {
+ private final List channels = new ArrayList<>();
+ private final List registrations = new ArrayList<>();
+
+ private static class Registration {
+ final String name;
+ final String script;
+ final RegisterOptions options;
+
+ Registration(String name, String script, RegisterOptions options) {
+ this.name = name;
+ this.script = script;
+ this.options = options;
+ }
+ }
+
+ @Override
+ public void register(String name, String script, RegisterOptions options) {
+ withLogging("Selectors.register", () -> registerImpl(name, script, options));
+ }
+
+ @Override
+ public void register(String name, Path path, RegisterOptions options) {
+ withLogging("Selectors.register", () -> {
+ byte[] buffer;
+ try {
+ buffer = Files.readAllBytes(path);
+ } catch (IOException e) {
+ throw new PlaywrightException("Failed to read selector from file: " + path, e);
+ }
+ registerImpl(name, new String(buffer, UTF_8), options);
+ });
+ }
+
+ void addChannel(SelectorsImpl channel) {
+ registrations.forEach(r -> channel.registerImpl(r.name, r.script, r.options));
+ channels.add(channel);
+ }
+
+ void removeChannel(SelectorsImpl channel) {
+ channels.remove(channel);
+ }
+
+ private void registerImpl(String name, String script, RegisterOptions options) {
+ channels.forEach(impl -> impl.registerImpl(name, script, options));
+ registrations.add(new Registration(name, script, options));
+ }
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Transport.java b/playwright/src/main/java/com/microsoft/playwright/impl/Transport.java
index af6959eb..9bfb0768 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Transport.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Transport.java
@@ -13,152 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.microsoft.playwright.impl;
-import com.microsoft.playwright.PlaywrightException;
-
-import java.io.*;
-import java.nio.charset.StandardCharsets;
+import java.io.IOException;
import java.time.Duration;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-public class Transport {
- private final BlockingQueue incoming = new ArrayBlockingQueue<>(1000);
- private final BlockingQueue outgoing= new ArrayBlockingQueue<>(1000);
-
- private final ReaderThread readerThread;
- private final WriterThread writerThread;
-
- private boolean isClosed;
-
- Transport(InputStream input, OutputStream output) {
- DataInputStream in = new DataInputStream(new BufferedInputStream(input));
- readerThread = new ReaderThread(in, incoming);
- readerThread.start();
- writerThread = new WriterThread(output, outgoing);
- writerThread.start();
- }
-
- public void send(String message) {
- if (isClosed) {
- throw new PlaywrightException("Playwright connection closed");
- }
- try {
- outgoing.put(message);
- } catch (InterruptedException e) {
- throw new PlaywrightException("Failed to send message", e);
- }
- }
-
- public String poll(Duration timeout) {
- if (isClosed) {
- throw new PlaywrightException("Playwright connection closed");
- }
- try {
- return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- throw new PlaywrightException("Failed to read message", e);
- }
- }
-
- void close() throws IOException {
- if (isClosed) {
- return;
- }
- isClosed = true;
- // We interrupt only the outgoing pipe and keep reader thread running as
- // otherwise child process may block on writing to its stdout and never
- // exit (observed on Windows).
- readerThread.isClosing = true;
- writerThread.out.close();
- writerThread.interrupt();
- }
-}
-
-class ReaderThread extends Thread {
- private final DataInputStream in;
- private final BlockingQueue queue;
- volatile boolean isClosing;
-
- private static int readIntLE(DataInputStream in) throws IOException {
- int ch1 = in.read();
- int ch2 = in.read();
- int ch3 = in.read();
- int ch4 = in.read();
- if ((ch1 | ch2 | ch3 | ch4) < 0) {
- throw new EOFException();
- } else {
- return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
- }
- }
-
- ReaderThread(DataInputStream in, BlockingQueue queue) {
- this.in = in;
- this.queue = queue;
- }
-
- @Override
- public void run() {
- while (!isInterrupted()) {
- try {
- queue.put(readMessage());
- } catch (IOException e) {
- if (!isInterrupted() && !isClosing) {
- e.printStackTrace();
- }
- break;
- } catch (InterruptedException e) {
- break;
- }
- }
- }
-
- private String readMessage() throws IOException {
- int len = readIntLE(in);
- byte[] raw = new byte[len];
- in.readFully(raw, 0, len);
- return new String(raw, StandardCharsets.UTF_8);
- }
-}
-
-class WriterThread extends Thread {
- final OutputStream out;
- private final BlockingQueue queue;
-
- private static void writeIntLE(OutputStream out, int v) throws IOException {
- out.write(v >>> 0 & 255);
- out.write(v >>> 8 & 255);
- out.write(v >>> 16 & 255);
- out.write(v >>> 24 & 255);
- }
-
- WriterThread(OutputStream out, BlockingQueue queue) {
- this.out = out;
- this.queue = queue;
- }
-
- @Override
- public void run() {
- while (!isInterrupted()) {
- try {
- if (queue.isEmpty())
- out.flush();
- sendMessage(queue.take());
- } catch (IOException e) {
- if (!isInterrupted())
- e.printStackTrace();
- break;
- } catch (InterruptedException e) {
- break;
- }
- }
- }
-
- private void sendMessage(String message) throws IOException {
- byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
- writeIntLE(out, bytes.length);
- out.write(bytes);
- }
+public interface Transport {
+ void send(String message);
+ String poll(Duration timeout);
+ void close() throws IOException;
}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
index 6d673a70..f620b2ca 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
@@ -21,8 +21,10 @@ import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@@ -126,8 +128,8 @@ class Utils {
return payloads.toArray(new FilePayload[0]);
}
- static void writeToFile(byte[] buffer, Path path) {
- Path dir = path.getParent();
+ static void mkParentDirs(Path file) {
+ Path dir = file.getParent();
if (dir != null) {
if (!Files.exists(dir)) {
try {
@@ -137,6 +139,10 @@ class Utils {
}
}
}
+ }
+
+ static void writeToFile(byte[] buffer, Path path) {
+ mkParentDirs(path);
try (FileOutputStream out = new FileOutputStream(path.toFile())) {
out.write(buffer);
} catch (IOException e) {
@@ -144,6 +150,19 @@ class Utils {
}
}
+ static void writeToFile(InputStream inputStream, Path path) {
+ mkParentDirs(path);
+ try (FileOutputStream out = new FileOutputStream(path.toFile())) {
+ byte[] buf = new byte[8192];
+ int length;
+ while ((length = inputStream.read(buf)) > 0) {
+ out.write(buf, 0, length);
+ }
+ } catch (IOException e) {
+ throw new PlaywrightException("Failed to write to file", e);
+ }
+ }
+
static boolean isSafeCloseError(PlaywrightException exception) {
return isSafeCloseError(exception.getMessage());
}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketTransport.java b/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketTransport.java
new file mode 100644
index 00000000..d85aafa3
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketTransport.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed 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 com.microsoft.playwright.impl;
+
+import com.microsoft.playwright.PlaywrightException;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import java.io.IOException;
+import java.net.URI;
+import java.time.Duration;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+class WebSocketTransport implements Transport {
+ private final BlockingQueue incoming = new ArrayBlockingQueue<>(1000);
+ private final ClientConnection clientConnection;
+ private boolean isClosed;
+ private volatile Exception lastError;
+
+ private class ClientConnection extends WebSocketClient {
+ ClientConnection(URI serverUri) {
+ super(serverUri);
+ }
+
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ }
+
+ @Override
+ public void onMessage(String message) {
+ incoming.add(message);
+ }
+
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ lastError = ex;
+ }
+ }
+
+ WebSocketTransport(URI uri, Duration timeout) {
+ clientConnection = new ClientConnection(uri);
+ try {
+ if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
+ throw new PlaywrightException("Failed to connect", lastError);
+ }
+ } catch (InterruptedException e) {
+ throw new PlaywrightException("Failed to connect", e);
+ }
+ }
+
+ @Override
+ public void send(String message) {
+ if (clientConnection.isClosed()) {
+ throw new PlaywrightException("Playwright connection closed");
+ }
+ clientConnection.send(message);
+ }
+
+ @Override
+ public String poll(Duration timeout) {
+ if (isClosed || clientConnection.isClosed()) {
+ throw new PlaywrightException("Playwright connection closed");
+ }
+ try {
+ return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new PlaywrightException("Failed to read message", e);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+ isClosed = true;
+ clientConnection.close();
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2ca127f3..d8458353 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,6 @@
org.java-websocket
Java-WebSocket
${websocket.version}
- test
diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION
index 2c34c074..d6d01125 100644
--- a/scripts/CLI_VERSION
+++ b/scripts/CLI_VERSION
@@ -1 +1 @@
-1.9.1-1614654987000
+1.9.1-1614734424000