Skip to content

Commit 1df7898

Browse files
committed
Attempt to avoid sending doubles instead of longs in JSON
I mean, longs are totally fine since JS doesn't differentiate, but client code does. Apparently.
1 parent 60480b0 commit 1df7898

File tree

2 files changed

+149
-11
lines changed

2 files changed

+149
-11
lines changed

java/client/src/org/openqa/selenium/remote/ProtocolHandshake.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.google.gson.JsonArray;
3737
import com.google.gson.JsonElement;
3838
import com.google.gson.JsonObject;
39-
import com.google.gson.JsonParseException;
4039
import com.google.gson.JsonPrimitive;
4140
import com.google.gson.reflect.TypeToken;
4241
import com.google.gson.stream.JsonWriter;
@@ -342,8 +341,8 @@ public Optional<Result> createSession(HttpClient client, InputStream newSessionB
342341
// W3C spec properly. Oh well.
343342
Map<?, ?> blob;
344343
try {
345-
blob = new Gson().fromJson(response.getContentString(), MAP_TYPE);
346-
} catch (JsonParseException e) {
344+
blob = new JsonToBeanConverter().convert(Map.class, response.getContentString());
345+
} catch (JsonException e) {
347346
throw new WebDriverException(
348347
"Unable to parse remote response: " + response.getContentString());
349348
}

java/server/src/org/openqa/selenium/remote/server/NewSessionPayload.java

Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static java.nio.file.StandardOpenOption.CREATE;
55
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
66

7-
import com.google.common.base.Strings;
87
import com.google.common.collect.ImmutableList;
98
import com.google.common.collect.ImmutableMap;
109
import com.google.common.collect.ImmutableSet;
@@ -14,14 +13,20 @@
1413
import com.google.common.io.CharStreams;
1514
import com.google.gson.Gson;
1615
import com.google.gson.GsonBuilder;
16+
import com.google.gson.JsonParseException;
17+
import com.google.gson.TypeAdapter;
18+
import com.google.gson.TypeAdapterFactory;
1719
import com.google.gson.reflect.TypeToken;
1820
import com.google.gson.stream.JsonReader;
21+
import com.google.gson.stream.JsonToken;
22+
import com.google.gson.stream.JsonWriter;
1923

2024
import org.openqa.selenium.Capabilities;
2125
import org.openqa.selenium.ImmutableCapabilities;
2226
import org.openqa.selenium.io.FileHandler;
2327
import org.openqa.selenium.remote.BeanToJsonConverter;
2428
import org.openqa.selenium.remote.Dialect;
29+
import org.openqa.selenium.remote.JsonToBeanConverter;
2530

2631
import java.io.ByteArrayInputStream;
2732
import java.io.Closeable;
@@ -73,7 +78,12 @@ public class NewSessionPayload implements Closeable {
7378
// Dedicate up to 10% of max ram to holding the payload
7479
private static final long THRESHOLD = Runtime.getRuntime().maxMemory() / 10;
7580

76-
private static final Gson GSON = new GsonBuilder().setLenient().serializeNulls().create();
81+
private static final Gson GSON = new GsonBuilder()
82+
.registerTypeAdapterFactory(ListAdapter.FACTORY)
83+
.registerTypeAdapterFactory(MapAdapter.FACTORY)
84+
.setLenient()
85+
.serializeNulls()
86+
.create();
7787
private static final Type MAP_TYPE = new TypeToken<Map<String, Object>>(){}.getType();
7888

7989
private final Path root;
@@ -128,7 +138,9 @@ public NewSessionPayload(long size, Reader source) throws IOException {
128138
sources = diskBackedSource(source);
129139
} else {
130140
this.root = null;
131-
sources = memoryBackedSource(GSON.fromJson(source, MAP_TYPE));
141+
sources =
142+
memoryBackedSource(
143+
new JsonToBeanConverter().convert(Map.class, CharStreams.toString(source)));
132144
}
133145

134146
validate(sources);
@@ -200,7 +212,7 @@ private Sources rewrite(Sources sources) {
200212
return sources;
201213
}
202214

203-
LOG.info( "Mismatched capabilities. Creating a synthetic w3c capability.");
215+
LOG.info("Mismatched capabilities. Creating a synthetic w3c capability.");
204216

205217
ImmutableList.Builder<Supplier<Map<String, Object>>> newFirstMatches = ImmutableList.builder();
206218
newFirstMatches.add(sources.getOss());
@@ -221,7 +233,7 @@ private Sources rewrite(Sources sources) {
221233
sources.getDialects());
222234
}
223235

224-
private Sources memoryBackedSource(Map<String, ?> source) {
236+
private Sources memoryBackedSource(Map<?, ?> source) {
225237
LOG.fine("Memory-based payload for: " + source);
226238

227239
Set<Dialect> dialects = new TreeSet<>();
@@ -387,8 +399,8 @@ public Stream<Capabilities> stream() throws IOException {
387399
if (getDownstreamDialects().contains(Dialect.W3C)) {
388400
Map<String, Object> always = sources.getAlwaysMatch().get();
389401
mapStream = sources.getFirstMatch().stream()
390-
.map(Supplier::get)
391-
.map(m -> ImmutableMap.<String, Object>builder().putAll(always).putAll(m).build());
402+
.map(Supplier::get)
403+
.map(m -> ImmutableMap.<String, Object>builder().putAll(always).putAll(m).build());
392404
} else if (getDownstreamDialects().contains(Dialect.OSS)) {
393405
mapStream = Stream.of(sources.getOss().get());
394406
} else {
@@ -401,7 +413,7 @@ public Stream<Capabilities> stream() throws IOException {
401413
public ImmutableSet<Dialect> getDownstreamDialects() {
402414
return sources.getDialects().isEmpty() ?
403415
ImmutableSet.of(DEFAULT_DIALECT) :
404-
sources .getDialects();
416+
sources.getDialects();
405417
}
406418

407419
public Supplier<InputStream> getPayload() {
@@ -421,6 +433,7 @@ public void close() {
421433

422434

423435
private static class Sources {
436+
424437
private final Supplier<InputStream> originalPayload;
425438
private final long payloadSizeInBytes;
426439
private final Supplier<Map<String, Object>> oss;
@@ -467,4 +480,130 @@ public long getPayloadSize() {
467480
return payloadSizeInBytes;
468481
}
469482
}
483+
484+
private static Object readValue(JsonReader in, Gson gson) throws IOException {
485+
switch (in.peek()) {
486+
case BEGIN_ARRAY:
487+
case BEGIN_OBJECT:
488+
case BOOLEAN:
489+
case NULL:
490+
case STRING:
491+
return gson.fromJson(in, Object.class);
492+
493+
case NUMBER:
494+
String number = in.nextString();
495+
if (number.indexOf('.') != -1) {
496+
return Double.parseDouble(number);
497+
}
498+
return Long.parseLong(number);
499+
500+
default:
501+
throw new JsonParseException("Unexpected type: " + in.peek());
502+
}
503+
}
504+
505+
private static class MapAdapter extends TypeAdapter<Map<?, ?>> {
506+
507+
private final static TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
508+
@SuppressWarnings("unchecked")
509+
@Override
510+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
511+
if (type.getRawType() == Map.class) {
512+
return (TypeAdapter<T>) new MapAdapter(gson);
513+
}
514+
return null;
515+
}
516+
};
517+
518+
private final Gson gson;
519+
520+
private MapAdapter(Gson gson) {
521+
this.gson = Objects.requireNonNull(gson);
522+
}
523+
524+
@Override
525+
public Map<?, ?> read(JsonReader in) throws IOException {
526+
if (in.peek() == JsonToken.NULL) {
527+
in.nextNull();
528+
return null;
529+
}
530+
531+
Map<String, Object> map = new TreeMap<>();
532+
in.beginObject();
533+
534+
while (in.hasNext()) {
535+
String key = in.nextName();
536+
Object value = readValue(in, gson);
537+
538+
map.put(key, value);
539+
}
540+
541+
in.endObject();
542+
return map;
543+
}
544+
545+
@Override
546+
public void write(JsonWriter out, Map<?, ?> value) throws IOException {
547+
// It's fine to use GSON's own default writer for this.
548+
out.beginObject();
549+
for (Map.Entry<?, ?> entry : value.entrySet()) {
550+
out.name(String.valueOf(entry.getKey()));
551+
@SuppressWarnings("unchecked")
552+
TypeAdapter<Object>
553+
adapter =
554+
(TypeAdapter<Object>) gson.getAdapter(entry.getValue().getClass());
555+
adapter.write(out, entry.getValue());
556+
}
557+
out.endObject();
558+
}
559+
}
560+
561+
private static class ListAdapter extends TypeAdapter<List<?>> {
562+
563+
private final static TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
564+
@SuppressWarnings("unchecked")
565+
@Override
566+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
567+
if (type.getRawType() == List.class) {
568+
return (TypeAdapter<T>) new ListAdapter(gson);
569+
}
570+
return null;
571+
}
572+
};
573+
574+
private final Gson gson;
575+
576+
private ListAdapter(Gson gson) {
577+
this.gson = Objects.requireNonNull(gson);
578+
}
579+
580+
@Override
581+
public List<?> read(JsonReader in) throws IOException {
582+
if (in.peek() == JsonToken.NULL) {
583+
in.nextNull();
584+
return null;
585+
}
586+
587+
List<Object> list = new LinkedList<>();
588+
in.beginArray();
589+
590+
while (in.hasNext()) {
591+
list.add(readValue(in, gson));
592+
}
593+
594+
in.endArray();
595+
return list;
596+
}
597+
598+
@Override
599+
public void write(JsonWriter out, List<?> value) throws IOException {
600+
out.beginArray();
601+
for (Object entry : value) {
602+
@SuppressWarnings("unchecked")
603+
TypeAdapter<Object> adapter = (TypeAdapter<Object>) gson.getAdapter(entry.getClass());
604+
adapter.write(out, entry);
605+
}
606+
out.endArray();
607+
}
608+
}
470609
}

0 commit comments

Comments
 (0)