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.worker;
16  
17  import java.net.URL;
18  
19  import org.htmlunit.WebDriverTestCase;
20  import org.htmlunit.junit.annotation.Alerts;
21  import org.htmlunit.util.MimeType;
22  import org.junit.jupiter.api.Test;
23  import org.openqa.selenium.WebDriver;
24  
25  /**
26   * Unit tests for {@code Worker}.
27   *
28   * @author Marc Guillemot
29   * @author Ronald Brill
30   */
31  public class WorkerTest extends WebDriverTestCase {
32  
33      /**
34       * @throws Exception if the test fails
35       */
36      @Test
37      @Alerts("Received:worker loaded")
38      public void postMessageFromWorker() throws Exception {
39          final String html = DOCTYPE_HTML
40              + "<html><body>\n"
41              + "<script async>\n"
42              + LOG_TITLE_FUNCTION
43              + "try {\n"
44              + "  var myWorker = new Worker('worker.js');\n"
45              + "  myWorker.onmessage = function(e) {\n"
46              + "    log('Received:' + e.data);\n"
47              + "  };\n"
48              + "} catch(e) { logEx(e); }\n"
49              + "</script></body></html>\n";
50  
51          final String workerJs = "postMessage('worker loaded');\n";
52  
53          getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
54  
55          loadPage2(html);
56          verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
57      }
58  
59      /**
60       * @throws Exception if the test fails
61       */
62      @Test
63      @Alerts("Received:worker loaded")
64      public void postMessageFromWorker2() throws Exception {
65          final String html = DOCTYPE_HTML
66              + "<html><body>\n"
67              + "<script async>\n"
68              + LOG_TITLE_FUNCTION
69              + "try {\n"
70              + "  var myWorker = new Worker('worker.js');\n"
71              + "  myWorker.addEventListener('message', (e) => {\n"
72              + "    log('Received:' + e.data);\n"
73              + "  });\n"
74              + "} catch(e) { logEx(e); }\n"
75              + "</script></body></html>\n";
76  
77          final String workerJs = "postMessage('worker loaded');\n";
78  
79          getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
80  
81          loadPage2(html);
82          verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
83      }
84  
85      /**
86       * @throws Exception if the test fails
87       */
88      @Test
89      @Alerts("Received: Result = 15")
90      public void postMessageToWorker() throws Exception {
91          final String html = DOCTYPE_HTML
92              + "<html><body><script>\n"
93              + LOG_TITLE_FUNCTION
94              + "try {\n"
95              + "  var myWorker = new Worker('worker.js');\n"
96              + "  myWorker.onmessage = function(e) {\n"
97              + "    log('Received: ' + e.data);\n"
98              + "  };\n"
99              + "  setTimeout(function() { myWorker.postMessage([5, 3]);}, 10);\n"
100             + "} catch(e) { logEx(e); }\n"
101             + "</script></body></html>\n";
102 
103         final String workerJs = "onmessage = function(e) {\n"
104                 + "  var workerResult = 'Result = ' + (e.data[0] * e.data[1]);\n"
105                 + "  postMessage(workerResult);\n"
106                 + "}\n";
107 
108         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
109 
110         loadPage2(html);
111         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
112     }
113 
114     /**
115      * @throws Exception if the test fails
116      */
117     @Test
118     @Alerts("start worker in imported script1 in imported script2 end worker")
119     public void importScripts() throws Exception {
120         final String html = DOCTYPE_HTML
121             + "<html><body><script>\n"
122             + "try {\n"
123             + "  var myWorker = new Worker('worker.js');\n"
124             + "  myWorker.onmessage = function(e) {\n"
125             + "    document.title += e.data;\n"
126             + "  };\n"
127             + "} catch(e) { document.title += ' exception'; }\n"
128             + "</script></body></html>\n";
129 
130         final String workerJs = "postMessage('start worker');\n"
131                 + "importScripts('scriptToImport1.js', 'scriptToImport2.js');\n"
132                 + "postMessage(' end worker');\n";
133 
134         final String scriptToImportJs1 = "postMessage(' in imported script1');\n";
135         final String scriptToImportJs2 = "postMessage(' in imported script2');\n";
136 
137         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
138         getMockWebConnection().setResponse(new URL(URL_FIRST, "scriptToImport1.js"), scriptToImportJs1,
139                                                     MimeType.TEXT_JAVASCRIPT);
140         getMockWebConnection().setResponse(new URL(URL_FIRST, "scriptToImport2.js"), scriptToImportJs2,
141                                                     MimeType.TEXT_JAVASCRIPT);
142 
143         final WebDriver driver = loadPage2(html);
144         assertTitle(driver, getExpectedAlerts()[0]);
145     }
146 
147     /**
148      * @throws Exception if the test fails
149      */
150     @Test
151     @Alerts("start worker import exception end worker")
152     public void importScriptsWrongContentType() throws Exception {
153         importScripts(MimeType.TEXT_HTML);
154     }
155 
156     /**
157      * @throws Exception if the test fails
158      */
159     @Test
160     @Alerts("start worker in imported script1 end worker")
161     public void importScriptsContentType() throws Exception {
162         importScripts("application/ecmascript");
163         importScripts("application/javascript");
164         importScripts("application/x-ecmascript");
165         importScripts("application/x-javascript");
166         importScripts(MimeType.TEXT_JAVASCRIPT);
167         importScripts("text/javascript");
168         importScripts("text/javascript1.0");
169         importScripts("text/javascript1.1");
170         importScripts("text/javascript1.2");
171         importScripts("text/javascript1.3");
172         importScripts("text/javascript1.4");
173         importScripts("text/javascript1.5");
174         importScripts("text/jscript");
175         importScripts("text/livescript");
176         importScripts("text/x-ecmascript");
177         importScripts("text/x-javascript");
178     }
179 
180     private void importScripts(final String contentType) throws Exception {
181         final String html = DOCTYPE_HTML
182             + "<html><body><script>\n"
183             + "try {\n"
184             + "  var myWorker = new Worker('worker.js');\n"
185             + "  myWorker.onmessage = function(e) {\n"
186             + "    document.title += e.data;\n"
187             + "  };\n"
188             + "} catch(e) { document.title += ' exception'; }\n"
189             + "</script></body></html>\n";
190 
191         final String workerJs = "postMessage('start worker');\n"
192                 + "try {\n"
193                 + "  importScripts('scriptToImport1.js');\n"
194                 + "} catch(e) { postMessage(' import exception'); }\n"
195                 + "postMessage(' end worker');\n";
196 
197         final String scriptToImportJs1 = "postMessage(' in imported script1');\n";
198 
199         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
200         getMockWebConnection().setResponse(new URL(URL_FIRST, "scriptToImport1.js"), scriptToImportJs1,
201                 contentType);
202 
203         final WebDriver driver = loadPage2(html);
204         assertTitle(driver, getExpectedAlerts()[0]);
205     }
206 
207     /**
208      * @throws Exception if the test fails
209      */
210     @Test
211     @Alerts("[object DedicatedWorkerGlobalScope] [object DedicatedWorkerGlobalScope] true")
212     public void thisAndSelf() throws Exception {
213         final String html = DOCTYPE_HTML
214             + "<html><body><script>\n"
215             + "try {\n"
216             + "  var myWorker = new Worker('worker.js');\n"
217             + "  myWorker.onmessage = function(e) {\n"
218             + "    document.title += e.data;\n"
219             + "  };\n"
220             + "} catch(e) { document.tilte += ' exception'; }\n"
221             + "</script></body></html>\n";
222 
223         final String workerJs = "postMessage(' ' + this);\n"
224                 + "postMessage(' ' + self);\n"
225                 + "postMessage(' ' + (this == self));\n";
226 
227         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
228 
229         final WebDriver driver = loadPage2(html);
230         assertTitle(driver, getExpectedAlerts()[0]);
231     }
232 
233     /**
234      * @throws Exception if the test fails
235      */
236     @Test
237     @Alerts("TypeError")
238     public void createFromPrototypeAndDefineProperty() throws Exception {
239         final String html = DOCTYPE_HTML
240             + "<html><body><script>\n"
241             + LOG_TITLE_FUNCTION
242             + "var f = function() {};\n"
243             + "f.prototype = Object.create(window.Worker.prototype);\n"
244             + "try {\n"
245             + "  f.prototype['onmessage'] = function() {};\n"
246             + "  log('no exception');\n"
247             + "} catch(e) { logEx(e); }\n"
248             + "</script></body></html>";
249 
250         loadPageVerifyTitle2(html);
251     }
252 
253     /**
254      * @throws Exception if the test fails
255      */
256     @Test
257     @Alerts("function")
258     public void onmessageFunction() throws Exception {
259         final String html = DOCTYPE_HTML
260                 + "<html><body><script>\n"
261                 + LOG_TITLE_FUNCTION
262                 + "  var myWorker = new Worker('worker.js');\n"
263                 + "  myWorker.onmessage = function(e) {};\n"
264                 + "  log(typeof myWorker.onmessage);\n"
265                 + "</script></body></html>\n";
266         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
267 
268         loadPageVerifyTitle2(html);
269     }
270 
271     /**
272      * @throws Exception if the test fails
273      */
274     @Test
275     @Alerts("null")
276     public void onmessageNumber() throws Exception {
277         final String html = DOCTYPE_HTML
278                 + "<html><body><script>\n"
279                 + LOG_TITLE_FUNCTION
280                 + "  var myWorker = new Worker('worker.js');\n"
281                 + "  try {\n"
282                 + "    myWorker.onmessage = 17;\n"
283                 + "    log(myWorker.onmessage);\n"
284                 + "  } catch(e) { log('exception ' + e.name); }\n"
285                 + "</script></body></html>\n";
286         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
287 
288         loadPageVerifyTitle2(html);
289     }
290 
291     /**
292      * @throws Exception if the test fails
293      */
294     @Test
295     @Alerts("null")
296     public void onmessageString() throws Exception {
297         final String html = "<html><body><script>\n"
298                 + LOG_TITLE_FUNCTION
299                 + "  var myWorker = new Worker('worker.js');\n"
300                 + "  try {\n"
301                 + "    myWorker.onmessage = 'HtmlUnit';\n"
302                 + "    log(myWorker.onmessage);\n"
303                 + "  } catch(e) { log('exception ' + e.name); }\n"
304                 + "</script></body></html>\n";
305         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
306 
307         loadPageVerifyTitle2(html);
308     }
309 
310     /**
311      * @throws Exception if the test fails
312      */
313     @Test
314     @Alerts({"SGVsbG8gV29ybGQh", "Hello\\sWorld!"})
315     public void atob() throws Exception {
316         final String workerJs
317             = "  var data = btoa('Hello World!');\n"
318             + "  postMessage(data);\n"
319             + "  postMessage(atob(data));\n";
320         testJs(workerJs);
321     }
322 
323     /**
324      * @throws Exception if the test fails
325      */
326     @Test
327     @Alerts({"exception", "exception"})
328     public void atobUnicode() throws Exception {
329         final String workerJs
330             = "  try {\n"
331             + "    btoa('I \\u2661 Unicode!');\n"
332             + "  } catch(e) {postMessage('exception')}\n"
333             + "  try {\n"
334             + "    atob('I \\u2661 Unicode!');\n"
335             + "  } catch(e) {postMessage('exception')}\n";
336         testJs(workerJs);
337     }
338 
339     /**
340      * @throws Exception if the test fails
341      */
342     @Test
343     @Alerts({"M8OuwqY=", "3\u00C3\u00AE\u00C2\u00A6"})
344     public void atobUnicodeOutput() throws Exception {
345         final String workerJs
346             = "  var data = btoa('3\u00C3\u00AE\u00C2\u00A6');\n"
347             + "  postMessage(data);\n"
348             + "  postMessage(atob(data));\n";
349         testJs(workerJs);
350     }
351 
352     /**
353      * @throws Exception if the test fails
354      */
355     @Test
356     @Alerts({"CSAe", "\\t\\s\\u001e"})
357     public void atobControlChar() throws Exception {
358         final String workerJs
359             = "  var data = btoa('\\t \\u001e');\n"
360             + "  postMessage(data);\n"
361             + "  postMessage(atob(data));\n";
362         testJs(workerJs);
363     }
364 
365     /**
366      * @throws Exception if the test fails
367      */
368     @Test
369     @Alerts({"bnVsbA==", "null"})
370     public void atobNull() throws Exception {
371         final String workerJs
372             = "  var data = btoa(null);\n"
373             + "  postMessage(data);\n"
374             + "  postMessage(atob(data));\n";
375         testJs(workerJs);
376     }
377 
378     /**
379      * @throws Exception if the test fails
380      */
381     @Test
382     @Alerts({"dW5kZWZpbmVk", "undefined"})
383     public void atobUndefined() throws Exception {
384         final String workerJs
385             = "  var data = btoa(undefined);\n"
386             + "  postMessage(data);\n"
387             + "  postMessage(atob(data));\n";
388         testJs(workerJs);
389     }
390 
391     /**
392      * @throws Exception if the test fails
393      */
394     @Test
395     @Alerts({"object", "true"})
396     public void globalThis() throws Exception {
397         final String workerJs
398             = "  try {\n"
399             + "    postMessage(typeof globalThis);\n"
400             + "    postMessage(self === globalThis);\n"
401             + "  } catch(e) { postMessage('globalThis is undefined'); }";
402         testJs(workerJs);
403     }
404 
405     private void testJs(final String workerJs) throws Exception {
406         final String html = DOCTYPE_HTML
407             + "<html><body>\n"
408             + "<script async>\n"
409             + LOG_TITLE_FUNCTION_NORMALIZE
410             + "try {\n"
411             + "  var myWorker = new Worker('worker.js');\n"
412             + "  myWorker.onmessage = function(e) {\n"
413             + "    log(e.data);\n"
414             + "  };\n"
415             + "} catch(e) { logEx(e); }\n"
416             + "</script></body></html>\n";
417 
418         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_JAVASCRIPT);
419 
420         loadPage2(html);
421         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
422     }
423 
424     /**
425      * @throws Exception if the test fails
426      */
427     @Test
428     @Alerts(DEFAULT = "Received:worker loaded",
429             FF = {},
430             FF_ESR = {})
431     public void workerCodeWithWrongMimeType() throws Exception {
432         final String html = DOCTYPE_HTML
433             + "<html><body>\n"
434             + "<script async>\n"
435             + LOG_TITLE_FUNCTION
436             + "try {\n"
437             + "  var myWorker = new Worker('worker.js');\n"
438             + "  myWorker.onmessage = function(e) {\n"
439             + "    log('Received:' + e.data);\n"
440             + "  };\n"
441             + "} catch(e) { logEx(e); }\n"
442             + "</script></body></html>\n";
443 
444         final String workerJs = "postMessage('worker loaded');\n";
445 
446         getMockWebConnection().setResponse(new URL(URL_FIRST, "worker.js"), workerJs, MimeType.TEXT_HTML);
447 
448         loadPage2(html);
449         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
450     }
451 }