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