Skip to content

Commit dd57d52

Browse files
authored
chore: switch to connect implementation in driver (#615)
1 parent b000a8b commit dd57d52

File tree

11 files changed

+244
-225
lines changed

11 files changed

+244
-225
lines changed

playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ private void closeImpl() {
7171
} catch (IOException e) {
7272
throw new PlaywrightException("Failed to close browser connection", e);
7373
}
74-
notifyRemoteClosed();
7574
return;
7675
}
7776
try {

playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616

1717
package com.microsoft.playwright.impl;
1818

19+
import com.google.gson.Gson;
1920
import com.google.gson.JsonElement;
2021
import com.google.gson.JsonObject;
2122
import com.microsoft.playwright.Browser;
2223
import com.microsoft.playwright.BrowserType;
2324
import com.microsoft.playwright.PlaywrightException;
2425

2526
import java.io.IOException;
26-
import java.net.URI;
27-
import java.net.URISyntaxException;
2827
import java.nio.file.Path;
29-
import java.time.Duration;
30-
import java.util.Collections;
31-
import java.util.Map;
3228
import java.util.function.Consumer;
3329

3430
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -58,51 +54,40 @@ public Browser connect(String wsEndpoint, ConnectOptions options) {
5854
}
5955

6056
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
61-
try {
62-
Duration timeout = Duration.ofDays(1);
63-
Map<String, String> headers = Collections.emptyMap();
64-
Duration slowMo = null;
65-
if (options != null) {
66-
if (options.timeout != null) {
67-
timeout = Duration.ofMillis(Math.round(options.timeout));
68-
}
69-
if (options.headers != null) {
70-
headers = options.headers;
71-
}
72-
if (options.slowMo != null) {
73-
slowMo = Duration.ofMillis(options.slowMo.intValue());
74-
}
75-
}
76-
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), headers, timeout, slowMo);
77-
Connection connection = new Connection(transport);
78-
PlaywrightImpl playwright = connection.initializePlaywright();
79-
if (!playwright.initializer.has("preLaunchedBrowser")) {
80-
try {
81-
connection.close();
82-
} catch (IOException e) {
83-
e.printStackTrace(System.err);
84-
}
85-
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
57+
if (options == null) {
58+
options = new ConnectOptions();
59+
}
60+
// We don't use gson() here as the headers map should be serialized to a json object.
61+
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
62+
params.addProperty("wsEndpoint", wsEndpoint);
63+
JsonObject json = sendMessage("connect", params).getAsJsonObject();
64+
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
65+
Connection connection = new Connection(pipe);
66+
PlaywrightImpl playwright = connection.initializePlaywright();
67+
if (!playwright.initializer.has("preLaunchedBrowser")) {
68+
try {
69+
connection.close();
70+
} catch (IOException e) {
71+
e.printStackTrace(System.err);
8672
}
87-
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
88-
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
89-
browser.isRemote = true;
90-
browser.isConnectedOverWebSocket = true;
91-
Consumer<WebSocketTransport> connectionCloseListener = t -> browser.notifyRemoteClosed();
92-
transport.onClose(connectionCloseListener);
93-
browser.onDisconnected(b -> {
94-
playwright.unregisterSelectors();
95-
transport.offClose(connectionCloseListener);
96-
try {
97-
connection.close();
98-
} catch (IOException e) {
99-
e.printStackTrace(System.err);
100-
}
101-
});
102-
return browser;
103-
} catch (URISyntaxException e) {
104-
throw new PlaywrightException("Failed to connect", e);
73+
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
10574
}
75+
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
76+
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
77+
browser.isRemote = true;
78+
browser.isConnectedOverWebSocket = true;
79+
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
80+
pipe.onClose(connectionCloseListener);
81+
browser.onDisconnected(b -> {
82+
playwright.unregisterSelectors();
83+
pipe.offClose(connectionCloseListener);
84+
try {
85+
connection.close();
86+
} catch (IOException e) {
87+
e.printStackTrace(System.err);
88+
}
89+
});
90+
return browser;
10691
}
10792

10893
@Override

playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ Playwright initialize() {
8585
}
8686

8787
Connection(Transport transport) {
88+
if (isLogging) {
89+
transport = new TransportLogger(transport);
90+
}
8891
this.transport = transport;
8992
root = new Root(this);
9093
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
@@ -172,11 +175,7 @@ private WaitableResult<JsonElement> internalSendMessage(String guid, String meth
172175
metadata.addProperty("apiName", apiName);
173176
}
174177
message.add("metadata", metadata);
175-
String messageString = gson().toJson(message);
176-
if (isLogging) {
177-
logWithTimestamp("SEND ► " + messageString);
178-
}
179-
transport.send(messageString);
178+
transport.send(message);
180179
return result;
181180
}
182181

@@ -200,16 +199,13 @@ void unregisterObject(String guid) {
200199
}
201200

