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