Skip to content

Commit 33365ce

Browse files
Delta456VietND96
andauthored
[java][BiDi] add clearListners via browsingContextIds for inspectors (#17376)
Co-authored-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
1 parent 6a68466 commit 33365ce

7 files changed

Lines changed: 333 additions & 5 deletions

File tree

java/src/org/openqa/selenium/bidi/BiDi.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.concurrent.CopyOnWriteArrayList;
2729
import java.util.function.Consumer;
2830
import java.util.logging.Logger;
2931
import org.openqa.selenium.WebDriverException;
@@ -34,6 +36,7 @@ public class BiDi implements Closeable {
3436

3537
private final Duration timeout;
3638
private final Connection connection;
39+
private final Map<Event<?>, List<Long>> contextListenerIds = new ConcurrentHashMap<>();
3740

3841
/**
3942
* @deprecated Use constructor with timeout parameter: {@link #BiDi(Connection, Duration)}
@@ -100,11 +103,13 @@ public <X> long addListener(String browsingContextId, Event<X> event, Consumer<X
100103
"session.subscribe",
101104
Map.of("contexts", List.of(browsingContextId), "events", List.of(event.getMethod()))));
102105

103-
return connection.addListener(event, handler);
106+
long id = connection.addListener(event, handler);
107+
contextListenerIds.computeIfAbsent(event, k -> new CopyOnWriteArrayList<>()).add(id);
108+
return id;
104109
}
105110

106111
public <X> long addListener(Set<String> browsingContextIds, Event<X> event, Consumer<X> handler) {
107-
Require.nonNull("List of browsing context ids", browsingContextIds);
112+
Require.nonEmpty("List of browsing context ids", browsingContextIds);
108113
Require.nonNull("Event to listen for", event);
109114
Require.nonNull("Handler to call", handler);
110115

@@ -113,7 +118,9 @@ public <X> long addListener(Set<String> browsingContextIds, Event<X> event, Cons
113118
"session.subscribe",
114119
Map.of("contexts", browsingContextIds, "events", List.of(event.getMethod()))));
115120

116-
return connection.addListener(event, handler);
121+
long id = connection.addListener(event, handler);
122+
contextListenerIds.computeIfAbsent(event, k -> new CopyOnWriteArrayList<>()).add(id);
123+
return id;
117124
}
118125

119126
public <X> void clearListener(Event<X> event) {
@@ -127,6 +134,32 @@ public <X> void clearListener(Event<X> event) {
127134
}
128135
}
129136

137+
public <X> void clearListener(Set<String> browsingContextIds, Event<X> event) {
138+
Require.nonEmpty("List of browsing context ids", browsingContextIds);
139+
Require.nonNull("Event to listen for", event);
140+
141+
// The browser throws an error if we try to unsubscribe an event that was not subscribed in the
142+
// first place
143+
if (!connection.isEventSubscribed(event)) {
144+
return;
145+
}
146+
147+
List<Long> ids = contextListenerIds.remove(event);
148+
if (ids != null && !ids.isEmpty()) {
149+
// Subscription was context-specific: unsubscribe only for those contexts.
150+
send(
151+
new Command<>(
152+
"session.unsubscribe",
153+
Map.of("contexts", browsingContextIds, "events", List.of(event.getMethod()))));
154+
ids.forEach(connection::removeListener);
155+
} else {
156+
// Subscription was global: context-specific unsubscription is invalid per the BiDi protocol,
157+
// so fall back to a global unsubscription.
158+
send(new Command<>("session.unsubscribe", Map.of("events", List.of(event.getMethod()))));
159+
connection.clearListener(event);
160+
}
161+
}
162+
130163
public void removeListener(long id) {
131164
connection.removeListener(id);
132165
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.StringReader;
2121
import java.util.Collections;
2222
import java.util.HashSet;
23+
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
2526
import java.util.function.Consumer;
@@ -240,6 +241,27 @@ private void addNavigationEventListener(String name, Consumer<NavigationInfo> co
240241
}
241242
}
242243

244+
public void clearListener(String browsingContextId) {
245+
Require.nonNull("Browsing context id", browsingContextId);
246+
clearListeners(Collections.singleton(browsingContextId));
247+
}
248+
249+
public void clearListeners(Set<String> browsingContextIds) {
250+
Require.nonNull("Browsing context id list", browsingContextIds);
251+
252+
List.of(
253+
browsingContextCreated,
254+
browsingContextDestroyed,
255+
userPromptOpened,
256+
userPromptClosed,
257+
historyUpdated,
258+
downloadWillBeginEvent,
259+
downloadEndEvent)
260+
.forEach(event -> this.bidi.clearListener(browsingContextIds, event));
261+
262+
navigationEventSet.forEach(event -> this.bidi.clearListener(browsingContextIds, event));
263+
}
264+
243265
@Override
244266
public void close() {
245267
this.bidi.clearListener(browsingContextCreated);

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,18 @@ private long addLogEntryAddedListener(Consumer<LogEntry> consumer) {
152152
}
153153
}
154154

155+
public void clearListener(String browsingContextId) {
156+
Require.nonNull("Browsing context id", browsingContextId);
157+
clearListeners(Collections.singleton(browsingContextId));
158+
}
159+
160+
public void clearListeners(Set<String> browsingContextIds) {
161+
Require.nonNull("Browsing context id list", browsingContextIds);
162+
this.bidi.clearListener(browsingContextIds, this.logEntryAddedEvent);
163+
}
164+
155165
@Override
156166
public void close() {
157-
this.bidi.clearListener(Log.entryAdded());
167+
this.bidi.clearListener(this.logEntryAddedEvent);
158168
}
159169
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,18 @@ public void removeListener(long subscriptionId) {
6969
this.bidi.removeListener(subscriptionId);
7070
}
7171

72+
public void clearListener(String browsingContextId) {
73+
Require.nonNull("Browsing context id", browsingContextId);
74+
clearListeners(Collections.singleton(browsingContextId));
75+
}
76+
77+
public void clearListeners(Set<String> browsingContextIds) {
78+
Require.nonNull("Browsing context id list", browsingContextIds);
79+
this.bidi.clearListener(browsingContextIds, this.prefetchStatusUpdatedEvent);
80+
}
81+
7282
@Override
7383
public void close() {
74-
this.bidi.clearListener(Speculation.prefetchStatusUpdated());
84+
this.bidi.clearListener(this.prefetchStatusUpdatedEvent);
7585
}
7686
}

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@
2424
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2525
import static org.openqa.selenium.testing.drivers.Browser.FIREFOX;
2626

27+
import java.util.ArrayList;
2728
import java.util.HashMap;
29+
import java.util.HashSet;
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.Optional;
33+
import java.util.Set;
3134
import java.util.concurrent.CompletableFuture;
35+
import java.util.concurrent.CountDownLatch;
3236
import java.util.concurrent.ExecutionException;
3337
import java.util.concurrent.TimeUnit;
3438
import java.util.concurrent.TimeoutException;
@@ -318,6 +322,71 @@ void canListenToNavigationFailedEvent()
318322
}
319323
}
320324

325+
@Test
326+
@NeedsFreshDriver
327+
void canClearListenersForBrowsingContext()
328+
throws ExecutionException, InterruptedException, TimeoutException {
329+
try (BrowsingContextInspector inspector = new BrowsingContextInspector(driver)) {
330+
CompletableFuture<NavigationInfo> future = new CompletableFuture<>();
331+
inspector.onDomContentLoaded(future::complete);
332+
333+
BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
334+
context.navigate(appServer.whereIs("/bidi/logEntryAdded.html"), ReadinessState.COMPLETE);
335+
336+
NavigationInfo navigationInfo = future.get(5, TimeUnit.SECONDS);
337+
assertThat(navigationInfo.getBrowsingContextId()).isEqualTo(context.getId());
338+
339+
// Clear listeners for this browsing context
340+
inspector.clearListener(context.getId());
341+
342+
// Re-subscribe and verify events still work after re-subscribing
343+
CompletableFuture<NavigationInfo> newFuture = new CompletableFuture<>();
344+
inspector.onDomContentLoaded(newFuture::complete);
345+
346+
context.navigate(appServer.whereIs("/simpleTest.html"), ReadinessState.COMPLETE);
347+
348+
NavigationInfo newNavigationInfo = newFuture.get(5, TimeUnit.SECONDS);
349+
assertThat(newNavigationInfo.getBrowsingContextId()).isEqualTo(context.getId());
350+
assertThat(newNavigationInfo.getUrl()).contains("/simpleTest.html");
351+
}
352+
}
353+
354+
@Test
355+
@NeedsFreshDriver
356+
void canClearListenersForMultipleBrowsingContexts()
357+
throws ExecutionException, InterruptedException, TimeoutException {
358+
Set<String> browsingContextIds = new HashSet<>();
359+
browsingContextIds.add(driver.getWindowHandle());
360+
361+
try (BrowsingContextInspector inspector = new BrowsingContextInspector(driver)) {
362+
List<NavigationInfo> receivedEvents = new ArrayList<>();
363+
CountDownLatch latch = new CountDownLatch(1);
364+
inspector.onNavigationStarted(
365+
info -> {
366+
receivedEvents.add(info);
367+
latch.countDown();
368+
});
369+
370+
BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
371+
context.navigate(appServer.whereIs("/bidi/logEntryAdded.html"), ReadinessState.COMPLETE);
372+
373+
latch.await(5, TimeUnit.SECONDS);
374+
assertThat(receivedEvents).hasSizeGreaterThanOrEqualTo(1);
375+
376+
// Clear listeners for the set of browsing context ids
377+
inspector.clearListeners(browsingContextIds);
378+
379+
// Re-subscribe with a new listener after clearing
380+
CompletableFuture<NavigationInfo> newFuture = new CompletableFuture<>();
381+
inspector.onNavigationStarted(newFuture::complete);
382+
383+
context.navigate(appServer.whereIs("/simpleTest.html"), ReadinessState.COMPLETE);
384+
385+
NavigationInfo newNavigationInfo = newFuture.get(5, TimeUnit.SECONDS);
386+
assertThat(newNavigationInfo.getBrowsingContextId()).isEqualTo(context.getId());
387+
}
388+
}
389+
321390
@Test
322391
@NeedsFreshDriver
323392
void canListenToHistoryUpdatedEvent()

java/test/org/openqa/selenium/bidi/log/LogInspectorTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import static org.assertj.core.api.Assertions.assertThat;
2121
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2222

23+
import java.util.ArrayList;
2324
import java.util.HashSet;
25+
import java.util.List;
2426
import java.util.Set;
2527
import java.util.concurrent.CompletableFuture;
2628
import java.util.concurrent.CountDownLatch;
@@ -430,6 +432,74 @@ void canListenToAnyTypeOfLogForMultipleBrowsingContexts() throws InterruptedExce
430432
}
431433
}
432434

435+
@Test
436+
@NeedsFreshDriver
437+
void canClearListenersForBrowsingContext()
438+
throws ExecutionException, InterruptedException, TimeoutException {
439+
String browsingContextId = driver.getWindowHandle();
440+
try (LogInspector logInspector = new LogInspector(driver)) {
441+
CompletableFuture<ConsoleLogEntry> future = new CompletableFuture<>();
442+
logInspector.onConsoleEntry(future::complete);
443+
444+
page = appServer.whereIs("/bidi/logEntryAdded.html");
445+
driver.get(page);
446+
driver.findElement(By.id("consoleLog")).click();
447+
448+
ConsoleLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
449+
assertThat(logEntry.getText()).isEqualTo("Hello, world!");
450+
451+
// Clear listeners for this browsing context
452+
logInspector.clearListener(browsingContextId);
453+
454+
// Re-subscribe and verify events still work after re-subscribing
455+
CompletableFuture<ConsoleLogEntry> newFuture = new CompletableFuture<>();
456+
logInspector.onConsoleEntry(newFuture::complete);
457+
458+
driver.findElement(By.id("consoleLog")).click();
459+
460+
ConsoleLogEntry newLogEntry = newFuture.get(5, TimeUnit.SECONDS);
461+
assertThat(newLogEntry.getText()).isEqualTo("Hello, world!");
462+
}
463+
}
464+
465+
@Test
466+
@NeedsFreshDriver
467+
void canClearListenersForMultipleBrowsingContexts()
468+
throws ExecutionException, InterruptedException, TimeoutException {
469+
Set<String> browsingContextIds = new HashSet<>();
470+
browsingContextIds.add(driver.getWindowHandle());
471+
472+
try (LogInspector logInspector = new LogInspector(driver)) {
473+
List<ConsoleLogEntry> receivedEntries = new ArrayList<>();
474+
CountDownLatch latch = new CountDownLatch(1);
475+
logInspector.onConsoleEntry(
476+
entry -> {
477+
receivedEntries.add(entry);
478+
latch.countDown();
479+
});
480+
481+
page = appServer.whereIs("/bidi/logEntryAdded.html");
482+
driver.get(page);
483+
driver.findElement(By.id("consoleLog")).click();
484+
485+
latch.await(5, TimeUnit.SECONDS);
486+
assertThat(receivedEntries).hasSize(1);
487+
assertThat(receivedEntries.get(0).getText()).isEqualTo("Hello, world!");
488+
489+
// Clear listeners for the set of browsing context ids
490+
logInspector.clearListeners(browsingContextIds);
491+
492+
// Re-subscribe with a new listener after clearing
493+
CompletableFuture<ConsoleLogEntry> newFuture = new CompletableFuture<>();
494+
logInspector.onConsoleEntry(newFuture::complete);
495+
496+
driver.findElement(By.id("consoleLog")).click();
497+
498+
ConsoleLogEntry newLogEntry = newFuture.get(5, TimeUnit.SECONDS);
499+
assertThat(newLogEntry.getText()).isEqualTo("Hello, world!");
500+
}
501+
}
502+
433503
@Test
434504
@NeedsFreshDriver
435505
void canListenToLogsWithMultipleConsumers()

0 commit comments

Comments
 (0)