202201
void processOneMessage() {
203-
String messageString = transport.poll(Duration.ofMillis(10));
204-
if (messageString == null) {
202+
JsonObject message = transport.poll(Duration.ofMillis(10));
203+
if (message == null) {
205204
return;
206205
}
207-
if (isLogging) {
208-
logWithTimestamp("◀ RECV " + messageString);
209-
}
210206
Gson gson = gson();
211-
Message message = gson.fromJson(messageString, Message.class);
212-
dispatch(message);
207+
Message messageObj = gson.fromJson(message, Message.class);
208+
dispatch(messageObj);
213209
}
214210

215211
private void dispatch(Message message) {
@@ -315,6 +311,9 @@ private ChannelOwner createRemoteObject(String parentGuid, JsonObject params) {
315311
case "JSHandle":
316312
result = new JSHandleImpl(parent, type, guid, initializer);
317313
break;
314+
case "JsonPipe":
315+
result = new JsonPipe(parent, type, guid, initializer);
316+
break;
318317
case "Page":
319318
result = new PageImpl(parent, type, guid, initializer);
320319
break;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.microsoft.playwright.impl;
18+
19+
import com.google.gson.JsonObject;
20+
import com.microsoft.playwright.PlaywrightException;
21+
22+
import java.io.IOException;
23+
import java.time.Duration;
24+
import java.time.Instant;
25+
import java.util.LinkedList;
26+
import java.util.Queue;
27+
import java.util.function.Consumer;
28+
29+
import static com.microsoft.playwright.impl.Serialization.gson;
30+
31+
class JsonPipe extends ChannelOwner implements Transport {
32+
private final Queue<JsonObject> incoming = new LinkedList<>();
33+
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
34+
private enum EventType { CLOSE }
35+
private boolean isClosed;
36+
37+
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
38+
super(parent, type, guid, initializer);
39+
}
40+
41+
@Override
42+
public void send(JsonObject message) {
43+
checkIfClosed();
44+
JsonObject params = new JsonObject();
45+
params.add("message", message);
46+
sendMessage("send", params);
47+
}
48+
49+
@Override
50+
public JsonObject poll(Duration timeout) {
51+
Instant start = Instant.now();
52+
return runUntil(() -> {}, new Waitable<JsonObject>() {
53+
JsonObject message;
54+
@Override
55+
public boolean isDone() {
56+
if (!incoming.isEmpty()) {
57+
message = incoming.remove();
58+
return true;
59+
}
60+
checkIfClosed();
61+
if (Duration.between(start, Instant.now()).compareTo(timeout) > 0) {
62+
return true;
63+
}
64+
return false;
65+
}
66+
67+
@Override
68+
public JsonObject get() {
69+
return message;
70+
}
71+
72+
@Override
73+
public void dispose() {
74+
}
75+
});
76+
}
77+
78+
@Override
79+
public void close() throws IOException {
80+
if (!isClosed) {
81+
sendMessage("close");
82+
}
83+
}
84+
85+
void onClose(Consumer<JsonPipe> handler) {
86+
listeners.add(EventType.CLOSE, handler);
87+
}
88+
89+
void offClose(Consumer<JsonPipe> handler) {
90+
listeners.remove(EventType.CLOSE, handler);
91+
}
92+
93+
94+
@Override
95+
protected void handleEvent(String event, JsonObject params) {
96+
if ("message".equals(event)) {
97+
incoming.add(params.get("message").getAsJsonObject());
98+
} else if ("closed".equals(event)) {
99+
isClosed = true;
100+
listeners.notify(EventType.CLOSE, this);
101+
}
102+
}
103+
104+
private void checkIfClosed() {
105+
if (isClosed) {
106+
throw new PlaywrightException("Browser has been closed");
107+
}
108+
}
109+
}

playwright/src/main/java/com/microsoft/playwright/impl/PipeTransport.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.microsoft.playwright.impl;
1717

18+
import com.google.gson.JsonObject;
1819
import com.microsoft.playwright.PlaywrightException;
1920

2021
import java.io.*;
@@ -24,8 +25,10 @@
2425
import java.util.concurrent.BlockingQueue;
2526
import java.util.concurrent.TimeUnit;
2627

28+
import static com.microsoft.playwright.impl.Serialization.gson;
29+
2730
public class PipeTransport implements Transport {
28-
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
31+
private final BlockingQueue<JsonObject> incoming = new ArrayBlockingQueue<>(1000);
2932
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
3033

3134
private final ReaderThread readerThread;
@@ -42,24 +45,27 @@ public class PipeTransport implements Transport {
4245
}
4346

4447
@Override
45-
public void send(String message) {
48+
public void send(JsonObject message) {
4649
if (isClosed) {
4750
throw new PlaywrightException("Playwright connection closed");
4851
}
4952
try {
50-
outgoing.put(message);
53+
// We could serialize the message on the IO thread but there is no guarantee
54+
// that the message object won't be modified on this thread after it's added
55+
// to the queue.
56+
outgoing.put(gson().toJson(message));
5157
} catch (InterruptedException e) {
5258
throw new PlaywrightException("Failed to send message", e);
5359
}
5460
}
5561

5662
@Override
57-
public String poll(Duration timeout) {
63+
public JsonObject poll(Duration timeout) {
5864
if (isClosed) {
5965
throw new PlaywrightException("Playwright connection closed");
6066
}
6167
try {
62-
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
68+
JsonObject message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
6369
if (message == null && readerThread.exception != null) {
6470
try {
6571
close();
@@ -91,7 +97,7 @@ public void close() throws IOException {
9197

9298
class ReaderThread extends Thread {
9399
private final DataInputStream in;
94-
private final BlockingQueue<String> queue;
100+
private final BlockingQueue<JsonObject> queue;
95101
volatile boolean isClosing;
96102
volatile Exception exception;
97103

@@ -107,7 +113,7 @@ private static int readIntLE(DataInputStream in) throws IOException {
107113
}
108114
}
109115

110-
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
116+
ReaderThread(DataInputStream in, BlockingQueue<JsonObject> queue) {
111117
this.in = in;
112118
this.queue = queue;
113119
}
@@ -116,7 +122,8 @@ private static int readIntLE(DataInputStream in) throws IOException {
116122
public void run() {
117123
while (!isInterrupted()) {
118124
try {
119-
queue.put(readMessage());
125+
JsonObject message = gson().fromJson(readMessage(), JsonObject.class);
126+
queue.put(message);
120127
} catch (IOException e) {
121128
if (!isInterrupted() && !isClosing) {
122129
exception = e;

0 commit comments

Comments
 (0)