Skip to content

Commit 12dd11f

Browse files
authored
Merge branch 'trunk' into add-js-filter-auth
2 parents 9cd48b7 + f4ef7be commit 12dd11f

File tree

24 files changed

+383
-73
lines changed

24 files changed

+383
-73
lines changed

common/src/web/javascriptPage.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ <h1>Type Stuff</h1>
279279
</div>
280280
</div>
281281

282+
<form id="aParentFormId">
283+
<input type="text" name="tagName">
284+
</form>
285+
282286
</body>
283287
</html>
284288

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<html>
2+
<head>
3+
<title>Modern Modal</title>
4+
<style>
5+
/* Modal background */
6+
#modalBackground {
7+
display: none;
8+
position: fixed;
9+
z-index: 1;
10+
left: 0;
11+
top: 0;
12+
width: 100%;
13+
height: 100%;
14+
overflow: auto;
15+
background-color: rgba(0,0,0,0.4);
16+
}
17+
18+
/* Modal content */
19+
#modalContent {
20+
position: absolute;
21+
background-color: #fff;
22+
padding: 20px;
23+
border: 1px solid #888;
24+
width: 250px;
25+
height: 200px;
26+
z-index: 2;
27+
28+
/* Center the modal */
29+
top: 50%;
30+
left: 50%;
31+
transform: translate(-50%, -50%);
32+
}
33+
34+
/* Close button */
35+
.close {
36+
color: #aaa;
37+
float: right;
38+
font-size: 28px;
39+
font-weight: bold;
40+
cursor: pointer;
41+
}
42+
43+
.close:hover,
44+
.close:focus {
45+
color: black;
46+
text-decoration: none;
47+
cursor: pointer;
48+
}
49+
</style>
50+
</head>
51+
52+
<body>
53+
<p>Modal dialog sample</p>
54+
55+
<input id="trigger-modal-btn" type="button" value="trigger modal" onclick="openModal();">
56+
57+
<a id="trigger-modal-link" href="javascript:openModal()">trigger modal</a>
58+
59+
<!-- Modal structure -->
60+
<div id="modalBackground">
61+
<div id="modalContent">
62+
<span id="modal-close" class="close" onclick="closeModal()">&times;</span>
63+
<span id="modal-text">I am a modal</span>
64+
<input type="text" id="modal-input"/>
65+
</div>
66+
</div>
67+
68+
<script>
69+
function openModal() {
70+
// Display the modal and dim the background
71+
document.getElementById('modalBackground').style.display = 'block';
72+
}
73+
74+
function closeModal() {
75+
// Hide the modal and remove the dimming effect
76+
document.getElementById('modalBackground').style.display = 'none';
77+
}
78+
79+
// Optional: Close the modal when clicking outside of it
80+
window.onclick = function(event) {
81+
var modal = document.getElementById('modalBackground');
82+
if (event.target == modal) {
83+
closeModal();
84+
}
85+
}
86+
</script>
87+
</body>
88+
</html>

java/src/org/openqa/selenium/JavascriptExecutor.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Collections;
2121
import java.util.Set;
2222
import java.util.stream.Collectors;
23+
import org.jspecify.annotations.NullMarked;
24+
import org.jspecify.annotations.Nullable;
2325
import org.openqa.selenium.internal.Require;
2426

