1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript;
16
17 import static org.htmlunit.BrowserVersionFeatures.JS_ERROR_STACK_TRACE_LIMIT;
18 import static org.htmlunit.BrowserVersionFeatures.JS_ERROR_STACK_TRACE_LIMIT_128;
19 import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_INSTALL_TRIGGER_NULL;
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.lang.reflect.Member;
24 import java.lang.reflect.Method;
25 import java.net.URL;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.function.Consumer;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.htmlunit.BrowserVersion;
36 import org.htmlunit.Page;
37 import org.htmlunit.ScriptException;
38 import org.htmlunit.WebAssert;
39 import org.htmlunit.WebClient;
40 import org.htmlunit.WebWindow;
41 import org.htmlunit.corejs.javascript.AbstractEcmaObjectOperations;
42 import org.htmlunit.corejs.javascript.BaseFunction;
43 import org.htmlunit.corejs.javascript.Callable;
44 import org.htmlunit.corejs.javascript.Context;
45 import org.htmlunit.corejs.javascript.ContextAction;
46 import org.htmlunit.corejs.javascript.ContextFactory;
47 import org.htmlunit.corejs.javascript.EcmaError;
48 import org.htmlunit.corejs.javascript.Function;
49 import org.htmlunit.corejs.javascript.FunctionObject;
50 import org.htmlunit.corejs.javascript.JavaScriptException;
51 import org.htmlunit.corejs.javascript.NativeArray;
52 import org.htmlunit.corejs.javascript.NativeArrayIterator;
53 import org.htmlunit.corejs.javascript.NativeConsole;
54 import org.htmlunit.corejs.javascript.NativeObject;
55 import org.htmlunit.corejs.javascript.RhinoException;
56 import org.htmlunit.corejs.javascript.Script;
57 import org.htmlunit.corejs.javascript.ScriptRuntime;
58 import org.htmlunit.corejs.javascript.Scriptable;
59 import org.htmlunit.corejs.javascript.ScriptableObject;
60 import org.htmlunit.corejs.javascript.StackStyle;
61 import org.htmlunit.corejs.javascript.Symbol;
62 import org.htmlunit.corejs.javascript.TopLevel;
63 import org.htmlunit.corejs.javascript.VarScope;
64 import org.htmlunit.corejs.javascript.WithScope;
65 import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
66 import org.htmlunit.corejs.javascript.typedarrays.NativeUint8Array;
67 import org.htmlunit.html.DomNode;
68 import org.htmlunit.html.HtmlElement;
69 import org.htmlunit.html.HtmlForm;
70 import org.htmlunit.html.HtmlPage;
71 import org.htmlunit.html.SubmittableElement;
72 import org.htmlunit.javascript.background.BackgroundJavaScriptFactory;
73 import org.htmlunit.javascript.background.JavaScriptExecutor;
74 import org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
75 import org.htmlunit.javascript.configuration.ClassConfiguration;
76 import org.htmlunit.javascript.configuration.ClassConfiguration.ConstantInfo;
77 import org.htmlunit.javascript.configuration.ClassConfiguration.PropertyInfo;
78 import org.htmlunit.javascript.configuration.JavaScriptConfiguration;
79 import org.htmlunit.javascript.configuration.ProxyAutoConfigJavaScriptConfiguration;
80 import org.htmlunit.javascript.host.ConsoleCustom;
81 import org.htmlunit.javascript.host.URLSearchParams;
82 import org.htmlunit.javascript.host.Window;
83 import org.htmlunit.javascript.host.WindowOrWorkerGlobalScope;
84 import org.htmlunit.javascript.host.dom.DOMException;
85 import org.htmlunit.javascript.host.html.HTMLElement;
86 import org.htmlunit.javascript.host.html.HTMLImageElement;
87 import org.htmlunit.javascript.host.html.HTMLOptionElement;
88 import org.htmlunit.javascript.host.intl.Intl;
89 import org.htmlunit.javascript.host.worker.WorkerGlobalScope;
90 import org.htmlunit.javascript.host.xml.FormData;
91 import org.htmlunit.javascript.polyfill.Polyfill;
92 import org.htmlunit.util.StringUtils;
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 public class JavaScriptEngine implements AbstractJavaScriptEngine<Script> {
120
121 private static final Log LOG = LogFactory.getLog(JavaScriptEngine.class);
122
123
124 public static final Object[] EMPTY_ARGS = ScriptRuntime.emptyArgs;
125
126
127 public static final Object UNDEFINED = org.htmlunit.corejs.javascript.Undefined.instance;
128
129 private WebClient webClient_;
130 private HtmlUnitContextFactory contextFactory_;
131 private JavaScriptConfiguration jsConfig_;
132
133 private transient ThreadLocal<Boolean> javaScriptRunning_;
134 private transient ThreadLocal<List<PostponedAction>> postponedActions_;
135 private transient boolean holdPostponedActions_;
136 private transient boolean shutdownPending_;
137
138
139 private transient JavaScriptExecutor javaScriptExecutor_;
140
141
142
143
144
145 public static final String KEY_STARTING_PAGE = "startingPage";
146
147
148
149
150
151
152 public JavaScriptEngine(final WebClient webClient) {
153 if (webClient == null) {
154 throw new IllegalArgumentException("JavaScriptEngine ctor requires a webClient");
155 }
156
157 webClient_ = webClient;
158 contextFactory_ = new HtmlUnitContextFactory(webClient);
159 initTransientFields();
160
161 jsConfig_ = JavaScriptConfiguration.getInstance(webClient.getBrowserVersion());
162 RhinoException.setStackStyle(StackStyle.MOZILLA_LF);
163 }
164
165
166
167
168
169 private WebClient getWebClient() {
170 return webClient_;
171 }
172
173
174
175
176 @Override
177 public HtmlUnitContextFactory getContextFactory() {
178 return contextFactory_;
179 }
180
181
182
183
184
185 @Override
186 public void initialize(final WebWindow webWindow, final Page page) {
187 WebAssert.notNull("webWindow", webWindow);
188
189 if (shutdownPending_) {
190 return;
191 }
192
193 getContextFactory().call(cx -> {
194 try {
195 init(webWindow, page, cx);
196 }
197 catch (final Exception e) {
198 LOG.error("Exception while initializing JavaScript for the page", e);
199 throw new ScriptException(null, e);
200 }
201 return null;
202 });
203 }
204
205
206
207
208
209
210 public JavaScriptExecutor getJavaScriptExecutor() {
211 return javaScriptExecutor_;
212 }
213
214
215
216
217
218
219
220 private void init(final WebWindow webWindow, final Page page, final Context cx) throws Exception {
221 final WebClient webClient = getWebClient();
222 final BrowserVersion browserVersion = webClient.getBrowserVersion();
223
224 final Window jsWindow = new Window();
225 jsWindow.setClassName("Window");
226
227 final TopLevel scope = cx.initSafeStandardObjects(new TopLevel(jsWindow));
228 jsWindow.setParentScope(scope);
229 configureRhino(webClient, browserVersion, scope, jsWindow);
230
231 final Map<Class<? extends Scriptable>, Scriptable> prototypes = new HashMap<>();
232 final Map<String, Scriptable> prototypesPerJSName = new HashMap<>();
233
234 final ClassConfiguration windowConfig = jsConfig_.getWindowClassConfiguration();
235 final FunctionObject functionObject = new FunctionObject(jsWindow.getClassName(), windowConfig.getJsConstructor().getValue(), scope);
236 ScriptableObject.defineProperty(jsWindow, "constructor", functionObject,
237 ScriptableObject.DONTENUM | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
238
239 configureConstantsPropertiesAndFunctions(windowConfig, scope, jsWindow);
240
241 final HtmlUnitScriptable windowPrototype = configureClass(windowConfig, scope);
242 jsWindow.setPrototype(windowPrototype);
243 prototypes.put(windowConfig.getHostClass(), windowPrototype);
244 prototypesPerJSName.put(windowConfig.getClassName(), windowPrototype);
245
246 configureGlobalThis(scope, jsWindow, windowConfig, functionObject, jsConfig_, browserVersion, prototypes, prototypesPerJSName);
247
248
249 URLSearchParams.NativeParamsIterator.init(cx, scope, "URLSearchParams Iterator");
250 FormData.FormDataIterator.init(cx, scope, "FormData Iterator");
251
252
253
254
255
256
257 if (browserVersion.hasFeature(JS_WINDOW_INSTALL_TRIGGER_NULL)) {
258 jsWindow.put("InstallTrigger", jsWindow, null);
259 }
260
261
262 final Method imageCtor = HTMLImageElement.class.getDeclaredMethod("jsConstructorImage");
263 additionalCtor(scope, jsWindow, prototypesPerJSName.get("HTMLImageElement"), imageCtor, "Image", "HTMLImageElement");
264 final Method optionCtor = HTMLOptionElement.class.getDeclaredMethod("jsConstructorOption",
265 Object.class, String.class, boolean.class, boolean.class);
266 additionalCtor(scope, jsWindow, prototypesPerJSName.get("HTMLOptionElement"), optionCtor, "Option", "HTMLOptionElement");
267
268 if (!webClient.getOptions().isWebSocketEnabled()) {
269 deleteProperties(jsWindow, "WebSocket");
270 }
271
272 jsWindow.setPrototypes(prototypes);
273 jsWindow.initialize(scope, webWindow, page);
274
275 applyPolyfills(webClient, browserVersion, cx, scope, jsWindow);
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 public static void configureGlobalThis(
292 final TopLevel scope,
293 final HtmlUnitScriptable globalThis,
294 final ClassConfiguration scopeConfig,
295 final FunctionObject scopeContructorFunctionObject,
296 final AbstractJavaScriptConfiguration jsConfig,
297 final BrowserVersion browserVersion,
298 final Map<Class<? extends Scriptable>, Scriptable> prototypes,
299 final Map<String, Scriptable> prototypesPerJSName) throws Exception {
300
301 final Scriptable objectPrototype = ScriptableObject.getObjectPrototype(scope);
302
303 final Map<String, Function> ctorPrototypesPerJSName = new HashMap<>();
304 for (final ClassConfiguration config : jsConfig.getAll()) {
305 final String jsClassName = config.getClassName();
306 Scriptable prototype = prototypesPerJSName.get(jsClassName);
307 final String extendedClassName =
308 StringUtils.isEmptyOrNull(config.getExtendedClassName()) ? null : config.getExtendedClassName();
309
310
311 if (config == scopeConfig) {
312 if (extendedClassName == null) {
313 prototype.setPrototype(objectPrototype);
314 }
315 else {
316 prototype.setPrototype(prototypesPerJSName.get(extendedClassName));
317 }
318
319
320 addAsConstructorAndAlias(scopeContructorFunctionObject, scope, globalThis, prototype, config);
321 configureConstantsStaticPropertiesAndStaticFunctions(config, scope, scopeContructorFunctionObject);
322
323
324 if (extendedClassName != null) {
325 scopeContructorFunctionObject.setPrototype(ctorPrototypesPerJSName.get(extendedClassName));
326 }
327 }
328 else {
329 final HtmlUnitScriptable classPrototype = configureClass(config, scope);
330 prototypes.put(config.getHostClass(), classPrototype);
331 prototypesPerJSName.put(jsClassName, classPrototype);
332 prototype = classPrototype;
333
334 if (extendedClassName == null) {
335 classPrototype.setPrototype(objectPrototype);
336 }
337 else {
338 classPrototype.setPrototype(prototypesPerJSName.get(extendedClassName));
339 }
340
341
342 if (prototype != null) {
343 final Map.Entry<String, Member> jsConstructor = config.getJsConstructor();
344 if (jsConstructor == null) {
345 final HtmlUnitScriptable constructor = config.getHostClass().getDeclaredConstructor().newInstance();
346 constructor.setClassName(jsClassName);
347 defineConstructor(scope, prototype, constructor);
348 configureConstantsStaticPropertiesAndStaticFunctions(config, scope, constructor);
349
350 if (config.isJsObject()) {
351 globalThis.defineProperty(jsClassName, constructor, ScriptableObject.DONTENUM);
352 }
353 }
354 else {
355 final FunctionObject function = new FunctionObject(jsConstructor.getKey(), jsConstructor.getValue(), scope);
356 ctorPrototypesPerJSName.put(jsClassName, function);
357
358 addAsConstructorAndAlias(function, scope, globalThis, prototype, config);
359 configureConstantsStaticPropertiesAndStaticFunctions(config, scope, function);
360
361 if (!config.isJsObject()) {
362
363
364
365 globalThis.delete(prototype.getClassName());
366 }
367
368
369 if (extendedClassName != null) {
370 function.setPrototype(ctorPrototypesPerJSName.get(extendedClassName));
371 }
372 }
373 }
374 }
375 }
376 }
377
378 private static void addAsConstructorAndAlias(final FunctionObject function,
379 final VarScope scope,
380 final HtmlUnitScriptable destination,
381 final Scriptable prototype,
382 final ClassConfiguration config) {
383 try {
384 function.addAsConstructor(scope, prototype, ScriptableObject.DONTENUM);
385
386 final String alias = config.getJsConstructorAlias();
387 if (alias != null) {
388 ScriptableObject.defineProperty(destination, alias, function, ScriptableObject.DONTENUM);
389 }
390 }
391 catch (final Exception e) {
392
393 if (LOG.isWarnEnabled()) {
394 final String newline = System.lineSeparator();
395 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
396 + e.getMessage() + newline
397 + "prototype: " + prototype.getClassName(), e);
398 }
399 }
400 }
401
402 private static void additionalCtor(final VarScope scope, final Window window, final Scriptable proto,
403 final Method ctorMethod, final String prop, final String clazzName) throws Exception {
404 final FunctionObject function = new FunctionObject(prop, ctorMethod, scope);
405 final Object prototypeProperty = ScriptableObject.getProperty(window, clazzName);
406 try {
407 function.addAsConstructor(scope, proto, ScriptableObject.DONTENUM);
408 }
409 catch (final Exception e) {
410
411 if (LOG.isWarnEnabled()) {
412 final String newline = System.lineSeparator();
413 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
414 + e.getMessage() + newline
415 + "prototype: " + proto.getClassName(), e);
416 }
417 }
418 ScriptableObject.defineProperty(window, prop, function, ScriptableObject.DONTENUM);
419 ScriptableObject.defineProperty(window, clazzName, prototypeProperty, ScriptableObject.DONTENUM);
420 }
421
422
423
424
425
426
427
428
429
430 public static void configureRhino(final WebClient webClient, final BrowserVersion browserVersion,
431 final TopLevel scope, final HtmlUnitScriptable globalThis) {
432
433
434
435
436 NativeConsole.init(scope, false, webClient.getWebConsole());
437
438
439
440 final ScriptableObject console = (ScriptableObject) ScriptableObject.getProperty(globalThis, "console");
441 console.defineFunctionProperties(scope, new String[] {"timeStamp"}, ConsoleCustom.class, ScriptableObject.DONTENUM);
442
443
444 deleteProperties(globalThis, "Continuation", "StopIteration", "uneval", "global", "__GeneratorFunction");
445
446
447 final ScriptableObject stringPrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "String");
448 deleteProperties(stringPrototype, "equals", "equalsIgnoreCase", "toSource");
449
450 final ScriptableObject numberPrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "Number");
451 deleteProperties(numberPrototype, "toSource");
452 final ScriptableObject datePrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "Date");
453 deleteProperties(datePrototype, "toSource");
454
455 removePrototypeProperties(scope, "Object", "toSource");
456 removePrototypeProperties(scope, "Array", "toSource");
457 removePrototypeProperties(scope, "Function", "toSource");
458
459 deleteProperties(globalThis, "isXMLName");
460
461 NativeFunctionToStringFunction.installFix(scope, browserVersion);
462
463 final ScriptableObject errorObject = (ScriptableObject) ScriptableObject.getProperty(globalThis, "Error");
464 if (browserVersion.hasFeature(JS_ERROR_STACK_TRACE_LIMIT)) {
465 errorObject.defineProperty("stackTraceLimit", 10, ScriptableObject.EMPTY);
466 }
467 else if (browserVersion.hasFeature(JS_ERROR_STACK_TRACE_LIMIT_128)) {
468 errorObject.defineProperty("stackTraceLimit", 128, ScriptableObject.EMPTY);
469 }
470 else {
471 ScriptableObject.deleteProperty(errorObject, "stackTraceLimit");
472 }
473
474
475 Intl.init(scope, globalThis, browserVersion);
476 }
477
478
479
480
481
482
483
484
485
486
487
488 public static void applyPolyfills(final WebClient webClient, final BrowserVersion browserVersion,
489 final Context context, final VarScope scope, final HtmlUnitScriptable scriptable) throws IOException {
490
491 if (webClient.getOptions().isFetchPolyfillEnabled()) {
492 Polyfill.getFetchPolyfill().apply(context, scope, scriptable);
493 }
494 }
495
496 private static void defineConstructor(final VarScope scope,
497 final Scriptable prototype, final ScriptableObject constructor) {
498 constructor.setParentScope(scope);
499 try {
500 ScriptableObject.defineProperty(prototype, "constructor", constructor,
501 ScriptableObject.DONTENUM | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
502 }
503 catch (final Exception e) {
504
505 if (LOG.isWarnEnabled()) {
506 final String newline = System.lineSeparator();
507 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
508 + e.getMessage() + newline
509 + "prototype: " + prototype.getClassName(), e);
510 }
511 }
512
513 try {
514 ScriptableObject.defineProperty(constructor, "prototype", prototype,
515 ScriptableObject.DONTENUM | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
516 }
517 catch (final Exception e) {
518
519 if (LOG.isWarnEnabled()) {
520 final String newline = System.lineSeparator();
521 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
522 + e.getMessage() + newline
523 + "prototype: " + prototype.getClassName(), e);
524 }
525 }
526 }
527
528
529
530
531
532
533 private static void deleteProperties(final Scriptable destination, final String... propertiesToDelete) {
534 for (final String property : propertiesToDelete) {
535 destination.delete(property);
536 }
537 }
538
539
540
541
542
543
544
545 private static void removePrototypeProperties(final VarScope scope, final String className,
546 final String... properties) {
547 final ScriptableObject prototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, className);
548 for (final String property : properties) {
549 prototype.delete(property);
550 }
551 }
552
553
554
555
556
557
558
559
560
561
562 public static HtmlUnitScriptable configureClass(final ClassConfiguration config,
563 final TopLevel scope)
564 throws Exception {
565
566 final HtmlUnitScriptable prototype = config.getHostClass().getDeclaredConstructor().newInstance();
567 prototype.setParentScope(scope);
568 prototype.setClassName(config.getClassName());
569
570 configureConstantsPropertiesAndFunctions(config, scope, prototype);
571
572 return prototype;
573 }
574
575
576
577
578
579
580 private static void configureConstantsStaticPropertiesAndStaticFunctions(final ClassConfiguration config,
581 final TopLevel scope, final ScriptableObject scriptable) {
582 configureConstants(config, scriptable);
583 configureStaticProperties(config, scope, scriptable);
584 configureStaticFunctions(config, scope, scriptable);
585 }
586
587
588
589
590
591
592
593 private static void configureConstantsPropertiesAndFunctions(final ClassConfiguration config,
594 final TopLevel scope, final ScriptableObject scriptable) {
595 configureConstants(config, scriptable);
596 configureProperties(config, scope, scriptable);
597 configureFunctions(config, scope, scriptable);
598 configureSymbolConstants(config, scriptable);
599 configureSymbols(config, scope, scriptable);
600 }
601
602 private static void configureFunctions(final ClassConfiguration config,
603 final TopLevel scope, final ScriptableObject scriptable) {
604
605 final Map<String, Method> functionMap = config.getFunctionMap();
606 if (functionMap != null) {
607 for (final Entry<String, Method> functionInfo : functionMap.entrySet()) {
608 final String functionName = functionInfo.getKey();
609 final Method method = functionInfo.getValue();
610 final FunctionObject functionObject = new FunctionObject(functionName, method, scope);
611 scriptable.defineProperty(functionName, functionObject, ScriptableObject.EMPTY);
612 }
613 }
614 }
615
616 private static void configureConstants(final ClassConfiguration config, final ScriptableObject scriptable) {
617 final List<ConstantInfo> constants = config.getConstants();
618 if (constants != null) {
619 for (final ConstantInfo constantInfo : constants) {
620 scriptable.defineProperty(constantInfo.getName(), constantInfo.getValue(), constantInfo.getFlag());
621 }
622 }
623 }
624
625 private static void configureProperties(final ClassConfiguration config,
626 final TopLevel scope, final ScriptableObject scriptable) {
627 final Map<String, PropertyInfo> propertyMap = config.getPropertyMap();
628 if (propertyMap != null) {
629 for (final Entry<String, PropertyInfo> propertyEntry : propertyMap.entrySet()) {
630 final PropertyInfo info = propertyEntry.getValue();
631 final Method readMethod = info.getReadMethod();
632 final Method writeMethod = info.getWriteMethod();
633 scriptable.defineProperty(scope, propertyEntry.getKey(), null, readMethod, writeMethod, ScriptableObject.EMPTY);
634 }
635 }
636 }
637
638 private static void configureStaticProperties(final ClassConfiguration config,
639 final TopLevel scope, final ScriptableObject scriptable) {
640 final Map<String, PropertyInfo> staticPropertyMap = config.getStaticPropertyMap();
641 if (staticPropertyMap != null) {
642 for (final Entry<String, ClassConfiguration.PropertyInfo> propertyEntry : staticPropertyMap.entrySet()) {
643 final String propertyName = propertyEntry.getKey();
644 final Method readMethod = propertyEntry.getValue().getReadMethod();
645 final Method writeMethod = propertyEntry.getValue().getWriteMethod();
646 final int flag = ScriptableObject.EMPTY;
647
648 scriptable.defineProperty(scope, propertyName, null, readMethod, writeMethod, flag);
649 }
650 }
651 }
652
653 private static void configureStaticFunctions(final ClassConfiguration config,
654 final TopLevel scope, final ScriptableObject scriptable) {
655 final Map<String, Method> staticFunctionMap = config.getStaticFunctionMap();
656 if (staticFunctionMap != null) {
657 for (final Entry<String, Method> staticFunctionInfo : staticFunctionMap.entrySet()) {
658 final String functionName = staticFunctionInfo.getKey();
659 final Method method = staticFunctionInfo.getValue();
660 final FunctionObject staticFunctionObject = new FunctionObject(functionName, method, scope);
661 scriptable.defineProperty(functionName, staticFunctionObject, ScriptableObject.EMPTY);
662 }
663 }
664 }
665
666 private static void configureSymbolConstants(final ClassConfiguration config, final ScriptableObject scriptable) {
667 final Map<Symbol, String> symbolConstantMap = config.getSymbolConstantMap();
668 if (symbolConstantMap != null) {
669 for (final Entry<Symbol, String> symbolInfo : symbolConstantMap.entrySet()) {
670 scriptable.defineProperty(symbolInfo.getKey(), symbolInfo.getValue(), ScriptableObject.DONTENUM | ScriptableObject.READONLY);
671 }
672 }
673 }
674
675 private static void configureSymbols(final ClassConfiguration config,
676 final TopLevel scope, final ScriptableObject scriptable) {
677 final Map<Symbol, Method> symbolMap = config.getSymbolMap();
678 if (symbolMap != null) {
679 for (final Entry<Symbol, Method> symbolInfo : symbolMap.entrySet()) {
680 final Symbol symbol = symbolInfo.getKey();
681 final Method method = symbolInfo.getValue();
682 final String methodName = method.getName();
683
684 final Callable symbolFunction;
685
686 final Object property = scriptable.get(methodName, scriptable);
687 if (property == Scriptable.NOT_FOUND) {
688 symbolFunction = new FunctionObject(methodName, method, scope);
689 }
690 else {
691 symbolFunction = (Callable) property;
692 }
693 scriptable.defineProperty(symbol, symbolFunction, ScriptableObject.DONTENUM);
694 }
695 }
696 }
697
698
699
700
701
702 @Override
703 public synchronized void registerWindowAndMaybeStartEventLoop(final WebWindow webWindow) {
704 if (shutdownPending_) {
705 return;
706 }
707
708 final WebClient webClient = getWebClient();
709 if (webClient != null) {
710 if (javaScriptExecutor_ == null) {
711 javaScriptExecutor_ = BackgroundJavaScriptFactory.theFactory().createJavaScriptExecutor(webClient);
712 }
713 javaScriptExecutor_.addWindow(webWindow);
714 }
715 }
716
717
718
719
720 @Override
721 public void prepareShutdown() {
722 shutdownPending_ = true;
723 }
724
725
726
727
728 @Override
729 public void shutdown() {
730 webClient_ = null;
731 contextFactory_ = null;
732 jsConfig_ = null;
733
734 if (javaScriptExecutor_ != null) {
735 javaScriptExecutor_.shutdown();
736 javaScriptExecutor_ = null;
737 }
738 if (postponedActions_ != null) {
739 postponedActions_.remove();
740 }
741 if (javaScriptRunning_ != null) {
742 javaScriptRunning_.remove();
743 }
744 holdPostponedActions_ = false;
745 }
746
747
748
749
750 @Override
751 public Script compile(final HtmlPage owningPage, final VarScope scope, final String sourceCode,
752 final String sourceName, final int startLine) {
753 WebAssert.notNull("sourceCode", sourceCode);
754
755 if (LOG.isTraceEnabled()) {
756 final String newline = System.lineSeparator();
757 LOG.trace("Javascript compile " + sourceName + newline + sourceCode + newline);
758 }
759
760 final HtmlUnitCompileContextAction action = new HtmlUnitCompileContextAction(owningPage, sourceCode, sourceName, startLine);
761 return (Script) getContextFactory().callSecured(action, owningPage);
762 }
763
764
765
766
767
768
769
770
771
772 public final <T> T callSecured(final ContextAction<T> action, final HtmlPage page) {
773 if (shutdownPending_ || webClient_ == null) {
774
775 return null;
776 }
777
778 return getContextFactory().callSecured(action, page);
779 }
780
781
782
783
784 @Override
785 public Object execute(final HtmlPage page,
786 final VarScope scope,
787 final String sourceCode,
788 final String sourceName,
789 final int startLine) {
790 final Script script = compile(page, scope, sourceCode, sourceName, startLine);
791 if (script == null) {
792
793 return null;
794 }
795 return execute(page, scope, script);
796 }
797
798
799
800
801 @Override
802 public Object execute(final HtmlPage page, final VarScope scope, final Script script) {
803 if (shutdownPending_ || webClient_ == null) {
804
805 return null;
806 }
807
808 final HtmlUnitContextAction action = new HtmlUnitContextAction(page) {
809 @Override
810 public Object doRun(final Context cx) {
811 return script.exec(cx, scope, ScriptableObject.getTopLevelScope(scope).getGlobalThis());
812 }
813
814 @Override
815 protected String getSourceCode(final Context cx) {
816 return null;
817 }
818 };
819
820 return getContextFactory().callSecured(action, page);
821 }
822
823
824
825
826
827
828
829
830
831
832 public Object callFunction(
833 final HtmlPage page,
834 final Function javaScriptFunction,
835 final Scriptable thisObject,
836 final Object[] args,
837 final DomNode node) {
838
839 VarScope scope = ScriptableObject.getTopLevelScope(thisObject.getParentScope());
840
841 if (node != null && node instanceof HtmlElement htmlElement) {
842 final HTMLElement elem = htmlElement.getScriptableObject();
843 scope = new WithScope(scope, elem.getOwnerDocument());
844
845 if (htmlElement instanceof SubmittableElement) {
846 final HtmlForm enclosingForm = htmlElement.getEnclosingForm();
847 if (enclosingForm != null) {
848 scope = new WithScope(scope, enclosingForm.getScriptableObject());
849 }
850 }
851
852 scope = new WithScope(scope, node.getScriptableObject());
853 }
854
855 return callFunction(page, javaScriptFunction, scope, thisObject, args);
856 }
857
858
859
860
861
862
863
864
865
866
867 public Object callFunction(final HtmlPage page, final Function function,
868 final VarScope scope, final Scriptable thisObject, final Object[] args) {
869 if (shutdownPending_ || webClient_ == null) {
870
871 return null;
872 }
873
874 final HtmlUnitContextAction action = new HtmlUnitContextAction(page) {
875 @Override
876 public Object doRun(final Context cx) {
877 if (ScriptRuntime.hasTopCall(cx)) {
878 return function.call(cx, scope, thisObject, args);
879 }
880 return ScriptRuntime.doTopCall(function, cx, scope, thisObject, args, cx.isStrictMode());
881 }
882
883 @Override
884 protected String getSourceCode(final Context cx) {
885 return cx.decompileFunction(function, 2);
886 }
887 };
888 return getContextFactory().callSecured(action, page);
889 }
890
891
892
893
894
895
896
897 @Override
898 public boolean isScriptRunning() {
899 return Boolean.TRUE.equals(javaScriptRunning_.get());
900 }
901
902
903
904
905
906 private final class HtmlUnitCompileContextAction implements ContextAction<Object> {
907 private final HtmlPage page_;
908 private final String sourceCode_;
909 private final String sourceName_;
910 private final int startLine_;
911
912 HtmlUnitCompileContextAction(final HtmlPage page, final String sourceCode, final String sourceName, final int startLine) {
913 page_ = page;
914 sourceCode_ = sourceCode;
915 sourceName_ = sourceName;
916 startLine_ = startLine;
917 }
918
919 @Override
920 public Object run(final Context cx) {
921 try {
922 final Object response;
923 cx.putThreadLocal(KEY_STARTING_PAGE, page_);
924 synchronized (page_) {
925 if (page_ != page_.getEnclosingWindow().getEnclosedPage()) {
926 return null;
927 }
928 response = cx.compileString(sourceCode_, sourceName_, startLine_, null);
929
930 }
931
932 return response;
933 }
934 catch (final Exception e) {
935 handleJavaScriptException(new ScriptException(page_, e, sourceCode_), true);
936 return null;
937 }
938 catch (final TimeoutError e) {
939 handleJavaScriptTimeoutError(page_, e);
940 return null;
941 }
942 }
943 }
944
945
946
947
948
949
950 private abstract class HtmlUnitContextAction implements ContextAction<Object> {
951 private final HtmlPage page_;
952
953 HtmlUnitContextAction(final HtmlPage page) {
954 page_ = page;
955 }
956
957 @Override
958 public final Object run(final Context cx) {
959 final Boolean javaScriptAlreadyRunning = javaScriptRunning_.get();
960 javaScriptRunning_.set(Boolean.TRUE);
961
962 try {
963 final Object response;
964 synchronized (page_) {
965 if (page_ != page_.getEnclosingWindow().getEnclosedPage()) {
966 return null;
967 }
968 response = doRun(cx);
969 }
970
971 cx.processMicrotasks();
972
973
974
975 if (!holdPostponedActions_) {
976 doProcessPostponedActions();
977 }
978
979 return response;
980 }
981 catch (final Exception e) {
982 handleJavaScriptException(new ScriptException(page_, e, getSourceCode(cx)), true);
983 return null;
984 }
985 catch (final TimeoutError e) {
986 handleJavaScriptTimeoutError(page_, e);
987 return null;
988 }
989 finally {
990 javaScriptRunning_.set(javaScriptAlreadyRunning);
991 }
992 }
993
994 protected abstract Object doRun(Context cx);
995
996 protected abstract String getSourceCode(Context cx);
997 }
998
999 private void doProcessPostponedActions() {
1000 holdPostponedActions_ = false;
1001
1002 final WebClient webClient = getWebClient();
1003 if (webClient == null) {
1004
1005 postponedActions_.set(null);
1006 return;
1007 }
1008
1009 try {
1010 webClient.loadDownloadedResponses();
1011 }
1012 catch (final RuntimeException e) {
1013 throw e;
1014 }
1015 catch (final Exception e) {
1016 throw new RuntimeException(e);
1017 }
1018
1019 final List<PostponedAction> actions = postponedActions_.get();
1020 if (actions != null && !actions.isEmpty()) {
1021 postponedActions_.set(new ArrayList<>());
1022 try {
1023 for (final PostponedAction action : actions) {
1024 if (LOG.isDebugEnabled()) {
1025 LOG.debug("Processing PostponedAction " + action);
1026 }
1027
1028
1029 if (action.isStillAlive()) {
1030 action.execute();
1031 }
1032 }
1033 }
1034 catch (final RuntimeException e) {
1035 throw e;
1036 }
1037 catch (final Exception e) {
1038 throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
1039 }
1040 }
1041 }
1042
1043
1044
1045
1046
1047 @Override
1048 public void addPostponedAction(final PostponedAction action) {
1049 if (shutdownPending_) {
1050 return;
1051 }
1052
1053 List<PostponedAction> actions = postponedActions_.get();
1054 if (actions == null) {
1055 actions = new ArrayList<>();
1056 postponedActions_.set(actions);
1057 }
1058 actions.add(action);
1059 }
1060
1061
1062
1063
1064
1065
1066 protected void handleJavaScriptException(final ScriptException scriptException, final boolean triggerOnError) {
1067 final WebClient webClient = getWebClient();
1068 if (shutdownPending_ || webClient == null) {
1069
1070 return;
1071 }
1072
1073
1074 final HtmlPage page = scriptException.getPage();
1075 if (triggerOnError && page != null) {
1076 final WebWindow window = page.getEnclosingWindow();
1077 if (window != null) {
1078 final Window w = window.getScriptableObject();
1079 if (w != null) {
1080 try {
1081 w.triggerOnError(scriptException);
1082 }
1083 catch (final Exception e) {
1084 handleJavaScriptException(new ScriptException(page, e, null), false);
1085 }
1086 }
1087 }
1088 }
1089
1090 webClient.getJavaScriptErrorListener().scriptException(page, scriptException);
1091
1092 if (webClient.getOptions().isThrowExceptionOnScriptError()) {
1093 throw scriptException;
1094 }
1095 }
1096
1097
1098
1099
1100
1101
1102 protected void handleJavaScriptTimeoutError(final HtmlPage page, final TimeoutError e) {
1103 final WebClient webClient = getWebClient();
1104 if (shutdownPending_ || webClient == null) {
1105
1106 return;
1107 }
1108
1109 webClient.getJavaScriptErrorListener().timeoutError(page, e.getAllowedTime(), e.getExecutionTime());
1110 if (webClient.getOptions().isThrowExceptionOnScriptError()) {
1111 throw new RuntimeException(e);
1112 }
1113 LOG.info("Caught script timeout error", e);
1114 }
1115
1116
1117
1118
1119
1120 @Override
1121 public void holdPosponedActions() {
1122 holdPostponedActions_ = true;
1123 }
1124
1125
1126
1127
1128
1129 @Override
1130 public void processPostponedActions() {
1131 doProcessPostponedActions();
1132 }
1133
1134
1135
1136
1137 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1138 in.defaultReadObject();
1139 initTransientFields();
1140 }
1141
1142 private void initTransientFields() {
1143 javaScriptRunning_ = new ThreadLocal<>();
1144 postponedActions_ = new ThreadLocal<>();
1145 holdPostponedActions_ = false;
1146 shutdownPending_ = false;
1147 }
1148
1149
1150
1151
1152
1153
1154 public Class<? extends HtmlUnitScriptable> getJavaScriptClass(final Class<?> c) {
1155 return jsConfig_.getDomJavaScriptMappingFor(c);
1156 }
1157
1158
1159
1160
1161
1162 @Override
1163 public JavaScriptConfiguration getJavaScriptConfiguration() {
1164 return jsConfig_;
1165 }
1166
1167
1168
1169
1170
1171 @Override
1172 public long getJavaScriptTimeout() {
1173 return getContextFactory().getTimeout();
1174 }
1175
1176
1177
1178
1179
1180 @Override
1181 public void setJavaScriptTimeout(final long timeout) {
1182 getContextFactory().setTimeout(timeout);
1183 }
1184
1185
1186
1187
1188
1189
1190
1191 public static double toNumber(final Object value) {
1192 return ScriptRuntime.toNumber(value);
1193 }
1194
1195
1196
1197
1198
1199
1200
1201 public static String toString(final Object value) {
1202 return ScriptRuntime.toString(value);
1203 }
1204
1205
1206
1207
1208
1209
1210
1211 public static boolean toBoolean(final Object value) {
1212 return ScriptRuntime.toBoolean(value);
1213 }
1214
1215
1216
1217
1218
1219
1220
1221 public static RuntimeException throwAsScriptRuntimeEx(final Throwable e) {
1222 throw Context.throwAsScriptRuntimeEx(e);
1223 }
1224
1225
1226
1227
1228
1229
1230
1231 public static RuntimeException reportRuntimeError(final String message) {
1232 throw Context.reportRuntimeError(message);
1233 }
1234
1235
1236
1237
1238
1239
1240
1241 public static EcmaError syntaxError(final String message) {
1242 return ScriptRuntime.syntaxError(message);
1243 }
1244
1245
1246
1247
1248
1249
1250
1251 public static EcmaError typeError(final String message) {
1252 return ScriptRuntime.typeError(message);
1253 }
1254
1255
1256
1257
1258
1259
1260 public static EcmaError typeErrorIllegalConstructor() {
1261 throw JavaScriptEngine.typeError("Illegal constructor.");
1262 }
1263
1264
1265
1266
1267
1268
1269
1270 public static EcmaError rangeError(final String message) {
1271 return ScriptRuntime.rangeError(message);
1272 }
1273
1274
1275
1276
1277
1278
1279
1280
1281 public static EcmaError constructError(final String error, final String message) {
1282 return ScriptRuntime.constructError(error, message);
1283 }
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295 public static RhinoException asJavaScriptException(final HtmlUnitScriptable scriptable, final String message, final int type) {
1296 final DOMException domException = new DOMException(message, type);
1297 domException.setParentScope(scriptable.getParentScope());
1298
1299 final WindowOrWorkerGlobalScope wow = HtmlUnitScriptable.getWindowOrWorkerGlobalScope(scriptable);
1300 if (wow instanceof Window window) {
1301 domException.setPrototype(window.getPrototype(DOMException.class));
1302 }
1303 else if (wow instanceof WorkerGlobalScope w) {
1304 domException.setPrototype(w.getPrototype(DOMException.class));
1305 }
1306
1307 final EcmaError helper = ScriptRuntime.syntaxError("helper");
1308 String fileName = helper.sourceName();
1309 if (fileName != null) {
1310 fileName = fileName.replaceFirst("script in (.*) from .*", "$1");
1311 }
1312 domException.setLocation(fileName, helper.lineNumber());
1313
1314 return new JavaScriptException(domException, fileName, helper.lineNumber());
1315 }
1316
1317
1318
1319
1320
1321
1322
1323
1324 public static void setFunctionProtoAndParent(final BaseFunction fn, final Context cx, final VarScope scope) {
1325 ScriptRuntime.setFunctionProtoAndParent(fn, cx, scope);
1326 }
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336 public static Scriptable newObject(final VarScope scope, final String constructorName, final Object[] args) {
1337 return ScriptRuntime.newObject(Context.getCurrentContext(), scope, constructorName, args);
1338 }
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349 public static Scriptable newObject(final VarScope scope) {
1350 final NativeObject result = new NativeObject();
1351 ScriptRuntime.setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Object);
1352 return result;
1353 }
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363 public static Scriptable newArray(final VarScope scope, final int length) {
1364 final NativeArray result = new NativeArray(length);
1365 ScriptRuntime.setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Array);
1366 return result;
1367 }
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378 public static Scriptable newArrayIteratorTypeKeys(final VarScope scope, final Scriptable arrayLike) {
1379 return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.KEYS);
1380 }
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391 public static Scriptable newArrayIteratorTypeValues(final VarScope scope, final Scriptable arrayLike) {
1392 return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.VALUES);
1393 }
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404 public static Scriptable newArrayIteratorTypeEntries(final VarScope scope, final Scriptable arrayLike) {
1405 return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.ENTRIES);
1406 }
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416 public static Scriptable newArray(final VarScope scope, final Object[] elements) {
1417 if (elements.getClass().getComponentType() != ScriptRuntime.ObjectClass) {
1418 throw new IllegalArgumentException();
1419 }
1420
1421 final NativeArray result = new NativeArray(elements);
1422 ScriptRuntime.setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Array);
1423 return result;
1424 }
1425
1426
1427
1428
1429
1430
1431
1432
1433 public static NativeUint8Array newUint8Array(final VarScope scope, final byte[] elements) {
1434 final NativeArrayBuffer arrayBuffer = new NativeArrayBuffer(elements.length);
1435 ScriptRuntime.setBuiltinProtoAndParent(arrayBuffer, scope, TopLevel.Builtins.ArrayBuffer);
1436 System.arraycopy(elements, 0, arrayBuffer.getBuffer(), 0, elements.length);
1437
1438 final NativeUint8Array uint8Array = new NativeUint8Array(arrayBuffer, 0, elements.length);
1439 ScriptRuntime.setBuiltinProtoAndParent(uint8Array, scope, TopLevel.Builtins.Uint8Array);
1440
1441 return uint8Array;
1442 }
1443
1444
1445
1446
1447
1448
1449
1450 public static int toInt32(final Object o) {
1451 return ScriptRuntime.toInt32(o);
1452 }
1453
1454
1455
1456
1457
1458
1459
1460 public static double toInteger(final Object o) {
1461 return ScriptRuntime.toInteger(o);
1462 }
1463
1464
1465
1466
1467
1468
1469
1470
1471 public static double toInteger(final Object[] args, final int index) {
1472 return ScriptRuntime.toInteger(args, index);
1473 }
1474
1475
1476
1477
1478
1479
1480
1481 public static boolean isUndefined(final Object obj) {
1482 return org.htmlunit.corejs.javascript.Undefined.isUndefined(obj);
1483 }
1484
1485
1486
1487
1488
1489
1490
1491 public static boolean isNaN(final Object obj) {
1492 return ScriptRuntime.isNaN(obj);
1493 }
1494
1495
1496
1497
1498
1499
1500
1501 public static boolean isArray(final Object obj) {
1502 return (obj instanceof Scriptable s)
1503 && "Array".equals(s.getClassName());
1504 }
1505
1506
1507
1508
1509
1510
1511
1512 public static boolean isArrayLike(final Scriptable obj) {
1513 return ScriptRuntime.isArrayLike(obj);
1514 }
1515
1516
1517
1518
1519
1520
1521
1522
1523 public static long lengthOfArrayLike(final Context cx, final Scriptable obj) {
1524 return AbstractEcmaObjectOperations.lengthOfArrayLike(cx, obj);
1525 }
1526
1527
1528
1529
1530
1531
1532
1533
1534 public static void iterateArrayLike(final Context cx, final Scriptable arrayLike, final Consumer<Object> consumer) {
1535 final Context context = cx == null ? Context.getCurrentContext() : cx;
1536
1537 final long len = lengthOfArrayLike(context, arrayLike);
1538 for (int i = 0; i < len; i++) {
1539 final Object item = arrayLike.get(i, arrayLike);
1540 consumer.accept(item);
1541 }
1542 }
1543
1544
1545
1546
1547
1548
1549 public static TopLevel getTopCallScope() {
1550 return ScriptRuntime.getTopCallScope(Context.getCurrentContext());
1551 }
1552
1553
1554
1555
1556
1557
1558
1559 public static String uncompressJavaScript(final String scriptSource, final String scriptName) {
1560 final ContextFactory factory = new ContextFactory();
1561 final ContextAction<Object> action = cx -> {
1562 cx.setInterpretedMode(true);
1563 final Script script = cx.compileString(scriptSource, scriptName, 0, null);
1564 return cx.decompileScript(script, 4);
1565 };
1566
1567 return (String) factory.call(action);
1568 }
1569
1570
1571
1572
1573
1574
1575
1576
1577 public static String evaluateProxyAutoConfig(final BrowserVersion browserVersion, final String content, final URL url) {
1578 try (Context cx = Context.enter()) {
1579 final ProxyAutoConfigJavaScriptConfiguration jsConfig =
1580 ProxyAutoConfigJavaScriptConfiguration.getInstance(browserVersion);
1581
1582 final ScriptableObject globalThis = new NativeObject();
1583 final TopLevel scope = cx.initSafeStandardObjects(new TopLevel(globalThis));
1584
1585 for (final ClassConfiguration config : jsConfig.getAll()) {
1586 configureFunctions(config, scope, globalThis);
1587 }
1588
1589 cx.evaluateString(scope, "var ProxyConfig = function() {}; ProxyConfig.bindings = {}; ProxyConfig", "<init>", 1, null);
1590 cx.evaluateString(scope, content, "<Proxy Auto-Config>", 1, null);
1591
1592 final Object[] functionArgs = {url.toExternalForm(), url.getHost()};
1593 final Function f = (Function) scope.get("FindProxyForURL", scope);
1594 final Object result = f.call(cx, scope, globalThis, functionArgs);
1595 return toString(result);
1596 }
1597 }
1598 }