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