2527
/**
@@ -30,6 +32,7 @@
3032
* request or when trying to access another frame. Most times when troubleshooting failure it's best
3133
* to view the browser's console after executing the WebDriver request.
3234
*/
35+
@NullMarked
3336
public interface JavascriptExecutor {
3437
/**
3538
* Executes JavaScript in the context of the currently selected frame or window. The script
@@ -63,7 +66,7 @@ public interface JavascriptExecutor {
6366
* @param args The arguments to the script. May be empty
6467
* @return One of Boolean, Long, Double, String, List, Map or WebElement. Or null.
6568
*/
66-
Object executeScript(String script, Object... args);
69+
@Nullable Object executeScript(String script, @Nullable Object... args);
6770

6871
/**
6972
* Execute an asynchronous piece of JavaScript in the context of the currently selected frame or
@@ -139,7 +142,7 @@ public interface JavascriptExecutor {
139142
* @return One of Boolean, Long, String, List, Map, WebElement, or null.
140143
* @see WebDriver.Timeouts#scriptTimeout(java.time.Duration)
141144
*/
142-
Object executeAsyncScript(String script, Object... args);
145+
@Nullable Object executeAsyncScript(String script, @Nullable Object... args);
143146

144147
/**
145148
* Commonly used scripts may be "pinned" to the WebDriver session, allowing them to be called
@@ -186,7 +189,7 @@ default Set<ScriptKey> getPinnedScripts() {
186189
*
187190
* @see #executeScript(String, Object...)
188191
*/
189-
default Object executeScript(ScriptKey key, Object... args) {
192+
default @Nullable Object executeScript(ScriptKey key, @Nullable Object... args) {
190193
Require.stateCondition(
191194
key instanceof UnpinnedScriptKey, "Script key should have been generated by this driver");
192195

java/src/org/openqa/selenium/OutputType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
2424
import java.util.Base64;
25+
import org.jspecify.annotations.NullMarked;
2526

2627
/**
2728
* Defines the output type for a screenshot.
2829
*
2930
* @see TakesScreenshot
3031
* @param <T> Type for the screenshot output.
3132
*/
33+
@NullMarked
3234
public interface OutputType<T> {
3335
/** Obtain the screenshot as base64 data. */
3436
OutputType<String> BASE64 =

java/src/org/openqa/selenium/TakesScreenshot.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// under the License.
1717
package org.openqa.selenium;
1818

19+
import org.jspecify.annotations.NullMarked;
20+
1921
/**
2022
* Indicates a driver or an HTML element that can capture a screenshot and store it in different
2123
* ways.
@@ -29,6 +31,7 @@
2931
*
3032
* @see OutputType
3133
*/
34+
@NullMarked
3235
public interface TakesScreenshot {
3336
/**
3437
* Capture the screenshot and store it in the specified location.

java/src/org/openqa/selenium/WebDriver.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.List;
2323
import java.util.Set;
2424
import java.util.concurrent.TimeUnit;
25+
import org.jspecify.annotations.NullMarked;
26+
import org.jspecify.annotations.Nullable;
2527
import org.openqa.selenium.logging.LoggingPreferences;
2628
import org.openqa.selenium.logging.Logs;
2729

@@ -46,6 +48,7 @@
4648
* <p>Most implementations of this interface follow <a href="https://w3c.github.io/webdriver/">W3C
4749
* WebDriver specification</a>
4850
*/
51+
@NullMarked
4952
public interface WebDriver extends SearchContext {
5053
// Navigation
5154

@@ -74,7 +77,7 @@ public interface WebDriver extends SearchContext {
7477
*
7578
* @return The URL of the page currently loaded in the browser
7679
*/
77-
String getCurrentUrl();
80+
@Nullable String getCurrentUrl();
7881

7982
// General properties
8083

@@ -87,7 +90,7 @@ public interface WebDriver extends SearchContext {
8790
* @return The title of the current page, with leading and trailing whitespace stripped, or null
8891
* if one is not already set
8992
*/
90-
String getTitle();
93+
@Nullable String getTitle();
9194

9295
/**
9396
* Find all elements within the current page using the given mechanism. This method is affected by
@@ -142,7 +145,7 @@ public interface WebDriver extends SearchContext {
142145
*
143146
* @return The source of the current page
144147
*/
145-
String getPageSource();
148+
@Nullable String getPageSource();
146149

147150
/**
148151
* Close the current window, quitting the browser if it's the last window currently open.
@@ -261,7 +264,7 @@ interface Options {
261264
* @param name the name of the cookie
262265
* @return the cookie, or null if no cookie with the given name is present
263266
*/
264-
Cookie getCookieNamed(String name);
267+
@Nullable Cookie getCookieNamed(String name);
265268

266269
/**
267270
* @return the interface for managing driver timeouts.

java/src/org/openqa/selenium/grid/jmx/MBean.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ public Object getAttribute(String attribute) {
221221
return ((Map<?, ?>) res)
222222
.entrySet().stream()
223223
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()));
224+
} else if (res instanceof Number) {
225+
return res;
224226
} else {
225227
return res.toString();
226228
}
@@ -241,7 +243,17 @@ public void setAttribute(Attribute attribute) {
241243

242244
@Override
243245
public AttributeList getAttributes(String[] attributes) {
244-
return null;
246+
AttributeList resultList = new AttributeList();
247+
248+
// if attributeNames is empty, return an empty result list
249+
if (attributes == null || attributes.length == 0) return resultList;
250+
251+
for (int i = 0; i < attributes.length; i++) {
252+
Object value = getAttribute(attributes[i]);
253+
resultList.add(new Attribute(attributes[i], value));
254+
}
255+
256+
return resultList;
245257
}
246258

247259
@Override

java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,30 @@ private Consumer<Message> createWsEndPoint(
226226
LOG.info("Establishing connection to " + uri);
227227

228228
HttpClient client = clientFactory.createClient(ClientConfig.defaultConfig().baseUri(uri));
229-
WebSocket upstream =
230-
client.openSocket(
231-
new HttpRequest(GET, uri.toString()),
232-
new ForwardingListener(downstream, sessionConsumer, sessionId));
233-
return upstream::send;
229+
try {
230+
WebSocket upstream =
231+
client.openSocket(
232+
new HttpRequest(GET, uri.toString()),
233+
new ForwardingListener(downstream, sessionConsumer, sessionId));
234+
235+
return (msg) -> {
236+
try {
237+
upstream.send(msg);
238+
} finally {
239+
if (msg instanceof CloseMessage) {
240+
try {
241+
client.close();
242+
} catch (Exception e) {
243+
LOG.log(Level.WARNING, "Failed to shutdown the client of " + uri, e);
244+
}
245+
}
246+
}
247+
};
248+
} catch (Exception e) {
249+
LOG.log(Level.WARNING, "Connecting to upstream websocket failed", e);
250+
client.close();
251+
throw e;
252+
}
234253
}
235254

236255
private static class ForwardingListener implements WebSocket.Listener {

java/src/org/openqa/selenium/grid/router/ProxyWebsocketsIntoGrid.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,29 @@ public Optional<Consumer<Message>> apply(String uri, Consumer<Message> downstrea
6868

6969
HttpClient client =
7070
clientFactory.createClient(ClientConfig.defaultConfig().baseUri(sessionUri));
71-
WebSocket upstream =
72-
client.openSocket(new HttpRequest(GET, uri), new ForwardingListener(downstream));
73-
74-
return Optional.of(upstream::send);
75-
71+
try {
72+
WebSocket upstream =
73+
client.openSocket(new HttpRequest(GET, uri), new ForwardingListener(downstream));
74+
75+
return Optional.of(
76+
(msg) -> {
77+
try {
78+
upstream.send(msg);
79+
} finally {
80+
if (msg instanceof CloseMessage) {
81+
try {
82+
client.close();
83+
} catch (Exception e) {
84+
LOG.log(Level.WARNING, "Failed to shutdown the client of " + sessionUri, e);
85+
}
86+
}
87+
}
88+
});
89+
} catch (Exception e) {
90+
LOG.log(Level.WARNING, "Connecting to upstream websocket failed", e);
91+
client.close();
92+
return Optional.empty();
93+
}
7694
} catch (NoSuchSessionException e) {
7795
LOG.warning("Attempt to connect to non-existent session: " + uri);
7896
return Optional.empty();

java/src/org/openqa/selenium/netty/server/WebSocketUpgradeHandler.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,15 @@ private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) {
165165

166166
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
167167
if (frame instanceof CloseWebSocketFrame) {
168-
CloseWebSocketFrame close = (CloseWebSocketFrame) frame.retain();
169-
handshaker.close(ctx.channel(), close);
170-
// Pass on to the rest of the channel
171-
ctx.fireChannelRead(close);
168+
try {
169+
CloseWebSocketFrame close = (CloseWebSocketFrame) frame.retain();
170+
handshaker.close(ctx.channel(), close);
171+
// Pass on to the rest of the channel
172+
ctx.fireChannelRead(close);
173+
} finally {
174+
// set null to ensure we do not send another close
175+
ctx.channel().attr(key).set(null);
176+
}
172177
} else if (frame instanceof PingWebSocketFrame) {
173178
ctx.write(new PongWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.content()));
174179
} else if (frame instanceof PongWebSocketFrame) {
@@ -187,7 +192,7 @@ private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame fram
187192
@Override
188193
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
189194
try {
190-
Consumer<Message> consumer = ctx.channel().attr(key).get();
195+
Consumer<Message> consumer = ctx.channel().attr(key).getAndSet(null);
191196

192197
if (consumer != null) {
193198
byte[] reason = Objects.toString(cause).getBytes(UTF_8);
@@ -201,12 +206,29 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
201206
try {
202207
consumer.accept(new CloseMessage(1011, new String(reason, UTF_8)));
203208
} catch (Exception ex) {
204-
LOG.log(Level.FINE, "failed to send the close message", ex);
209+
LOG.log(Level.FINE, "failed to send the close message, code: 1011", ex);
205210
}
206211
}
207212
} finally {
208213
LOG.log(Level.FINE, "exception caught, close the context", cause);
209214
ctx.close();
210215
}
211216
}
217+
218+
@Override
219+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
220+
try {
221+
super.channelInactive(ctx);
222+
} finally {
223+
Consumer<Message> consumer = ctx.channel().attr(key).getAndSet(null);
224+
225+
if (consumer != null) {
226+
try {
227+
consumer.accept(new CloseMessage(1001, "channel got inactive"));
228+
} catch (Exception ex) {
229+
LOG.log(Level.FINE, "failed to send the close message, code: 1001", ex);
230+
}
231+
}
232+
}
233+
}
212234
}

0 commit comments

Comments
 (0)