Skip to content

Commit b60d3f4

Browse files
[java][BiDi] implement browsingContext.downloadEnd event (#16347)
Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
1 parent 5fdd334 commit b60d3f4

File tree

5 files changed

+291
-0
lines changed

5 files changed

+291
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.bidi.browsingcontext;
19+
20+
import static java.util.Objects.requireNonNullElse;
21+
22+
import org.openqa.selenium.json.JsonInput;
23+
24+
public class DownloadCanceled extends NavigationInfo {
25+
26+
private final String status;
27+
28+
private static final String CANCELED = "canceled";
29+
30+
DownloadCanceled(
31+
String browsingContextId, String navigationId, long timestamp, String url, String status) {
32+
super(browsingContextId, navigationId, timestamp, url);
33+
this.status = requireNonNullElse(status, CANCELED);
34+
}
35+
36+
public static DownloadCanceled fromJson(JsonInput input) {
37+
String browsingContextId = null;
38+
String navigationId = null;
39+
long timestamp = 0;
40+
String url = null;
41+
String status = CANCELED;
42+
43+
input.beginObject();
44+
while (input.hasNext()) {
45+
switch (input.nextName()) {
46+
case "context":
47+
browsingContextId = input.read(String.class);
48+
break;
49+
50+
case "navigation":
51+
navigationId = input.read(String.class);
52+
break;
53+
54+
case "timestamp":
55+
timestamp = input.read(Long.class);
56+
break;
57+
58+
case "url":
59+
url = input.read(String.class);
60+
break;
61+
62+
case "status":
63+
status = input.read(String.class);
64+
if (!CANCELED.equals(status)) {
65+
throw new IllegalArgumentException(
66+
"Expected status '" + CANCELED + "' , but got: " + status);
67+
}
68+
break;
69+
70+
default:
71+
input.skipValue();
72+
break;
73+
}
74+
}
75+
76+
input.endObject();
77+
78+
return new DownloadCanceled(browsingContextId, navigationId, timestamp, url, status);
79+
}
80+
81+
public String getStatus() {
82+
return status;
83+
}
84+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.bidi.browsingcontext;
19+
20+
import static java.util.Objects.requireNonNullElse;
21+
22+
import org.openqa.selenium.json.JsonInput;
23+
24+
public class DownloadCompleted extends NavigationInfo {
25+
26+
private final String status;
27+
private final String filepath;
28+
29+
private static final String COMPLETE = "complete";
30+
31+
DownloadCompleted(
32+
String browsingContextId,
33+
String navigationId,
34+
long timestamp,
35+
String url,
36+
String status,
37+
String filepath) {
38+
super(browsingContextId, navigationId, timestamp, url);
39+
this.status = requireNonNullElse(status, COMPLETE);
40+
this.filepath = filepath;
41+
}
42+
43+
public static DownloadCompleted fromJson(JsonInput input) {
44+
String browsingContextId = null;
45+
String navigationId = null;
46+
long timestamp = 0;
47+
String url = null;
48+
String status = COMPLETE;
49+
String filepath = null;
50+
51+
input.beginObject();
52+
while (input.hasNext()) {
53+
switch (input.nextName()) {
54+
case "context":
55+
browsingContextId = input.read(String.class);
56+
break;
57+
58+
case "navigation":
59+
navigationId = input.read(String.class);
60+
break;
61+
62+
case "timestamp":
63+
timestamp = input.read(Long.class);
64+
break;
65+
66+
case "url":
67+
url = input.read(String.class);
68+
break;
69+
70+
case "status":
71+
status = input.read(String.class);
72+
if (!COMPLETE.equals(status)) {
73+
throw new IllegalArgumentException(
74+
"Expected status '" + COMPLETE + "' , but got: " + status);
75+
}
76+
break;
77+
78+
case "filepath":
79+
filepath = input.read(String.class);
80+
break;
81+
82+
default:
83+
input.skipValue();
84+
break;
85+
}
86+
}
87+
88+
input.endObject();
89+
90+
return new DownloadCompleted(browsingContextId, navigationId, timestamp, url, status, filepath);
91+
}
92+
93+
public String getStatus() {
94+
return status;
95+
}
96+
97+
public String getFilepath() {
98+
return filepath;
99+
}
100+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.bidi.browsingcontext;
19+
20+
import java.io.StringReader;
21+
import java.util.Map;
22+
import org.openqa.selenium.json.Json;
23+
import org.openqa.selenium.json.JsonInput;
24+
25+
public class DownloadEnded {
26+
27+
private static final String CANCELED = "canceled";
28+
private static final String COMPLETE = "complete";
29+
30+
private final NavigationInfo downloadParams;
31+
32+
public DownloadEnded(NavigationInfo downloadParams) {
33+
this.downloadParams = downloadParams;
34+
}
35+
36+
public static DownloadEnded fromJson(JsonInput input) {
37+
Map<String, Object> jsonMap = input.read(Map.class);
38+
String status = (String) jsonMap.get("status");
39+
40+
try (StringReader reader = new StringReader(new Json().toJson(jsonMap));
41+
JsonInput jsonInput = new Json().newInput(reader)) {
42+
if (CANCELED.equals(status)) {
43+
return new DownloadEnded(DownloadCanceled.fromJson(jsonInput));
44+
} else if (COMPLETE.equals(status)) {
45+
return new DownloadEnded(DownloadCompleted.fromJson(jsonInput));
46+
} else {
47+
throw new IllegalArgumentException(
48+
"status must be either '" + CANCELED + "' or '" + COMPLETE + "', but got: " + status);
49+
}
50+
}
51+
}
52+
53+
public NavigationInfo getDownloadParams() {
54+
return downloadParams;
55+
}
56+
57+
public boolean isCanceled() {
58+
return downloadParams instanceof DownloadCanceled;
59+
}
60+
61+
public boolean isCompleted() {
62+
return downloadParams instanceof DownloadCompleted;
63+
}
64+
}

java/src/org/openqa/selenium/bidi/module/BrowsingContextInspector.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.openqa.selenium.bidi.Event;
3030
import org.openqa.selenium.bidi.HasBiDi;
3131
import org.openqa.selenium.bidi.browsingcontext.BrowsingContextInfo;
32+
import org.openqa.selenium.bidi.browsingcontext.DownloadEnded;
3233
import org.openqa.selenium.bidi.browsingcontext.DownloadInfo;
3334
import org.openqa.selenium.bidi.browsingcontext.HistoryUpdated;
3435
import org.openqa.selenium.bidi.browsingcontext.NavigationInfo;
@@ -70,6 +71,14 @@ public class BrowsingContextInspector implements AutoCloseable {
7071
}
7172
};
7273

74+
private final Function<Map<String, Object>, DownloadEnded> downloadEndMapper =
75+
params -> {
76+
try (StringReader reader = new StringReader(JSON.toJson(params));
77+
JsonInput input = JSON.newInput(reader)) {
78+
return input.read(DownloadEnded.class);
79+
}
80+
};
81+
7382
private final Event<BrowsingContextInfo> browsingContextCreated =
7483
new Event<>("browsingContext.contextCreated", browsingContextInfoMapper);
7584

@@ -91,6 +100,9 @@ public class BrowsingContextInspector implements AutoCloseable {
91100
private final Event<DownloadInfo> downloadWillBeginEvent =
92101
new Event<>("browsingContext.downloadWillBegin", downloadWillBeginMapper);
93102

103+
private final Event<DownloadEnded> downloadEndEvent =
104+
new Event<>("browsingContext.downloadEnd", downloadEndMapper);
105+
94106
private final Event<UserPromptOpened> userPromptOpened =
95107
new Event<>(
96108
"browsingContext.userPromptOpened",
@@ -171,6 +183,14 @@ public void onDownloadWillBegin(Consumer<DownloadInfo> consumer) {
171183
}
172184
}
173185

186+
public void onDownloadEnd(Consumer<DownloadEnded> consumer) {
187+
if (browsingContextIds.isEmpty()) {
188+
this.bidi.addListener(downloadEndEvent, consumer);
189+
} else {
190+
this.bidi.addListener(browsingContextIds, downloadEndEvent, consumer);
191+
}
192+
}
193+
174194
public void onNavigationAborted(Consumer<NavigationInfo> consumer) {
175195
addNavigationEventListener("browsingContext.navigationAborted", consumer);
176196
}
@@ -227,6 +247,7 @@ public void close() {
227247
this.bidi.clearListener(userPromptClosed);
228248
this.bidi.clearListener(historyUpdated);
229249
this.bidi.clearListener(downloadWillBeginEvent);
250+
this.bidi.clearListener(downloadEndEvent);
230251

231252
navigationEventSet.forEach(this.bidi::clearListener);
232253
}

java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInspectorTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,28 @@ void canListenToDownloadWillBeginEvent()
256256
}
257257
}
258258

259+
@Test
260+
@NeedsFreshDriver
261+
@NotYetImplemented(FIREFOX)
262+
void canListenToDownloadEnd() throws ExecutionException, InterruptedException, TimeoutException {
263+
try (BrowsingContextInspector inspector = new BrowsingContextInspector(driver)) {
264+
CompletableFuture<DownloadEnded> future = new CompletableFuture<>();
265+
266+
inspector.onDownloadEnd(future::complete);
267+
268+
BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
269+
context.navigate(appServer.whereIs("/downloads/download.html"), ReadinessState.COMPLETE);
270+
271+
driver.findElement(By.id("file-1")).click();
272+
273+
DownloadEnded downloadEnded = future.get(5, TimeUnit.SECONDS);
274+
assertThat(downloadEnded.getDownloadParams().getBrowsingContextId())
275+
.isEqualTo(context.getId());
276+
assertThat(downloadEnded.isCompleted()).isTrue();
277+
assertThat(downloadEnded.getDownloadParams().getUrl()).contains("/downloads/file_1.txt");
278+
}
279+
}
280+
259281
@Test
260282
@NeedsFreshDriver
261283
@NotYetImplemented(FIREFOX)

0 commit comments

Comments
 (0)