View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_ERROR_CAPTURE_STACK_TRACE;
18  import static org.htmlunit.BrowserVersionFeatures.JS_ERROR_STACK_TRACE_LIMIT;
19  import static org.htmlunit.BrowserVersionFeatures.JS_ITERATOR_VISIBLE_IN_WINDOW;
20  import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_INSTALL_TRIGGER_NULL;
21  
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.lang.reflect.Member;
25  import java.lang.reflect.Method;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  
33  import org.apache.commons.lang3.StringUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.htmlunit.BrowserVersion;
37  import org.htmlunit.Page;
38  import org.htmlunit.ScriptException;
39  import org.htmlunit.WebAssert;
40  import org.htmlunit.WebClient;
41  import org.htmlunit.WebWindow;
42  import org.htmlunit.corejs.javascript.Callable;
43  import org.htmlunit.corejs.javascript.Context;
44  import org.htmlunit.corejs.javascript.ContextAction;
45  import org.htmlunit.corejs.javascript.ContextFactory;
46  import org.htmlunit.corejs.javascript.EcmaError;
47  import org.htmlunit.corejs.javascript.Function;
48  import org.htmlunit.corejs.javascript.FunctionObject;
49  import org.htmlunit.corejs.javascript.JavaScriptException;
50  import org.htmlunit.corejs.javascript.NativeArray;
51  import org.htmlunit.corejs.javascript.NativeArrayIterator;
52  import org.htmlunit.corejs.javascript.NativeConsole;
53  import org.htmlunit.corejs.javascript.NativeFunction;
54  import org.htmlunit.corejs.javascript.RhinoException;
55  import org.htmlunit.corejs.javascript.Script;
56  import org.htmlunit.corejs.javascript.ScriptRuntime;
57  import org.htmlunit.corejs.javascript.Scriptable;
58  import org.htmlunit.corejs.javascript.ScriptableObject;
59  import org.htmlunit.corejs.javascript.StackStyle;
60  import org.htmlunit.corejs.javascript.Symbol;
61  import org.htmlunit.corejs.javascript.TopLevel;
62  import org.htmlunit.html.DomNode;
63  import org.htmlunit.html.HtmlPage;
64  import org.htmlunit.javascript.background.BackgroundJavaScriptFactory;
65  import org.htmlunit.javascript.background.JavaScriptExecutor;
66  import org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
67  import org.htmlunit.javascript.configuration.ClassConfiguration;
68  import org.htmlunit.javascript.configuration.ClassConfiguration.ConstantInfo;
69  import org.htmlunit.javascript.configuration.ClassConfiguration.PropertyInfo;
70  import org.htmlunit.javascript.configuration.JavaScriptConfiguration;
71  import org.htmlunit.javascript.configuration.ProxyAutoConfigJavaScriptConfiguration;
72  import org.htmlunit.javascript.host.ConsoleCustom;
73  import org.htmlunit.javascript.host.NumberCustom;
74  import org.htmlunit.javascript.host.URLSearchParams;
75  import org.htmlunit.javascript.host.Window;
76  import org.htmlunit.javascript.host.dom.DOMException;
77  import org.htmlunit.javascript.host.html.HTMLImageElement;
78  import org.htmlunit.javascript.host.html.HTMLOptionElement;
79  import org.htmlunit.javascript.host.intl.Intl;
80  import org.htmlunit.javascript.host.xml.FormData;
81  import org.htmlunit.javascript.polyfill.Polyfill;
82  
83  /**
84   * A wrapper for the <a href="http://www.mozilla.org/rhino">Rhino JavaScript engine</a>
85   * that provides browser specific features.
86   *
87   * <p>Like all classes in this package, this class is not intended for direct use
88   * and may change without notice.</p>
89   *
90   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
91   * @author <a href="mailto:chen_jun@users.sourceforge.net">Chen Jun</a>
92   * @author David K. Taylor
93   * @author Chris Erskine
94   * @author <a href="mailto:bcurren@esomnie.com">Ben Curren</a>
95   * @author David D. Kilzer
96   * @author Marc Guillemot
97   * @author Daniel Gredler
98   * @author Ahmed Ashour
99   * @author Amit Manjhi
100  * @author Ronald Brill
101  * @author Frank Danek
102  * @author Lai Quang Duong
103  * @author Sven Strickroth
104  *
105  * @see <a href="http://groups-beta.google.com/group/netscape.public.mozilla.jseng/browse_thread/thread/b4edac57329cf49f/069e9307ec89111f">
106  * Rhino and Java Browser</a>
107  */
108 public class JavaScriptEngine implements AbstractJavaScriptEngine<Script> {
109 
110     private static final Log LOG = LogFactory.getLog(JavaScriptEngine.class);
111 
112     /** ScriptRuntime.emptyArgs. */
113     public static final Object[] EMPTY_ARGS = ScriptRuntime.emptyArgs;
114 
115     /** org.htmlunit.corejs.javascript.Undefined.instance. */
116     public static final Object UNDEFINED = org.htmlunit.corejs.javascript.Undefined.instance;
117 
118     private WebClient webClient_;
119     private HtmlUnitContextFactory contextFactory_;
120     private JavaScriptConfiguration jsConfig_;
121 
122     private transient ThreadLocal<Boolean> javaScriptRunning_;
123     private transient ThreadLocal<List<PostponedAction>> postponedActions_;
124     private transient boolean holdPostponedActions_;
125     private transient boolean shutdownPending_;
126 
127     /** The JavaScriptExecutor corresponding to all windows of this Web client */
128     private transient JavaScriptExecutor javaScriptExecutor_;
129 
130     /**
131      * Key used to place the {@link HtmlPage} for which the JavaScript code is executed
132      * as thread local attribute in current context.
133      */
134     public static final String KEY_STARTING_PAGE = "startingPage";
135 
136     /**
137      * Creates an instance for the specified {@link WebClient}.
138      *
139      * @param webClient the client that will own this engine
140      */
141     public JavaScriptEngine(final WebClient webClient) {
142         if (webClient == null) {
143             throw new IllegalArgumentException("JavaScriptEngine ctor requires a webClient");
144         }
145 
146         webClient_ = webClient;
147         contextFactory_ = new HtmlUnitContextFactory(webClient);
148         initTransientFields();
149 
150         jsConfig_ = JavaScriptConfiguration.getInstance(webClient.getBrowserVersion());
151         RhinoException.setStackStyle(StackStyle.MOZILLA_LF);
152     }
153 
154     /**
155      * Returns the web client that this engine is associated with.
156      * @return the web client
157      */
158     private WebClient getWebClient() {
159         return webClient_;
160     }
161 
162     /**
163      * {@inheritDoc}
164      */
165     @Override
166     public HtmlUnitContextFactory getContextFactory() {
167         return contextFactory_;
168     }
169 
170     /**
171      * Performs initialization for the given webWindow.
172      * @param webWindow the web window to initialize for
173      */
174     @Override
175     public void initialize(final WebWindow webWindow, final Page page) {
176         WebAssert.notNull("webWindow", webWindow);
177 
178         if (shutdownPending_) {
179             return;
180         }
181 
182         getContextFactory().call(cx -> {
183             try {
184                 init(webWindow, page, cx);
185             }
186             catch (final Exception e) {
187                 LOG.error("Exception while initializing JavaScript for the page", e);
188                 throw new ScriptException(null, e); // BUG: null is not useful.
189             }
190             return null;
191         });
192     }
193 
194     /**
195      * Returns the JavaScriptExecutor.
196      * @return the JavaScriptExecutor or null if javascript is disabled
197      *         or no executor was required so far.
198      */
199     public JavaScriptExecutor getJavaScriptExecutor() {
200         return javaScriptExecutor_;
201     }
202 
203     /**
204      * Initializes all the JS stuff for the window.
205      * @param webWindow the web window
206      * @param context the current context
207      * @throws Exception if something goes wrong
208      */
209     private void init(final WebWindow webWindow, final Page page, final Context context) throws Exception {
210         final WebClient webClient = getWebClient();
211         final BrowserVersion browserVersion = webClient.getBrowserVersion();
212 
213         final Window jsWindowScope = new Window();
214         jsWindowScope.setClassName("Window");
215 
216         context.initSafeStandardObjects(jsWindowScope);
217         configureRhino(webClient, browserVersion, jsWindowScope);
218 
219         final Map<Class<? extends Scriptable>, Scriptable> prototypes = new HashMap<>();
220         final Map<String, Scriptable> prototypesPerJSName = new HashMap<>();
221 
222         final ClassConfiguration windowConfig = jsConfig_.getClassConfiguration("Window");
223         final FunctionObject functionObject = new RecursiveFunctionObject(jsWindowScope.getClassName(),
224                         windowConfig.getJsConstructor().getValue(), jsWindowScope, browserVersion);
225         ScriptableObject.defineProperty(jsWindowScope, "constructor", functionObject,
226                 ScriptableObject.DONTENUM  | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
227 
228         configureConstantsPropertiesAndFunctions(windowConfig, jsWindowScope);
229 
230         final HtmlUnitScriptable windowPrototype = configureClass(windowConfig, jsWindowScope);
231         jsWindowScope.setPrototype(windowPrototype);
232         prototypes.put(windowConfig.getHostClass(), windowPrototype);
233         prototypesPerJSName.put(windowConfig.getClassName(), windowPrototype);
234 
235         configureScope(jsWindowScope, windowConfig, functionObject, jsConfig_, browserVersion, prototypes, prototypesPerJSName);
236 
237         URLSearchParams.NativeParamsIterator.init(jsWindowScope, "URLSearchParams Iterator");
238         FormData.FormDataIterator.init(jsWindowScope, "FormData Iterator");
239 
240         // strange but this is the reality for browsers
241         // because there will be still some sites using this for browser detection the property is
242         // set to null
243         // https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browsers
244         // https://bugzilla.mozilla.org/show_bug.cgi?id=1442035
245         if (browserVersion.hasFeature(JS_WINDOW_INSTALL_TRIGGER_NULL)) {
246             jsWindowScope.put("InstallTrigger", jsWindowScope, null);
247         }
248 
249         // special handling for image/option
250         final Method imageCtor = HTMLImageElement.class.getDeclaredMethod("jsConstructorImage");
251         additionalCtor(jsWindowScope, prototypesPerJSName.get("HTMLImageElement"), imageCtor, "Image", "HTMLImageElement");
252         final Method optionCtor = HTMLOptionElement.class.getDeclaredMethod("jsConstructorOption",
253                 new Class[] {Object.class, String.class, boolean.class, boolean.class});
254         additionalCtor(jsWindowScope, prototypesPerJSName.get("HTMLOptionElement"), optionCtor, "Option", "HTMLOptionElement");
255 
256         if (!webClient.getOptions().isWebSocketEnabled()) {
257             deleteProperties(jsWindowScope, "WebSocket");
258         }
259 
260         jsWindowScope.setPrototypes(prototypes);
261         jsWindowScope.initialize(webWindow, page);
262 
263         applyPolyfills(webClient, browserVersion, context, jsWindowScope);
264     }
265 
266     /**
267      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
268      *
269      * @param jsScope the js scope to set up
270      * @param scopeConfig the {@link ClassConfiguration} that is used for the scope
271      * @param scopeContructorFunctionObject the (already registered) ctor
272      * @param jsConfig the complete jsConfig
273      * @param browserVersion the {@link BrowserVersion}
274      * @param prototypes map of prototypes
275      * @param prototypesPerJSName map of prototypes with the class name as key
276      * @throws Exception in case of error
277      */
278     public static void configureScope(final HtmlUnitScriptable jsScope,
279             final ClassConfiguration scopeConfig,
280             final FunctionObject scopeContructorFunctionObject,
281             final AbstractJavaScriptConfiguration jsConfig,
282             final BrowserVersion browserVersion,
283             final Map<Class<? extends Scriptable>, Scriptable> prototypes,
284             final Map<String, Scriptable> prototypesPerJSName) throws Exception {
285         // setup the prototypes
286         for (final ClassConfiguration config : jsConfig.getAll()) {
287             if (config != scopeConfig) {
288                 final HtmlUnitScriptable prototype = configureClass(config, jsScope);
289                 if (config.isJsObject()) {
290                     // Place object with prototype property in Window scope
291                     final HtmlUnitScriptable obj = config.getHostClass().getDeclaredConstructor().newInstance();
292                     prototype.defineProperty("__proto__", prototype, ScriptableObject.DONTENUM);
293                     obj.defineProperty("prototype", prototype, ScriptableObject.DONTENUM); // but not setPrototype!
294                     obj.setParentScope(jsScope);
295                     obj.setClassName(config.getClassName());
296                     ScriptableObject.defineProperty(jsScope, obj.getClassName(), obj, ScriptableObject.DONTENUM);
297                     // this obj won't have prototype, constants need to be configured on it again
298                     configureConstants(config, obj);
299                 }
300                 prototypes.put(config.getHostClass(), prototype);
301                 prototypesPerJSName.put(config.getClassName(), prototype);
302             }
303         }
304 
305         // once all prototypes have been build, it's possible to configure the chains
306         final Scriptable objectPrototype = ScriptableObject.getObjectPrototype(jsScope);
307         for (final Map.Entry<String, Scriptable> entry : prototypesPerJSName.entrySet()) {
308             final String name = entry.getKey();
309             final ClassConfiguration config = jsConfig.getClassConfiguration(name);
310             final Scriptable prototype = entry.getValue();
311             if (!StringUtils.isEmpty(config.getExtendedClassName())) {
312                 final Scriptable parentPrototype = prototypesPerJSName.get(config.getExtendedClassName());
313                 prototype.setPrototype(parentPrototype);
314             }
315             else {
316                 prototype.setPrototype(objectPrototype);
317             }
318         }
319 
320         // setup constructors
321         for (final ClassConfiguration config : jsConfig.getAll()) {
322             final String jsClassName = config.getClassName();
323             final Scriptable prototype = prototypesPerJSName.get(jsClassName);
324 
325             if (config == scopeConfig) {
326                 addAsConstructorAndAlias(scopeContructorFunctionObject, jsScope, prototype, config);
327                 configureConstantsStaticPropertiesAndStaticFunctions(config, scopeContructorFunctionObject);
328                 continue;
329             }
330 
331             final Map.Entry<String, Member> jsConstructor = config.getJsConstructor();
332             if (prototype != null && config.isJsObject()) {
333                 if (jsConstructor == null) {
334                     final ScriptableObject constructor = config.getHostClass().getDeclaredConstructor().newInstance();
335                     ((HtmlUnitScriptable) constructor).setClassName(jsClassName);
336                     defineConstructor(jsScope, prototype, constructor);
337                     configureConstantsStaticPropertiesAndStaticFunctions(config, constructor);
338                 }
339                 else {
340                     final FunctionObject function = new RecursiveFunctionObject(jsConstructor.getKey(), jsConstructor.getValue(), jsScope, browserVersion);
341                     addAsConstructorAndAlias(function, jsScope, prototype, config);
342                     configureConstantsStaticPropertiesAndStaticFunctions(config, function);
343                 }
344             }
345         }
346     }
347 
348     private static void addAsConstructorAndAlias(final FunctionObject function,
349             final Scriptable scope, final Scriptable prototype, final ClassConfiguration config) {
350         try {
351             function.addAsConstructor(scope, prototype, ScriptableObject.DONTENUM);
352 
353             final String alias = config.getJsConstructorAlias();
354             if (alias != null) {
355                 ScriptableObject.defineProperty(scope, alias, function, ScriptableObject.DONTENUM);
356             }
357         }
358         catch (final Exception e) {
359             // TODO see issue #1897
360             if (LOG.isWarnEnabled()) {
361                 final String newline = System.lineSeparator();
362                 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
363                         + e.getMessage() + newline
364                         + "prototype: " + prototype.getClassName(), e);
365             }
366         }
367     }
368 
369     private static void additionalCtor(final Window window, final Scriptable proto,
370             final Method ctorMethod, final String prop, final String clazzName) throws Exception {
371         final FunctionObject function = new FunctionObject(prop, ctorMethod, window);
372         final Object prototypeProperty = ScriptableObject.getProperty(window, clazzName);
373         try {
374             function.addAsConstructor(window, proto, ScriptableObject.DONTENUM);
375         }
376         catch (final Exception e) {
377             // TODO see issue #1897
378             if (LOG.isWarnEnabled()) {
379                 final String newline = System.lineSeparator();
380                 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
381                         + e.getMessage() + newline
382                         + "prototype: " + proto.getClassName(), e);
383             }
384         }
385         ScriptableObject.defineProperty(window, prop, function, ScriptableObject.DONTENUM);
386         ScriptableObject.defineProperty(window, clazzName, prototypeProperty, ScriptableObject.DONTENUM);
387     }
388 
389     /**
390      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
391      *
392      * @param webClient the WebClient
393      * @param browserVersion the BrowserVersion
394      * @param scope the window or the DedicatedWorkerGlobalScope
395      */
396     public static void configureRhino(final WebClient webClient,
397             final BrowserVersion browserVersion, final HtmlUnitScriptable scope) {
398 
399         NativeConsole.init(scope, false, webClient.getWebConsole());
400         final ScriptableObject console = (ScriptableObject) ScriptableObject.getProperty(scope, "console");
401         console.defineFunctionProperties(new String[] {"timeStamp"}, ConsoleCustom.class, ScriptableObject.DONTENUM);
402 
403         // Rhino defines too much methods for us, particularly since implementation of ECMAScript5
404         final ScriptableObject stringPrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "String");
405         deleteProperties(stringPrototype, "equals", "equalsIgnoreCase", "toSource");
406 
407         final ScriptableObject numberPrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "Number");
408         deleteProperties(numberPrototype, "toSource");
409         final ScriptableObject datePrototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, "Date");
410         deleteProperties(datePrototype, "toSource");
411 
412         deleteProperties(scope, "uneval");
413         removePrototypeProperties(scope, "Object", "toSource");
414         removePrototypeProperties(scope, "Array", "toSource");
415         removePrototypeProperties(scope, "Function", "toSource");
416 
417         deleteProperties(scope, "isXMLName");
418 
419         NativeFunctionToStringFunction.installFix(scope, browserVersion);
420 
421         numberPrototype.defineFunctionProperties(new String[] {"toLocaleString"},
422                 NumberCustom.class, ScriptableObject.DONTENUM);
423 
424         // remove some objects, that Rhino defines in top scope but that we don't want
425         deleteProperties(scope, "Continuation", "StopIteration");
426         if (!browserVersion.hasFeature(JS_ITERATOR_VISIBLE_IN_WINDOW)) {
427             deleteProperties(scope, "Iterator");
428         }
429 
430         final ScriptableObject errorObject = (ScriptableObject) ScriptableObject.getProperty(scope, "Error");
431         if (browserVersion.hasFeature(JS_ERROR_STACK_TRACE_LIMIT)) {
432             errorObject.defineProperty("stackTraceLimit", 10, ScriptableObject.EMPTY);
433         }
434         else {
435             ScriptableObject.deleteProperty(errorObject, "stackTraceLimit");
436         }
437         if (!browserVersion.hasFeature(JS_ERROR_CAPTURE_STACK_TRACE)) {
438             ScriptableObject.deleteProperty(errorObject, "captureStackTrace");
439         }
440 
441         // add Intl
442         final Intl intl = new Intl();
443         intl.setParentScope(scope);
444         scope.defineProperty(intl.getClassName(), intl, ScriptableObject.DONTENUM);
445         intl.defineProperties(browserVersion);
446     }
447 
448     /**
449      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
450      *
451      * @param webClient the WebClient
452      * @param browserVersion the BrowserVersion
453      * @param context the current context
454      * @param scriptable the window or the DedicatedWorkerGlobalScope
455      * @throws IOException in case of problems
456      */
457     public static void applyPolyfills(final WebClient webClient, final BrowserVersion browserVersion,
458             final Context context, final HtmlUnitScriptable scriptable) throws IOException {
459 
460         if (webClient.getOptions().isFetchPolyfillEnabled()) {
461             Polyfill.getFetchPolyfill().apply(context, scriptable);
462         }
463     }
464 
465     private static void defineConstructor(final HtmlUnitScriptable window,
466             final Scriptable prototype, final ScriptableObject constructor) {
467         constructor.setParentScope(window);
468         try {
469             ScriptableObject.defineProperty(prototype, "constructor", constructor,
470                     ScriptableObject.DONTENUM  | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
471         }
472         catch (final Exception e) {
473             // TODO see issue #1897
474             if (LOG.isWarnEnabled()) {
475                 final String newline = System.lineSeparator();
476                 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
477                         + e.getMessage() + newline
478                         + "prototype: " + prototype.getClassName(), e);
479             }
480         }
481 
482         try {
483             ScriptableObject.defineProperty(constructor, "prototype", prototype,
484                     ScriptableObject.DONTENUM  | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
485         }
486         catch (final Exception e) {
487             // TODO see issue #1897
488             if (LOG.isWarnEnabled()) {
489                 final String newline = System.lineSeparator();
490                 LOG.warn("Error during JavaScriptEngine.init(WebWindow, Context)" + newline
491                         + e.getMessage() + newline
492                         + "prototype: " + prototype.getClassName(), e);
493             }
494         }
495 
496         window.defineProperty(constructor.getClassName(), constructor, ScriptableObject.DONTENUM);
497     }
498 
499     /**
500      * Deletes the properties with the provided names.
501      * @param scope the scope from which properties have to be removed
502      * @param propertiesToDelete the list of property names
503      */
504     private static void deleteProperties(final Scriptable scope, final String... propertiesToDelete) {
505         for (final String property : propertiesToDelete) {
506             scope.delete(property);
507         }
508     }
509 
510     /**
511      * Removes prototype properties.
512      * @param scope the scope
513      * @param className the class for which properties should be removed
514      * @param properties the properties to remove
515      */
516     private static void removePrototypeProperties(final Scriptable scope, final String className,
517             final String... properties) {
518         final ScriptableObject prototype = (ScriptableObject) ScriptableObject.getClassPrototype(scope, className);
519         for (final String property : properties) {
520             prototype.delete(property);
521         }
522     }
523 
524     /**
525      * Configures the specified class for access via JavaScript.
526      * @param config the configuration settings for the class to be configured
527      * @param window the scope within which to configure the class
528      * @throws InstantiationException if the new class cannot be instantiated
529      * @throws IllegalAccessException if we don't have access to create the new instance
530      * @return the created prototype
531      * @throws Exception in case of errors
532      */
533     public static HtmlUnitScriptable configureClass(final ClassConfiguration config, final Scriptable window)
534         throws Exception {
535 
536         final HtmlUnitScriptable prototype = config.getHostClass().getDeclaredConstructor().newInstance();
537         prototype.setParentScope(window);
538         prototype.setClassName(config.getClassName());
539 
540         configureConstantsPropertiesAndFunctions(config, prototype);
541 
542         return prototype;
543     }
544 
545     /**
546      * Configures constants, static properties and static functions on the object.
547      * @param config the configuration for the object
548      * @param scriptable the object to configure
549      */
550     private static void configureConstantsStaticPropertiesAndStaticFunctions(final ClassConfiguration config,
551             final ScriptableObject scriptable) {
552         configureConstants(config, scriptable);
553         configureStaticProperties(config, scriptable);
554         configureStaticFunctions(config, scriptable);
555     }
556 
557     /**
558      * Configures constants, properties and functions on the object.
559      * @param config the configuration for the object
560      * @param scriptable the object to configure
561      */
562     private static void configureConstantsPropertiesAndFunctions(final ClassConfiguration config,
563             final ScriptableObject scriptable) {
564         configureConstants(config, scriptable);
565         configureProperties(config, scriptable);
566         configureFunctions(config, scriptable);
567         configureSymbolConstants(config, scriptable);
568         configureSymbols(config, scriptable);
569     }
570 
571     private static void configureFunctions(final ClassConfiguration config, final ScriptableObject scriptable) {
572         // the functions
573         final Map<String, Method> functionMap = config.getFunctionMap();
574         if (functionMap != null) {
575             for (final Entry<String, Method> functionInfo : functionMap.entrySet()) {
576                 final String functionName = functionInfo.getKey();
577                 final Method method = functionInfo.getValue();
578                 final FunctionObject functionObject = new FunctionObject(functionName, method, scriptable);
579                 scriptable.defineProperty(functionName, functionObject, ScriptableObject.EMPTY);
580             }
581         }
582     }
583 
584     private static void configureConstants(final ClassConfiguration config, final ScriptableObject scriptable) {
585         final List<ConstantInfo> constants = config.getConstants();
586         if (constants != null) {
587             for (final ConstantInfo constantInfo : constants) {
588                 scriptable.defineProperty(constantInfo.getName(), constantInfo.getValue(), constantInfo.getFlag());
589             }
590         }
591     }
592 
593     private static void configureProperties(final ClassConfiguration config, final ScriptableObject scriptable) {
594         final Map<String, PropertyInfo> propertyMap = config.getPropertyMap();
595         if (propertyMap != null) {
596             for (final Entry<String, PropertyInfo> propertyEntry : propertyMap.entrySet()) {
597                 final PropertyInfo info = propertyEntry.getValue();
598                 final Method readMethod = info.getReadMethod();
599                 final Method writeMethod = info.getWriteMethod();
600                 scriptable.defineProperty(propertyEntry.getKey(), null, readMethod, writeMethod, ScriptableObject.EMPTY);
601             }
602         }
603     }
604 
605     private static void configureStaticProperties(final ClassConfiguration config, final ScriptableObject scriptable) {
606         final Map<String, PropertyInfo> staticPropertyMap = config.getStaticPropertyMap();
607         if (staticPropertyMap != null) {
608             for (final Entry<String, ClassConfiguration.PropertyInfo> propertyEntry : staticPropertyMap.entrySet()) {
609                 final String propertyName = propertyEntry.getKey();
610                 final Method readMethod = propertyEntry.getValue().getReadMethod();
611                 final Method writeMethod = propertyEntry.getValue().getWriteMethod();
612                 final int flag = ScriptableObject.EMPTY;
613 
614                 scriptable.defineProperty(propertyName, null, readMethod, writeMethod, flag);
615             }
616         }
617     }
618 
619     private static void configureStaticFunctions(final ClassConfiguration config,
620             final ScriptableObject scriptable) {
621         final Map<String, Method> staticFunctionMap = config.getStaticFunctionMap();
622         if (staticFunctionMap != null) {
623             for (final Entry<String, Method> staticFunctionInfo : staticFunctionMap.entrySet()) {
624                 final String functionName = staticFunctionInfo.getKey();
625                 final Method method = staticFunctionInfo.getValue();
626                 final FunctionObject staticFunctionObject = new FunctionObject(functionName, method,
627                         scriptable);
628                 scriptable.defineProperty(functionName, staticFunctionObject, ScriptableObject.EMPTY);
629             }
630         }
631     }
632 
633     private static void configureSymbolConstants(final ClassConfiguration config, final ScriptableObject scriptable) {
634         final Map<Symbol, String> symbolConstantMap = config.getSymbolConstantMap();
635         if (symbolConstantMap != null) {
636             for (final Entry<Symbol, String> symbolInfo : symbolConstantMap.entrySet()) {
637                 scriptable.defineProperty(symbolInfo.getKey(), symbolInfo.getValue(), ScriptableObject.DONTENUM | ScriptableObject.READONLY);
638             }
639         }
640     }
641 
642     private static void configureSymbols(final ClassConfiguration config,
643             final ScriptableObject scriptable) {
644         final Map<Symbol, Method> symbolMap = config.getSymbolMap();
645         if (symbolMap != null) {
646             for (final Entry<Symbol, Method> symbolInfo : symbolMap.entrySet()) {
647                 final Symbol symbol = symbolInfo.getKey();
648                 final Method method = symbolInfo.getValue();
649                 final String methodName = method.getName();
650 
651                 final Callable symbolFunction = scriptable.has(methodName, scriptable)
652                         ? (Callable) scriptable.get(methodName, scriptable)
653                         : new FunctionObject(methodName, method, scriptable);
654                 scriptable.defineProperty(symbol, symbolFunction, ScriptableObject.DONTENUM);
655             }
656         }
657     }
658 
659     /**
660      * Register WebWindow with the JavaScriptExecutor.
661      * @param webWindow the WebWindow to be registered.
662      */
663     @Override
664     public synchronized void registerWindowAndMaybeStartEventLoop(final WebWindow webWindow) {
665         if (shutdownPending_) {
666             return;
667         }
668 
669         final WebClient webClient = getWebClient();
670         if (webClient != null) {
671             if (javaScriptExecutor_ == null) {
672                 javaScriptExecutor_ = BackgroundJavaScriptFactory.theFactory().createJavaScriptExecutor(webClient);
673             }
674             javaScriptExecutor_.addWindow(webWindow);
675         }
676     }
677 
678     /**
679      * {@inheritDoc}
680      */
681     @Override
682     public void prepareShutdown() {
683         shutdownPending_ = true;
684     }
685 
686     /**
687      * Shutdown the JavaScriptEngine.
688      */
689     @Override
690     public void shutdown() {
691         webClient_ = null;
692         contextFactory_ = null;
693         jsConfig_ = null;
694 
695         if (javaScriptExecutor_ != null) {
696             javaScriptExecutor_.shutdown();
697             javaScriptExecutor_ = null;
698         }
699         if (postponedActions_ != null) {
700             postponedActions_.remove();
701         }
702         if (javaScriptRunning_ != null) {
703             javaScriptRunning_.remove();
704         }
705         holdPostponedActions_ = false;
706     }
707 
708     /**
709      * {@inheritDoc}
710      */
711     @Override
712     public Script compile(final HtmlPage owningPage, final Scriptable scope, final String sourceCode,
713             final String sourceName, final int startLine) {
714         WebAssert.notNull("sourceCode", sourceCode);
715 
716         if (LOG.isTraceEnabled()) {
717             final String newline = System.lineSeparator();
718             LOG.trace("Javascript compile " + sourceName + newline + sourceCode + newline);
719         }
720 
721         final HtmlUnitCompileContextAction action = new HtmlUnitCompileContextAction(owningPage, sourceCode, sourceName, startLine);
722         return (Script) getContextFactory().callSecured(action, owningPage);
723     }
724 
725     /**
726      * Forwards this to the {@link HtmlUnitContextFactory} but with checking shutdown handling.
727      *
728      * @param <T> return type of the action
729      * @param action the contextAction
730      * @param page the page
731      * @return the result of the call
732      */
733     public final <T> T callSecured(final ContextAction<T> action, final HtmlPage page) {
734         if (shutdownPending_ || webClient_ == null) {
735             // shutdown was already called
736             return null;
737         }
738 
739         return getContextFactory().callSecured(action, page);
740     }
741 
742     /**
743      * {@inheritDoc}
744      */
745     @Override
746     public Object execute(final HtmlPage page,
747                            final Scriptable scope,
748                            final String sourceCode,
749                            final String sourceName,
750                            final int startLine) {
751         final Script script = compile(page, scope, sourceCode, sourceName, startLine);
752         if (script == null) {
753             // happens with syntax error + throwExceptionOnScriptError = false
754             return null;
755         }
756         return execute(page, scope, script);
757     }
758 
759     /**
760      * {@inheritDoc}
761      */
762     @Override
763     public Object execute(final HtmlPage page, final Scriptable scope, final Script script) {
764         if (shutdownPending_ || webClient_ == null) {
765             // shutdown was already called
766             return null;
767         }
768 
769         final HtmlUnitContextAction action = new HtmlUnitContextAction(page) {
770             @Override
771             public Object doRun(final Context cx) {
772                 return script.exec(cx, scope);
773             }
774 
775             @Override
776             protected String getSourceCode(final Context cx) {
777                 return null;
778             }
779         };
780 
781         return getContextFactory().callSecured(action, page);
782     }
783 
784     /**
785      * Calls a JavaScript function and return the result.
786      * @param page the page
787      * @param javaScriptFunction the function to call
788      * @param thisObject the this object for class method calls
789      * @param args the list of arguments to pass to the function
790      * @param node the HTML element that will act as the context
791      * @return the result of the function call
792      */
793     public Object callFunction(
794             final HtmlPage page,
795             final Function javaScriptFunction,
796             final Scriptable thisObject,
797             final Object[] args,
798             final DomNode node) {
799 
800         final Scriptable scope = getScope(page, node);
801 
802         return callFunction(page, javaScriptFunction, scope, thisObject, args);
803     }
804 
805     /**
806      * Calls the given function taking care of synchronization issues.
807      * @param page the interactive page that caused this script to executed
808      * @param function the JavaScript function to execute
809      * @param scope the execution scope
810      * @param thisObject the 'this' object
811      * @param args the function's arguments
812      * @return the function result
813      */
814     public Object callFunction(final HtmlPage page, final Function function,
815             final Scriptable scope, final Scriptable thisObject, final Object[] args) {
816         if (shutdownPending_ || webClient_ == null) {
817             // shutdown was already called
818             return null;
819         }
820 
821         final HtmlUnitContextAction action = new HtmlUnitContextAction(page) {
822             @Override
823             public Object doRun(final Context cx) {
824                 if (ScriptRuntime.hasTopCall(cx)) {
825                     return function.call(cx, scope, thisObject, args);
826                 }
827                 return ScriptRuntime.doTopCall(function, cx, scope, thisObject, args, cx.isStrictMode());
828             }
829             @Override
830             protected String getSourceCode(final Context cx) {
831                 return cx.decompileFunction(function, 2);
832             }
833         };
834         return getContextFactory().callSecured(action, page);
835     }
836 
837     private static Scriptable getScope(final HtmlPage page, final DomNode node) {
838         if (node != null) {
839             return node.getScriptableObject();
840         }
841         return page.getEnclosingWindow().getScriptableObject();
842     }
843 
844     /**
845      * Indicates if JavaScript is running in current thread.
846      * <p>This allows code to know if there own evaluation is has been triggered by some JS code.
847      * @return {@code true} if JavaScript is running
848      */
849     @Override
850     public boolean isScriptRunning() {
851         return Boolean.TRUE.equals(javaScriptRunning_.get());
852     }
853 
854     /**
855      * Special ContextAction only for compiling. This reduces some code and avoid
856      * some calls.
857      */
858     private final class HtmlUnitCompileContextAction implements ContextAction<Object> {
859         private final HtmlPage page_;
860         private final String sourceCode_;
861         private final String sourceName_;
862         private final int startLine_;
863 
864         HtmlUnitCompileContextAction(final HtmlPage page, final String sourceCode, final String sourceName, final int startLine) {
865             page_ = page;
866             sourceCode_ = sourceCode;
867             sourceName_ = sourceName;
868             startLine_ = startLine;
869         }
870 
871         @Override
872         public Object run(final Context cx) {
873             try {
874                 final Object response;
875                 cx.putThreadLocal(KEY_STARTING_PAGE, page_);
876                 synchronized (page_) { // 2 scripts can't be executed in parallel for one page
877                     if (page_ != page_.getEnclosingWindow().getEnclosedPage()) {
878                         return null; // page has been unloaded
879                     }
880                     response = cx.compileString(sourceCode_, sourceName_, startLine_, null);
881 
882                 }
883 
884                 return response;
885             }
886             catch (final Exception e) {
887                 handleJavaScriptException(new ScriptException(page_, e, sourceCode_), true);
888                 return null;
889             }
890             catch (final TimeoutError e) {
891                 handleJavaScriptTimeoutError(page_, e);
892                 return null;
893             }
894         }
895     }
896 
897     /**
898      * Facility for ContextAction usage.
899      * ContextAction should be preferred because according to Rhino doc it
900      * "guarantees proper association of Context instances with the current thread and is faster".
901      */
902     private abstract class HtmlUnitContextAction implements ContextAction<Object> {
903         private final HtmlPage page_;
904 
905         HtmlUnitContextAction(final HtmlPage page) {
906             page_ = page;
907         }
908 
909         @Override
910         public final Object run(final Context cx) {
911             final Boolean javaScriptAlreadyRunning = javaScriptRunning_.get();
912             javaScriptRunning_.set(Boolean.TRUE);
913 
914             try {
915                 final Object response;
916                 synchronized (page_) { // 2 scripts can't be executed in parallel for one page
917                     if (page_ != page_.getEnclosingWindow().getEnclosedPage()) {
918                         return null; // page has been unloaded
919                     }
920                     response = doRun(cx);
921                 }
922 
923                 cx.processMicrotasks();
924 
925                 // doProcessPostponedActions is synchronized
926                 // moved out of the sync block to avoid deadlocks
927                 if (!holdPostponedActions_) {
928                     doProcessPostponedActions();
929                 }
930 
931                 return response;
932             }
933             catch (final Exception e) {
934                 handleJavaScriptException(new ScriptException(page_, e, getSourceCode(cx)), true);
935                 return null;
936             }
937             catch (final TimeoutError e) {
938                 handleJavaScriptTimeoutError(page_, e);
939                 return null;
940             }
941             finally {
942                 javaScriptRunning_.set(javaScriptAlreadyRunning);
943             }
944         }
945 
946         protected abstract Object doRun(Context cx);
947 
948         protected abstract String getSourceCode(Context cx);
949     }
950 
951     private void doProcessPostponedActions() {
952         holdPostponedActions_ = false;
953 
954         final WebClient webClient = getWebClient();
955         if (webClient == null) {
956             // shutdown was already called
957             postponedActions_.set(null);
958             return;
959         }
960 
961         try {
962             webClient.loadDownloadedResponses();
963         }
964         catch (final RuntimeException e) {
965             throw e;
966         }
967         catch (final Exception e) {
968             throw new RuntimeException(e);
969         }
970 
971         final List<PostponedAction> actions = postponedActions_.get();
972         if (actions != null && actions.size() > 0) {
973             postponedActions_.set(new ArrayList<>());
974             try {
975                 for (final PostponedAction action : actions) {
976                     if (LOG.isDebugEnabled()) {
977                         LOG.debug("Processing PostponedAction " + action);
978                     }
979 
980                     // verify that the page that registered this PostponedAction is still alive
981                     if (action.isStillAlive()) {
982                         action.execute();
983                     }
984                 }
985             }
986             catch (final RuntimeException e) {
987                 throw e;
988             }
989             catch (final Exception e) {
990                 throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
991             }
992         }
993     }
994 
995     /**
996      * Adds an action that should be executed first when the script currently being executed has finished.
997      * @param action the action
998      */
999     @Override
1000     public void addPostponedAction(final PostponedAction action) {
1001         if (shutdownPending_) {
1002             return;
1003         }
1004 
1005         List<PostponedAction> actions = postponedActions_.get();
1006         if (actions == null) {
1007             actions = new ArrayList<>();
1008             postponedActions_.set(actions);
1009         }
1010         actions.add(action);
1011     }
1012 
1013     /**
1014      * Handles an exception that occurred during execution of JavaScript code.
1015      * @param scriptException the exception
1016      * @param triggerOnError if true, this triggers the onerror handler
1017      */
1018     protected void handleJavaScriptException(final ScriptException scriptException, final boolean triggerOnError) {
1019         final WebClient webClient = getWebClient();
1020         if (shutdownPending_ || webClient == null) {
1021             // shutdown was already called
1022             return;
1023         }
1024 
1025         // Trigger window.onerror, if it has been set.
1026         final HtmlPage page = scriptException.getPage();
1027         if (triggerOnError && page != null) {
1028             final WebWindow window = page.getEnclosingWindow();
1029             if (window != null) {
1030                 final Window w = window.getScriptableObject();
1031                 if (w != null) {
1032                     try {
1033                         w.triggerOnError(scriptException);
1034                     }
1035                     catch (final Exception e) {
1036                         handleJavaScriptException(new ScriptException(page, e, null), false);
1037                     }
1038                 }
1039             }
1040         }
1041 
1042         webClient.getJavaScriptErrorListener().scriptException(page, scriptException);
1043         // Throw a Java exception if the user wants us to.
1044         if (webClient.getOptions().isThrowExceptionOnScriptError()) {
1045             throw scriptException;
1046         }
1047     }
1048 
1049     /**
1050      * Handles an exception that occurred during execution of JavaScript code.
1051      * @param page the page in which the script causing this exception was executed
1052      * @param e the timeout error that was thrown from the script engine
1053      */
1054     protected void handleJavaScriptTimeoutError(final HtmlPage page, final TimeoutError e) {
1055         final WebClient webClient = getWebClient();
1056         if (shutdownPending_ || webClient == null) {
1057             // shutdown was already called
1058             return;
1059         }
1060 
1061         webClient.getJavaScriptErrorListener().timeoutError(page, e.getAllowedTime(), e.getExecutionTime());
1062         if (webClient.getOptions().isThrowExceptionOnScriptError()) {
1063             throw new RuntimeException(e);
1064         }
1065         LOG.info("Caught script timeout error", e);
1066     }
1067 
1068     /**
1069      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1070      * Indicates that no postponed action should be executed.
1071      */
1072     @Override
1073     public void holdPosponedActions() {
1074         holdPostponedActions_ = true;
1075     }
1076 
1077     /**
1078      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1079      * Process postponed actions, if any.
1080      */
1081     @Override
1082     public void processPostponedActions() {
1083         doProcessPostponedActions();
1084     }
1085 
1086     /**
1087      * Re-initializes transient fields when an object of this type is deserialized.
1088      */
1089     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1090         in.defaultReadObject();
1091         initTransientFields();
1092     }
1093 
1094     private void initTransientFields() {
1095         javaScriptRunning_ = new ThreadLocal<>();
1096         postponedActions_ = new ThreadLocal<>();
1097         holdPostponedActions_ = false;
1098         shutdownPending_ = false;
1099     }
1100 
1101     /**
1102      * Gets the class of the JavaScript object for the node class.
1103      * @param c the node class {@link DomNode} or some subclass.
1104      * @return {@code null} if none found
1105      */
1106     public Class<? extends HtmlUnitScriptable> getJavaScriptClass(final Class<?> c) {
1107         return jsConfig_.getDomJavaScriptMappingFor(c);
1108     }
1109 
1110     /**
1111      * Gets the associated configuration.
1112      * @return the configuration
1113      */
1114     @Override
1115     public JavaScriptConfiguration getJavaScriptConfiguration() {
1116         return jsConfig_;
1117     }
1118 
1119     /**
1120      * Returns the javascript timeout.
1121      * @return the javascript timeout
1122      */
1123     @Override
1124     public long getJavaScriptTimeout() {
1125         return getContextFactory().getTimeout();
1126     }
1127 
1128     /**
1129      * Sets the javascript timeout.
1130      * @param timeout the timeout
1131      */
1132     @Override
1133     public void setJavaScriptTimeout(final long timeout) {
1134         getContextFactory().setTimeout(timeout);
1135     }
1136 
1137     /**
1138      * Convert the value to a JavaScript Number value.
1139      *
1140      * @param value a JavaScript value
1141      * @return the corresponding double value converted using the ECMA rules
1142      */
1143     public static double toNumber(final Object value) {
1144         return ScriptRuntime.toNumber(value);
1145     }
1146 
1147     /**
1148      * Convert the value to a JavaScript String value.
1149      *
1150      * @param value a JavaScript value
1151      * @return the corresponding String value converted using the ECMA rules
1152      */
1153     public static String toString(final Object value) {
1154         return ScriptRuntime.toString(value);
1155     }
1156 
1157     /**
1158      * Convert the value to a JavaScript boolean value.
1159      *
1160      * @param value a JavaScript value
1161      * @return the corresponding boolean value converted using the ECMA rules
1162      */
1163     public static boolean toBoolean(final Object value) {
1164         return ScriptRuntime.toBoolean(value);
1165     }
1166 
1167     /**
1168      * Rethrow the exception wrapping it as the script runtime exception.
1169      *
1170      * @param e the exception to rethrow
1171      * @return RuntimeException as dummy the method always throws
1172      */
1173     public static RuntimeException throwAsScriptRuntimeEx(final Throwable e) {
1174         throw Context.throwAsScriptRuntimeEx(e);
1175     }
1176 
1177     /**
1178      * Report a runtime error using the error reporter for the current thread.
1179      *
1180      * @param message the error message to report
1181      * @return RuntimeException as dummy the method always throws
1182      */
1183     public static RuntimeException reportRuntimeError(final String message) {
1184         throw Context.reportRuntimeError(message);
1185     }
1186 
1187     /**
1188      * Report a runtime error using the error reporter for the current thread.
1189      *
1190      * @param message the error message to report
1191      * @return EcmaError
1192      */
1193     public static EcmaError syntaxError(final String message) {
1194         return ScriptRuntime.syntaxError(message);
1195     }
1196 
1197     /**
1198      * Report a runtime error using the error reporter for the current thread.
1199      *
1200      * @param message the error message to report
1201      * @return EcmaError
1202      */
1203     public static EcmaError typeError(final String message) {
1204         return ScriptRuntime.typeError(message);
1205     }
1206 
1207     /**
1208      * Report a TypeError with the message "Illegal constructor.".
1209      *
1210      * @return EcmaError
1211      */
1212     public static EcmaError typeErrorIllegalConstructor() {
1213         throw JavaScriptEngine.typeError("Illegal constructor.");
1214     }
1215 
1216     /**
1217      * Report a runtime error using the error reporter for the current thread.
1218      *
1219      * @param message the error message to report
1220      * @return EcmaError
1221      */
1222     public static EcmaError rangeError(final String message) {
1223         return ScriptRuntime.rangeError(message);
1224     }
1225 
1226     /**
1227      * @param error the error
1228      * @param message the message
1229      * @return a new EcmaError
1230      */
1231     public static EcmaError constructError(final String error, final String message) {
1232         return ScriptRuntime.constructError(error, message);
1233     }
1234 
1235     /**
1236      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1237      *
1238      * Creates a {@link DOMException} and encapsulates it into a Rhino-compatible exception.
1239      *
1240      * @param scope the parent scope
1241      * @param message the exception message
1242      * @param type the exception type
1243      * @return the created exception
1244      */
1245     public static RhinoException asJavaScriptException(final HtmlUnitScriptable scope, final String message, final int type) {
1246         final DOMException domException = new DOMException(message, type);
1247         domException.setParentScope(scope);
1248         domException.setPrototype(scope.getPrototype(DOMException.class));
1249 
1250         final EcmaError helper = ScriptRuntime.syntaxError("helper");
1251         String fileName = helper.sourceName();
1252         if (fileName != null) {
1253             fileName = fileName.replaceFirst("script in (.*) from .*", "$1");
1254         }
1255         domException.setLocation(fileName, helper.lineNumber());
1256 
1257         return new JavaScriptException(domException, fileName, helper.lineNumber());
1258     }
1259 
1260     /**
1261      * Create an array with a specified initial length.
1262      *
1263      * @param scope the scope to create the object in
1264      * @param length the initial length (JavaScript arrays may have additional properties added
1265      *     dynamically).
1266      * @return the new array object
1267      */
1268     public static Scriptable newArray(final Scriptable scope, final int length) {
1269         final NativeArray result = new NativeArray(length);
1270         ScriptRuntime.setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Array);
1271         return result;
1272     }
1273 
1274     /**
1275      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1276      *
1277      * Create a new ArrayIterator of type NativeArrayIterator.ARRAY_ITERATOR_TYPE.KEYS
1278      *
1279      * @param scope the scope to create the object in
1280      * @param arrayLike the backend
1281      * @return the new NativeArrayIterator
1282      */
1283     public static Scriptable newArrayIteratorTypeKeys(final Scriptable scope, final Scriptable arrayLike) {
1284         return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.KEYS);
1285     }
1286 
1287     /**
1288      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1289      *
1290      * Create a new ArrayIterator of type NativeArrayIterator.ARRAY_ITERATOR_TYPE.VALUES
1291      *
1292      * @param scope the scope to create the object in
1293      * @param arrayLike the backend
1294      * @return the new NativeArrayIterator
1295      */
1296     public static Scriptable newArrayIteratorTypeValues(final Scriptable scope, final Scriptable arrayLike) {
1297         return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.VALUES);
1298     }
1299 
1300     /**
1301      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1302      *
1303      * Create a new ArrayIterator of type NativeArrayIterator.ARRAY_ITERATOR_TYPE.ENTRIES
1304      *
1305      * @param scope the scope to create the object in
1306      * @param arrayLike the backend
1307      * @return the new NativeArrayIterator
1308      */
1309     public static Scriptable newArrayIteratorTypeEntries(final Scriptable scope, final Scriptable arrayLike) {
1310         return new NativeArrayIterator(scope, arrayLike, NativeArrayIterator.ARRAY_ITERATOR_TYPE.ENTRIES);
1311     }
1312 
1313     /**
1314      * Create an array with a specified initial length.
1315      *
1316      * @param scope the scope to create the object in
1317      * @param elements the initial elements. Each object in this array must be an acceptable
1318      *     JavaScript type and type of array should be exactly Object[], not SomeObjectSubclass[].
1319      * @return the new array object
1320      */
1321     public static Scriptable newArray(final Scriptable scope, final Object[] elements) {
1322         if (elements.getClass().getComponentType() != ScriptRuntime.ObjectClass) {
1323             throw new IllegalArgumentException();
1324         }
1325 
1326         final NativeArray result = new NativeArray(elements);
1327         ScriptRuntime.setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Array);
1328         return result;
1329     }
1330 
1331     /**
1332      * @param o the object to convert
1333      * @return int value
1334      */
1335     public static int toInt32(final Object o) {
1336         return ScriptRuntime.toInt32(o);
1337     }
1338 
1339     /**
1340      * @param o the object to convert
1341      * @return double value
1342      */
1343     public static double toInteger(final Object o) {
1344         return ScriptRuntime.toInteger(o);
1345     }
1346 
1347     /**
1348      * @param args an array
1349      * @param index the index in the array
1350      * @return double value
1351      */
1352     public static double toInteger(final Object[] args, final int index) {
1353         return ScriptRuntime.toInteger(args, index);
1354     }
1355 
1356     /**
1357      * @param obj the value to check
1358      * @return whether obj is undefined
1359      */
1360     public static boolean isUndefined(final Object obj) {
1361         return org.htmlunit.corejs.javascript.Undefined.isUndefined(obj);
1362     }
1363 
1364     /**
1365      * @param obj the value to check
1366      * @return whether obj is NAN
1367      */
1368     public static boolean isNaN(final Object obj) {
1369         return ScriptRuntime.isNaN(obj);
1370     }
1371 
1372     /**
1373      * @return the top call scope
1374      */
1375     public static Scriptable getTopCallScope() {
1376         return ScriptRuntime.getTopCallScope(Context.getCurrentContext());
1377     }
1378 
1379     /**
1380      * Tries to uncompress the JavaScript code in the provided response.
1381      * @param scriptSource the souce
1382      * @param scriptName the name
1383      * @return the uncompressed JavaScript code
1384      */
1385     public static String uncompressJavaScript(final String scriptSource, final String scriptName) {
1386         final ContextFactory factory = new ContextFactory();
1387         final ContextAction<Object> action = cx -> {
1388             cx.setInterpretedMode(true);
1389             final Script script = cx.compileString(scriptSource, scriptName, 0, null);
1390             return cx.decompileScript(script, 4);
1391         };
1392 
1393         return (String) factory.call(action);
1394     }
1395 
1396     /**
1397      * Evaluates the <code>FindProxyForURL</code> method of the specified content.
1398      * @param browserVersion the browser version to use
1399      * @param content the JavaScript content
1400      * @param url the URL to be retrieved
1401      * @return semicolon-separated result
1402      */
1403     public static String evaluateProxyAutoConfig(final BrowserVersion browserVersion, final String content, final URL url) {
1404         try (Context cx = Context.enter()) {
1405             final ProxyAutoConfigJavaScriptConfiguration jsConfig =
1406                     ProxyAutoConfigJavaScriptConfiguration.getInstance(browserVersion);
1407 
1408             final ScriptableObject scope = cx.initSafeStandardObjects();
1409 
1410             for (final ClassConfiguration config : jsConfig.getAll()) {
1411                 configureFunctions(config, scope);
1412             }
1413 
1414             cx.evaluateString(scope, "var ProxyConfig = function() {}; ProxyConfig.bindings = {}", "<init>", 1, null);
1415             cx.evaluateString(scope, content, "<Proxy Auto-Config>", 1, null);
1416 
1417             final Object[] functionArgs = {url.toExternalForm(), url.getHost()};
1418             final NativeFunction f = (NativeFunction) scope.get("FindProxyForURL", scope);
1419             final Object result = f.call(cx, scope, scope, functionArgs);
1420             return toString(result);
1421         }
1422     }
1423 }