1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.worker;
16
17 import static org.htmlunit.BrowserVersionFeatures.JS_WORKER_SCRIPT_HONORS_CHARSET_HEADER;
18
19 import java.io.IOException;
20 import java.lang.reflect.Method;
21 import java.net.URL;
22 import java.nio.charset.Charset;
23 import java.nio.charset.StandardCharsets;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.htmlunit.BrowserVersion;
31 import org.htmlunit.WebClient;
32 import org.htmlunit.WebRequest;
33 import org.htmlunit.WebResponse;
34 import org.htmlunit.corejs.javascript.Context;
35 import org.htmlunit.corejs.javascript.ContextAction;
36 import org.htmlunit.corejs.javascript.ContextFactory;
37 import org.htmlunit.corejs.javascript.Function;
38 import org.htmlunit.corejs.javascript.FunctionObject;
39 import org.htmlunit.corejs.javascript.Scriptable;
40 import org.htmlunit.corejs.javascript.ScriptableObject;
41 import org.htmlunit.corejs.javascript.TopLevel;
42 import org.htmlunit.corejs.javascript.VarScope;
43 import org.htmlunit.html.HtmlPage;
44 import org.htmlunit.javascript.AbstractJavaScriptEngine;
45 import org.htmlunit.javascript.HtmlUnitContextFactory;
46 import org.htmlunit.javascript.HtmlUnitScriptable;
47 import org.htmlunit.javascript.JavaScriptEngine;
48 import org.htmlunit.javascript.background.BasicJavaScriptJob;
49 import org.htmlunit.javascript.background.JavaScriptJob;
50 import org.htmlunit.javascript.configuration.ClassConfiguration;
51 import org.htmlunit.javascript.configuration.JsxClass;
52 import org.htmlunit.javascript.configuration.JsxConstructor;
53 import org.htmlunit.javascript.configuration.JsxFunction;
54 import org.htmlunit.javascript.configuration.JsxGetter;
55 import org.htmlunit.javascript.configuration.JsxSetter;
56 import org.htmlunit.javascript.configuration.WorkerJavaScriptConfiguration;
57 import org.htmlunit.javascript.host.Window;
58 import org.htmlunit.javascript.host.WindowOrWorkerGlobalScopeMixin;
59 import org.htmlunit.javascript.host.event.Event;
60 import org.htmlunit.javascript.host.event.MessageEvent;
61 import org.htmlunit.javascript.host.event.SecurityPolicyViolationEvent;
62 import org.htmlunit.javascript.host.media.MediaSource;
63 import org.htmlunit.javascript.host.media.SourceBuffer;
64 import org.htmlunit.javascript.host.media.SourceBufferList;
65 import org.htmlunit.util.MimeType;
66
67
68
69
70
71
72
73
74
75
76 @JsxClass
77 public class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
78
79 private static final Log LOG = LogFactory.getLog(DedicatedWorkerGlobalScope.class);
80
81 private static final Method GETTER_NAME;
82 private static final Method SETTER_NAME;
83
84 private Map<Class<? extends Scriptable>, Scriptable> prototypes_ = new HashMap<>();
85 private final Window owningWindow_;
86 private final String origin_;
87 private String name_;
88 private final Worker worker_;
89 private WorkerLocation workerLocation_;
90 private WorkerNavigator workerNavigator_;
91
92 static {
93 try {
94 GETTER_NAME = DedicatedWorkerGlobalScope.class.getDeclaredMethod("jsGetName");
95 SETTER_NAME = DedicatedWorkerGlobalScope.class.getDeclaredMethod("jsSetName", Scriptable.class);
96 }
97 catch (NoSuchMethodException | SecurityException e) {
98 throw new RuntimeException(e);
99 }
100 }
101
102
103
104
105 public DedicatedWorkerGlobalScope() {
106
107 super();
108 owningWindow_ = null;
109 origin_ = null;
110 name_ = null;
111 worker_ = null;
112 workerLocation_ = null;
113 }
114
115
116
117
118 @Override
119 @JsxConstructor
120 public void jsConstructor() {
121
122 }
123
124
125
126
127
128
129
130
131
132
133
134 DedicatedWorkerGlobalScope(final Window owningWindow, final Context context, final WebClient webClient,
135 final String name, final Worker worker) throws Exception {
136 super();
137
138 final BrowserVersion browserVersion = webClient.getBrowserVersion();
139
140 final TopLevel scope = context.initSafeStandardObjects(new TopLevel(this));
141 this.setParentScope(scope);
142
143 JavaScriptEngine.configureRhino(webClient, browserVersion, scope, this);
144
145 final WorkerJavaScriptConfiguration jsConfig = WorkerJavaScriptConfiguration.getInstance(browserVersion);
146
147 final ClassConfiguration config = jsConfig.getDedicatedWorkerGlobalScopeClassConfiguration();
148 final HtmlUnitScriptable prototype = JavaScriptEngine.configureClass(config, scope);
149 setPrototype(prototype);
150
151 final Map<Class<? extends Scriptable>, Scriptable> prototypes = new HashMap<>();
152 final Map<String, Scriptable> prototypesPerJSName = new HashMap<>();
153
154 prototypes.put(config.getHostClass(), prototype);
155 prototypesPerJSName.put(config.getClassName(), prototype);
156
157 final FunctionObject functionObject =
158 new FunctionObject(DedicatedWorkerGlobalScope.class.getSimpleName(),
159 config.getJsConstructor().getValue(), scope);
160 functionObject.addAsConstructor(scope, prototype, ScriptableObject.DONTENUM);
161
162 JavaScriptEngine.configureGlobalThis(scope, this, config, functionObject, jsConfig,
163 browserVersion, prototypes, prototypesPerJSName);
164
165 delete("webkitURL");
166 delete("WebKitCSSMatrix");
167
168
169 if (browserVersion.isFirefox()) {
170 delete(MediaSource.class.getSimpleName());
171 delete(SecurityPolicyViolationEvent.class.getSimpleName());
172 delete(SourceBuffer.class.getSimpleName());
173 delete(SourceBufferList.class.getSimpleName());
174 }
175
176 if (!webClient.getOptions().isWebSocketEnabled()) {
177 delete("WebSocket");
178 }
179
180 setPrototypes(prototypes);
181
182 owningWindow_ = owningWindow;
183 final URL currentURL = owningWindow.getWebWindow().getEnclosedPage().getUrl();
184 origin_ = currentURL.getProtocol() + "://" + currentURL.getHost() + ':' + currentURL.getPort();
185
186 name_ = name;
187 defineProperty(scope, "name", null, GETTER_NAME, SETTER_NAME, ScriptableObject.READONLY);
188
189 worker_ = worker;
190 workerLocation_ = null;
191 }
192
193
194
195
196
197
198 @JsxGetter
199 public Object getSelf() {
200 return this;
201 }
202
203
204
205
206
207
208 @JsxGetter
209 public Function getOnmessage() {
210 return getEventHandler(Event.TYPE_MESSAGE);
211 }
212
213
214
215
216
217
218 @JsxSetter
219 public void setOnmessage(final Object onmessage) {
220 setEventHandler(Event.TYPE_MESSAGE, onmessage);
221 }
222
223
224
225
226
227
228 @JsxGetter
229 public WorkerLocation getLocation() {
230 return workerLocation_;
231 }
232
233
234
235
236
237
238 @JsxGetter
239 public WorkerNavigator getNavigator() {
240 return workerNavigator_;
241 }
242
243
244
245
246
247
248 public String jsGetName() {
249 return name_;
250 }
251
252
253
254
255
256
257 public void jsSetName(final Scriptable name) {
258 name_ = JavaScriptEngine.toString(name);
259 }
260
261
262
263
264
265
266 @JsxFunction
267 public void postMessage(final Object message) {
268 final MessageEvent event = new MessageEvent();
269 event.initMessageEvent(Event.TYPE_MESSAGE, false, false, message, origin_, "",
270 owningWindow_, JavaScriptEngine.UNDEFINED);
271 event.setParentScope(getTopLevelScope(getParentScope()));
272 event.setPrototype(owningWindow_.getPrototype(event.getClass()));
273
274 if (LOG.isDebugEnabled()) {
275 LOG.debug("[DedicatedWorker] postMessage: {}" + message);
276 }
277 final JavaScriptEngine jsEngine =
278 (JavaScriptEngine) owningWindow_.getWebWindow().getWebClient().getJavaScriptEngine();
279 final ContextAction<Object> action = cx -> {
280 worker_.getEventListenersContainer().executeCapturingListeners(event, null);
281 final Object[] args = {event};
282 worker_.getEventListenersContainer().executeBubblingListeners(event, args);
283 return null;
284 };
285
286 final HtmlUnitContextFactory cf = jsEngine.getContextFactory();
287
288 final JavaScriptJob job = new WorkerJob(cf, action, "postMessage: " + JavaScriptEngine.toString(message));
289
290 final HtmlPage page = (HtmlPage) owningWindow_.getDocument().getPage();
291 owningWindow_.getWebWindow().getJobManager().addJob(job, page);
292 }
293
294 void messagePosted(final Object message) {
295 final MessageEvent event = new MessageEvent();
296 event.initMessageEvent(Event.TYPE_MESSAGE, false, false, message, origin_, "",
297 owningWindow_, JavaScriptEngine.UNDEFINED);
298 event.setParentScope(getTopLevelScope(getParentScope()));
299 event.setPrototype(owningWindow_.getPrototype(event.getClass()));
300
301 final JavaScriptEngine jsEngine =
302 (JavaScriptEngine) owningWindow_.getWebWindow().getWebClient().getJavaScriptEngine();
303 final ContextAction<Object> action = cx -> {
304 executeEvent(cx, event);
305 return null;
306 };
307
308 final HtmlUnitContextFactory cf = jsEngine.getContextFactory();
309
310 final JavaScriptJob job = new WorkerJob(cf, action, "messagePosted: " + JavaScriptEngine.toString(message));
311
312 final HtmlPage page = (HtmlPage) owningWindow_.getDocument().getPage();
313 owningWindow_.getWebWindow().getJobManager().addJob(job, page);
314 }
315
316 void executeEvent(final Context cx, final MessageEvent event) {
317 final List<Scriptable> handlers = getEventListenersContainer().getListeners(Event.TYPE_MESSAGE, false);
318 if (handlers != null) {
319 final Object[] args = {event};
320 for (final Scriptable scriptable : handlers) {
321 if (scriptable instanceof Function handlerFunction) {
322 handlerFunction.call(cx, ScriptableObject.getTopLevelScope(event.getParentScope()), this, args);
323 }
324 }
325 }
326
327 final Function handlerFunction = getEventHandler(Event.TYPE_MESSAGE);
328 if (handlerFunction != null) {
329 final Object[] args = {event};
330 handlerFunction.call(cx, getParentScope(), this, args);
331 }
332 }
333
334
335
336
337
338
339
340
341
342
343
344 @JsxFunction
345 public static void importScripts(final Context cx, final VarScope scope,
346 final Scriptable thisObj, final Object[] args, final Function funObj) throws IOException {
347 final DedicatedWorkerGlobalScope workerScope = (DedicatedWorkerGlobalScope) thisObj;
348
349 final WebClient webClient = workerScope.owningWindow_.getWebWindow().getWebClient();
350 for (final Object arg : args) {
351 final String url = JavaScriptEngine.toString(arg);
352 workerScope.loadAndExecute(webClient, url, cx, true);
353 }
354 }
355
356 void loadAndExecute(final WebClient webClient, final String url,
357 final Context context, final boolean checkMimeType) throws IOException {
358 final HtmlPage page = (HtmlPage) owningWindow_.getDocument().getPage();
359 final URL fullUrl = page.getFullyQualifiedUrl(url);
360
361 workerLocation_ = new WorkerLocation(fullUrl, origin_);
362 workerLocation_.setParentScope(getParentScope());
363 workerLocation_.setPrototype(getPrototype(workerLocation_.getClass()));
364
365 workerNavigator_ = new WorkerNavigator(webClient.getBrowserVersion());
366 workerNavigator_.setParentScope(getParentScope());
367 workerNavigator_.setPrototype(getPrototype(workerNavigator_.getClass()));
368
369 final WebRequest webRequest = new WebRequest(fullUrl);
370 final WebResponse response = webClient.loadWebResponse(webRequest);
371 if (checkMimeType && !MimeType.isJavascriptMimeType(response.getContentType())) {
372 throw JavaScriptEngine.reportRuntimeError(
373 "NetworkError: importScripts response is not a javascript response");
374 }
375
376 Charset scriptCharset = StandardCharsets.UTF_8;
377 if (webClient.getBrowserVersion().hasFeature(JS_WORKER_SCRIPT_HONORS_CHARSET_HEADER)) {
378 final Charset headerCharset = response.getHeaderContentCharset();
379 if (headerCharset != null) {
380 scriptCharset = headerCharset;
381 }
382 }
383 final String scriptCode = response.getContentAsString(scriptCharset);
384 final AbstractJavaScriptEngine<?> javaScriptEngine = webClient.getJavaScriptEngine();
385
386 final ContextAction<Object> action =
387 cx -> javaScriptEngine.execute(page, getParentScope(), scriptCode, fullUrl.toExternalForm(), 1);
388
389 final HtmlUnitContextFactory cf = javaScriptEngine.getContextFactory();
390
391 if (context != null) {
392 action.run(context);
393 }
394 else {
395 final JavaScriptJob job = new WorkerJob(cf, action, "loadAndExecute " + url);
396 owningWindow_.getWebWindow().getJobManager().addJob(job, page);
397 }
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414 @JsxFunction
415 public static Object setTimeout(final Context context, final VarScope scope,
416 final Scriptable thisObj, final Object[] args, final Function function) {
417 return WindowOrWorkerGlobalScopeMixin.setTimeout(context,
418 ((DedicatedWorkerGlobalScope) thisObj).owningWindow_, args, function);
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433 @JsxFunction
434 public static Object setInterval(final Context context, final VarScope scope,
435 final Scriptable thisObj, final Object[] args, final Function function) {
436 return WindowOrWorkerGlobalScopeMixin.setInterval(context,
437 ((DedicatedWorkerGlobalScope) thisObj).owningWindow_, args, function);
438 }
439
440
441
442
443
444
445
446 @Override
447 public Scriptable getPrototype(final Class<? extends HtmlUnitScriptable> jsClass) {
448 return prototypes_.get(jsClass);
449 }
450
451
452
453
454
455
456 public void setPrototypes(final Map<Class<? extends Scriptable>, Scriptable> map) {
457 prototypes_ = map;
458 }
459 }
460
461 class WorkerJob extends BasicJavaScriptJob {
462 private final ContextFactory contextFactory_;
463 private final ContextAction<Object> action_;
464 private final String description_;
465
466 WorkerJob(final ContextFactory contextFactory, final ContextAction<Object> action, final String description) {
467 super();
468 contextFactory_ = contextFactory;
469 action_ = action;
470 description_ = description;
471 }
472
473 @Override
474 public void run() {
475 contextFactory_.call(action_);
476 }
477
478 @Override
479 public String toString() {
480 return "WorkerJob(" + description_ + ")";
481 }
482 }