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