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