View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host;
16  
17  import static java.nio.charset.StandardCharsets.UTF_16LE;
18  import static org.htmlunit.junit.annotation.TestedBrowser.FF;
19  import static org.htmlunit.junit.annotation.TestedBrowser.FF_ESR;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.Set;
24  import java.util.concurrent.CopyOnWriteArraySet;
25  
26  import org.eclipse.jetty.websocket.api.Session;
27  import org.eclipse.jetty.websocket.api.WebSocketAdapter;
28  import org.eclipse.jetty.websocket.server.WebSocketHandler;
29  import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
30  import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
31  import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
32  import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
33  import org.htmlunit.HttpHeader;
34  import org.htmlunit.WebDriverTestCase;
35  import org.htmlunit.junit.BrowserRunner;
36  import org.htmlunit.junit.annotation.Alerts;
37  import org.htmlunit.junit.annotation.HtmlUnitNYI;
38  import org.htmlunit.junit.annotation.NotYetImplemented;
39  import org.junit.After;
40  import org.junit.Test;
41  import org.junit.runner.RunWith;
42  import org.openqa.selenium.By;
43  import org.openqa.selenium.WebDriver;
44  import org.openqa.selenium.WebElement;
45  
46  /**
47   * Tests for {@link WebSocket}.
48   *
49   * @author Ahmed Ashour
50   * @author Ronald Brill
51   * @author Madis Pärn
52   */
53  @RunWith(BrowserRunner.class)
54  public class WebSocketTest extends WebDriverTestCase {
55  
56      /**
57       * @throws Exception if the test fails
58       */
59      @Test
60      @Alerts({"§§URL§§", "", "blob"})
61      public void initialNoServerAvailable() throws Exception {
62          final String html = DOCTYPE_HTML
63              + "<html>\n"
64              + "<head>\n"
65              + "<script>\n"
66              + LOG_TITLE_FUNCTION
67              + "  function test() {\n"
68              + "    var location = 'ws://localhost:" + PORT2 + "/';\n"
69              + "    var ws = new WebSocket(location);\n"
70              + "    log(ws.url);\n"
71              + "    log(ws.protocol);\n"
72              // this makes our test instable because the real connect is
73              // done by an executor and maybe already finished
74              // + "    log(ws.readyState);\n"
75              + "    log(ws.binaryType);\n"
76              + "  }\n"
77              + "</script>\n"
78              + "</head>\n"
79              + "<body onload='test()'>\n"
80              + "</body></html>";
81  
82          expandExpectedAlertsVariables("ws://localhost:" + PORT2 + "/");
83          final WebDriver driver = loadPage2(html);
84          verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
85      }
86  
87      /**
88       * @throws Exception if the test fails
89       */
90      @Test
91      @Alerts({"[object WebSocket]", "§§URL§§"})
92      public void earlyConstruction() throws Exception {
93          final String html = DOCTYPE_HTML
94              + "<html><head><script>\n"
95              + LOG_TITLE_FUNCTION
96              + "  function test() {\n"
97              + "    var location = 'ws://localhost:" + PORT + "/';\n"
98              + "    var ws = new WebSocket(location);\n"
99              + "    log(ws);\n"
100             + "    log(ws.url);\n"
101             + "  }\n"
102             + "  test();\n"
103             + "</script>\n"
104             + "</head>\n"
105             + "<body>\n"
106             + "</body></html>";
107 
108         expandExpectedAlertsVariables("ws://localhost:" + PORT + "/");
109         final WebDriver driver = loadPage2(html);
110         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
111     }
112 
113     /**
114      * @throws Exception if the test fails
115      */
116     @Test
117     @Alerts(DEFAULT = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
118                        "ws://localhost:22222/", "ws://localhost:22222/", "exception invalid"},
119             FF = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
120                   "exception empty", "ws://localhost:22222/", "exception invalid"},
121             FF_ESR = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
122                       "exception empty", "ws://localhost:22222/", "exception invalid"})
123     @HtmlUnitNYI(
124             CHROME = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
125                       "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
126             EDGE = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
127                     "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
128             FF = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
129                   "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
130             FF_ESR = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
131                       "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"})
132     public void initialWithoutUrl() throws Exception {
133         final String html = DOCTYPE_HTML
134             + "<html><head><script>\n"
135             + LOG_TITLE_FUNCTION
136             + "  function test() {\n"
137             + "    try {\n"
138             + "      let ws = new WebSocket();\n"
139             + "      log(ws.url);"
140             + "    } catch(e) { log('exception no param') }\n"
141 
142             + "    try {\n"
143             + "      let ws = new WebSocket(undefined);\n"
144             + "      log(ws.url);"
145             + "    } catch(e) { log('exception undefined') }\n"
146 
147             + "    try {\n"
148             + "      let ws = new WebSocket(null);\n"
149             + "      log(ws.url);"
150             + "    } catch(e) { log('exception null') }\n"
151 
152             + "    try {\n"
153             + "      let ws = new WebSocket('');\n"
154             + "      log(ws.url);"
155             + "    } catch(e) { log('exception empty') }\n"
156 
157             + "    try {\n"
158             + "      let ws = new WebSocket(' ');\n"
159             + "      log(ws.url);"
160             + "    } catch(e) { log('exception blank') }\n"
161 
162             + "    try {\n"
163             + "      let ws = new WebSocket('#');\n"
164             + "      log(ws.url);"
165             + "    } catch(e) { log('exception invalid') }\n"
166             + "  }\n"
167             + "</script></head><body onload='test()'>\n"
168             + "</body></html>";
169 
170         loadPageVerifyTitle2(html);
171     }
172 
173     /**
174      * @throws Exception if the test fails
175      */
176     @Test
177     @Alerts({"blob", "blob", "arraybuffer", "blob", "blob"})
178     public void binaryType() throws Exception {
179         final String html = DOCTYPE_HTML
180             + "<html><head><script>\n"
181             + LOG_TITLE_FUNCTION
182             + "  function test() {\n"
183             + "    var location = 'ws://localhost:" + PORT + "/';\n"
184             + "    var ws = new WebSocket(location);\n"
185             + "    log(ws.binaryType);\n"
186 
187             + "    try {\n"
188             + "      ws.binaryType = 'abc';\n"
189             + "      log(ws.binaryType);\n"
190             + "    } catch(e) { logEx(e) }\n"
191 
192             + "    try {\n"
193             + "      ws.binaryType = 'arraybuffer';\n"
194             + "      log(ws.binaryType);\n"
195             + "    } catch(e) { logEx(e) }\n"
196 
197             + "    try {\n"
198             + "      ws.binaryType = 'blob';\n"
199             + "      log(ws.binaryType);\n"
200             + "    } catch(e) { logEx(e) }\n"
201 
202             + "    try {\n"
203             + "      ws.binaryType = '';\n"
204             + "      log(ws.binaryType);\n"
205             + "    } catch(e) { logEx(e) }\n"
206             + "  }\n"
207             + "</script></head><body onload='test()'>\n"
208             + "</body></html>";
209 
210         loadPageVerifyTitle2(html);
211     }
212 
213     /**
214      * Test case taken from <a href="http://angelozerr.wordpress.com/2011/07/23/websockets_jetty_step1/">here</a>.
215      * @throws Exception if the test fails
216      */
217     @Test
218     public void chat() throws Exception {
219         final String firstResponse = "Browser: has joined!";
220         final String secondResponse = "Browser: Hope you are fine!";
221 
222         startWebServer("src/test/resources/org/htmlunit/javascript/host",
223             null, null, new ChatWebSocketHandler());
224         try {
225             final WebDriver driver = getWebDriver();
226             driver.get(URL_FIRST + "WebSocketTest_chat.html");
227 
228             driver.findElement(By.id("username")).sendKeys("Browser");
229             driver.findElement(By.id("joinB")).click();
230 
231             assertVisible("joined", driver);
232 
233             final WebElement chatE = driver.findElement(By.id("chat"));
234             long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
235 
236             do {
237                 Thread.sleep(100);
238             }
239             while (chatE.getText().length() <= firstResponse.length() && System.currentTimeMillis() < maxWait);
240 
241             assertEquals(firstResponse, chatE.getText());
242 
243             driver.findElement(By.id("phrase")).sendKeys("Hope you are fine!");
244             driver.findElement(By.id("sendB")).click();
245 
246             maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
247             do {
248                 Thread.sleep(100);
249             }
250             while (!chatE.getText().contains(secondResponse) && System.currentTimeMillis() < maxWait);
251 
252             assertEquals(firstResponse + "\n" + secondResponse, chatE.getText());
253         }
254         finally {
255             stopWebServers();
256         }
257     }
258 
259     private static class ChatWebSocketHandler extends WebSocketHandler {
260 
261         private final Set<ChatWebSocket> webSockets_ = new CopyOnWriteArraySet<>();
262 
263         ChatWebSocketHandler() {
264         }
265 
266         @Override
267         public void configure(final WebSocketServletFactory factory) {
268             factory.register(ChatWebSocket.class);
269             factory.setCreator(new WebSocketCreator() {
270                 @Override
271                 public Object createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
272                         final ServletUpgradeResponse servletUpgradeResponse) {
273                     return new ChatWebSocket();
274                 }
275             });
276         }
277 
278         private class ChatWebSocket extends WebSocketAdapter {
279             private Session session_;
280 
281             ChatWebSocket() {
282             }
283 
284             @Override
285             public void onWebSocketConnect(final Session session) {
286                 session_ = session;
287                 webSockets_.add(this);
288             }
289 
290             @Override
291             public void onWebSocketText(final String data) {
292                 try {
293                     for (final ChatWebSocket webSocket : webSockets_) {
294                         webSocket.session_.getRemote().sendString(data);
295                     }
296                 }
297                 catch (final IOException e) {
298                     session_.close();
299                 }
300             }
301 
302             @Override
303             public void onWebSocketClose(final int closeCode, final String message) {
304                 webSockets_.remove(this);
305             }
306         }
307     }
308 
309     /**
310      * {@inheritDoc}
311      */
312     @After
313     @Override
314     public void releaseResources() {
315         super.releaseResources();
316 
317         for (final Thread thread : Thread.getAllStackTraces().keySet()) {
318             if (thread.getName().contains("WebSocket")) {
319                 try {
320                     // ok found one but let's wait a bit to start a second check before
321                     // pressing the panic button
322                     Thread.sleep(400);
323                 }
324                 catch (final InterruptedException e) {
325                     e.printStackTrace();
326                 }
327             }
328         }
329 
330         String lastFailing = null;
331         for (final java.util.Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
332             final Thread thread = entry.getKey();
333             if (thread.getName().contains("WebSocket")) {
334                 lastFailing = thread.getName();
335                 System.err.println();
336                 System.err.println("WebSocket thread named '" + lastFailing + "' still running");
337                 final StackTraceElement[] traces = entry.getValue();
338                 for (int i = 0; i < traces.length; i++) {
339                     System.err.println(traces[i]);
340                 }
341             }
342         }
343 
344         assertNull("WebSocket thread named '" + lastFailing + "' still running", lastFailing);
345     }
346 
347     /**
348      * @throws Exception if the test fails
349      */
350     @Test
351     @Alerts({": myname=My value!1", ": myname=My value!2"})
352     public void cookies() throws Exception {
353         final String[] expected = getExpectedAlerts();
354 
355         startWebServer("src/test/resources/org/htmlunit/javascript/host",
356             null, null, new CookiesWebSocketHandler());
357         try {
358             final WebDriver driver = getWebDriver();
359             driver.get(URL_FIRST + "WebSocketTest_cookies.html");
360 
361             driver.findElement(By.id("username")).sendKeys("Browser");
362             driver.findElement(By.id("joinB")).click();
363             final WebElement chatE = driver.findElement(By.id("chat"));
364 
365             long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
366             do {
367                 Thread.sleep(100);
368             }
369             while (chatE.getText().length() <= expected[0].length() && System.currentTimeMillis() < maxWait);
370 
371             assertEquals(expected[0], chatE.getText());
372 
373             driver.findElement(By.id("phrase")).sendKeys("Hope you are fine!");
374             driver.findElement(By.id("sendB")).click();
375 
376             maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
377             do {
378                 Thread.sleep(100);
379             }
380             while (!chatE.getText().contains(expected[1]) && System.currentTimeMillis() < maxWait);
381 
382             assertEquals(expected[0] + "\n" + expected[1], chatE.getText());
383         }
384         finally {
385             stopWebServers();
386         }
387     }
388 
389     private static class CookiesWebSocketHandler extends WebSocketHandler {
390 
391         private final Set<CookiesWebSocket> webSockets_ = new CopyOnWriteArraySet<>();
392 
393         CookiesWebSocketHandler() {
394         }
395 
396         @Override
397         public void configure(final WebSocketServletFactory factory) {
398             factory.register(CookiesWebSocket.class);
399             factory.setCreator(new WebSocketCreator() {
400                 @Override
401                 public Object createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
402                         final ServletUpgradeResponse servletUpgradeResponse) {
403                     return new CookiesWebSocket();
404                 }
405             });
406         }
407 
408         private class CookiesWebSocket extends WebSocketAdapter {
409             private Session session_;
410             private int counter_ = 1;
411 
412             CookiesWebSocket() {
413             }
414 
415             @Override
416             public void onWebSocketConnect(final Session session) {
417                 session_ = session;
418                 webSockets_.add(this);
419             }
420 
421             @Override
422             public void onWebSocketText(final String data) {
423                 try {
424                     final String cookie = session_.getUpgradeRequest().getHeaders()
425                                                         .get(HttpHeader.COOKIE).get(0) + counter_++;
426                     for (final CookiesWebSocket webSocket : webSockets_) {
427                         webSocket.session_.getRemote().sendString(cookie);
428                     }
429                 }
430                 catch (final IOException e) {
431                     session_.close();
432                 }
433             }
434 
435             @Override
436             public void onWebSocketClose(final int closeCode, final String message) {
437                 webSockets_.remove(this);
438             }
439         }
440     }
441 
442     /**
443      * @throws Exception if the test fails
444      */
445     @Test
446     @Alerts({"onOpenListener",
447              "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
448              "undefined", "undefined", "undefined", "undefined",
449              "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
450              "server_text", "§§URL§§", "", "null",
451              "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
452              "server_text", "§§URL§§", "", "null",
453              "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
454              "[object ArrayBuffer]", "§§URL§§", "", "null",
455              "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
456              "[object ArrayBuffer]", "§§URL§§", "", "null",
457              "onCloseListener code: 1000",
458              "onClose code: 1000"})
459     public void events() throws Exception {
460         expandExpectedAlertsVariables("ws://localhost:" + PORT);
461         final String expected = String.join("\n", getExpectedAlerts());
462 
463         startWebServer("src/test/resources/org/htmlunit/javascript/host",
464             null, null, new EventsWebSocketHandler());
465         try {
466             final WebDriver driver = getWebDriver();
467             driver.get(URL_FIRST + "WebSocketTest_events.html");
468 
469             final WebElement logElement = driver.findElement(By.id("log"));
470             final long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
471 
472             String text;
473             do {
474                 Thread.sleep(100);
475 
476                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
477             }
478             while (text.length() <= expected.length() && System.currentTimeMillis() < maxWait);
479 
480             assertEquals(expected, text);
481         }
482         finally {
483             stopWebServers();
484         }
485     }
486 
487     /**
488      * @throws Exception if the test fails
489      */
490     @Test
491     @Alerts(DEFAULT = {"onOpenListener",
492                        "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
493                        "undefined", "undefined", "undefined", "undefined",
494                        "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
495                        "server_text", "§§URL§§", "", "null",
496                        "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
497                        "server_text", "§§URL§§", "", "null",
498                        "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
499                        "[object ArrayBuffer]", "§§URL§§", "", "null",
500                        "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
501                        "[object ArrayBuffer]", "§§URL§§", "", "null",
502                        "onCloseListener code: 1000  wasClean: true",
503                        "onClose code: 1000  wasClean: true"},
504             FF = {"onOpenListener",
505                   "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
506                   "undefined", "undefined", "undefined", "undefined",
507                   "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
508                   "server_text", "§§URL§§", "", "null",
509                   "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
510                   "server_text", "§§URL§§", "", "null",
511                   "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
512                   "[object ArrayBuffer]", "§§URL§§", "", "null",
513                   "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
514                   "[object ArrayBuffer]", "§§URL§§", "", "null",
515                   "onCloseListener code: 1000  wasClean: false",
516                   "onClose code: 1000  wasClean: false"},
517             FF_ESR = {"onOpenListener",
518                       "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
519                       "undefined", "undefined", "undefined", "undefined",
520                       "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
521                       "server_text", "§§URL§§", "", "null",
522                       "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
523                       "server_text", "§§URL§§", "", "null",
524                       "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
525                       "[object ArrayBuffer]", "§§URL§§", "", "null",
526                       "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
527                       "[object ArrayBuffer]", "§§URL§§", "", "null",
528                       "onCloseListener code: 1000  wasClean: false",
529                       "onClose code: 1000  wasClean: false"})
530     @NotYetImplemented({FF, FF_ESR})
531     public void wasClean() throws Exception {
532         expandExpectedAlertsVariables("ws://localhost:" + PORT);
533         final String expected = String.join("\n", getExpectedAlerts());
534 
535         startWebServer("src/test/resources/org/htmlunit/javascript/host",
536             null, null, new EventsWebSocketHandler());
537         try {
538             final WebDriver driver = getWebDriver();
539             driver.get(URL_FIRST + "WebSocketTest_wasClean.html");
540 
541             final WebElement logElement = driver.findElement(By.id("log"));
542             final long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
543 
544             String text;
545             do {
546                 Thread.sleep(100);
547 
548                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
549             }
550             while (text.length() <= expected.length() && System.currentTimeMillis() < maxWait);
551 
552             assertEquals(expected, text);
553         }
554         finally {
555             stopWebServers();
556         }
557     }
558 
559     /**
560      * @throws Exception if the test fails
561      */
562     @Test
563     @Alerts({"onError[object Event]",
564              "onCloseListener code: 1006  wasClean: false",
565              "onClose code: 1006  wasClean: false"})
566     public void eventsNoSocketServer() throws Exception {
567         startWebServer("src/test/resources/org/htmlunit/javascript/host", null, null, null);
568         try {
569             final WebDriver driver = getWebDriver();
570             driver.get(URL_FIRST + "WebSocketTest_wasClean.html");
571 
572             final WebElement logElement = driver.findElement(By.id("log"));
573             int counter = 0;
574             String text;
575             do {
576                 Thread.sleep(DEFAULT_WAIT_TIME.toMillis());
577 
578                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
579             }
580             while (text.length() > 0 && counter++ < 10);
581 
582             assertEquals(String.join("\n", getExpectedAlerts()), text);
583         }
584         finally {
585             stopWebServers();
586         }
587     }
588 
589     private static void assertVisible(final String domId, final WebDriver driver) throws Exception {
590         final WebElement domE = driver.findElement(By.id(domId));
591         int counter = 0;
592         do {
593             Thread.sleep(100);
594         }
595         while (!domE.isDisplayed() && counter++ < 10);
596 
597         assertEquals("Node should be visible, domId: " + domId, true, domE.isDisplayed());
598     }
599 
600     private static class EventsWebSocketHandler extends WebSocketHandler {
601 
602         EventsWebSocketHandler() {
603         }
604 
605         @Override
606         public void configure(final WebSocketServletFactory factory) {
607             factory.register(EventsWebSocket.class);
608             factory.setCreator(new WebSocketCreator() {
609                 @Override
610                 public EventsWebSocket createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
611                         final ServletUpgradeResponse servletUpgradeResponse) {
612                     return new EventsWebSocket();
613                 }
614             });
615         }
616 
617         private static class EventsWebSocket extends WebSocketAdapter {
618 
619             EventsWebSocket() {
620             }
621 
622             @Override
623             public void onWebSocketText(final String data) {
624                 if ("text".equals(data)) {
625                     try {
626                         getRemote().sendString("server_text");
627                     }
628                     catch (final IOException e) {
629                         throw new IllegalStateException(e.getMessage(), e);
630                     }
631                 }
632                 else if ("close".equals(data)) {
633                     getSession().close();
634                 }
635                 else {
636                     throw new IllegalArgumentException("Unknown request: " + data);
637                 }
638             }
639 
640             @Override
641             public void onWebSocketBinary(final byte[] payload, final int offset, final int len) {
642                 final String data = new String(payload, offset, len, UTF_16LE);
643                 if ("binary".equals(data)) {
644                     final ByteBuffer response = ByteBuffer.wrap("server_binary".getBytes(UTF_16LE));
645                     try {
646                         getRemote().sendBytes(response);
647                     }
648                     catch (final IOException e) {
649                         throw new IllegalStateException(e.getMessage(), e);
650                     }
651                 }
652                 else {
653                     throw new IllegalArgumentException("Unknown request: " + data);
654                 }
655             }
656         }
657     }
658 
659     /**
660      * @throws Exception if the test fails
661      */
662     @Test
663     @Alerts("true")
664     public void prototypeUrl() throws Exception {
665         final String html = DOCTYPE_HTML
666             + "<html><head><script>\n"
667             + LOG_TITLE_FUNCTION
668             + "  function test() {\n"
669             + "    try {\n"
670             + "      var u = WebSocket.prototype.url;\n"
671             + "      log(u);\n"
672             + "    } catch(e) { log(e instanceof TypeError) }\n"
673             + "  }\n"
674             + "</script></head><body onload='test()'>\n"
675             + "</body></html>";
676 
677         loadPageVerifyTitle2(html);
678     }
679 
680     /**
681      * @throws Exception if the test fails
682      */
683     @Test
684     public void socketsGetClosedOnPageReplace() throws Exception {
685         startWebServer("src/test/resources/org/htmlunit/javascript/host",
686                 null, null, new ChatWebSocketHandler());
687         try {
688             final WebDriver driver = getWebDriver();
689             driver.get(URL_FIRST + "WebSocketTest_chat.html");
690 
691             driver.findElement(By.id("username")).sendKeys("Browser");
692             driver.findElement(By.id("joinB")).click();
693 
694             assertVisible("joined", driver);
695 
696             driver.get(URL_FIRST + "plain.html");
697         }
698         finally {
699             stopWebServers();
700         }
701     }
702 }