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